github.com/stolowski/snapd@v0.0.0-20210407085831-115137ce5a22/osutil/mountinfo_linux_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2017 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package osutil_test 21 22 import ( 23 "io/ioutil" 24 "os" 25 "path/filepath" 26 "strings" 27 28 . "gopkg.in/check.v1" 29 30 "github.com/snapcore/snapd/osutil" 31 ) 32 33 type mountinfoSuite struct{} 34 35 var _ = Suite(&mountinfoSuite{}) 36 37 // Check that parsing the example from kernel documentation works correctly. 38 func (s *mountinfoSuite) TestParseMountInfoEntry1(c *C) { 39 real := "36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue" 40 canonical := "36 35 98:0 /mnt1 /mnt2 noatime,rw master:1 - ext3 /dev/root errors=continue,rw" 41 entry, err := osutil.ParseMountInfoEntry(real) 42 c.Assert(err, IsNil) 43 c.Assert(entry.String(), Equals, canonical) 44 45 c.Assert(entry.MountID, Equals, 36) 46 c.Assert(entry.ParentID, Equals, 35) 47 c.Assert(entry.DevMajor, Equals, 98) 48 c.Assert(entry.DevMinor, Equals, 0) 49 c.Assert(entry.Root, Equals, "/mnt1") 50 c.Assert(entry.MountDir, Equals, "/mnt2") 51 c.Assert(entry.MountOptions, DeepEquals, map[string]string{"rw": "", "noatime": ""}) 52 c.Assert(entry.OptionalFields, DeepEquals, []string{"master:1"}) 53 c.Assert(entry.FsType, Equals, "ext3") 54 c.Assert(entry.MountSource, Equals, "/dev/root") 55 c.Assert(entry.SuperOptions, DeepEquals, map[string]string{"rw": "", "errors": "continue"}) 56 } 57 58 // Check that various combinations of optional fields are parsed correctly. 59 func (s *mountinfoSuite) TestParseMountInfoEntry2(c *C) { 60 // No optional fields. 61 real := "36 35 98:0 /mnt1 /mnt2 rw,noatime - ext3 /dev/root rw,errors=continue" 62 canonical := "36 35 98:0 /mnt1 /mnt2 noatime,rw - ext3 /dev/root errors=continue,rw" 63 entry, err := osutil.ParseMountInfoEntry(real) 64 c.Assert(err, IsNil) 65 c.Assert(entry.String(), Equals, canonical) 66 67 c.Assert(entry.MountOptions, DeepEquals, map[string]string{"rw": "", "noatime": ""}) 68 c.Assert(entry.OptionalFields, HasLen, 0) 69 c.Assert(entry.FsType, Equals, "ext3") 70 // One optional field. 71 entry, err = osutil.ParseMountInfoEntry( 72 "36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue") 73 c.Assert(err, IsNil) 74 c.Assert(entry.MountOptions, DeepEquals, map[string]string{"rw": "", "noatime": ""}) 75 c.Assert(entry.OptionalFields, DeepEquals, []string{"master:1"}) 76 c.Assert(entry.FsType, Equals, "ext3") 77 // Two optional fields. 78 entry, err = osutil.ParseMountInfoEntry( 79 "36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 slave:2 - ext3 /dev/root rw,errors=continue") 80 c.Assert(err, IsNil) 81 c.Assert(entry.MountOptions, DeepEquals, map[string]string{"rw": "", "noatime": ""}) 82 c.Assert(entry.OptionalFields, DeepEquals, []string{"master:1", "slave:2"}) 83 c.Assert(entry.FsType, Equals, "ext3") 84 } 85 86 // Check that white-space is unescaped correctly. 87 func (s *mountinfoSuite) TestParseMountInfoEntry3(c *C) { 88 real := `36 35 98:0 /mnt\0401 /mnt\0402 noatime,rw\040 mas\040ter:1 - ext\0403 /dev/ro\040ot rw\040,errors=continue` 89 canonical := `36 35 98:0 /mnt\0401 /mnt\0402 noatime,rw\040 mas\040ter:1 - ext\0403 /dev/ro\040ot errors=continue,rw\040` 90 entry, err := osutil.ParseMountInfoEntry(real) 91 c.Assert(err, IsNil) 92 c.Assert(entry.String(), Equals, canonical) 93 94 c.Assert(entry.MountID, Equals, 36) 95 c.Assert(entry.ParentID, Equals, 35) 96 c.Assert(entry.DevMajor, Equals, 98) 97 c.Assert(entry.DevMinor, Equals, 0) 98 c.Assert(entry.Root, Equals, "/mnt 1") 99 c.Assert(entry.MountDir, Equals, "/mnt 2") 100 c.Assert(entry.MountOptions, DeepEquals, map[string]string{"rw ": "", "noatime": ""}) 101 // This field is still escaped as it is space-separated and needs further parsing. 102 c.Assert(entry.OptionalFields, DeepEquals, []string{"mas ter:1"}) 103 c.Assert(entry.FsType, Equals, "ext 3") 104 c.Assert(entry.MountSource, Equals, "/dev/ro ot") 105 c.Assert(entry.SuperOptions, DeepEquals, map[string]string{"rw ": "", "errors": "continue"}) 106 } 107 108 // Check that various malformed entries are detected. 109 func (s *mountinfoSuite) TestParseMountInfoEntry4(c *C) { 110 var err error 111 _, err = osutil.ParseMountInfoEntry("36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue foo") 112 c.Assert(err, ErrorMatches, "incorrect number of tail fields, expected 3 but found 4") 113 _, err = osutil.ParseMountInfoEntry("36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root") 114 c.Assert(err, ErrorMatches, "incorrect number of tail fields, expected 3 but found 2") 115 _, err = osutil.ParseMountInfoEntry("36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3") 116 c.Assert(err, ErrorMatches, "incorrect number of fields, expected at least 10 but found 9") 117 _, err = osutil.ParseMountInfoEntry("36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 -") 118 c.Assert(err, ErrorMatches, "incorrect number of fields, expected at least 10 but found 8") 119 _, err = osutil.ParseMountInfoEntry("36 35 98:0 /mnt1 /mnt2 rw,noatime master:1") 120 c.Assert(err, ErrorMatches, "incorrect number of fields, expected at least 10 but found 7") 121 _, err = osutil.ParseMountInfoEntry("36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 garbage1 garbage2 garbage3") 122 c.Assert(err, ErrorMatches, "list of optional fields is not terminated properly") 123 _, err = osutil.ParseMountInfoEntry("foo 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue foo") 124 c.Assert(err, ErrorMatches, `cannot parse mount ID: "foo"`) 125 _, err = osutil.ParseMountInfoEntry("36 bar 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue foo") 126 c.Assert(err, ErrorMatches, `cannot parse parent mount ID: "bar"`) 127 _, err = osutil.ParseMountInfoEntry("36 35 froz:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue foo") 128 c.Assert(err, ErrorMatches, `cannot parse device major number: "froz"`) 129 _, err = osutil.ParseMountInfoEntry("36 35 98:bot /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue foo") 130 c.Assert(err, ErrorMatches, `cannot parse device minor number: "bot"`) 131 _, err = osutil.ParseMountInfoEntry("36 35 corrupt /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue foo") 132 c.Assert(err, ErrorMatches, `cannot parse device major:minor number pair: "corrupt"`) 133 } 134 135 // Check that \r is parsed correctly. 136 func (s *mountinfoSuite) TestParseMountInfoEntry5(c *C) { 137 real := "2074 27 0:54 / /tmp/strange\rdir rw,relatime shared:1039 - tmpfs tmpfs rw" 138 canonical := "2074 27 0:54 / /tmp/strange\rdir relatime,rw shared:1039 - tmpfs tmpfs rw" 139 entry, err := osutil.ParseMountInfoEntry(real) 140 c.Assert(err, IsNil) 141 c.Assert(entry.String(), Equals, canonical) 142 c.Assert(entry.MountDir, Equals, "/tmp/strange\rdir") 143 } 144 145 // Test that empty mountinfo is parsed without errors. 146 func (s *mountinfoSuite) TestReadMountInfo1(c *C) { 147 entries, err := osutil.ReadMountInfo(strings.NewReader("")) 148 c.Assert(err, IsNil) 149 c.Assert(entries, HasLen, 0) 150 } 151 152 const mountInfoSample = "" + 153 "19 25 0:18 / /sys rw,nosuid,nodev,noexec,relatime shared:7 - sysfs sysfs rw\n" + 154 "20 25 0:4 / /proc rw,nosuid,nodev,noexec,relatime shared:13 - proc proc rw\n" + 155 "21 25 0:6 / /dev rw,nosuid,relatime shared:2 - devtmpfs udev rw,size=1937696k,nr_inodes=484424,mode=755\n" 156 157 // Test that mountinfo is parsed without errors. 158 func (s *mountinfoSuite) TestReadMountInfo2(c *C) { 159 entries, err := osutil.ReadMountInfo(strings.NewReader(mountInfoSample)) 160 c.Assert(err, IsNil) 161 c.Assert(entries, HasLen, 3) 162 } 163 164 // Test that loading mountinfo from a file works as expected. 165 func (s *mountinfoSuite) TestLoadMountInfo1(c *C) { 166 fname := filepath.Join(c.MkDir(), "mountinfo") 167 err := ioutil.WriteFile(fname, []byte(mountInfoSample), 0644) 168 c.Assert(err, IsNil) 169 restore := osutil.MountInfoMustMock(false) 170 defer restore() 171 restore = osutil.MockProcSelfMountInfoLocation(fname) 172 defer restore() 173 entries, err := osutil.LoadMountInfo() 174 c.Assert(err, IsNil) 175 c.Assert(entries, HasLen, 3) 176 } 177 178 // Test that loading mountinfo from a missing file reports an error. 179 func (s *mountinfoSuite) TestLoadMountInfo2(c *C) { 180 fname := filepath.Join(c.MkDir(), "mountinfo") 181 restore := osutil.MountInfoMustMock(false) 182 defer restore() 183 restore = osutil.MockProcSelfMountInfoLocation(fname) 184 defer restore() 185 _, err := osutil.LoadMountInfo() 186 c.Assert(err, ErrorMatches, "*. no such file or directory") 187 } 188 189 // Test that trying to load mountinfo without permissions reports an error. 190 func (s *mountinfoSuite) TestLoadMountInfo3(c *C) { 191 fname := filepath.Join(c.MkDir(), "mountinfo") 192 err := ioutil.WriteFile(fname, []byte(mountInfoSample), 0644) 193 c.Assert(err, IsNil) 194 err = os.Chmod(fname, 0000) 195 c.Assert(err, IsNil) 196 restore := osutil.MountInfoMustMock(false) 197 defer restore() 198 restore = osutil.MockProcSelfMountInfoLocation(fname) 199 defer restore() 200 _, err = osutil.LoadMountInfo() 201 c.Assert(err, ErrorMatches, "*. permission denied") 202 }