github.com/stolowski/snapd@v0.0.0-20210407085831-115137ce5a22/osutil/mountentry_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 "math" 24 "os" 25 "syscall" 26 27 . "gopkg.in/check.v1" 28 29 "github.com/snapcore/snapd/osutil" 30 ) 31 32 type entrySuite struct{} 33 34 var _ = Suite(&entrySuite{}) 35 36 func (s *entrySuite) TestString(c *C) { 37 ent0 := osutil.MountEntry{} 38 c.Assert(ent0.String(), Equals, "none none none defaults 0 0") 39 ent1 := osutil.MountEntry{ 40 Name: "/var/snap/foo/common", 41 Dir: "/var/snap/bar/common", 42 Options: []string{"bind"}, 43 } 44 c.Assert(ent1.String(), Equals, 45 "/var/snap/foo/common /var/snap/bar/common none bind 0 0") 46 ent2 := osutil.MountEntry{ 47 Name: "/dev/sda5", 48 Dir: "/media/foo", 49 Type: "ext4", 50 Options: []string{"rw,noatime"}, 51 } 52 c.Assert(ent2.String(), Equals, "/dev/sda5 /media/foo ext4 rw,noatime 0 0") 53 ent3 := osutil.MountEntry{ 54 Name: "/dev/sda5", 55 Dir: "/media/My Files", 56 Type: "ext4", 57 Options: []string{"rw,noatime"}, 58 } 59 c.Assert(ent3.String(), Equals, `/dev/sda5 /media/My\040Files ext4 rw,noatime 0 0`) 60 } 61 62 func (s *entrySuite) TestEqual(c *C) { 63 var a, b *osutil.MountEntry 64 a = &osutil.MountEntry{} 65 b = &osutil.MountEntry{} 66 c.Assert(a.Equal(b), Equals, true) 67 a = &osutil.MountEntry{Dir: "foo"} 68 b = &osutil.MountEntry{Dir: "foo"} 69 c.Assert(a.Equal(b), Equals, true) 70 a = &osutil.MountEntry{Options: []string{"ro"}} 71 b = &osutil.MountEntry{Options: []string{"ro"}} 72 c.Assert(a.Equal(b), Equals, true) 73 a = &osutil.MountEntry{Dir: "foo"} 74 b = &osutil.MountEntry{Dir: "bar"} 75 c.Assert(a.Equal(b), Equals, false) 76 a = &osutil.MountEntry{} 77 b = &osutil.MountEntry{Options: []string{"ro"}} 78 c.Assert(a.Equal(b), Equals, false) 79 a = &osutil.MountEntry{Options: []string{"ro"}} 80 b = &osutil.MountEntry{Options: []string{"rw"}} 81 c.Assert(a.Equal(b), Equals, false) 82 } 83 84 // Test that typical fstab entry is parsed correctly. 85 func (s *entrySuite) TestParseMountEntry1(c *C) { 86 e, err := osutil.ParseMountEntry("UUID=394f32c0-1f94-4005-9717-f9ab4a4b570b / ext4 errors=remount-ro 0 1") 87 c.Assert(err, IsNil) 88 c.Assert(e.Name, Equals, "UUID=394f32c0-1f94-4005-9717-f9ab4a4b570b") 89 c.Assert(e.Dir, Equals, "/") 90 c.Assert(e.Type, Equals, "ext4") 91 c.Assert(e.Options, DeepEquals, []string{"errors=remount-ro"}) 92 c.Assert(e.DumpFrequency, Equals, 0) 93 c.Assert(e.CheckPassNumber, Equals, 1) 94 95 e, err = osutil.ParseMountEntry("none /tmp tmpfs") 96 c.Assert(err, IsNil) 97 c.Assert(e.Name, Equals, "none") 98 c.Assert(e.Dir, Equals, "/tmp") 99 c.Assert(e.Type, Equals, "tmpfs") 100 c.Assert(e.Options, IsNil) 101 c.Assert(e.DumpFrequency, Equals, 0) 102 c.Assert(e.CheckPassNumber, Equals, 0) 103 } 104 105 // Test that hash inside a field value is supported. 106 func (s *entrySuite) TestHashInFieldValue(c *C) { 107 e, err := osutil.ParseMountEntry("mhddfs#/mnt/dir1,/mnt/dir2 /mnt/dir fuse defaults,allow_other 0 0") 108 c.Assert(err, IsNil) 109 c.Assert(e.Name, Equals, "mhddfs#/mnt/dir1,/mnt/dir2") 110 c.Assert(e.Dir, Equals, "/mnt/dir") 111 c.Assert(e.Type, Equals, "fuse") 112 c.Assert(e.Options, DeepEquals, []string{"defaults", "allow_other"}) 113 c.Assert(e.DumpFrequency, Equals, 0) 114 c.Assert(e.CheckPassNumber, Equals, 0) 115 } 116 117 // Test that options are parsed correctly 118 func (s *entrySuite) TestParseMountEntry2(c *C) { 119 e, err := osutil.ParseMountEntry("name dir type options,comma,separated 0 0") 120 c.Assert(err, IsNil) 121 c.Assert(e.Name, Equals, "name") 122 c.Assert(e.Dir, Equals, "dir") 123 c.Assert(e.Type, Equals, "type") 124 c.Assert(e.Options, DeepEquals, []string{"options", "comma", "separated"}) 125 c.Assert(e.DumpFrequency, Equals, 0) 126 c.Assert(e.CheckPassNumber, Equals, 0) 127 } 128 129 // Test that whitespace escape codes are honored 130 func (s *entrySuite) TestParseMountEntry3(c *C) { 131 e, err := osutil.ParseMountEntry(`na\040me d\011ir ty\012pe optio\134ns 0 0`) 132 c.Assert(err, IsNil) 133 c.Assert(e.Name, Equals, "na me") 134 c.Assert(e.Dir, Equals, "d\tir") 135 c.Assert(e.Type, Equals, "ty\npe") 136 c.Assert(e.Options, DeepEquals, []string{`optio\ns`}) 137 c.Assert(e.DumpFrequency, Equals, 0) 138 c.Assert(e.CheckPassNumber, Equals, 0) 139 } 140 141 // Test that number of fields is checked 142 func (s *entrySuite) TestParseMountEntry4(c *C) { 143 for _, s := range []string{ 144 "", "1", "1 2" /* skip 3, 4, 5 and 6 fields (valid case) */, "1 2 3 4 5 6 7", 145 } { 146 _, err := osutil.ParseMountEntry(s) 147 c.Assert(err, ErrorMatches, "expected between 3 and 6 fields, found [01237]") 148 } 149 } 150 151 // Test that integers are parsed and error checked 152 func (s *entrySuite) TestParseMountEntry5(c *C) { 153 _, err := osutil.ParseMountEntry("name dir type options foo 0") 154 c.Assert(err, ErrorMatches, "cannot parse dump frequency: .*") 155 _, err = osutil.ParseMountEntry("name dir type options 0 foo") 156 c.Assert(err, ErrorMatches, "cannot parse check pass number: .*") 157 } 158 159 // Test that last two integer fields default to zero if not present. 160 func (s *entrySuite) TestParseMountEntry6(c *C) { 161 e, err := osutil.ParseMountEntry("name dir type options") 162 c.Assert(err, IsNil) 163 c.Assert(e.DumpFrequency, Equals, 0) 164 c.Assert(e.CheckPassNumber, Equals, 0) 165 166 e, err = osutil.ParseMountEntry("name dir type options 5") 167 c.Assert(err, IsNil) 168 c.Assert(e.DumpFrequency, Equals, 5) 169 c.Assert(e.CheckPassNumber, Equals, 0) 170 171 e, err = osutil.ParseMountEntry("name dir type options 5 7") 172 c.Assert(err, IsNil) 173 c.Assert(e.DumpFrequency, Equals, 5) 174 c.Assert(e.CheckPassNumber, Equals, 7) 175 } 176 177 // Test (string) options -> (int) flag conversion code. 178 func (s *entrySuite) TestMountOptsToFlags(c *C) { 179 flags, err := osutil.MountOptsToFlags(nil) 180 c.Assert(err, IsNil) 181 c.Assert(flags, Equals, 0) 182 flags, err = osutil.MountOptsToFlags([]string{"ro", "nodev", "nosuid"}) 183 c.Assert(err, IsNil) 184 c.Assert(flags, Equals, syscall.MS_RDONLY|syscall.MS_NODEV|syscall.MS_NOSUID) 185 _, err = osutil.MountOptsToFlags([]string{"bogus"}) 186 c.Assert(err, ErrorMatches, `unsupported mount option: "bogus"`) 187 // The x-snapd-prefix is reserved for non-kernel parameters that do not 188 // translate to kernel level mount flags. This is similar to systemd or 189 // udisks that use fstab options to convey additional data. 190 flags, err = osutil.MountOptsToFlags([]string{"x-snapd.foo"}) 191 c.Assert(err, IsNil) 192 c.Assert(flags, Equals, 0) 193 } 194 195 // Test (string) options -> (int, unparsed) flag conversion code. 196 func (s *entrySuite) TestMountOptsToCommonFlags(c *C) { 197 flags, unparsed := osutil.MountOptsToCommonFlags(nil) 198 c.Assert(flags, Equals, 0) 199 c.Assert(unparsed, HasLen, 0) 200 flags, unparsed = osutil.MountOptsToCommonFlags([]string{"ro", "nodev", "nosuid"}) 201 c.Assert(flags, Equals, syscall.MS_RDONLY|syscall.MS_NODEV|syscall.MS_NOSUID) 202 c.Assert(unparsed, HasLen, 0) 203 flags, unparsed = osutil.MountOptsToCommonFlags([]string{"bogus"}) 204 c.Assert(flags, Equals, 0) 205 c.Assert(unparsed, DeepEquals, []string{"bogus"}) 206 // The x-snapd-prefix is reserved for non-kernel parameters that do not 207 // translate to kernel level mount flags. This is similar to systemd or 208 // udisks that use fstab options to convey additional data. Those are not 209 // returned as "unparsed" as we don't want to pass them to the kernel. 210 flags, unparsed = osutil.MountOptsToCommonFlags([]string{"x-snapd.foo"}) 211 c.Assert(flags, Equals, 0) 212 c.Assert(unparsed, HasLen, 0) 213 // The "rw" flag is recognized but doesn't translate to an actual value 214 // since read-write is the implicit default and there are no kernel level 215 // flags to express it. 216 flags, unparsed = osutil.MountOptsToCommonFlags([]string{"rw"}) 217 c.Assert(flags, Equals, 0) 218 c.Assert(unparsed, DeepEquals, []string(nil)) 219 } 220 221 func (s *entrySuite) TestOptStr(c *C) { 222 e := &osutil.MountEntry{Options: []string{"key=value"}} 223 val, ok := e.OptStr("key") 224 c.Assert(ok, Equals, true) 225 c.Assert(val, Equals, "value") 226 227 val, ok = e.OptStr("missing") 228 c.Assert(ok, Equals, false) 229 c.Assert(val, Equals, "") 230 } 231 232 func (s *entrySuite) TestOptBool(c *C) { 233 e := &osutil.MountEntry{Options: []string{"key"}} 234 val := e.OptBool("key") 235 c.Assert(val, Equals, true) 236 237 val = e.OptBool("missing") 238 c.Assert(val, Equals, false) 239 } 240 241 func (s *entrySuite) TestOptionHelpers(c *C) { 242 c.Assert(osutil.XSnapdUser(1000), Equals, "x-snapd.user=1000") 243 c.Assert(osutil.XSnapdGroup(1000), Equals, "x-snapd.group=1000") 244 c.Assert(osutil.XSnapdMode(0755), Equals, "x-snapd.mode=0755") 245 c.Assert(osutil.XSnapdSymlink("oldname"), Equals, "x-snapd.symlink=oldname") 246 } 247 248 func (s *entrySuite) TestXSnapdMode(c *C) { 249 // Mode has a default value. 250 e := &osutil.MountEntry{} 251 mode, err := e.XSnapdMode() 252 c.Assert(err, IsNil) 253 c.Assert(mode, Equals, os.FileMode(0755)) 254 255 // Mode is parsed from the x-snapd.mode= option. 256 e = &osutil.MountEntry{Options: []string{"x-snapd.mode=0700"}} 257 mode, err = e.XSnapdMode() 258 c.Assert(err, IsNil) 259 c.Assert(mode, Equals, os.FileMode(0700)) 260 261 // Empty value is invalid. 262 e = &osutil.MountEntry{Options: []string{"x-snapd.mode="}} 263 _, err = e.XSnapdMode() 264 c.Assert(err, ErrorMatches, `cannot parse octal file mode from ""`) 265 266 // As well as other bogus values. 267 e = &osutil.MountEntry{Options: []string{"x-snapd.mode=pasta"}} 268 _, err = e.XSnapdMode() 269 c.Assert(err, ErrorMatches, `cannot parse octal file mode from "pasta"`) 270 271 // And even valid values with trailing garbage. 272 e = &osutil.MountEntry{Options: []string{"x-snapd.mode=0700pasta"}} 273 mode, err = e.XSnapdMode() 274 c.Assert(err, ErrorMatches, `cannot parse octal file mode from "0700pasta"`) 275 c.Assert(mode, Equals, os.FileMode(0)) 276 } 277 278 func (s *entrySuite) TestXSnapdUID(c *C) { 279 // User has a default value. 280 e := &osutil.MountEntry{} 281 uid, err := e.XSnapdUID() 282 c.Assert(err, IsNil) 283 c.Assert(uid, Equals, uint64(0)) 284 285 // User is parsed from the x-snapd.uid= option. 286 e = &osutil.MountEntry{Options: []string{"x-snapd.uid=root"}} 287 uid, err = e.XSnapdUID() 288 c.Assert(err, ErrorMatches, `cannot parse user name "root"`) 289 c.Assert(uid, Equals, uint64(math.MaxUint64)) 290 291 // Numeric names are used as-is. 292 e = &osutil.MountEntry{Options: []string{"x-snapd.uid=123"}} 293 uid, err = e.XSnapdUID() 294 c.Assert(err, IsNil) 295 c.Assert(uid, Equals, uint64(123)) 296 297 // And even valid values with trailing garbage. 298 e = &osutil.MountEntry{Options: []string{"x-snapd.uid=0bogus"}} 299 uid, err = e.XSnapdUID() 300 c.Assert(err, ErrorMatches, `cannot parse user name "0bogus"`) 301 c.Assert(uid, Equals, uint64(math.MaxUint64)) 302 } 303 304 func (s *entrySuite) TestXSnapdGID(c *C) { 305 // Group has a default value. 306 e := &osutil.MountEntry{} 307 gid, err := e.XSnapdGID() 308 c.Assert(err, IsNil) 309 c.Assert(gid, Equals, uint64(0)) 310 311 e = &osutil.MountEntry{Options: []string{"x-snapd.gid=root"}} 312 gid, err = e.XSnapdGID() 313 c.Assert(err, ErrorMatches, `cannot parse group name "root"`) 314 c.Assert(gid, Equals, uint64(math.MaxUint64)) 315 316 // Numeric names are used as-is. 317 e = &osutil.MountEntry{Options: []string{"x-snapd.gid=456"}} 318 gid, err = e.XSnapdGID() 319 c.Assert(err, IsNil) 320 c.Assert(gid, Equals, uint64(456)) 321 322 // And even valid values with trailing garbage. 323 e = &osutil.MountEntry{Options: []string{"x-snapd.gid=0bogus"}} 324 gid, err = e.XSnapdGID() 325 c.Assert(err, ErrorMatches, `cannot parse group name "0bogus"`) 326 c.Assert(gid, Equals, uint64(math.MaxUint64)) 327 } 328 329 func (s *entrySuite) TestXSnapdEntryID(c *C) { 330 // Entry ID is optional and defaults to the mount point. 331 e := &osutil.MountEntry{Dir: "/foo"} 332 c.Assert(e.XSnapdEntryID(), Equals, "/foo") 333 334 // Entry ID is parsed from the x-snapd.id= option. 335 e = &osutil.MountEntry{Dir: "/foo", Options: []string{"x-snapd.id=foo"}} 336 c.Assert(e.XSnapdEntryID(), Equals, "foo") 337 } 338 339 func (s *entrySuite) TestXSnapdNeededBy(c *C) { 340 // The needed-by attribute is optional. 341 e := &osutil.MountEntry{} 342 c.Assert(e.XSnapdNeededBy(), Equals, "") 343 344 // The needed-by attribute parsed from the x-snapd.needed-by= option. 345 e = &osutil.MountEntry{Options: []string{"x-snap.id=foo", "x-snapd.needed-by=bar"}} 346 c.Assert(e.XSnapdNeededBy(), Equals, "bar") 347 348 // There's a helper function that returns this option string. 349 c.Assert(osutil.XSnapdNeededBy("foo"), Equals, "x-snapd.needed-by=foo") 350 } 351 352 func (s *entrySuite) TestXSnapdSynthetic(c *C) { 353 // Entries are not synthetic unless tagged as such. 354 e := &osutil.MountEntry{} 355 c.Assert(e.XSnapdSynthetic(), Equals, false) 356 357 // Tagging is done with x-snapd.synthetic option. 358 e = &osutil.MountEntry{Options: []string{"x-snapd.synthetic"}} 359 c.Assert(e.XSnapdSynthetic(), Equals, true) 360 361 // There's a helper function that returns this option string. 362 c.Assert(osutil.XSnapdSynthetic(), Equals, "x-snapd.synthetic") 363 } 364 365 func (s *entrySuite) TestXSnapdOrigin(c *C) { 366 // Entries have no origin by default. 367 e := &osutil.MountEntry{} 368 c.Assert(e.XSnapdOrigin(), Equals, "") 369 370 // Origin can be indicated with the x-snapd.origin= option. 371 e = &osutil.MountEntry{Options: []string{osutil.XSnapdOriginLayout()}} 372 c.Assert(e.XSnapdOrigin(), Equals, "layout") 373 374 // There's a helper function that returns this option string. 375 c.Assert(osutil.XSnapdOriginLayout(), Equals, "x-snapd.origin=layout") 376 377 // Origin can also indicate a parallel snap instance setup 378 e = &osutil.MountEntry{Options: []string{osutil.XSnapdOriginOvername()}} 379 c.Assert(e.XSnapdOrigin(), Equals, "overname") 380 c.Assert(osutil.XSnapdOriginOvername(), Equals, "x-snapd.origin=overname") 381 } 382 383 func (s *entrySuite) TestXSnapdDetach(c *C) { 384 // Entries are not detached by default. 385 e := &osutil.MountEntry{} 386 c.Assert(e.XSnapdDetach(), Equals, false) 387 388 // Detach can be requested with the x-snapd.detach option. 389 e = &osutil.MountEntry{Options: []string{osutil.XSnapdDetach()}} 390 c.Assert(e.XSnapdDetach(), Equals, true) 391 392 // There's a helper function that returns this option string. 393 c.Assert(osutil.XSnapdDetach(), Equals, "x-snapd.detach") 394 } 395 396 func (s *entrySuite) TestXSnapdKind(c *C) { 397 // Entries have a kind (directory, file or symlink). Directory is spelled 398 // as an empty string though, for backwards compatibility. 399 e := &osutil.MountEntry{} 400 c.Assert(e.XSnapdKind(), Equals, "") 401 402 // A bind mount entry can refer to a file using the x-snapd.kind=file option string. 403 e = &osutil.MountEntry{Options: []string{osutil.XSnapdKindFile()}} 404 c.Assert(e.XSnapdKind(), Equals, "file") 405 406 // There's a helper function that returns this option string. 407 c.Assert(osutil.XSnapdKindFile(), Equals, "x-snapd.kind=file") 408 409 // A mount entry can create a symlink by using the x-snapd.kind=symlink option string. 410 e = &osutil.MountEntry{Options: []string{osutil.XSnapdKindSymlink()}} 411 c.Assert(e.XSnapdKind(), Equals, "symlink") 412 413 // There's a helper function that returns this option string. 414 c.Assert(osutil.XSnapdKindSymlink(), Equals, "x-snapd.kind=symlink") 415 } 416 417 func (s *entrySuite) TestXSnapdSymlink(c *C) { 418 // Entries without the x-snapd.symlink key return an empty string 419 e := &osutil.MountEntry{} 420 c.Assert(e.XSnapdSymlink(), Equals, "") 421 422 // A mount entry can list a symlink target 423 e = &osutil.MountEntry{Options: []string{osutil.XSnapdSymlink("target")}} 424 c.Assert(e.XSnapdSymlink(), Equals, "target") 425 } 426 427 func (s *entrySuite) TestXSnapdIgnoreMissing(c *C) { 428 // By default entries will not have the ignore missing flag set 429 e := &osutil.MountEntry{} 430 c.Assert(e.XSnapdIgnoreMissing(), Equals, false) 431 432 // A mount entry can specify that it should be ignored if the 433 // mount source or target are missing with the 434 // x-snapd.ignore-missing option. 435 e = &osutil.MountEntry{Options: []string{osutil.XSnapdIgnoreMissing()}} 436 c.Assert(e.XSnapdIgnoreMissing(), Equals, true) 437 438 // There's a helper function that returns this option string. 439 c.Assert(osutil.XSnapdIgnoreMissing(), Equals, "x-snapd.ignore-missing") 440 }