github.com/rigado/snapd@v2.42.5-go-mod+incompatible/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 } 214 215 func (s *entrySuite) TestOptStr(c *C) { 216 e := &osutil.MountEntry{Options: []string{"key=value"}} 217 val, ok := e.OptStr("key") 218 c.Assert(ok, Equals, true) 219 c.Assert(val, Equals, "value") 220 221 val, ok = e.OptStr("missing") 222 c.Assert(ok, Equals, false) 223 c.Assert(val, Equals, "") 224 } 225 226 func (s *entrySuite) TestOptBool(c *C) { 227 e := &osutil.MountEntry{Options: []string{"key"}} 228 val := e.OptBool("key") 229 c.Assert(val, Equals, true) 230 231 val = e.OptBool("missing") 232 c.Assert(val, Equals, false) 233 } 234 235 func (s *entrySuite) TestOptionHelpers(c *C) { 236 c.Assert(osutil.XSnapdUser(1000), Equals, "x-snapd.user=1000") 237 c.Assert(osutil.XSnapdGroup(1000), Equals, "x-snapd.group=1000") 238 c.Assert(osutil.XSnapdMode(0755), Equals, "x-snapd.mode=0755") 239 c.Assert(osutil.XSnapdSymlink("oldname"), Equals, "x-snapd.symlink=oldname") 240 } 241 242 func (s *entrySuite) TestXSnapdMode(c *C) { 243 // Mode has a default value. 244 e := &osutil.MountEntry{} 245 mode, err := e.XSnapdMode() 246 c.Assert(err, IsNil) 247 c.Assert(mode, Equals, os.FileMode(0755)) 248 249 // Mode is parsed from the x-snapd.mode= option. 250 e = &osutil.MountEntry{Options: []string{"x-snapd.mode=0700"}} 251 mode, err = e.XSnapdMode() 252 c.Assert(err, IsNil) 253 c.Assert(mode, Equals, os.FileMode(0700)) 254 255 // Empty value is invalid. 256 e = &osutil.MountEntry{Options: []string{"x-snapd.mode="}} 257 _, err = e.XSnapdMode() 258 c.Assert(err, ErrorMatches, `cannot parse octal file mode from ""`) 259 260 // As well as other bogus values. 261 e = &osutil.MountEntry{Options: []string{"x-snapd.mode=pasta"}} 262 _, err = e.XSnapdMode() 263 c.Assert(err, ErrorMatches, `cannot parse octal file mode from "pasta"`) 264 265 // And even valid values with trailing garbage. 266 e = &osutil.MountEntry{Options: []string{"x-snapd.mode=0700pasta"}} 267 mode, err = e.XSnapdMode() 268 c.Assert(err, ErrorMatches, `cannot parse octal file mode from "0700pasta"`) 269 c.Assert(mode, Equals, os.FileMode(0)) 270 } 271 272 func (s *entrySuite) TestXSnapdUID(c *C) { 273 // User has a default value. 274 e := &osutil.MountEntry{} 275 uid, err := e.XSnapdUID() 276 c.Assert(err, IsNil) 277 c.Assert(uid, Equals, uint64(0)) 278 279 // User is parsed from the x-snapd.uid= option. 280 e = &osutil.MountEntry{Options: []string{"x-snapd.uid=root"}} 281 uid, err = e.XSnapdUID() 282 c.Assert(err, ErrorMatches, `cannot parse user name "root"`) 283 c.Assert(uid, Equals, uint64(math.MaxUint64)) 284 285 // Numeric names are used as-is. 286 e = &osutil.MountEntry{Options: []string{"x-snapd.uid=123"}} 287 uid, err = e.XSnapdUID() 288 c.Assert(err, IsNil) 289 c.Assert(uid, Equals, uint64(123)) 290 291 // And even valid values with trailing garbage. 292 e = &osutil.MountEntry{Options: []string{"x-snapd.uid=0bogus"}} 293 uid, err = e.XSnapdUID() 294 c.Assert(err, ErrorMatches, `cannot parse user name "0bogus"`) 295 c.Assert(uid, Equals, uint64(math.MaxUint64)) 296 } 297 298 func (s *entrySuite) TestXSnapdGID(c *C) { 299 // Group has a default value. 300 e := &osutil.MountEntry{} 301 gid, err := e.XSnapdGID() 302 c.Assert(err, IsNil) 303 c.Assert(gid, Equals, uint64(0)) 304 305 e = &osutil.MountEntry{Options: []string{"x-snapd.gid=root"}} 306 gid, err = e.XSnapdGID() 307 c.Assert(err, ErrorMatches, `cannot parse group name "root"`) 308 c.Assert(gid, Equals, uint64(math.MaxUint64)) 309 310 // Numeric names are used as-is. 311 e = &osutil.MountEntry{Options: []string{"x-snapd.gid=456"}} 312 gid, err = e.XSnapdGID() 313 c.Assert(err, IsNil) 314 c.Assert(gid, Equals, uint64(456)) 315 316 // And even valid values with trailing garbage. 317 e = &osutil.MountEntry{Options: []string{"x-snapd.gid=0bogus"}} 318 gid, err = e.XSnapdGID() 319 c.Assert(err, ErrorMatches, `cannot parse group name "0bogus"`) 320 c.Assert(gid, Equals, uint64(math.MaxUint64)) 321 } 322 323 func (s *entrySuite) TestXSnapdEntryID(c *C) { 324 // Entry ID is optional and defaults to the mount point. 325 e := &osutil.MountEntry{Dir: "/foo"} 326 c.Assert(e.XSnapdEntryID(), Equals, "/foo") 327 328 // Entry ID is parsed from the x-snapd.id= option. 329 e = &osutil.MountEntry{Dir: "/foo", Options: []string{"x-snapd.id=foo"}} 330 c.Assert(e.XSnapdEntryID(), Equals, "foo") 331 } 332 333 func (s *entrySuite) TestXSnapdNeededBy(c *C) { 334 // The needed-by attribute is optional. 335 e := &osutil.MountEntry{} 336 c.Assert(e.XSnapdNeededBy(), Equals, "") 337 338 // The needed-by attribute parsed from the x-snapd.needed-by= option. 339 e = &osutil.MountEntry{Options: []string{"x-snap.id=foo", "x-snapd.needed-by=bar"}} 340 c.Assert(e.XSnapdNeededBy(), Equals, "bar") 341 342 // There's a helper function that returns this option string. 343 c.Assert(osutil.XSnapdNeededBy("foo"), Equals, "x-snapd.needed-by=foo") 344 } 345 346 func (s *entrySuite) TestXSnapdSynthetic(c *C) { 347 // Entries are not synthetic unless tagged as such. 348 e := &osutil.MountEntry{} 349 c.Assert(e.XSnapdSynthetic(), Equals, false) 350 351 // Tagging is done with x-snapd.synthetic option. 352 e = &osutil.MountEntry{Options: []string{"x-snapd.synthetic"}} 353 c.Assert(e.XSnapdSynthetic(), Equals, true) 354 355 // There's a helper function that returns this option string. 356 c.Assert(osutil.XSnapdSynthetic(), Equals, "x-snapd.synthetic") 357 } 358 359 func (s *entrySuite) TestXSnapdOrigin(c *C) { 360 // Entries have no origin by default. 361 e := &osutil.MountEntry{} 362 c.Assert(e.XSnapdOrigin(), Equals, "") 363 364 // Origin can be indicated with the x-snapd.origin= option. 365 e = &osutil.MountEntry{Options: []string{osutil.XSnapdOriginLayout()}} 366 c.Assert(e.XSnapdOrigin(), Equals, "layout") 367 368 // There's a helper function that returns this option string. 369 c.Assert(osutil.XSnapdOriginLayout(), Equals, "x-snapd.origin=layout") 370 371 // Origin can also indicate a parallel snap instance setup 372 e = &osutil.MountEntry{Options: []string{osutil.XSnapdOriginOvername()}} 373 c.Assert(e.XSnapdOrigin(), Equals, "overname") 374 c.Assert(osutil.XSnapdOriginOvername(), Equals, "x-snapd.origin=overname") 375 } 376 377 func (s *entrySuite) TestXSnapdDetach(c *C) { 378 // Entries are not detached by default. 379 e := &osutil.MountEntry{} 380 c.Assert(e.XSnapdDetach(), Equals, false) 381 382 // Detach can be requested with the x-snapd.detach option. 383 e = &osutil.MountEntry{Options: []string{osutil.XSnapdDetach()}} 384 c.Assert(e.XSnapdDetach(), Equals, true) 385 386 // There's a helper function that returns this option string. 387 c.Assert(osutil.XSnapdDetach(), Equals, "x-snapd.detach") 388 } 389 390 func (s *entrySuite) TestXSnapdKind(c *C) { 391 // Entries have a kind (directory, file or symlink). Directory is spelled 392 // as an empty string though, for backwards compatibility. 393 e := &osutil.MountEntry{} 394 c.Assert(e.XSnapdKind(), Equals, "") 395 396 // A bind mount entry can refer to a file using the x-snapd.kind=file option string. 397 e = &osutil.MountEntry{Options: []string{osutil.XSnapdKindFile()}} 398 c.Assert(e.XSnapdKind(), Equals, "file") 399 400 // There's a helper function that returns this option string. 401 c.Assert(osutil.XSnapdKindFile(), Equals, "x-snapd.kind=file") 402 403 // A mount entry can create a symlink by using the x-snapd.kind=symlink option string. 404 e = &osutil.MountEntry{Options: []string{osutil.XSnapdKindSymlink()}} 405 c.Assert(e.XSnapdKind(), Equals, "symlink") 406 407 // There's a helper function that returns this option string. 408 c.Assert(osutil.XSnapdKindSymlink(), Equals, "x-snapd.kind=symlink") 409 } 410 411 func (s *entrySuite) TestXSnapdSymlink(c *C) { 412 // Entries without the x-snapd.symlink key return an empty string 413 e := &osutil.MountEntry{} 414 c.Assert(e.XSnapdSymlink(), Equals, "") 415 416 // A mount entry can list a symlink target 417 e = &osutil.MountEntry{Options: []string{osutil.XSnapdSymlink("target")}} 418 c.Assert(e.XSnapdSymlink(), Equals, "target") 419 } 420 421 func (s *entrySuite) TestXSnapdIgnoreMissing(c *C) { 422 // By default entries will not have the ignore missing flag set 423 e := &osutil.MountEntry{} 424 c.Assert(e.XSnapdIgnoreMissing(), Equals, false) 425 426 // A mount entry can specify that it should be ignored if the 427 // mount source or target are missing with the 428 // x-snapd.ignore-missing option. 429 e = &osutil.MountEntry{Options: []string{osutil.XSnapdIgnoreMissing()}} 430 c.Assert(e.XSnapdIgnoreMissing(), Equals, true) 431 432 // There's a helper function that returns this option string. 433 c.Assert(osutil.XSnapdIgnoreMissing(), Equals, "x-snapd.ignore-missing") 434 }