From c22effb1686972d1a12f9aa32f1ad06d1aa253f0 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Tue, 2 Apr 2019 12:06:23 +0200 Subject: [PATCH 1/2] fix parseInfoFile does not handle spaces in filenames `/proc/self/mountinfo` uses `\040` for spaces, however, `parseInfoFile()` did not decode those spaces in paths, therefore attempting to use `\040` as a literal part of the path. This patch un-quotes the `root` and `mount point` fields to fix situations where paths contain spaces. Note that the `mount source` field is not modified, given that this field is documented (man `PROC(5)`) as: filesystem-specific information or "none" Which I interpreted as "the format in this field is undefined". Reported-by: Daniil Yaroslavtsev Reported-by: Nathan Ringo Based-on-patch-by: Diego Becciolini Based-on-patch-by: Sergei Utinski Signed-off-by: Sebastiaan van Stijn --- mount/mountinfo_linux.go | 18 ++++++++++--- mount/mountinfo_linux_test.go | 48 +++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 4 deletions(-) diff --git a/mount/mountinfo_linux.go b/mount/mountinfo_linux.go index 369d045d7..fbb5b5c06 100644 --- a/mount/mountinfo_linux.go +++ b/mount/mountinfo_linux.go @@ -25,6 +25,8 @@ import ( "os" "strconv" "strings" + + "github.com/pkg/errors" ) // Self retrieves a list of mounts for the current running process. @@ -41,13 +43,15 @@ func Self() ([]Info, error) { func parseInfoFile(r io.Reader) ([]Info, error) { s := bufio.NewScanner(r) out := []Info{} - + var err error for s.Scan() { - if err := s.Err(); err != nil { + 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) @@ -81,8 +85,14 @@ func parseInfoFile(r io.Reader) ([]Info, error) { p.Major, _ = strconv.Atoi(mm[0]) p.Minor, _ = strconv.Atoi(mm[1]) - p.Root = fields[3] - p.Mountpoint = fields[4] + p.Root, err = strconv.Unquote(`"` + fields[3] + `"`) + if err != nil { + return nil, errors.Wrapf(err, "parsing '%s' failed: unable to unquote root field", fields[3]) + } + p.Mountpoint, err = strconv.Unquote(`"` + fields[4] + `"`) + if err != nil { + return nil, errors.Wrapf(err, "parsing '%s' failed: unable to unquote mount point field", fields[4]) + } p.Options = fields[5] // one or more optional fields, when a separator (-) diff --git a/mount/mountinfo_linux_test.go b/mount/mountinfo_linux_test.go index a73c01272..08eb5e908 100644 --- a/mount/mountinfo_linux_test.go +++ b/mount/mountinfo_linux_test.go @@ -436,6 +436,9 @@ const ( 286 15 0:3631 / /var/lib/docker/aufs/mnt/ff28c27d5f894363993622de26d5dd352dba072f219e4691d6498c19bbbc15a9 rw,relatime - aufs none rw,si=9b4a7642265b339c 289 15 0:3634 / /var/lib/docker/aufs/mnt/aa128fe0e64fdede333aa48fd9de39530c91a9244a0f0649a3c411c61e372daa rw,relatime - aufs none rw,si=9b4a764012ada39c 99 15 8:33 / /media/REMOVE\040ME rw,nosuid,nodev,relatime - fuseblk /dev/sdc1 rw,user_id=0,group_id=0,allow_other,blksize=4096` + + mountInfoWithSpaces = `486 28 252:1 / /mnt/foo\040bar rw,relatime shared:243 - ext4 /dev/vda1 rw,data=ordered +31 21 0:23 / /DATA/foo_bla_bla rw,relatime - cifs //foo/BLA\040BLA\040BLA/ rw,sec=ntlm,cache=loose,unc=\\foo\BLA BLA BLA,username=my_login,domain=mydomain.com,uid=12345678,forceuid,gid=12345678,forcegid,addr=10.1.30.10,file_mode=0755,dir_mode=0755,nounix,rsize=61440,wsize=65536,actimeo=1` ) func TestParseFedoraMountinfo(t *testing.T) { @@ -490,3 +493,48 @@ func TestParseFedoraMountinfoFields(t *testing.T) { t.Fatalf("expected %#v, got %#v", mi, infos[0]) } } + +func TestParseMountinfoWithSpaces(t *testing.T) { + r := bytes.NewBuffer([]byte(mountInfoWithSpaces)) + infos, err := parseInfoFile(r) + if err != nil { + t.Fatal(err) + } + expected := []Info{ + { + ID: 486, + Parent: 28, + Major: 252, + Minor: 1, + Root: "/", + Mountpoint: "/mnt/foo bar", + Options: "rw,relatime", + Optional: "shared:243", + FSType: "ext4", + Source: "/dev/vda1", + VFSOptions: "rw,data=ordered", + }, + { + ID: 31, + Parent: 21, + Major: 0, + Minor: 23, + Root: "/", + Mountpoint: "/DATA/foo_bla_bla", + Options: "rw,relatime", + Optional: "", + FSType: "cifs", + Source: `//foo/BLA\040BLA\040BLA/`, + VFSOptions: `rw,sec=ntlm,cache=loose,unc=\\foo\BLA`, + }, + } + + if len(infos) != len(expected) { + t.Fatalf("expected %d entries, got %d", len(expected), len(infos)) + } + for i, mi := range expected { + if infos[i] != mi { + t.Fatalf("expected %#v, got %#v", mi, infos[i]) + } + } +} From adc4fa217bd8156ea5d5b448bed91bbf14b71a13 Mon Sep 17 00:00:00 2001 From: Sebastiaan van Stijn Date: Tue, 2 Apr 2019 12:08:39 +0200 Subject: [PATCH 2/2] Use pkg/errors for all errors Signed-off-by: Sebastiaan van Stijn --- mount/mountinfo_linux.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mount/mountinfo_linux.go b/mount/mountinfo_linux.go index fbb5b5c06..a7407c50e 100644 --- a/mount/mountinfo_linux.go +++ b/mount/mountinfo_linux.go @@ -72,7 +72,7 @@ func parseInfoFile(r io.Reader) ([]Info, error) { 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) + return nil, errors.Errorf("parsing '%s' failed: not enough fields (%d)", text, numFields) } p := Info{} // ignore any numbers parsing errors, as there should not be any @@ -80,7 +80,7 @@ func parseInfoFile(r io.Reader) ([]Info, error) { p.Parent, _ = strconv.Atoi(fields[1]) mm := strings.Split(fields[2], ":") if len(mm) != 2 { - return nil, fmt.Errorf("parsing '%s' failed: unexpected minor:major pair %s", text, mm) + return nil, errors.Errorf("parsing '%s' failed: unexpected minor:major pair %s", text, mm) } p.Major, _ = strconv.Atoi(mm[0]) p.Minor, _ = strconv.Atoi(mm[1]) @@ -111,11 +111,11 @@ func parseInfoFile(r io.Reader) ([]Info, error) { } } if i == numFields { - return nil, fmt.Errorf("parsing '%s' failed: missing separator ('-')", text) + return nil, errors.Errorf("parsing '%s' failed: missing separator ('-')", text) } // There should be 3 fields after the separator... if i+4 > numFields { - return nil, fmt.Errorf("parsing '%s' failed: not enough fields after a separator", text) + return nil, errors.Errorf("parsing '%s' failed: not enough fields after a separator", text) } // ... but in Linux <= 3.9 mounting a cifs with spaces in a share name // (like "//serv/My Documents") _may_ end up having a space in the last field