mirror of
https://git.mirrors.martin98.com/https://github.com/ceph/ceph-csi.git
synced 2025-08-19 16:39:15 +08:00

Several packages are only used while running the e2e suite. These packages are less important to update, as the they can not influence the final executable that is part of the Ceph-CSI container-image. By moving these dependencies out of the main Ceph-CSI go.mod, it is easier to identify if a reported CVE affects Ceph-CSI, or only the testing (like most of the Kubernetes CVEs). Signed-off-by: Niels de Vos <ndevos@ibm.com>
251 lines
7.2 KiB
Go
251 lines
7.2 KiB
Go
package mountinfo
|
|
|
|
import (
|
|
"bufio"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
|
|
"golang.org/x/sys/unix"
|
|
)
|
|
|
|
// GetMountsFromReader retrieves a list of mounts from the
|
|
// reader provided, with an optional filter applied (use nil
|
|
// for no filter). This can be useful in tests or benchmarks
|
|
// that provide fake mountinfo data, or when a source other
|
|
// than /proc/thread-self/mountinfo needs to be read from.
|
|
//
|
|
// This function is Linux-specific.
|
|
func GetMountsFromReader(r io.Reader, filter FilterFunc) ([]*Info, error) {
|
|
s := bufio.NewScanner(r)
|
|
out := []*Info{}
|
|
for s.Scan() {
|
|
var err error
|
|
|
|
/*
|
|
See http://man7.org/linux/man-pages/man5/proc.5.html
|
|
|
|
36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue
|
|
(1)(2)(3) (4) (5) (6) (7) (8) (9) (10) (11)
|
|
|
|
(1) mount ID: unique identifier of the mount (may be reused after umount)
|
|
(2) parent ID: ID of parent (or of self for the top of the mount tree)
|
|
(3) major:minor: value of st_dev for files on filesystem
|
|
(4) root: root of the mount within the filesystem
|
|
(5) mount point: mount point relative to the process's root
|
|
(6) mount options: per mount options
|
|
(7) optional fields: zero or more fields of the form "tag[:value]"
|
|
(8) separator: marks the end of the optional fields
|
|
(9) filesystem type: name of filesystem of the form "type[.subtype]"
|
|
(10) mount source: filesystem specific information or "none"
|
|
(11) super options: per super block options
|
|
|
|
In other words, we have:
|
|
* 6 mandatory fields (1)..(6)
|
|
* 0 or more optional fields (7)
|
|
* a separator field (8)
|
|
* 3 mandatory fields (9)..(11)
|
|
*/
|
|
|
|
text := s.Text()
|
|
fields := strings.Split(text, " ")
|
|
numFields := len(fields)
|
|
if numFields < 10 {
|
|
// should be at least 10 fields
|
|
return nil, fmt.Errorf("parsing '%s' failed: not enough fields (%d)", text, numFields)
|
|
}
|
|
|
|
// separator field
|
|
sepIdx := numFields - 4
|
|
// In Linux <= 3.9 mounting a cifs with spaces in a share
|
|
// name (like "//srv/My Docs") _may_ end up having a space
|
|
// in the last field of mountinfo (like "unc=//serv/My Docs").
|
|
// Since kernel 3.10-rc1, cifs option "unc=" is ignored,
|
|
// so spaces should not appear.
|
|
//
|
|
// Check for a separator, and work around the spaces bug
|
|
for fields[sepIdx] != "-" {
|
|
sepIdx--
|
|
if sepIdx == 5 {
|
|
return nil, fmt.Errorf("parsing '%s' failed: missing - separator", text)
|
|
}
|
|
}
|
|
|
|
p := &Info{}
|
|
|
|
p.Mountpoint, err = unescape(fields[4])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("parsing '%s' failed: mount point: %w", fields[4], err)
|
|
}
|
|
p.FSType, err = unescape(fields[sepIdx+1])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("parsing '%s' failed: fstype: %w", fields[sepIdx+1], err)
|
|
}
|
|
p.Source, err = unescape(fields[sepIdx+2])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("parsing '%s' failed: source: %w", fields[sepIdx+2], err)
|
|
}
|
|
p.VFSOptions = fields[sepIdx+3]
|
|
|
|
// ignore any numbers parsing errors, as there should not be any
|
|
p.ID, _ = strconv.Atoi(fields[0])
|
|
p.Parent, _ = strconv.Atoi(fields[1])
|
|
mm := strings.SplitN(fields[2], ":", 3)
|
|
if len(mm) != 2 {
|
|
return nil, fmt.Errorf("parsing '%s' failed: unexpected major:minor pair %s", text, mm)
|
|
}
|
|
p.Major, _ = strconv.Atoi(mm[0])
|
|
p.Minor, _ = strconv.Atoi(mm[1])
|
|
|
|
p.Root, err = unescape(fields[3])
|
|
if err != nil {
|
|
return nil, fmt.Errorf("parsing '%s' failed: root: %w", fields[3], err)
|
|
}
|
|
|
|
p.Options = fields[5]
|
|
|
|
// zero or more optional fields
|
|
p.Optional = strings.Join(fields[6:sepIdx], " ")
|
|
|
|
// Run the filter after parsing all fields.
|
|
var skip, stop bool
|
|
if filter != nil {
|
|
skip, stop = filter(p)
|
|
if skip {
|
|
continue
|
|
}
|
|
}
|
|
|
|
out = append(out, p)
|
|
if stop {
|
|
break
|
|
}
|
|
}
|
|
if err := s.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
var (
|
|
haveProcThreadSelf bool
|
|
haveProcThreadSelfOnce sync.Once
|
|
)
|
|
|
|
func parseMountTable(filter FilterFunc) (_ []*Info, err error) {
|
|
haveProcThreadSelfOnce.Do(func() {
|
|
_, err := os.Stat("/proc/thread-self/mountinfo")
|
|
haveProcThreadSelf = err == nil
|
|
})
|
|
|
|
// We need to lock ourselves to the current OS thread in order to make sure
|
|
// that the thread referenced by /proc/thread-self stays alive until we
|
|
// finish parsing the file.
|
|
runtime.LockOSThread()
|
|
defer runtime.UnlockOSThread()
|
|
|
|
var f *os.File
|
|
if haveProcThreadSelf {
|
|
f, err = os.Open("/proc/thread-self/mountinfo")
|
|
} else {
|
|
// On pre-3.17 kernels (such as CentOS 7), we don't have
|
|
// /proc/thread-self/ so we need to manually construct
|
|
// /proc/self/task/<tid>/ as a fallback.
|
|
f, err = os.Open("/proc/self/task/" + strconv.Itoa(unix.Gettid()) + "/mountinfo")
|
|
if os.IsNotExist(err) {
|
|
// If /proc/self/task/... failed, it means that our active pid
|
|
// namespace doesn't match the pid namespace of the /proc mount. In
|
|
// this case we just have to make do with /proc/self, since there
|
|
// is no other way of figuring out our tid in a parent pid
|
|
// namespace on pre-3.17 kernels.
|
|
f, err = os.Open("/proc/self/mountinfo")
|
|
}
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer f.Close()
|
|
|
|
return GetMountsFromReader(f, filter)
|
|
}
|
|
|
|
// PidMountInfo retrieves the list of mounts from a given process' mount
|
|
// namespace. Unless there is a need to get mounts from a mount namespace
|
|
// different from that of a calling process, use GetMounts.
|
|
//
|
|
// This function is Linux-specific.
|
|
//
|
|
// Deprecated: this will be removed before v1; use GetMountsFromReader with
|
|
// opened /proc/<pid>/mountinfo as an argument instead.
|
|
func PidMountInfo(pid int) ([]*Info, error) {
|
|
f, err := os.Open(fmt.Sprintf("/proc/%d/mountinfo", pid))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer f.Close()
|
|
|
|
return GetMountsFromReader(f, nil)
|
|
}
|
|
|
|
// A few specific characters in mountinfo path entries (root and mountpoint)
|
|
// are escaped using a backslash followed by a character's ascii code in octal.
|
|
//
|
|
// space -- as \040
|
|
// tab (aka \t) -- as \011
|
|
// newline (aka \n) -- as \012
|
|
// backslash (aka \\) -- as \134
|
|
//
|
|
// This function converts path from mountinfo back, i.e. it unescapes the above sequences.
|
|
func unescape(path string) (string, error) {
|
|
// try to avoid copying
|
|
if strings.IndexByte(path, '\\') == -1 {
|
|
return path, nil
|
|
}
|
|
|
|
// The following code is UTF-8 transparent as it only looks for some
|
|
// specific characters (backslash and 0..7) with values < utf8.RuneSelf,
|
|
// and everything else is passed through as is.
|
|
buf := make([]byte, len(path))
|
|
bufLen := 0
|
|
for i := 0; i < len(path); i++ {
|
|
if path[i] != '\\' {
|
|
buf[bufLen] = path[i]
|
|
bufLen++
|
|
continue
|
|
}
|
|
s := path[i:]
|
|
if len(s) < 4 {
|
|
// too short
|
|
return "", fmt.Errorf("bad escape sequence %q: too short", s)
|
|
}
|
|
c := s[1]
|
|
switch c {
|
|
case '0', '1', '2', '3', '4', '5', '6', '7':
|
|
v := c - '0'
|
|
for j := 2; j < 4; j++ { // one digit already; two more
|
|
if s[j] < '0' || s[j] > '7' {
|
|
return "", fmt.Errorf("bad escape sequence %q: not a digit", s[:3])
|
|
}
|
|
x := s[j] - '0'
|
|
v = (v << 3) | x
|
|
}
|
|
if v > 255 {
|
|
return "", fmt.Errorf("bad escape sequence %q: out of range" + s[:3])
|
|
}
|
|
buf[bufLen] = v
|
|
bufLen++
|
|
i += 3
|
|
continue
|
|
default:
|
|
return "", fmt.Errorf("bad escape sequence %q: not a digit" + s[:3])
|
|
|
|
}
|
|
}
|
|
|
|
return string(buf[:bufLen]), nil
|
|
}
|