From 4cfa6df001a8cb7811245a5b41a32da74f49e00a Mon Sep 17 00:00:00 2001 From: pnookala-px <127919775+pnookala-px@users.noreply.github.com> Date: Fri, 15 Sep 2023 06:38:15 +0530 Subject: [PATCH] PWX-30476: Upgrade GetMounts call to use the latest (#2293) * PWX-30476: Upgrade GetMounts call to use the latest Signed-off-by: pnookala-px * PWX-30476: Fix Test path Signed-off-by: pnookala-px --------- Signed-off-by: pnookala-px Co-authored-by: Harsh Desai --- go.mod | 4 +- pkg/mount/bind_mount.go | 4 +- pkg/mount/consistent_read.go | 168 +++++++++++++++--- pkg/mount/device.go | 4 +- pkg/mount/device_test.go | 6 +- pkg/mount/mount.go | 12 +- pkg/mount/nfs.go | 12 +- pkg/mount/raw_mount.go | 12 +- .../docker/docker/pkg/mount/deprecated.go | 67 ------- .../docker/pkg/mount/deprecated_linux.go | 19 -- .../docker/pkg/mount/deprecated_unix.go | 52 ------ vendor/modules.txt | 1 - 12 files changed, 171 insertions(+), 190 deletions(-) delete mode 100644 vendor/github.com/docker/docker/pkg/mount/deprecated.go delete mode 100644 vendor/github.com/docker/docker/pkg/mount/deprecated_linux.go delete mode 100644 vendor/github.com/docker/docker/pkg/mount/deprecated_unix.go diff --git a/go.mod b/go.mod index 869bfc919..e3524dbac 100644 --- a/go.mod +++ b/go.mod @@ -25,6 +25,8 @@ require ( github.com/libopenstorage/gossip v0.0.0-20220309192431-44c895e0923e github.com/libopenstorage/secrets v0.0.0-20200207034622-cdb443738c67 github.com/libopenstorage/systemutils v0.0.0-20160208220149-44ac83be3ce1 + github.com/moby/sys/mount v0.2.0 + github.com/moby/sys/mountinfo v0.4.0 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 github.com/onsi/ginkgo v1.16.5 github.com/onsi/gomega v1.16.0 @@ -103,8 +105,6 @@ require ( github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/mapstructure v1.3.3 // indirect github.com/moby/locker v1.0.1 // indirect - github.com/moby/sys/mount v0.2.0 // indirect - github.com/moby/sys/mountinfo v0.4.0 // indirect github.com/moby/sys/symlink v0.1.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect diff --git a/pkg/mount/bind_mount.go b/pkg/mount/bind_mount.go index db9069c41..43cbf19ed 100644 --- a/pkg/mount/bind_mount.go +++ b/pkg/mount/bind_mount.go @@ -6,7 +6,7 @@ import ( "regexp" "strings" - "github.com/docker/docker/pkg/mount" + "github.com/moby/sys/mountinfo" "github.com/libopenstorage/openstorage/pkg/keylock" ) @@ -60,7 +60,7 @@ func (b *bindMounter) Load(rootSubstrings []*regexp.Regexp) error { return b.load(rootSubstrings, bindFindMountPoint) } -func bindFindMountPoint(sInfo *mount.Info, destination *regexp.Regexp, infos []*mount.Info) (bool, string, string) { +func bindFindMountPoint(sInfo *mountinfo.Info, destination *regexp.Regexp, infos []*mountinfo.Info) (bool, string, string) { for _, dInfo := range infos { if !destination.MatchString(dInfo.Mountpoint) { continue diff --git a/pkg/mount/consistent_read.go b/pkg/mount/consistent_read.go index 9bbbb0aa7..437b7e4ac 100644 --- a/pkg/mount/consistent_read.go +++ b/pkg/mount/consistent_read.go @@ -30,8 +30,9 @@ import ( "io" "io/ioutil" "strings" + "strconv" - "github.com/docker/docker/pkg/mount" + "github.com/moby/sys/mountinfo" ) const ( @@ -58,7 +59,7 @@ var ErrLimitReached = errors.New("the read limit is reached") // Parse /proc/self/mountinfo because comparing Dev and ino does not work from // bind mounts. function is originally from // https://github.com/moby/sys/blob/65f80e71a828ef17e6f573176dc569e55f519937/mountinfo/mountinfo_linux.go -func parseMountTable() ([]*mount.Info, error) { +func parseMountTable() ([]*mountinfo.Info, error) { mountInfoBytes, err := consistentRead("/proc/self/mountinfo", 3) if err != nil { return nil, err @@ -90,43 +91,162 @@ func consistentRead(filename string, attempts int) ([]byte, error) { return nil, fmt.Errorf("could not get consistent content of mount table after %d attempts", attempts) } -func parseInfoFile(r io.Reader) ([]*mount.Info, error) { +func parseInfoFile(r io.Reader) ([]*mountinfo.Info, error) { var ( s = bufio.NewScanner(r) - out = []*mount.Info{} + out = []*mountinfo.Info{} ) for s.Scan() { - if err := s.Err(); err != nil { + var err error + if err = s.Err(); err != nil { return nil, err } + /* + 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) + } + } - var ( - p = &mount.Info{} - text = s.Text() - optionalFields string - ) + p := &mountinfo.Info{} - if _, err := fmt.Sscanf(text, mountinfoFormat, - &p.ID, &p.Parent, &p.Major, &p.Minor, - &p.Root, &p.Mountpoint, &p.Opts, &optionalFields); err != nil { - return nil, fmt.Errorf("Scanning '%s' failed: %s", text, err) + p.Mountpoint, err = unescape(fields[4]) + if err != nil { + return nil, fmt.Errorf("parsing '%s' failed: mount point: %w", fields[4], err) } - // Safe as mountinfo encodes mountpoints with spaces as \040. - index := strings.Index(text, " - ") - postSeparatorFields := strings.Fields(text[index+3:]) - if len(postSeparatorFields) < 3 { - return nil, fmt.Errorf("Error found less than 3 fields post '-' in %q", text) + 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]) - if optionalFields != "-" { - p.Optional = optionalFields + p.Root, err = unescape(fields[3]) + if err != nil { + return nil, fmt.Errorf("parsing '%s' failed: root: %w", fields[3], err) } - p.Fstype = postSeparatorFields[0] - p.Source = postSeparatorFields[1] - p.VfsOpts = strings.Join(postSeparatorFields[2:], " ") + p.Options = fields[5] + + // zero or more optional fields + p.Optional = strings.Join(fields[6:sepIdx], " ") + out = append(out, p) } return out, 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 +} diff --git a/pkg/mount/device.go b/pkg/mount/device.go index dd09b5680..bce9caf23 100644 --- a/pkg/mount/device.go +++ b/pkg/mount/device.go @@ -8,7 +8,7 @@ import ( "regexp" "strings" - "github.com/docker/docker/pkg/mount" + "github.com/moby/sys/mountinfo" "github.com/libopenstorage/openstorage/pkg/keylock" ) @@ -68,7 +68,7 @@ func (m *deviceMounter) Load(devRegexes []*regexp.Regexp) error { return m.load(devRegexes, deviceFindMountPoint) } -func deviceFindMountPoint(info *mount.Info, destination *regexp.Regexp, infos []*mount.Info) (bool, string, string) { +func deviceFindMountPoint(info *mountinfo.Info, destination *regexp.Regexp, infos []*mountinfo.Info) (bool, string, string) { if destination.MatchString(info.Source) { return true, info.Source, info.Source } diff --git a/pkg/mount/device_test.go b/pkg/mount/device_test.go index aa7501980..27c2d6fd2 100644 --- a/pkg/mount/device_test.go +++ b/pkg/mount/device_test.go @@ -5,7 +5,7 @@ import ( "regexp" "testing" - "github.com/docker/docker/pkg/mount" + "github.com/moby/sys/mountinfo" "github.com/stretchr/testify/require" ) @@ -21,8 +21,8 @@ func addMountEntry(t *testing.T, device string, mountpoint string) int { _, err := os.Create(device) require.NoError(t, err, "Failed to create test device: ", device) index := len(testMounts) - info := &mount.Info{ - Fstype: "ext4", + info := &mountinfo.Info{ + FSType: "ext4", Minor: index, Mountpoint: mountpoint, Source: device, diff --git a/pkg/mount/mount.go b/pkg/mount/mount.go index 71ca4c48d..da052e98a 100644 --- a/pkg/mount/mount.go +++ b/pkg/mount/mount.go @@ -18,7 +18,7 @@ import ( "syscall" "time" - "github.com/docker/docker/pkg/mount" + "github.com/moby/sys/mountinfo" "github.com/libopenstorage/openstorage/pkg/chattr" "github.com/libopenstorage/openstorage/pkg/keylock" "github.com/libopenstorage/openstorage/pkg/options" @@ -151,7 +151,7 @@ type Mounter struct { trashLocation string } -type findMountPoint func(source *mount.Info, destination *regexp.Regexp, mountInfo []*mount.Info) (bool, string, string) +type findMountPoint func(source *mountinfo.Info, destination *regexp.Regexp, mountInfo []*mountinfo.Info) (bool, string, string) // DefaultMounter defaults to syscall implementation. type DefaultMounter struct { @@ -398,7 +398,7 @@ func (m *Mounter) load(prefixes []*regexp.Regexp, fmp findMountPoint) error { if !ok { mount = &Info{ Device: deviceSourcePath, - Fs: v.Fstype, + Fs: v.FSType, Minor: v.Minor, Mountpoint: make([]*PathInfo, 0), } @@ -833,7 +833,7 @@ func New( // GetMounts is a wrapper over mount.GetMounts(). It is mainly used to add a switch // to enable device mounter tests. -func GetMounts() ([]*mount.Info, error) { +func GetMounts() ([]*mountinfo.Info, error) { if os.Getenv(testDeviceEnv) != "" { return testGetMounts() } @@ -842,12 +842,12 @@ func GetMounts() ([]*mount.Info, error) { var ( // testMounts is a global test list of mount table entries - testMounts []*mount.Info + testMounts []*mountinfo.Info ) // testGetMounts is only used in tests to get the test list of mount table // entries -func testGetMounts() ([]*mount.Info, error) { +func testGetMounts() ([]*mountinfo.Info, error) { var err error if len(testMounts) == 0 { testMounts, err = parseMountTable() diff --git a/pkg/mount/nfs.go b/pkg/mount/nfs.go index 7567ef823..61d483feb 100644 --- a/pkg/mount/nfs.go +++ b/pkg/mount/nfs.go @@ -9,7 +9,7 @@ import ( "regexp" "strings" - "github.com/docker/docker/pkg/mount" + "github.com/moby/sys/mountinfo" "github.com/libopenstorage/openstorage/pkg/keylock" ) @@ -83,8 +83,8 @@ func (m *nfsMounter) serverExists(server string) bool { // normalizeSource - NFS source is returned as IP:share or just :share // normalize that to always IP:share -func (m *nfsMounter) normalizeSource(info *mount.Info, host string) { - if info.Fstype != "nfs" { +func (m *nfsMounter) normalizeSource(info *mountinfo.Info, host string) { + if info.FSType != "nfs" { return } @@ -110,10 +110,10 @@ MountLoop: for _, v := range info { host := "localhost" if len(m.servers) != 0 { - if !strings.HasPrefix(v.Fstype, "nfs") { + if !strings.HasPrefix(v.FSType, "nfs") { continue } - matches := re.FindStringSubmatch(v.VfsOpts) + matches := re.FindStringSubmatch(v.VFSOptions) if len(matches) != 2 { continue } @@ -128,7 +128,7 @@ MountLoop: if !ok { mount = &Info{ Device: v.Source, - Fs: v.Fstype, + Fs: v.FSType, Minor: v.Minor, Mountpoint: make([]*PathInfo, 0), } diff --git a/pkg/mount/raw_mount.go b/pkg/mount/raw_mount.go index 292900184..2f40f8e0a 100644 --- a/pkg/mount/raw_mount.go +++ b/pkg/mount/raw_mount.go @@ -8,7 +8,7 @@ import ( "regexp" "strings" - "github.com/docker/docker/pkg/mount" + "github.com/moby/sys/mountinfo" "github.com/libopenstorage/openstorage/pkg/keylock" ) @@ -76,8 +76,8 @@ func (rm *rawMounter) Load(rawVolumeDevicesPaths []*regexp.Regexp) error { // try to find all bind mounts of raw volumes if len(rawVolumeDevicesPaths) == 0 || rawVolumeDevicesPaths[0].String() == "" { - mountPointsByMajMin := make(map[string]*[]mount.Info) - mountPointsByTarget := make(map[string]*mount.Info) + mountPointsByMajMin := make(map[string]*[]mountinfo.Info) + mountPointsByTarget := make(map[string]*mountinfo.Info) for _, mp := range mountPoints { // skip proc mount points @@ -88,7 +88,7 @@ func (rm *rawMounter) Load(rawVolumeDevicesPaths []*regexp.Regexp) error { mountPointsForNumber, exists := mountPointsByMajMin[majMin] if !exists { - mountPointsForNumber = &[]mount.Info{} + mountPointsForNumber = &[]mountinfo.Info{} mountPointsByMajMin[majMin] = mountPointsForNumber } @@ -107,7 +107,7 @@ func (rm *rawMounter) Load(rawVolumeDevicesPaths []*regexp.Regexp) error { mountPointsForNumber := mountPointsByMajMin[devMajMin] mps := *mountPointsForNumber - filteredMPs := []mount.Info{} + filteredMPs := []mountinfo.Info{} for i := range mps { if mps[i].Mountpoint != "/dev" { filteredMPs = append(filteredMPs, mps[i]) @@ -136,7 +136,7 @@ func (rm *rawMounter) Load(rawVolumeDevicesPaths []*regexp.Regexp) error { // find raw volume bind mounts for _, rawVolumeDevicePath := range rawVolumeDevicesPaths { - var mountPointForRoot *mount.Info + var mountPointForRoot *mountinfo.Info for _, mp := range mountPoints { if strings.HasSuffix(rawVolumeDevicePath.String(), mp.Root) || rawVolumeDevicePath.MatchString(mp.Root) { mountPointForRoot = mp diff --git a/vendor/github.com/docker/docker/pkg/mount/deprecated.go b/vendor/github.com/docker/docker/pkg/mount/deprecated.go deleted file mode 100644 index 16c8e5952..000000000 --- a/vendor/github.com/docker/docker/pkg/mount/deprecated.go +++ /dev/null @@ -1,67 +0,0 @@ -package mount // import "github.com/docker/docker/pkg/mount" - -// Deprecated: this package is not maintained and will be removed. -// Use github.com/moby/sys/mount and github.com/moby/sys/mountinfo instead. - -import ( - "github.com/moby/sys/mountinfo" -) - -//nolint:golint -type ( - // FilterFunc is a type. - // Deprecated: use github.com/moby/sys/mountinfo instead. - FilterFunc = func(*Info) (skip, stop bool) - - // Info is a type - // Deprecated: use github.com/moby/sys/mountinfo instead. - Info struct { - ID, Parent, Major, Minor int - Root, Mountpoint, Opts, Optional, Fstype, Source, VfsOpts string - } -) - -// Deprecated: use github.com/moby/sys/mountinfo instead. -//nolint:golint -var ( - Mounted = mountinfo.Mounted - PrefixFilter = mountinfo.PrefixFilter - SingleEntryFilter = mountinfo.SingleEntryFilter - ParentsFilter = mountinfo.ParentsFilter - FstypeFilter = mountinfo.FSTypeFilter -) - -// GetMounts is a function. -// -// Deprecated: use github.com/moby/sys/mountinfo.GetMounts() instead. -//nolint:golint -func GetMounts(f FilterFunc) ([]*Info, error) { - fi := func(i *mountinfo.Info) (skip, stop bool) { - return f(toLegacyInfo(i)) - } - - mounts, err := mountinfo.GetMounts(fi) - if err != nil { - return nil, err - } - mi := make([]*Info, len(mounts)) - for i, m := range mounts { - mi[i] = toLegacyInfo(m) - } - return mi, nil -} - -func toLegacyInfo(m *mountinfo.Info) *Info { - return &Info{ - ID: m.ID, - Parent: m.Parent, - Major: m.Major, - Minor: m.Minor, - Root: m.Root, - Mountpoint: m.Mountpoint, - Opts: m.Options, - Fstype: m.FSType, - Source: m.Source, - VfsOpts: m.VFSOptions, - } -} diff --git a/vendor/github.com/docker/docker/pkg/mount/deprecated_linux.go b/vendor/github.com/docker/docker/pkg/mount/deprecated_linux.go deleted file mode 100644 index e4a4407a3..000000000 --- a/vendor/github.com/docker/docker/pkg/mount/deprecated_linux.go +++ /dev/null @@ -1,19 +0,0 @@ -package mount // import "github.com/docker/docker/pkg/mount" - -import ( - sysmount "github.com/moby/sys/mount" -) - -// Deprecated: use github.com/moby/sys/mount instead. -//nolint:golint -var ( - MakeMount = sysmount.MakeMount - MakeShared = sysmount.MakeShared - MakeRShared = sysmount.MakeRShared - MakePrivate = sysmount.MakePrivate - MakeRPrivate = sysmount.MakeRPrivate - MakeSlave = sysmount.MakeSlave - MakeRSlave = sysmount.MakeRSlave - MakeUnbindable = sysmount.MakeUnbindable - MakeRUnbindable = sysmount.MakeRUnbindable -) diff --git a/vendor/github.com/docker/docker/pkg/mount/deprecated_unix.go b/vendor/github.com/docker/docker/pkg/mount/deprecated_unix.go deleted file mode 100644 index 1787f2042..000000000 --- a/vendor/github.com/docker/docker/pkg/mount/deprecated_unix.go +++ /dev/null @@ -1,52 +0,0 @@ -// +build !darwin,!windows - -package mount // import "github.com/docker/docker/pkg/mount" - -// Deprecated: this package is not maintained and will be removed. -// Use github.com/moby/sys/mount and github.com/moby/sys/mountinfo instead. - -import ( - sysmount "github.com/moby/sys/mount" -) - -// Deprecated: use github.com/moby/sys/mount instead. -//nolint:golint -var ( - Mount = sysmount.Mount - ForceMount = sysmount.Mount // a deprecated synonym - Unmount = sysmount.Unmount - RecursiveUnmount = sysmount.RecursiveUnmount -) - -// Deprecated: use github.com/moby/sys/mount instead. -//nolint:golint -const ( - RDONLY = sysmount.RDONLY - NOSUID = sysmount.NOSUID - NOEXEC = sysmount.NOEXEC - SYNCHRONOUS = sysmount.SYNCHRONOUS - NOATIME = sysmount.NOATIME - BIND = sysmount.BIND - DIRSYNC = sysmount.DIRSYNC - MANDLOCK = sysmount.MANDLOCK - NODEV = sysmount.NODEV - NODIRATIME = sysmount.NODIRATIME - UNBINDABLE = sysmount.UNBINDABLE - RUNBINDABLE = sysmount.RUNBINDABLE - PRIVATE = sysmount.PRIVATE - RPRIVATE = sysmount.RPRIVATE - SHARED = sysmount.SHARED - RSHARED = sysmount.RSHARED - SLAVE = sysmount.SLAVE - RSLAVE = sysmount.RSLAVE - RBIND = sysmount.RBIND - RELATIME = sysmount.RELATIME - REMOUNT = sysmount.REMOUNT - STRICTATIME = sysmount.STRICTATIME -) - -// Deprecated: use github.com/moby/sys/mount instead. -//nolint:golint -var ( - MergeTmpfsOptions = sysmount.MergeTmpfsOptions -) diff --git a/vendor/modules.txt b/vendor/modules.txt index 17715c5e9..52c9834cc 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -179,7 +179,6 @@ github.com/docker/docker/pkg/fsutils github.com/docker/docker/pkg/idtools github.com/docker/docker/pkg/ioutils github.com/docker/docker/pkg/longpath -github.com/docker/docker/pkg/mount github.com/docker/docker/pkg/parsers github.com/docker/docker/pkg/parsers/kernel github.com/docker/docker/pkg/plugingetter