gitee.com/mysnapcore/mysnapd@v0.1.0/cmd/snap-update-ns/change_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 main_test 21 22 import ( 23 "errors" 24 "os" 25 "path/filepath" 26 "syscall" 27 28 . "gopkg.in/check.v1" 29 30 update "gitee.com/mysnapcore/mysnapd/cmd/snap-update-ns" 31 "gitee.com/mysnapcore/mysnapd/dirs" 32 "gitee.com/mysnapcore/mysnapd/osutil" 33 "gitee.com/mysnapcore/mysnapd/strutil" 34 "gitee.com/mysnapcore/mysnapd/testutil" 35 ) 36 37 type changeSuite struct { 38 testutil.BaseTest 39 sys *testutil.SyscallRecorder 40 as *update.Assumptions 41 } 42 43 var ( 44 errTesting = errors.New("testing") 45 ) 46 47 var _ = Suite(&changeSuite{}) 48 49 func (s *changeSuite) SetUpTest(c *C) { 50 s.BaseTest.SetUpTest(c) 51 // This isolates us from host's experimental settings. 52 dirs.SetRootDir(c.MkDir()) 53 // Mock and record system interactions. 54 s.sys = &testutil.SyscallRecorder{} 55 s.BaseTest.AddCleanup(update.MockSystemCalls(s.sys)) 56 s.as = &update.Assumptions{} 57 } 58 59 func (s *changeSuite) TearDownTest(c *C) { 60 s.BaseTest.TearDownTest(c) 61 s.sys.CheckForStrayDescriptors(c) 62 dirs.SetRootDir("") 63 } 64 65 func (s *changeSuite) TestFakeFileInfo(c *C) { 66 c.Assert(testutil.FileInfoDir.IsDir(), Equals, true) 67 c.Assert(testutil.FileInfoFile.IsDir(), Equals, false) 68 c.Assert(testutil.FileInfoSymlink.IsDir(), Equals, false) 69 } 70 71 func (s *changeSuite) TestString(c *C) { 72 change := update.Change{ 73 Entry: osutil.MountEntry{Dir: "/a/b", Name: "/dev/sda1"}, 74 Action: update.Mount, 75 } 76 c.Assert(change.String(), Equals, "mount (/dev/sda1 /a/b none defaults 0 0)") 77 } 78 79 // When there are no profiles we don't do anything. 80 func (s *changeSuite) TestNeededChangesNoProfiles(c *C) { 81 current := &osutil.MountProfile{} 82 desired := &osutil.MountProfile{} 83 changes := update.NeededChanges(current, desired) 84 c.Assert(changes, IsNil) 85 } 86 87 // When the profiles are the same we don't do anything. 88 func (s *changeSuite) TestNeededChangesNoChange(c *C) { 89 current := &osutil.MountProfile{Entries: []osutil.MountEntry{{Dir: "/common/stuff"}}} 90 desired := &osutil.MountProfile{Entries: []osutil.MountEntry{{Dir: "/common/stuff"}}} 91 changes := update.NeededChanges(current, desired) 92 c.Assert(changes, DeepEquals, []*update.Change{ 93 {Entry: osutil.MountEntry{Dir: "/common/stuff"}, Action: update.Keep}, 94 }) 95 } 96 97 // When the content interface is connected we should mount the new entry. 98 func (s *changeSuite) TestNeededChangesTrivialMount(c *C) { 99 current := &osutil.MountProfile{} 100 desired := &osutil.MountProfile{Entries: []osutil.MountEntry{{Dir: "/common/stuff"}}} 101 changes := update.NeededChanges(current, desired) 102 c.Assert(changes, DeepEquals, []*update.Change{ 103 {Entry: desired.Entries[0], Action: update.Mount}, 104 }) 105 } 106 107 // When the content interface is disconnected we should unmount the mounted entry. 108 func (s *changeSuite) TestNeededChangesTrivialUnmount(c *C) { 109 current := &osutil.MountProfile{Entries: []osutil.MountEntry{{Dir: "/common/stuff"}}} 110 desired := &osutil.MountProfile{} 111 changes := update.NeededChanges(current, desired) 112 c.Assert(changes, DeepEquals, []*update.Change{ 113 {Entry: current.Entries[0], Action: update.Unmount}, 114 }) 115 } 116 117 // When the rootfs was setup by snap-confine, don't touch it 118 func (s *changeSuite) TestNeededChangesKeepRootfs(c *C) { 119 current := &osutil.MountProfile{Entries: []osutil.MountEntry{ 120 {Dir: "/", Options: []string{"x-snapd.origin=rootfs"}}, 121 }} 122 desired := &osutil.MountProfile{Entries: []osutil.MountEntry{{Dir: "/common/stuff"}}} 123 changes := update.NeededChanges(current, desired) 124 c.Assert(changes, DeepEquals, []*update.Change{ 125 {Entry: current.Entries[0], Action: update.Keep}, 126 {Entry: desired.Entries[0], Action: update.Mount}, 127 }) 128 } 129 130 // When the rootfs was *not* setup by snap-confine, it's umounted 131 func (s *changeSuite) TestNeededChangesUmountRootfs(c *C) { 132 current := &osutil.MountProfile{Entries: []osutil.MountEntry{ 133 // Like the test above, but without "x-snapd.origin=rootfs" 134 {Dir: "/"}, 135 }} 136 desired := &osutil.MountProfile{Entries: []osutil.MountEntry{{Dir: "/common/stuff"}}} 137 changes := update.NeededChanges(current, desired) 138 c.Assert(changes, DeepEquals, []*update.Change{ 139 {Entry: current.Entries[0], Action: update.Unmount}, 140 {Entry: desired.Entries[0], Action: update.Mount}, 141 }) 142 } 143 144 // When umounting we unmount children before parents. 145 func (s *changeSuite) TestNeededChangesUnmountOrder(c *C) { 146 current := &osutil.MountProfile{Entries: []osutil.MountEntry{ 147 {Dir: "/common/stuff"}, 148 {Dir: "/common/stuff/extra"}, 149 }} 150 desired := &osutil.MountProfile{} 151 changes := update.NeededChanges(current, desired) 152 c.Assert(changes, DeepEquals, []*update.Change{ 153 {Entry: osutil.MountEntry{Dir: "/common/stuff/extra"}, Action: update.Unmount}, 154 {Entry: osutil.MountEntry{Dir: "/common/stuff"}, Action: update.Unmount}, 155 }) 156 } 157 158 // When mounting we mount the parents before the children. 159 func (s *changeSuite) TestNeededChangesMountOrder(c *C) { 160 existingDirectories := []string{} 161 162 restore := update.MockIsDirectory(func(path string) bool { 163 return strutil.ListContains(existingDirectories, path) 164 }) 165 defer restore() 166 167 current := &osutil.MountProfile{} 168 desired := &osutil.MountProfile{Entries: []osutil.MountEntry{ 169 {Dir: "/c/stuff/dir/symlink1"}, 170 {Dir: "/c/stuff/dir/file2", Options: []string{"x-snapd.kind=file"}}, 171 {Dir: "/c/stuff/dir"}, 172 {Dir: "/c/stuff"}, 173 {Dir: "/c/stuff/dir/file1", Options: []string{"x-snapd.kind=file"}}, 174 }} 175 176 for _, testData := range []struct { 177 existingDirs []string 178 expectedOrder []string 179 }{ 180 { 181 existingDirs: []string{"/c"}, 182 expectedOrder: []string{"/c/stuff", "/c/stuff/dir", "/c/stuff/dir/file1", "/c/stuff/dir/file2", "/c/stuff/dir/symlink1"}, 183 }, 184 { 185 existingDirs: []string{"/c", "/c/stuff"}, 186 expectedOrder: []string{"/c/stuff", "/c/stuff/dir", "/c/stuff/dir/file1", "/c/stuff/dir/file2", "/c/stuff/dir/symlink1"}, 187 }, 188 { 189 existingDirs: []string{"/c", "/c/stuff", "/c/stuff/dir"}, 190 expectedOrder: []string{"/c/stuff", "/c/stuff/dir", "/c/stuff/dir/file1", "/c/stuff/dir/file2", "/c/stuff/dir/symlink1"}, 191 }, 192 } { 193 existingDirectories = testData.existingDirs 194 changes := update.NeededChanges(current, desired) 195 196 // Check that every change is sane, and extract their path in order 197 actualOrder := make([]string, 0, len(changes)) 198 for _, change := range changes { 199 c.Check(change.Action, Equals, update.Mount) 200 actualOrder = append(actualOrder, change.Entry.Dir) 201 } 202 203 c.Check(actualOrder, DeepEquals, testData.expectedOrder, 204 Commentf("Existing dirs: %q", existingDirectories)) 205 } 206 } 207 208 func (s *changeSuite) TestNeededChangesMountFromReal(c *C) { 209 existingDirectories := []string{} 210 211 restore := update.MockIsDirectory(func(path string) bool { 212 return strutil.ListContains(existingDirectories, path) 213 }) 214 defer restore() 215 216 current := &osutil.MountProfile{} 217 desired := &osutil.MountProfile{Entries: []osutil.MountEntry{ 218 {Dir: "/snap/test-snapd-layout/x1/fooo-top", Options: []string{"x-snapd.origin=layout"}}, 219 {Dir: "/snap/test-snapd-layout/x1/fooo/deeper", Options: []string{"x-snapd.origin=layout"}}, 220 {Dir: "/usr/lib/x86_64-linux-gnu/wpe-webkit-1.0", Options: []string{"x-snapd.origin=layout"}}, 221 {Dir: "/usr/libexec/wpe-webkit-1.0", Options: []string{"x-snapd.origin=layout"}}, 222 {Dir: "/var/fooo-top", Options: []string{"x-snapd.origin=layout"}}, 223 {Dir: "/var/fooo/deeper", Options: []string{"x-snapd.origin=layout"}}, 224 }} 225 226 for _, testData := range []struct { 227 existingDirs []string 228 expectedOrder []string 229 }{ 230 { 231 existingDirs: []string{"/snap/test-snapd-layout/x1", "/usr", "/usr/lib/x86_64-linux-gnu", "/var"}, 232 expectedOrder: []string{ 233 "/snap/test-snapd-layout/x1/fooo-top", "/snap/test-snapd-layout/x1/fooo/deeper", 234 // triggers a mimic on /usr 235 "/usr/libexec/wpe-webkit-1.0", 236 "/usr/lib/x86_64-linux-gnu/wpe-webkit-1.0", 237 "/var/fooo-top", "/var/fooo/deeper", 238 }, 239 }, 240 { 241 existingDirs: []string{"/snap/test-snapd-layout/x1", "/usr", "/usr/lib/x86_64-linux-gnu", "/usr/libexec", "/var"}, 242 expectedOrder: []string{ 243 // parents for all dirs exists, so entries are 244 // ordered lexicographically 245 "/snap/test-snapd-layout/x1/fooo-top", "/snap/test-snapd-layout/x1/fooo/deeper", 246 "/usr/lib/x86_64-linux-gnu/wpe-webkit-1.0", "/usr/libexec/wpe-webkit-1.0", 247 "/var/fooo-top", "/var/fooo/deeper", 248 }, 249 }, 250 } { 251 existingDirectories = testData.existingDirs 252 changes := update.NeededChanges(current, desired) 253 254 // Check that every change is sane, and extract their path in order 255 actualOrder := make([]string, 0, len(changes)) 256 for _, change := range changes { 257 c.Check(change.Action, Equals, update.Mount) 258 actualOrder = append(actualOrder, change.Entry.Dir) 259 } 260 261 c.Check(actualOrder, DeepEquals, testData.expectedOrder, 262 Commentf("Existing dirs: %q", existingDirectories)) 263 } 264 } 265 266 func (s *changeSuite) TestNeededChangesKind(c *C) { 267 current := &osutil.MountProfile{} 268 desired := &osutil.MountProfile{Entries: []osutil.MountEntry{ 269 {Dir: "/common/file", Options: []string{"x-snapd.kind=file"}}, 270 {Dir: "/common/symlink", Options: []string{"x-snapd.kind=symlink"}}, 271 }} 272 changes := update.NeededChanges(current, desired) 273 c.Assert(changes, DeepEquals, []*update.Change{ 274 {Entry: osutil.MountEntry{Dir: "/common/file", Options: []string{"x-snapd.kind=file"}}, Action: update.Mount}, 275 {Entry: osutil.MountEntry{Dir: "/common/symlink", Options: []string{"x-snapd.kind=symlink"}}, Action: update.Mount}, 276 }) 277 } 278 279 // When parent changes we don't reuse its children 280 281 func (s *changeSuite) TestNeededChangesChangedParentSameChild(c *C) { 282 current := &osutil.MountProfile{Entries: []osutil.MountEntry{ 283 {Dir: "/common/stuff", Name: "/dev/sda1"}, 284 {Dir: "/common/stuff/extra"}, 285 {Dir: "/common/unrelated"}, 286 }} 287 desired := &osutil.MountProfile{Entries: []osutil.MountEntry{ 288 {Dir: "/common/stuff", Name: "/dev/sda2"}, 289 {Dir: "/common/stuff/extra"}, 290 {Dir: "/common/unrelated"}, 291 }} 292 changes := update.NeededChanges(current, desired) 293 c.Assert(changes, DeepEquals, []*update.Change{ 294 {Entry: osutil.MountEntry{Dir: "/common/unrelated"}, Action: update.Keep}, 295 {Entry: osutil.MountEntry{Dir: "/common/stuff/extra"}, Action: update.Unmount}, 296 {Entry: osutil.MountEntry{Dir: "/common/stuff", Name: "/dev/sda1"}, Action: update.Unmount}, 297 {Entry: osutil.MountEntry{Dir: "/common/stuff", Name: "/dev/sda2"}, Action: update.Mount}, 298 {Entry: osutil.MountEntry{Dir: "/common/stuff/extra"}, Action: update.Mount}, 299 }) 300 } 301 302 // When child changes we don't touch the unchanged parent 303 func (s *changeSuite) TestNeededChangesSameParentChangedChild(c *C) { 304 current := &osutil.MountProfile{Entries: []osutil.MountEntry{ 305 {Dir: "/common/stuff"}, 306 {Dir: "/common/stuff/extra", Name: "/dev/sda1"}, 307 {Dir: "/common/unrelated"}, 308 }} 309 desired := &osutil.MountProfile{Entries: []osutil.MountEntry{ 310 {Dir: "/common/stuff"}, 311 {Dir: "/common/stuff/extra", Name: "/dev/sda2"}, 312 {Dir: "/common/unrelated"}, 313 }} 314 changes := update.NeededChanges(current, desired) 315 c.Assert(changes, DeepEquals, []*update.Change{ 316 {Entry: osutil.MountEntry{Dir: "/common/unrelated"}, Action: update.Keep}, 317 {Entry: osutil.MountEntry{Dir: "/common/stuff/extra", Name: "/dev/sda1"}, Action: update.Unmount}, 318 {Entry: osutil.MountEntry{Dir: "/common/stuff"}, Action: update.Keep}, 319 {Entry: osutil.MountEntry{Dir: "/common/stuff/extra", Name: "/dev/sda2"}, Action: update.Mount}, 320 }) 321 } 322 323 // Unused bind mount farms are unmounted. 324 func (s *changeSuite) TestNeededChangesTmpfsBindMountFarmUnused(c *C) { 325 current := &osutil.MountProfile{Entries: []osutil.MountEntry{{ 326 // The tmpfs that lets us write into immutable squashfs. We mock 327 // x-snapd.needed-by to the last entry in the current profile (the bind 328 // mount). Mark it synthetic since it is a helper mount that is needed 329 // to facilitate the following mounts. 330 Name: "tmpfs", 331 Dir: "/snap/name/42/subdir", 332 Type: "tmpfs", 333 Options: []string{"x-snapd.needed-by=/snap/name/42/subdir", "x-snapd.synthetic"}, 334 }, { 335 // A bind mount to preserve a directory hidden by the tmpfs (the mount 336 // point is created elsewhere). We mock x-snapd.needed-by to the 337 // location of the bind mount below that is no longer desired. 338 Name: "/var/lib/snapd/hostfs/snap/name/42/subdir/existing", 339 Dir: "/snap/name/42/subdir/existing", 340 Options: []string{"bind", "ro", "x-snapd.needed-by=/snap/name/42/subdir", "x-snapd.synthetic"}, 341 }, { 342 // A bind mount to put some content from another snap. The bind mount 343 // is nothing special but the fact that it is possible is the reason 344 // the two entries above exist. The mount point (created) is created 345 // elsewhere. 346 Name: "/snap/other/123/libs", 347 Dir: "/snap/name/42/subdir/created", 348 Options: []string{"bind", "ro"}, 349 }}} 350 351 desired := &osutil.MountProfile{} 352 353 changes := update.NeededChanges(current, desired) 354 355 c.Assert(changes, DeepEquals, []*update.Change{ 356 {Entry: osutil.MountEntry{ 357 Name: "/snap/other/123/libs", 358 Dir: "/snap/name/42/subdir/created", 359 Options: []string{"bind", "ro", "x-snapd.detach"}, 360 }, Action: update.Unmount}, 361 {Entry: osutil.MountEntry{ 362 Name: "/var/lib/snapd/hostfs/snap/name/42/subdir/existing", 363 Dir: "/snap/name/42/subdir/existing", 364 Options: []string{"bind", "ro", "x-snapd.needed-by=/snap/name/42/subdir", "x-snapd.synthetic", "x-snapd.detach"}, 365 }, Action: update.Unmount}, 366 {Entry: osutil.MountEntry{ 367 Name: "tmpfs", 368 Dir: "/snap/name/42/subdir", 369 Type: "tmpfs", 370 Options: []string{"x-snapd.needed-by=/snap/name/42/subdir", "x-snapd.synthetic", "x-snapd.detach"}, 371 }, Action: update.Unmount}, 372 }) 373 } 374 375 func (s *changeSuite) TestNeededChangesTmpfsBindMountFarmUsed(c *C) { 376 377 // NOTE: the current profile is the same as in the test 378 // TestNeededChangesTmpfsBindMountFarmUnused written above. 379 current := &osutil.MountProfile{Entries: []osutil.MountEntry{{ 380 Name: "tmpfs", 381 Dir: "/snap/name/42/subdir", 382 Type: "tmpfs", 383 Options: []string{"x-snapd.needed-by=/snap/name/42/subdir/created", "x-snapd.synthetic"}, 384 }, { 385 Name: "/var/lib/snapd/hostfs/snap/name/42/subdir/existing", 386 Dir: "/snap/name/42/subdir/existing", 387 Options: []string{"bind", "ro", "x-snapd.needed-by=/snap/name/42/subdir/created", "x-snapd.synthetic"}, 388 }, { 389 Name: "/snap/other/123/libs", 390 Dir: "/snap/name/42/subdir/created", 391 Options: []string{"bind", "ro"}, 392 }}} 393 394 desired := &osutil.MountProfile{Entries: []osutil.MountEntry{{ 395 // This is the only entry that we explicitly want but in order to 396 // support it we need to keep the remaining implicit entries. 397 Name: "/snap/other/123/libs", 398 Dir: "/snap/name/42/subdir/created", 399 Options: []string{"bind", "ro"}, 400 }}} 401 402 changes := update.NeededChanges(current, desired) 403 404 c.Assert(changes, DeepEquals, []*update.Change{ 405 {Entry: osutil.MountEntry{ 406 Name: "/snap/other/123/libs", 407 Dir: "/snap/name/42/subdir/created", 408 Options: []string{"bind", "ro"}, 409 }, Action: update.Keep}, 410 {Entry: osutil.MountEntry{ 411 Name: "/var/lib/snapd/hostfs/snap/name/42/subdir/existing", 412 Dir: "/snap/name/42/subdir/existing", 413 Options: []string{"bind", "ro", "x-snapd.needed-by=/snap/name/42/subdir/created", "x-snapd.synthetic"}, 414 }, Action: update.Keep}, 415 {Entry: osutil.MountEntry{ 416 Name: "tmpfs", 417 Dir: "/snap/name/42/subdir", 418 Type: "tmpfs", 419 Options: []string{"x-snapd.needed-by=/snap/name/42/subdir/created", "x-snapd.synthetic"}, 420 }, Action: update.Keep}, 421 }) 422 } 423 424 // cur = ['/a/b', '/a/b-1', '/a/b-1/3', '/a/b/c'] 425 // des = ['/a/b', '/a/b-1', '/a/b/c' 426 // 427 // We are smart about comparing entries as directories. Here even though "/a/b" 428 // is a prefix of "/a/b-1" it is correctly reused. 429 func (s *changeSuite) TestNeededChangesSmartEntryComparisonOld(c *C) { 430 431 current := &osutil.MountProfile{Entries: []osutil.MountEntry{ 432 {Dir: "/a/b", Name: "/dev/sda1"}, 433 {Dir: "/a/b/c"}, 434 {Dir: "/a/b-1"}, 435 {Dir: "/a/b-1/3"}, 436 }} 437 desired := &osutil.MountProfile{Entries: []osutil.MountEntry{ 438 {Dir: "/a/b", Name: "/dev/sda2"}, 439 {Dir: "/a/b-1"}, 440 {Dir: "/a/b/c"}, 441 }} 442 changes := update.NeededChanges(current, desired) 443 for _, chg := range changes { 444 c.Logf("- %+v", chg) 445 } 446 c.Assert(changes, DeepEquals, []*update.Change{ 447 {Entry: osutil.MountEntry{Dir: "/a/b-1/3"}, Action: update.Unmount}, 448 {Entry: osutil.MountEntry{Dir: "/a/b-1"}, Action: update.Keep}, 449 {Entry: osutil.MountEntry{Dir: "/a/b/c"}, Action: update.Unmount}, 450 {Entry: osutil.MountEntry{Dir: "/a/b", Name: "/dev/sda1"}, Action: update.Unmount}, 451 452 {Entry: osutil.MountEntry{Dir: "/a/b", Name: "/dev/sda2"}, Action: update.Mount}, 453 {Entry: osutil.MountEntry{Dir: "/a/b/c"}, Action: update.Mount}, 454 }) 455 } 456 457 // Parallel instance changes are executed first 458 func (s *changeSuite) TestNeededChangesParallelInstancesManyComeFirst(c *C) { 459 460 desired := &osutil.MountProfile{Entries: []osutil.MountEntry{ 461 {Dir: "/common/stuff", Name: "/dev/sda1"}, 462 {Dir: "/common/stuff/extra"}, 463 {Dir: "/common/unrelated"}, 464 {Dir: "/foo/bar", Name: "/foo/bar_bar", Options: []string{osutil.XSnapdOriginOvername()}}, 465 {Dir: "/snap/foo", Name: "/snap/foo_bar", Options: []string{osutil.XSnapdOriginOvername()}}, 466 }} 467 changes := update.NeededChanges(&osutil.MountProfile{}, desired) 468 c.Assert(changes, DeepEquals, []*update.Change{ 469 {Entry: osutil.MountEntry{Dir: "/foo/bar", Name: "/foo/bar_bar", Options: []string{osutil.XSnapdOriginOvername()}}, Action: update.Mount}, 470 {Entry: osutil.MountEntry{Dir: "/snap/foo", Name: "/snap/foo_bar", Options: []string{osutil.XSnapdOriginOvername()}}, Action: update.Mount}, 471 {Entry: osutil.MountEntry{Dir: "/common/stuff", Name: "/dev/sda1"}, Action: update.Mount}, 472 {Entry: osutil.MountEntry{Dir: "/common/stuff/extra"}, Action: update.Mount}, 473 {Entry: osutil.MountEntry{Dir: "/common/unrelated"}, Action: update.Mount}, 474 }) 475 } 476 477 // Parallel instance changes are kept if already present 478 func (s *changeSuite) TestNeededChangesParallelInstancesKeep(c *C) { 479 480 desired := &osutil.MountProfile{Entries: []osutil.MountEntry{ 481 {Dir: "/common/stuff", Name: "/dev/sda1"}, 482 {Dir: "/common/unrelated"}, 483 {Dir: "/foo/bar", Name: "/foo/bar_bar", Options: []string{osutil.XSnapdOriginOvername()}}, 484 {Dir: "/snap/foo", Name: "/snap/foo_bar", Options: []string{osutil.XSnapdOriginOvername()}}, 485 }} 486 current := &osutil.MountProfile{Entries: []osutil.MountEntry{ 487 {Dir: "/foo/bar", Name: "/foo/bar_bar", Options: []string{osutil.XSnapdOriginOvername()}}, 488 {Dir: "/snap/foo", Name: "/snap/foo_bar", Options: []string{osutil.XSnapdOriginOvername()}}, 489 }} 490 changes := update.NeededChanges(current, desired) 491 c.Assert(changes, DeepEquals, []*update.Change{ 492 {Entry: osutil.MountEntry{Dir: "/snap/foo", Name: "/snap/foo_bar", Options: []string{osutil.XSnapdOriginOvername()}}, Action: update.Keep}, 493 {Entry: osutil.MountEntry{Dir: "/foo/bar", Name: "/foo/bar_bar", Options: []string{osutil.XSnapdOriginOvername()}}, Action: update.Keep}, 494 {Entry: osutil.MountEntry{Dir: "/common/stuff", Name: "/dev/sda1"}, Action: update.Mount}, 495 {Entry: osutil.MountEntry{Dir: "/common/unrelated"}, Action: update.Mount}, 496 }) 497 } 498 499 // Parallel instance with mounts inside 500 func (s *changeSuite) TestNeededChangesParallelInstancesInsideMount(c *C) { 501 502 desired := &osutil.MountProfile{Entries: []osutil.MountEntry{ 503 {Dir: "/foo/bar/baz"}, 504 {Dir: "/foo/bar", Name: "/foo/bar_bar", Options: []string{osutil.XSnapdOriginOvername()}}, 505 {Dir: "/snap/foo", Name: "/snap/foo_bar", Options: []string{osutil.XSnapdOriginOvername()}}, 506 }} 507 current := &osutil.MountProfile{Entries: []osutil.MountEntry{ 508 {Dir: "/foo/bar", Name: "/foo/bar_bar", Options: []string{osutil.XSnapdOriginOvername()}}, 509 {Dir: "/foo/bar/zed"}, 510 {Dir: "/snap/foo", Name: "/snap/foo_bar", Options: []string{osutil.XSnapdOriginOvername()}}, 511 }} 512 changes := update.NeededChanges(current, desired) 513 c.Assert(changes, DeepEquals, []*update.Change{ 514 {Entry: osutil.MountEntry{Dir: "/snap/foo", Name: "/snap/foo_bar", Options: []string{osutil.XSnapdOriginOvername()}}, Action: update.Keep}, 515 {Entry: osutil.MountEntry{Dir: "/foo/bar/zed"}, Action: update.Unmount}, 516 {Entry: osutil.MountEntry{Dir: "/foo/bar", Name: "/foo/bar_bar", Options: []string{osutil.XSnapdOriginOvername()}}, Action: update.Keep}, 517 {Entry: osutil.MountEntry{Dir: "/foo/bar/baz"}, Action: update.Mount}, 518 }) 519 } 520 521 func (s *changeSuite) TestRuntimeUsingSymlinks(c *C) { 522 dirs.SetRootDir(c.MkDir()) 523 defer func() { 524 dirs.SetRootDir("") 525 }() 526 527 optDir := filepath.Join(dirs.GlobalRootDir, "/opt") 528 optFooRuntimeDir := filepath.Join(dirs.GlobalRootDir, "/opt/foo-runtime") 529 snapAppX1FooRuntimeDir := filepath.Join(dirs.GlobalRootDir, "/snap/app/x1/foo-runtime") 530 snapAppX2FooRuntimeDir := filepath.Join(dirs.GlobalRootDir, "/snap/app/x2/foo-runtime") 531 snapFooRuntimeX1OptFooRuntime := filepath.Join(dirs.GlobalRootDir, "/snap/foo-runtime/x1/opt/foo-runtime") 532 533 // We start with a runtime shared from one snap to another and then exposed 534 // to /opt with a symbolic link. This is the initial state of the 535 // application in version v1. 536 initial := &osutil.MountProfile{} 537 desiredV1 := &osutil.MountProfile{Entries: []osutil.MountEntry{ 538 {Name: "none", Dir: optFooRuntimeDir, Type: "none", Options: []string{"x-snapd.kind=symlink", "x-snapd.symlink=" + snapAppX1FooRuntimeDir, "x-snapd.origin=layout"}}, 539 {Name: snapFooRuntimeX1OptFooRuntime, Dir: snapAppX1FooRuntimeDir, Type: "none", Options: []string{"bind", "ro"}}, 540 }} 541 // The changes we compute are trivial, simply perform each operation in order. 542 changes := update.NeededChanges(initial, desiredV1) 543 c.Assert(changes, DeepEquals, []*update.Change{ 544 {Entry: osutil.MountEntry{Name: snapFooRuntimeX1OptFooRuntime, Dir: snapAppX1FooRuntimeDir, Type: "none", Options: []string{"bind", "ro"}}, Action: update.Mount}, 545 {Entry: osutil.MountEntry{Name: "none", Dir: optFooRuntimeDir, Type: "none", Options: []string{"x-snapd.kind=symlink", "x-snapd.symlink=" + snapAppX1FooRuntimeDir, "x-snapd.origin=layout"}}, Action: update.Mount}, 546 }) 547 // After performing both changes we have a new synthesized entry. We get an 548 // extra writable mimic over /opt so that we can add our symlink. The 549 // content sharing into $SNAP is applied as expected since the snap ships 550 // the required mount point. 551 currentV1 := &osutil.MountProfile{Entries: []osutil.MountEntry{ 552 {Name: snapFooRuntimeX1OptFooRuntime, Dir: snapAppX1FooRuntimeDir, Type: "none", Options: []string{"bind", "ro"}}, 553 {Name: "none", Dir: optFooRuntimeDir, Type: "none", Options: []string{"x-snapd.kind=symlink", "x-snapd.symlink=" + snapAppX1FooRuntimeDir, "x-snapd.origin=layout"}}, 554 {Name: "tmpfs", Dir: optDir, Type: "tmpfs", Options: []string{"x-snapd.synthetic", "x-snapd.needed-by=" + optFooRuntimeDir, "mode=0755", "uid=0", "gid=0"}}, 555 }} 556 // We now proceed to replace app v1 with v2 which uses a bind mount instead 557 // of a symlink. First, let's start with the updated desired profile: 558 desiredV2 := &osutil.MountProfile{Entries: []osutil.MountEntry{ 559 {Name: snapAppX2FooRuntimeDir, Dir: optFooRuntimeDir, Type: "none", Options: []string{"rbind", "rw", "x-snapd.origin=layout"}}, 560 {Name: snapFooRuntimeX1OptFooRuntime, Dir: snapAppX2FooRuntimeDir, Type: "none", Options: []string{"bind", "ro"}}, 561 }} 562 563 // Let's see what the update algorithm thinks. 564 changes = update.NeededChanges(currentV1, desiredV2) 565 c.Assert(changes, DeepEquals, []*update.Change{ 566 // We are keeping /opt, but re-creating /opt from scratch. 567 {Entry: osutil.MountEntry{Name: "tmpfs", Dir: optDir, Type: "tmpfs", Options: []string{"x-snapd.synthetic", "x-snapd.needed-by=" + optFooRuntimeDir, "mode=0755", "uid=0", "gid=0"}}, Action: update.Keep}, 568 {Entry: osutil.MountEntry{Name: "none", Dir: optFooRuntimeDir, Type: "none", Options: []string{"x-snapd.kind=symlink", "x-snapd.symlink=" + snapAppX1FooRuntimeDir, "x-snapd.origin=layout"}}, Action: update.Unmount}, 569 // We are dropping the content interface bind mount because app changed revision 570 {Entry: osutil.MountEntry{Name: snapFooRuntimeX1OptFooRuntime, Dir: snapAppX1FooRuntimeDir, Type: "none", Options: []string{"bind", "ro", "x-snapd.detach"}}, Action: update.Unmount}, 571 // We also adding the updated path of the content interface (for revision x2) 572 {Entry: osutil.MountEntry{Name: snapFooRuntimeX1OptFooRuntime, Dir: snapAppX2FooRuntimeDir, Type: "none", Options: []string{"bind", "ro"}}, Action: update.Mount}, 573 // We are adding a new bind mount for /opt/foo-runtime 574 {Entry: osutil.MountEntry{Name: snapAppX2FooRuntimeDir, Dir: optFooRuntimeDir, Type: "none", Options: []string{"rbind", "rw", "x-snapd.origin=layout"}}, Action: update.Mount}, 575 }) 576 577 // After performing all those changes this is the profile we observe. 578 currentV2 := &osutil.MountProfile{Entries: []osutil.MountEntry{ 579 {Name: "tmpfs", Dir: optDir, Type: "tmpfs", Options: []string{"x-snapd.synthetic", "x-snapd.needed-by=" + optFooRuntimeDir, "mode=0755", "uid=0", "gid=0", "x-snapd.detach"}}, 580 {Name: snapAppX2FooRuntimeDir, Dir: optFooRuntimeDir, Type: "none", Options: []string{"rbind", "rw", "x-snapd.origin=layout"}}, 581 {Name: snapFooRuntimeX1OptFooRuntime, Dir: snapAppX2FooRuntimeDir, Type: "none", Options: []string{"bind", "ro"}}, 582 }} 583 584 // So far so good. To trigger the issue we now revert or refresh to v1 585 // again. Let's see what happens here. The desired profiles are already 586 // known so let's see what the algorithm thinks now. 587 changes = update.NeededChanges(currentV2, desiredV1) 588 c.Assert(changes, DeepEquals, []*update.Change{ 589 // We are dropping the content interface bind mount because app changed revision 590 {Entry: osutil.MountEntry{Name: snapFooRuntimeX1OptFooRuntime, Dir: snapAppX2FooRuntimeDir, Type: "none", Options: []string{"bind", "ro", "x-snapd.detach"}}, Action: update.Unmount}, 591 // We are also dropping the bind mount from /opt/runtime since we want a symlink instead 592 {Entry: osutil.MountEntry{Name: snapAppX2FooRuntimeDir, Dir: optFooRuntimeDir, Type: "none", Options: []string{"rbind", "rw", "x-snapd.origin=layout", "x-snapd.detach"}}, Action: update.Unmount}, 593 // Keep the tmpfs on /opt 594 {Entry: osutil.MountEntry{Name: "tmpfs", Dir: optDir, Type: "tmpfs", Options: []string{"x-snapd.synthetic", "x-snapd.needed-by=" + optFooRuntimeDir, "mode=0755", "uid=0", "gid=0", "x-snapd.detach"}}, Action: update.Keep}, 595 // We are bind mounting the runtime from another snap into $SNAP/foo-runtime 596 {Entry: osutil.MountEntry{Name: snapFooRuntimeX1OptFooRuntime, Dir: snapAppX1FooRuntimeDir, Type: "none", Options: []string{"bind", "ro"}}, Action: update.Mount}, 597 // We are providing a symlink /opt/foo-runtime -> to $SNAP/foo-runtime. 598 {Entry: osutil.MountEntry{Name: "none", Dir: optFooRuntimeDir, Type: "none", Options: []string{"x-snapd.kind=symlink", "x-snapd.symlink=" + snapAppX1FooRuntimeDir, "x-snapd.origin=layout"}}, Action: update.Mount}, 599 }) 600 601 // The problem is that the tmpfs contains leftovers from the things we 602 // created and those prevent the execution of this mount profile. 603 } 604 605 // ######################################## 606 // Topic: mounting & unmounting filesystems 607 // ######################################## 608 609 // Change.Perform returns errors from os.Lstat (apart from ErrNotExist) 610 func (s *changeSuite) TestPerformFilesystemMountLstatError(c *C) { 611 s.sys.InsertFault(`lstat "/target"`, errTesting) 612 chg := &update.Change{Action: update.Mount, Entry: osutil.MountEntry{Name: "device", Dir: "/target", Type: "type"}} 613 synth, err := chg.Perform(s.as) 614 c.Assert(err, ErrorMatches, `cannot inspect "/target": testing`) 615 c.Assert(synth, HasLen, 0) 616 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 617 {C: `lstat "/target"`, E: errTesting}, 618 }) 619 } 620 621 // Change.Perform wants to mount a filesystem. 622 func (s *changeSuite) TestPerformFilesystemMount(c *C) { 623 s.sys.InsertOsLstatResult(`lstat "/target"`, testutil.FileInfoDir) 624 chg := &update.Change{Action: update.Mount, Entry: osutil.MountEntry{Name: "device", Dir: "/target", Type: "type"}} 625 synth, err := chg.Perform(s.as) 626 c.Assert(err, IsNil) 627 c.Assert(synth, HasLen, 0) 628 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 629 {C: `lstat "/target"`, R: testutil.FileInfoDir}, 630 {C: `mount "device" "/target" "type" 0 ""`}, 631 }) 632 } 633 634 // Change.Perform wants to mount a filesystem with sharing changes. 635 func (s *changeSuite) TestPerformFilesystemMountAndShareChanges(c *C) { 636 s.sys.InsertOsLstatResult(`lstat "/target"`, testutil.FileInfoDir) 637 chg := &update.Change{Action: update.Mount, Entry: osutil.MountEntry{Name: "device", Dir: "/target", Type: "type", Options: []string{"shared"}}} 638 synth, err := chg.Perform(s.as) 639 c.Assert(err, IsNil) 640 c.Assert(synth, HasLen, 0) 641 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 642 {C: `lstat "/target"`, R: testutil.FileInfoDir}, 643 {C: `mount "device" "/target" "type" 0 ""`}, 644 {C: `mount "none" "/target" "" MS_SHARED ""`}, 645 }) 646 } 647 648 // Change.Perform wants to mount a filesystem but it fails. 649 func (s *changeSuite) TestPerformFilesystemMountWithError(c *C) { 650 s.sys.InsertOsLstatResult(`lstat "/target"`, testutil.FileInfoDir) 651 s.sys.InsertFault(`mount "device" "/target" "type" 0 ""`, errTesting) 652 chg := &update.Change{Action: update.Mount, Entry: osutil.MountEntry{Name: "device", Dir: "/target", Type: "type"}} 653 synth, err := chg.Perform(s.as) 654 c.Assert(err, Equals, errTesting) 655 c.Assert(synth, HasLen, 0) 656 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 657 {C: `lstat "/target"`, R: testutil.FileInfoDir}, 658 {C: `mount "device" "/target" "type" 0 ""`, E: errTesting}, 659 }) 660 } 661 662 // Change.Perform wants to mount a filesystem with sharing changes but mounting fails. 663 func (s *changeSuite) TestPerformFilesystemMountAndShareWithError(c *C) { 664 s.sys.InsertOsLstatResult(`lstat "/target"`, testutil.FileInfoDir) 665 s.sys.InsertFault(`mount "device" "/target" "type" 0 ""`, errTesting) 666 chg := &update.Change{Action: update.Mount, Entry: osutil.MountEntry{Name: "device", Dir: "/target", Type: "type", Options: []string{"shared"}}} 667 synth, err := chg.Perform(s.as) 668 c.Assert(err, Equals, errTesting) 669 c.Assert(synth, HasLen, 0) 670 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 671 {C: `lstat "/target"`, R: testutil.FileInfoDir}, 672 {C: `mount "device" "/target" "type" 0 ""`, E: errTesting}, 673 }) 674 } 675 676 // Change.Perform wants to mount a filesystem but the mount point isn't there. 677 func (s *changeSuite) TestPerformFilesystemMountWithoutMountPoint(c *C) { 678 defer s.as.MockUnrestrictedPaths("/")() // Treat test path as unrestricted. 679 s.sys.InsertFault(`lstat "/target"`, syscall.ENOENT) 680 chg := &update.Change{Action: update.Mount, Entry: osutil.MountEntry{Name: "device", Dir: "/target", Type: "type"}} 681 synth, err := chg.Perform(s.as) 682 c.Assert(err, IsNil) 683 c.Assert(synth, HasLen, 0) 684 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 685 {C: `lstat "/target"`, E: syscall.ENOENT}, 686 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 687 {C: `mkdirat 3 "target" 0755`}, 688 {C: `openat 3 "target" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4}, 689 {C: `fchown 4 0 0`}, 690 {C: `close 4`}, 691 {C: `close 3`}, 692 {C: `mount "device" "/target" "type" 0 ""`}, 693 }) 694 } 695 696 // Change.Perform wants to create a filesystem but the mount point isn't there and cannot be created. 697 func (s *changeSuite) TestPerformFilesystemMountWithoutMountPointWithErrors(c *C) { 698 defer s.as.MockUnrestrictedPaths("/")() // Treat test path as unrestricted. 699 s.sys.InsertFault(`lstat "/target"`, syscall.ENOENT) 700 s.sys.InsertFault(`mkdirat 3 "target" 0755`, errTesting) 701 chg := &update.Change{Action: update.Mount, Entry: osutil.MountEntry{Name: "device", Dir: "/target", Type: "type"}} 702 synth, err := chg.Perform(s.as) 703 c.Assert(err, ErrorMatches, `cannot create directory "/target": testing`) 704 c.Assert(synth, HasLen, 0) 705 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 706 {C: `lstat "/target"`, E: syscall.ENOENT}, 707 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 708 {C: `mkdirat 3 "target" 0755`, E: errTesting}, 709 {C: `close 3`}, 710 }) 711 } 712 713 // Change.Perform wants to mount a filesystem but the mount point isn't there and the parent is read-only. 714 func (s *changeSuite) TestPerformFilesystemMountWithoutMountPointAndReadOnlyBase(c *C) { 715 defer s.as.MockUnrestrictedPaths("/")() // Treat test path as unrestricted. 716 s.sys.InsertFault(`lstat "/rofs/target"`, syscall.ENOENT) 717 s.sys.InsertFault(`mkdirat 3 "rofs" 0755`, syscall.EEXIST) 718 s.sys.InsertFault(`openat 3 "target" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, syscall.ENOENT, nil) // works on 2nd try 719 s.sys.InsertFault(`mkdirat 4 "target" 0755`, syscall.EROFS, nil) // works on 2nd try 720 s.sys.InsertSysLstatResult(`lstat "/rofs" <ptr>`, syscall.Stat_t{Uid: 0, Gid: 0, Mode: 0755}) 721 s.sys.InsertReadDirResult(`readdir "/rofs"`, nil) // pretend /rofs is empty. 722 s.sys.InsertFault(`lstat "/tmp/.snap/rofs"`, syscall.ENOENT) 723 s.sys.InsertOsLstatResult(`lstat "/rofs"`, testutil.FileInfoDir) 724 s.sys.InsertFstatResult(`fstat 4 <ptr>`, syscall.Stat_t{}) 725 s.sys.InsertFstatResult(`fstat 7 <ptr>`, syscall.Stat_t{}) 726 s.sys.InsertFstatResult(`fstat 6 <ptr>`, syscall.Stat_t{}) 727 s.sys.InsertFstatfsResult(`fstatfs 6 <ptr>`, syscall.Statfs_t{}) 728 729 chg := &update.Change{Action: update.Mount, Entry: osutil.MountEntry{Name: "device", Dir: "/rofs/target", Type: "type"}} 730 synth, err := chg.Perform(s.as) 731 c.Assert(err, IsNil) 732 c.Assert(synth, DeepEquals, []*update.Change{ 733 {Action: update.Mount, Entry: osutil.MountEntry{ 734 Name: "tmpfs", Dir: "/rofs", Type: "tmpfs", 735 Options: []string{"x-snapd.synthetic", "x-snapd.needed-by=/rofs/target", "mode=0755", "uid=0", "gid=0"}}, 736 }, 737 }) 738 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 739 // sniff mount target 740 {C: `lstat "/rofs/target"`, E: syscall.ENOENT}, 741 742 // /rofs/target is missing, create it 743 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 744 {C: `mkdirat 3 "rofs" 0755`, E: syscall.EEXIST}, 745 {C: `openat 3 "rofs" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4}, 746 {C: `close 3`}, 747 {C: `mkdirat 4 "target" 0755`, E: syscall.EROFS}, 748 {C: `close 4`}, 749 750 // error, read only filesystem, create a mimic 751 {C: `lstat "/rofs" <ptr>`, R: syscall.Stat_t{Uid: 0, Gid: 0, Mode: 0755}}, 752 {C: `readdir "/rofs"`, R: []os.FileInfo(nil)}, 753 {C: `lstat "/tmp/.snap/rofs"`, E: syscall.ENOENT}, 754 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 755 {C: `mkdirat 3 "tmp" 0755`}, 756 {C: `openat 3 "tmp" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4}, 757 {C: `fchown 4 0 0`}, 758 {C: `mkdirat 4 ".snap" 0755`}, 759 {C: `openat 4 ".snap" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 5}, 760 {C: `fchown 5 0 0`}, 761 {C: `close 4`}, 762 {C: `close 3`}, 763 {C: `mkdirat 5 "rofs" 0755`}, 764 {C: `openat 5 "rofs" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 765 {C: `fchown 3 0 0`}, 766 {C: `close 3`}, 767 {C: `close 5`}, 768 {C: `lstat "/rofs"`, R: testutil.FileInfoDir}, 769 770 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 3}, 771 {C: `openat 3 "rofs" O_NOFOLLOW|O_CLOEXEC|O_PATH 0`, R: 4}, 772 {C: `fstat 4 <ptr>`, R: syscall.Stat_t{}}, 773 {C: `close 3`}, 774 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 3}, 775 {C: `openat 3 "tmp" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 5}, 776 {C: `openat 5 ".snap" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 6}, 777 {C: `openat 6 "rofs" O_NOFOLLOW|O_CLOEXEC|O_PATH 0`, R: 7}, 778 {C: `fstat 7 <ptr>`, R: syscall.Stat_t{}}, 779 {C: `close 6`}, 780 {C: `close 5`}, 781 {C: `close 3`}, 782 {C: `mount "/proc/self/fd/4" "/proc/self/fd/7" "" MS_BIND|MS_REC ""`}, 783 {C: `close 7`}, 784 {C: `close 4`}, 785 786 {C: `lstat "/rofs"`, R: testutil.FileInfoDir}, 787 {C: `mount "tmpfs" "/rofs" "tmpfs" 0 "mode=0755,uid=0,gid=0"`}, 788 {C: `mount "none" "/tmp/.snap/rofs" "" MS_REC|MS_PRIVATE ""`}, 789 {C: `unmount "/tmp/.snap/rofs" UMOUNT_NOFOLLOW|MNT_DETACH`}, 790 791 // Perform clean up after the unmount operation. 792 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 3}, 793 {C: `openat 3 "tmp" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 4}, 794 {C: `openat 4 ".snap" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 5}, 795 {C: `openat 5 "rofs" O_NOFOLLOW|O_CLOEXEC|O_PATH 0`, R: 6}, 796 {C: `fstat 6 <ptr>`, R: syscall.Stat_t{}}, 797 {C: `close 5`}, 798 {C: `close 4`}, 799 {C: `close 3`}, 800 {C: `fstatfs 6 <ptr>`, R: syscall.Statfs_t{}}, 801 {C: `remove "/tmp/.snap/rofs"`}, 802 {C: `close 6`}, 803 804 // mimic ready, re-try initial mkdir 805 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 806 {C: `mkdirat 3 "rofs" 0755`, E: syscall.EEXIST}, 807 {C: `openat 3 "rofs" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4}, 808 {C: `close 3`}, 809 {C: `mkdirat 4 "target" 0755`}, 810 {C: `openat 4 "target" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 811 {C: `fchown 3 0 0`}, 812 {C: `close 3`}, 813 {C: `close 4`}, 814 815 // mount the filesystem 816 {C: `mount "device" "/rofs/target" "type" 0 ""`}, 817 }) 818 } 819 820 // Change.Perform wants to mount a filesystem but the mount point isn't there and the parent is read-only and mimic fails during planning. 821 func (s *changeSuite) TestPerformFilesystemMountWithoutMountPointAndReadOnlyBaseErrorWhilePlanning(c *C) { 822 defer s.as.MockUnrestrictedPaths("/")() // Treat test path as unrestricted. 823 s.sys.InsertFault(`lstat "/rofs/target"`, syscall.ENOENT) 824 s.sys.InsertFault(`mkdirat 3 "rofs" 0755`, syscall.EEXIST) 825 s.sys.InsertFault(`openat 3 "target" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, syscall.ENOENT) 826 s.sys.InsertFault(`mkdirat 4 "target" 0755`, syscall.EROFS) 827 s.sys.InsertFault(`lstat "/tmp/.snap/rofs"`, syscall.ENOENT) 828 s.sys.InsertOsLstatResult(`lstat "/rofs"`, testutil.FileInfoDir) 829 s.sys.InsertSysLstatResult(`lstat "/rofs" <ptr>`, syscall.Stat_t{Uid: 0, Gid: 0, Mode: 0755}) 830 s.sys.InsertReadDirResult(`readdir "/rofs"`, nil) 831 s.sys.InsertFault(`readdir "/rofs"`, errTesting) // make the writable mimic fail 832 833 chg := &update.Change{Action: update.Mount, Entry: osutil.MountEntry{Name: "device", Dir: "/rofs/target", Type: "type"}} 834 synth, err := chg.Perform(s.as) 835 c.Assert(err, ErrorMatches, `cannot create writable mimic over "/rofs": testing`) 836 c.Assert(synth, HasLen, 0) 837 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 838 // sniff mount target 839 {C: `lstat "/rofs/target"`, E: syscall.ENOENT}, 840 841 // /rofs/target is missing, create it 842 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 843 {C: `mkdirat 3 "rofs" 0755`, E: syscall.EEXIST}, 844 {C: `openat 3 "rofs" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4}, 845 {C: `close 3`}, 846 {C: `mkdirat 4 "target" 0755`, E: syscall.EROFS}, 847 {C: `close 4`}, 848 849 // error, read only filesystem, create a mimic 850 {C: `lstat "/rofs" <ptr>`, R: syscall.Stat_t{Uid: 0, Gid: 0, Mode: 0755}}, 851 {C: `readdir "/rofs"`, E: errTesting}, 852 // cannot create mimic, that's it 853 }) 854 } 855 856 // Change.Perform wants to mount a filesystem but the mount point isn't there and the parent is read-only and mimic fails during execution. 857 func (s *changeSuite) TestPerformFilesystemMountWithoutMountPointAndReadOnlyBaseErrorWhileExecuting(c *C) { 858 defer s.as.MockUnrestrictedPaths("/")() // Treat test path as unrestricted. 859 s.sys.InsertFault(`lstat "/rofs/target"`, syscall.ENOENT) 860 s.sys.InsertFault(`mkdirat 3 "rofs" 0755`, syscall.EEXIST) 861 s.sys.InsertFault(`openat 3 "target" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, syscall.ENOENT) 862 s.sys.InsertFault(`mkdirat 4 "target" 0755`, syscall.EROFS) 863 s.sys.InsertFault(`lstat "/tmp/.snap/rofs"`, syscall.ENOENT) 864 s.sys.InsertOsLstatResult(`lstat "/rofs"`, testutil.FileInfoDir) 865 s.sys.InsertSysLstatResult(`lstat "/rofs" <ptr>`, syscall.Stat_t{Uid: 0, Gid: 0, Mode: 0755}) 866 s.sys.InsertReadDirResult(`readdir "/rofs"`, nil) 867 s.sys.InsertFault(`mkdirat 4 ".snap" 0755`, errTesting) // make the writable mimic fail 868 869 chg := &update.Change{Action: update.Mount, Entry: osutil.MountEntry{Name: "device", Dir: "/rofs/target", Type: "type"}} 870 synth, err := chg.Perform(s.as) 871 c.Assert(err, ErrorMatches, `cannot create writable mimic over "/rofs": cannot create directory "/tmp/.snap": testing`) 872 c.Assert(synth, HasLen, 0) 873 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 874 // sniff mount target 875 {C: `lstat "/rofs/target"`, E: syscall.ENOENT}, 876 877 // /rofs/target is missing, create it 878 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 879 {C: `mkdirat 3 "rofs" 0755`, E: syscall.EEXIST}, 880 {C: `openat 3 "rofs" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4}, 881 {C: `close 3`}, 882 {C: `mkdirat 4 "target" 0755`, E: syscall.EROFS}, 883 {C: `close 4`}, 884 885 // error, read only filesystem, create a mimic 886 {C: `lstat "/rofs" <ptr>`, R: syscall.Stat_t{Uid: 0, Gid: 0, Mode: 0755}}, 887 {C: `readdir "/rofs"`, R: []os.FileInfo(nil)}, 888 {C: `lstat "/tmp/.snap/rofs"`, E: syscall.ENOENT}, 889 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 890 {C: `mkdirat 3 "tmp" 0755`}, 891 {C: `openat 3 "tmp" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4}, 892 {C: `fchown 4 0 0`}, 893 {C: `mkdirat 4 ".snap" 0755`, E: errTesting}, 894 {C: `close 4`}, 895 {C: `close 3`}, 896 // cannot create mimic, that's it 897 }) 898 } 899 900 // Change.Perform wants to mount a filesystem but there's a symlink in mount point. 901 func (s *changeSuite) TestPerformFilesystemMountWithSymlinkInMountPoint(c *C) { 902 s.sys.InsertOsLstatResult(`lstat "/target"`, testutil.FileInfoSymlink) 903 chg := &update.Change{Action: update.Mount, Entry: osutil.MountEntry{Name: "device", Dir: "/target", Type: "type"}} 904 synth, err := chg.Perform(s.as) 905 c.Assert(err, ErrorMatches, `cannot use "/target" as mount point: not a directory`) 906 c.Assert(synth, HasLen, 0) 907 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 908 {C: `lstat "/target"`, R: testutil.FileInfoSymlink}, 909 }) 910 } 911 912 // Change.Perform wants to mount a filesystem but there's a file in mount point. 913 func (s *changeSuite) TestPerformFilesystemMountWithFileInMountPoint(c *C) { 914 s.sys.InsertOsLstatResult(`lstat "/target"`, testutil.FileInfoFile) 915 chg := &update.Change{Action: update.Mount, Entry: osutil.MountEntry{Name: "device", Dir: "/target", Type: "type"}} 916 synth, err := chg.Perform(s.as) 917 c.Assert(err, ErrorMatches, `cannot use "/target" as mount point: not a directory`) 918 c.Assert(synth, HasLen, 0) 919 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 920 {C: `lstat "/target"`, R: testutil.FileInfoFile}, 921 }) 922 } 923 924 // Change.Perform wants to unmount a filesystem. 925 func (s *changeSuite) TestPerformFilesystemUnmount(c *C) { 926 s.sys.InsertFstatResult(`fstat 4 <ptr>`, syscall.Stat_t{}) 927 s.sys.InsertFstatfsResult(`fstatfs 4 <ptr>`, syscall.Statfs_t{}) 928 chg := &update.Change{Action: update.Unmount, Entry: osutil.MountEntry{Name: "device", Dir: "/target", Type: "type"}} 929 synth, err := chg.Perform(s.as) 930 c.Assert(err, IsNil) 931 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 932 {C: `unmount "/target" UMOUNT_NOFOLLOW`}, 933 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 3}, 934 {C: `openat 3 "target" O_NOFOLLOW|O_CLOEXEC|O_PATH 0`, R: 4}, 935 {C: `fstat 4 <ptr>`, R: syscall.Stat_t{}}, 936 {C: `close 3`}, 937 {C: `fstatfs 4 <ptr>`, R: syscall.Statfs_t{}}, 938 {C: `remove "/target"`}, 939 {C: `close 4`}, 940 }) 941 c.Assert(synth, HasLen, 0) 942 } 943 944 // Change.Perform wants to detach a bind mount. 945 func (s *changeSuite) TestPerformFilesystemDetch(c *C) { 946 s.sys.InsertFstatResult(`fstat 4 <ptr>`, syscall.Stat_t{}) 947 s.sys.InsertFstatfsResult(`fstatfs 4 <ptr>`, syscall.Statfs_t{}) 948 chg := &update.Change{Action: update.Unmount, Entry: osutil.MountEntry{Name: "/something", Dir: "/target", Options: []string{"x-snapd.detach"}}} 949 synth, err := chg.Perform(s.as) 950 c.Assert(err, IsNil) 951 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 952 {C: `mount "none" "/target" "" MS_REC|MS_PRIVATE ""`}, 953 {C: `unmount "/target" UMOUNT_NOFOLLOW|MNT_DETACH`}, 954 955 // Perform clean up after the unmount operation. 956 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 3}, 957 {C: `openat 3 "target" O_NOFOLLOW|O_CLOEXEC|O_PATH 0`, R: 4}, 958 {C: `fstat 4 <ptr>`, R: syscall.Stat_t{}}, 959 {C: `close 3`}, 960 {C: `fstatfs 4 <ptr>`, R: syscall.Statfs_t{}}, 961 {C: `remove "/target"`}, 962 {C: `close 4`}, 963 }) 964 c.Assert(synth, HasLen, 0) 965 } 966 967 // Change.Perform wants to unmount a filesystem but it fails. 968 func (s *changeSuite) TestPerformFilesystemUnmountError(c *C) { 969 s.sys.InsertFault(`unmount "/target" UMOUNT_NOFOLLOW`, errTesting) 970 chg := &update.Change{Action: update.Unmount, Entry: osutil.MountEntry{Name: "device", Dir: "/target", Type: "type"}} 971 synth, err := chg.Perform(s.as) 972 c.Assert(err, Equals, errTesting) 973 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 974 {C: `unmount "/target" UMOUNT_NOFOLLOW`, E: errTesting}, 975 }) 976 c.Assert(synth, HasLen, 0) 977 } 978 979 // Change.Perform passes non-flag options to the kernel. 980 func (s *changeSuite) TestPerformFilesystemMountWithOptions(c *C) { 981 s.sys.InsertOsLstatResult(`lstat "/target"`, testutil.FileInfoDir) 982 chg := &update.Change{Action: update.Mount, Entry: osutil.MountEntry{Name: "device", Dir: "/target", Type: "type", Options: []string{"ro", "funky"}}} 983 synth, err := chg.Perform(s.as) 984 c.Assert(err, IsNil) 985 c.Assert(synth, HasLen, 0) 986 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 987 {C: `lstat "/target"`, R: testutil.FileInfoDir}, 988 {C: `mount "device" "/target" "type" MS_RDONLY "funky"`}, 989 }) 990 } 991 992 // Change.Perform doesn't pass snapd-specific options to the kernel. 993 func (s *changeSuite) TestPerformFilesystemMountWithSnapdSpecificOptions(c *C) { 994 s.sys.InsertOsLstatResult(`lstat "/target"`, testutil.FileInfoDir) 995 chg := &update.Change{Action: update.Mount, Entry: osutil.MountEntry{Name: "device", Dir: "/target", Type: "type", Options: []string{"ro", "x-snapd.funky"}}} 996 synth, err := chg.Perform(s.as) 997 c.Assert(err, IsNil) 998 c.Assert(synth, HasLen, 0) 999 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 1000 {C: `lstat "/target"`, R: testutil.FileInfoDir}, 1001 {C: `mount "device" "/target" "type" MS_RDONLY ""`}, 1002 }) 1003 } 1004 1005 // ############################################### 1006 // Topic: bind-mounting and unmounting directories 1007 // ############################################### 1008 1009 // Change.Perform wants to bind mount a directory but the target cannot be stat'ed. 1010 func (s *changeSuite) TestPerformDirectoryBindMountTargetLstatError(c *C) { 1011 s.sys.InsertFault(`lstat "/target"`, errTesting) 1012 chg := &update.Change{Action: update.Mount, Entry: osutil.MountEntry{Name: "/source", Dir: "/target", Options: []string{"bind"}}} 1013 synth, err := chg.Perform(s.as) 1014 c.Assert(err, ErrorMatches, `cannot inspect "/target": testing`) 1015 c.Assert(synth, HasLen, 0) 1016 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 1017 {C: `lstat "/target"`, E: errTesting}, 1018 }) 1019 } 1020 1021 // Change.Perform wants to bind mount a directory but the source cannot be stat'ed. 1022 func (s *changeSuite) TestPerformDirectoryBindMountSourceLstatError(c *C) { 1023 s.sys.InsertOsLstatResult(`lstat "/target"`, testutil.FileInfoDir) 1024 s.sys.InsertFault(`lstat "/source"`, errTesting) 1025 chg := &update.Change{Action: update.Mount, Entry: osutil.MountEntry{Name: "/source", Dir: "/target", Options: []string{"bind"}}} 1026 synth, err := chg.Perform(s.as) 1027 c.Assert(err, ErrorMatches, `cannot inspect "/source": testing`) 1028 c.Assert(synth, HasLen, 0) 1029 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 1030 {C: `lstat "/target"`, R: testutil.FileInfoDir}, 1031 {C: `lstat "/source"`, E: errTesting}, 1032 }) 1033 } 1034 1035 // Change.Perform wants to bind mount a directory. 1036 func (s *changeSuite) TestPerformDirectoryBindMount(c *C) { 1037 s.sys.InsertOsLstatResult(`lstat "/source"`, testutil.FileInfoDir) 1038 s.sys.InsertOsLstatResult(`lstat "/target"`, testutil.FileInfoDir) 1039 s.sys.InsertFstatResult(`fstat 4 <ptr>`, syscall.Stat_t{}) 1040 s.sys.InsertFstatResult(`fstat 5 <ptr>`, syscall.Stat_t{}) 1041 chg := &update.Change{Action: update.Mount, Entry: osutil.MountEntry{Name: "/source", Dir: "/target", Options: []string{"bind"}}} 1042 synth, err := chg.Perform(s.as) 1043 c.Assert(err, IsNil) 1044 c.Assert(synth, HasLen, 0) 1045 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 1046 {C: `lstat "/target"`, R: testutil.FileInfoDir}, 1047 {C: `lstat "/source"`, R: testutil.FileInfoDir}, 1048 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 3}, 1049 {C: `openat 3 "source" O_NOFOLLOW|O_CLOEXEC|O_PATH 0`, R: 4}, 1050 {C: `fstat 4 <ptr>`, R: syscall.Stat_t{}}, 1051 {C: `close 3`}, 1052 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 3}, 1053 {C: `openat 3 "target" O_NOFOLLOW|O_CLOEXEC|O_PATH 0`, R: 5}, 1054 {C: `fstat 5 <ptr>`, R: syscall.Stat_t{}}, 1055 {C: `close 3`}, 1056 {C: `mount "/proc/self/fd/4" "/proc/self/fd/5" "" MS_BIND ""`}, 1057 {C: `close 5`}, 1058 {C: `close 4`}, 1059 }) 1060 } 1061 1062 // Change.Perform wants to bind mount a directory with sharing changes. 1063 func (s *changeSuite) TestPerformRecursiveDirectorySharedBindMount(c *C) { 1064 s.sys.InsertOsLstatResult(`lstat "/source"`, testutil.FileInfoDir) 1065 s.sys.InsertOsLstatResult(`lstat "/target"`, testutil.FileInfoDir) 1066 s.sys.InsertFstatResult(`fstat 4 <ptr>`, syscall.Stat_t{}) 1067 s.sys.InsertFstatResult(`fstat 5 <ptr>`, syscall.Stat_t{}) 1068 chg := &update.Change{Action: update.Mount, Entry: osutil.MountEntry{Name: "/source", Dir: "/target", Options: []string{"rshared", "rbind"}}} 1069 synth, err := chg.Perform(s.as) 1070 c.Assert(err, IsNil) 1071 c.Assert(synth, HasLen, 0) 1072 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 1073 {C: `lstat "/target"`, R: testutil.FileInfoDir}, 1074 {C: `lstat "/source"`, R: testutil.FileInfoDir}, 1075 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 3}, 1076 {C: `openat 3 "source" O_NOFOLLOW|O_CLOEXEC|O_PATH 0`, R: 4}, 1077 {C: `fstat 4 <ptr>`, R: syscall.Stat_t{}}, 1078 {C: `close 3`}, 1079 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 3}, 1080 {C: `openat 3 "target" O_NOFOLLOW|O_CLOEXEC|O_PATH 0`, R: 5}, 1081 {C: `fstat 5 <ptr>`, R: syscall.Stat_t{}}, 1082 {C: `close 3`}, 1083 {C: `mount "/proc/self/fd/4" "/proc/self/fd/5" "" MS_BIND|MS_REC ""`}, 1084 {C: `close 5`}, 1085 {C: `close 4`}, 1086 {C: `mount "none" "/target" "" MS_REC|MS_SHARED ""`}, 1087 }) 1088 } 1089 1090 // Change.Perform wants to bind mount a directory but it fails. 1091 func (s *changeSuite) TestPerformDirectoryBindMountWithError(c *C) { 1092 s.sys.InsertOsLstatResult(`lstat "/target"`, testutil.FileInfoDir) 1093 s.sys.InsertOsLstatResult(`lstat "/source"`, testutil.FileInfoDir) 1094 s.sys.InsertFstatResult(`fstat 4 <ptr>`, syscall.Stat_t{}) 1095 s.sys.InsertFstatResult(`fstat 5 <ptr>`, syscall.Stat_t{}) 1096 s.sys.InsertFault(`mount "/proc/self/fd/4" "/proc/self/fd/5" "" MS_BIND ""`, errTesting) 1097 chg := &update.Change{Action: update.Mount, Entry: osutil.MountEntry{Name: "/source", Dir: "/target", Options: []string{"bind"}}} 1098 synth, err := chg.Perform(s.as) 1099 c.Assert(err, Equals, errTesting) 1100 c.Assert(synth, HasLen, 0) 1101 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 1102 {C: `lstat "/target"`, R: testutil.FileInfoDir}, 1103 {C: `lstat "/source"`, R: testutil.FileInfoDir}, 1104 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 3}, 1105 {C: `openat 3 "source" O_NOFOLLOW|O_CLOEXEC|O_PATH 0`, R: 4}, 1106 {C: `fstat 4 <ptr>`, R: syscall.Stat_t{}}, 1107 {C: `close 3`}, 1108 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 3}, 1109 {C: `openat 3 "target" O_NOFOLLOW|O_CLOEXEC|O_PATH 0`, R: 5}, 1110 {C: `fstat 5 <ptr>`, R: syscall.Stat_t{}}, 1111 {C: `close 3`}, 1112 {C: `mount "/proc/self/fd/4" "/proc/self/fd/5" "" MS_BIND ""`, E: errTesting}, 1113 {C: `close 5`}, 1114 {C: `close 4`}, 1115 }) 1116 } 1117 1118 // Change.Perform wants to bind mount a directory but the mount point isn't there. 1119 func (s *changeSuite) TestPerformDirectoryBindMountWithoutMountPoint(c *C) { 1120 defer s.as.MockUnrestrictedPaths("/")() // Treat test path as unrestricted. 1121 s.sys.InsertOsLstatResult(`lstat "/source"`, testutil.FileInfoDir) 1122 s.sys.InsertFault(`lstat "/target"`, syscall.ENOENT) 1123 s.sys.InsertFstatResult(`fstat 4 <ptr>`, syscall.Stat_t{}) 1124 s.sys.InsertFstatResult(`fstat 5 <ptr>`, syscall.Stat_t{}) 1125 chg := &update.Change{Action: update.Mount, Entry: osutil.MountEntry{Name: "/source", Dir: "/target", Options: []string{"bind"}}} 1126 synth, err := chg.Perform(s.as) 1127 c.Assert(err, IsNil) 1128 c.Assert(synth, HasLen, 0) 1129 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 1130 {C: `lstat "/target"`, E: syscall.ENOENT}, 1131 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 1132 {C: `mkdirat 3 "target" 0755`}, 1133 {C: `openat 3 "target" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4}, 1134 {C: `fchown 4 0 0`}, 1135 {C: `close 4`}, 1136 {C: `close 3`}, 1137 {C: `lstat "/source"`, R: testutil.FileInfoDir}, 1138 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 3}, 1139 {C: `openat 3 "source" O_NOFOLLOW|O_CLOEXEC|O_PATH 0`, R: 4}, 1140 {C: `fstat 4 <ptr>`, R: syscall.Stat_t{}}, 1141 {C: `close 3`}, 1142 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 3}, 1143 {C: `openat 3 "target" O_NOFOLLOW|O_CLOEXEC|O_PATH 0`, R: 5}, 1144 {C: `fstat 5 <ptr>`, R: syscall.Stat_t{}}, 1145 {C: `close 3`}, 1146 {C: `mount "/proc/self/fd/4" "/proc/self/fd/5" "" MS_BIND ""`}, 1147 {C: `close 5`}, 1148 {C: `close 4`}, 1149 }) 1150 } 1151 1152 // Change.Perform wants to bind mount a directory but the mount source isn't there. 1153 func (s *changeSuite) TestPerformDirectoryBindMountWithoutMountSource(c *C) { 1154 defer s.as.MockUnrestrictedPaths("/")() // Treat test path as unrestricted. 1155 s.sys.InsertFault(`lstat "/source"`, syscall.ENOENT) 1156 s.sys.InsertOsLstatResult(`lstat "/target"`, testutil.FileInfoDir) 1157 s.sys.InsertFstatResult(`fstat 4 <ptr>`, syscall.Stat_t{}) 1158 s.sys.InsertFstatResult(`fstat 5 <ptr>`, syscall.Stat_t{}) 1159 chg := &update.Change{Action: update.Mount, Entry: osutil.MountEntry{Name: "/source", Dir: "/target", Options: []string{"bind"}}} 1160 synth, err := chg.Perform(s.as) 1161 c.Assert(err, IsNil) 1162 c.Assert(synth, HasLen, 0) 1163 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 1164 {C: `lstat "/target"`, R: testutil.FileInfoDir}, 1165 {C: `lstat "/source"`, E: syscall.ENOENT}, 1166 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 1167 {C: `mkdirat 3 "source" 0755`}, 1168 {C: `openat 3 "source" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4}, 1169 {C: `fchown 4 0 0`}, 1170 {C: `close 4`}, 1171 {C: `close 3`}, 1172 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 3}, 1173 {C: `openat 3 "source" O_NOFOLLOW|O_CLOEXEC|O_PATH 0`, R: 4}, 1174 {C: `fstat 4 <ptr>`, R: syscall.Stat_t{}}, 1175 {C: `close 3`}, 1176 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 3}, 1177 {C: `openat 3 "target" O_NOFOLLOW|O_CLOEXEC|O_PATH 0`, R: 5}, 1178 {C: `fstat 5 <ptr>`, R: syscall.Stat_t{}}, 1179 {C: `close 3`}, 1180 {C: `mount "/proc/self/fd/4" "/proc/self/fd/5" "" MS_BIND ""`}, 1181 {C: `close 5`}, 1182 {C: `close 4`}, 1183 }) 1184 } 1185 1186 // Change.Perform wants to create a directory bind mount but the mount point isn't there and cannot be created. 1187 func (s *changeSuite) TestPerformDirectoryBindMountWithoutMountPointWithErrors(c *C) { 1188 defer s.as.MockUnrestrictedPaths("/")() // Treat test path as unrestricted. 1189 s.sys.InsertFault(`lstat "/target"`, syscall.ENOENT) 1190 s.sys.InsertFault(`mkdirat 3 "target" 0755`, errTesting) 1191 chg := &update.Change{Action: update.Mount, Entry: osutil.MountEntry{Name: "/source", Dir: "/target", Options: []string{"bind"}}} 1192 synth, err := chg.Perform(s.as) 1193 c.Assert(err, ErrorMatches, `cannot create directory "/target": testing`) 1194 c.Assert(synth, HasLen, 0) 1195 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 1196 {C: `lstat "/target"`, E: syscall.ENOENT}, 1197 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 1198 {C: `mkdirat 3 "target" 0755`, E: errTesting}, 1199 {C: `close 3`}, 1200 }) 1201 } 1202 1203 // Change.Perform wants to create a directory bind mount but the mount source isn't there and cannot be created. 1204 func (s *changeSuite) TestPerformDirectoryBindMountWithoutMountSourceWithErrors(c *C) { 1205 defer s.as.MockUnrestrictedPaths("/")() // Treat test path as unrestricted. 1206 s.sys.InsertFault(`lstat "/source"`, syscall.ENOENT) 1207 s.sys.InsertFault(`mkdirat 3 "source" 0755`, errTesting) 1208 s.sys.InsertOsLstatResult(`lstat "/target"`, testutil.FileInfoDir) 1209 chg := &update.Change{Action: update.Mount, Entry: osutil.MountEntry{Name: "/source", Dir: "/target", Options: []string{"bind"}}} 1210 synth, err := chg.Perform(s.as) 1211 c.Assert(err, ErrorMatches, `cannot create directory "/source": testing`) 1212 c.Assert(synth, HasLen, 0) 1213 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 1214 {C: `lstat "/target"`, R: testutil.FileInfoDir}, 1215 {C: `lstat "/source"`, E: syscall.ENOENT}, 1216 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 1217 {C: `mkdirat 3 "source" 0755`, E: errTesting}, 1218 {C: `close 3`}, 1219 }) 1220 } 1221 1222 // Change.Perform wants to bind mount a directory but the mount point isn't there and the parent is read-only. 1223 func (s *changeSuite) TestPerformDirectoryBindMountWithoutMountPointAndReadOnlyBase(c *C) { 1224 defer s.as.MockUnrestrictedPaths("/")() // Treat test path as unrestricted. 1225 s.sys.InsertFault(`lstat "/rofs/target"`, syscall.ENOENT) 1226 s.sys.InsertFault(`mkdirat 3 "rofs" 0755`, syscall.EEXIST) 1227 s.sys.InsertFault(`openat 3 "target" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, syscall.ENOENT, nil) // works on 2nd try 1228 s.sys.InsertFault(`mkdirat 4 "target" 0755`, syscall.EROFS, nil) // works on 2nd try 1229 s.sys.InsertSysLstatResult(`lstat "/rofs" <ptr>`, syscall.Stat_t{Uid: 0, Gid: 0, Mode: 0755}) 1230 s.sys.InsertReadDirResult(`readdir "/rofs"`, nil) // pretend /rofs is empty. 1231 s.sys.InsertFault(`lstat "/tmp/.snap/rofs"`, syscall.ENOENT) 1232 s.sys.InsertOsLstatResult(`lstat "/rofs"`, testutil.FileInfoDir) 1233 s.sys.InsertOsLstatResult(`lstat "/source"`, testutil.FileInfoDir) 1234 s.sys.InsertFstatResult(`fstat 4 <ptr>`, syscall.Stat_t{}) 1235 s.sys.InsertFstatResult(`fstat 7 <ptr>`, syscall.Stat_t{}) 1236 s.sys.InsertFstatResult(`fstat 6 <ptr>`, syscall.Stat_t{}) 1237 s.sys.InsertFstatResult(`fstat 6 <ptr>`, syscall.Stat_t{}) 1238 s.sys.InsertFstatfsResult(`fstatfs 6 <ptr>`, syscall.Statfs_t{}) 1239 1240 chg := &update.Change{Action: update.Mount, Entry: osutil.MountEntry{Name: "/source", Dir: "/rofs/target", Options: []string{"bind"}}} 1241 synth, err := chg.Perform(s.as) 1242 c.Assert(err, IsNil) 1243 c.Assert(synth, DeepEquals, []*update.Change{ 1244 {Action: update.Mount, Entry: osutil.MountEntry{ 1245 Name: "tmpfs", Dir: "/rofs", Type: "tmpfs", 1246 Options: []string{"x-snapd.synthetic", "x-snapd.needed-by=/rofs/target", "mode=0755", "uid=0", "gid=0"}}, 1247 }, 1248 }) 1249 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 1250 // sniff mount target 1251 {C: `lstat "/rofs/target"`, E: syscall.ENOENT}, 1252 1253 // /rofs/target is missing, create it 1254 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 1255 {C: `mkdirat 3 "rofs" 0755`, E: syscall.EEXIST}, 1256 {C: `openat 3 "rofs" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4}, 1257 {C: `close 3`}, 1258 {C: `mkdirat 4 "target" 0755`, E: syscall.EROFS}, 1259 {C: `close 4`}, 1260 1261 // error, read only filesystem, create a mimic 1262 {C: `lstat "/rofs" <ptr>`, R: syscall.Stat_t{Uid: 0, Gid: 0, Mode: 0755}}, 1263 {C: `readdir "/rofs"`, R: []os.FileInfo(nil)}, 1264 {C: `lstat "/tmp/.snap/rofs"`, E: syscall.ENOENT}, 1265 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 1266 {C: `mkdirat 3 "tmp" 0755`}, 1267 {C: `openat 3 "tmp" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4}, 1268 {C: `fchown 4 0 0`}, 1269 {C: `mkdirat 4 ".snap" 0755`}, 1270 {C: `openat 4 ".snap" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 5}, 1271 {C: `fchown 5 0 0`}, 1272 {C: `close 4`}, 1273 {C: `close 3`}, 1274 {C: `mkdirat 5 "rofs" 0755`}, 1275 {C: `openat 5 "rofs" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 1276 {C: `fchown 3 0 0`}, 1277 {C: `close 3`}, 1278 {C: `close 5`}, 1279 {C: `lstat "/rofs"`, R: testutil.FileInfoDir}, 1280 1281 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 3}, 1282 {C: `openat 3 "rofs" O_NOFOLLOW|O_CLOEXEC|O_PATH 0`, R: 4}, 1283 {C: `fstat 4 <ptr>`, R: syscall.Stat_t{}}, 1284 {C: `close 3`}, 1285 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 3}, 1286 {C: `openat 3 "tmp" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 5}, 1287 {C: `openat 5 ".snap" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 6}, 1288 {C: `openat 6 "rofs" O_NOFOLLOW|O_CLOEXEC|O_PATH 0`, R: 7}, 1289 {C: `fstat 7 <ptr>`, R: syscall.Stat_t{}}, 1290 {C: `close 6`}, 1291 {C: `close 5`}, 1292 {C: `close 3`}, 1293 {C: `mount "/proc/self/fd/4" "/proc/self/fd/7" "" MS_BIND|MS_REC ""`}, 1294 {C: `close 7`}, 1295 {C: `close 4`}, 1296 1297 {C: `lstat "/rofs"`, R: testutil.FileInfoDir}, 1298 {C: `mount "tmpfs" "/rofs" "tmpfs" 0 "mode=0755,uid=0,gid=0"`}, 1299 {C: `mount "none" "/tmp/.snap/rofs" "" MS_REC|MS_PRIVATE ""`}, 1300 {C: `unmount "/tmp/.snap/rofs" UMOUNT_NOFOLLOW|MNT_DETACH`}, 1301 1302 // Perform clean up after the unmount operation. 1303 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 3}, 1304 {C: `openat 3 "tmp" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 4}, 1305 {C: `openat 4 ".snap" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 5}, 1306 {C: `openat 5 "rofs" O_NOFOLLOW|O_CLOEXEC|O_PATH 0`, R: 6}, 1307 {C: `fstat 6 <ptr>`, R: syscall.Stat_t{}}, 1308 {C: `close 5`}, 1309 {C: `close 4`}, 1310 {C: `close 3`}, 1311 {C: `fstatfs 6 <ptr>`, R: syscall.Statfs_t{}}, 1312 {C: `remove "/tmp/.snap/rofs"`}, 1313 {C: `close 6`}, 1314 1315 // mimic ready, re-try initial mkdir 1316 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 1317 {C: `mkdirat 3 "rofs" 0755`, E: syscall.EEXIST}, 1318 {C: `openat 3 "rofs" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4}, 1319 {C: `close 3`}, 1320 {C: `mkdirat 4 "target" 0755`}, 1321 {C: `openat 4 "target" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 1322 {C: `fchown 3 0 0`}, 1323 {C: `close 3`}, 1324 {C: `close 4`}, 1325 1326 // sniff mount source 1327 {C: `lstat "/source"`, R: testutil.FileInfoDir}, 1328 1329 // mount the filesystem 1330 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 3}, 1331 {C: `openat 3 "source" O_NOFOLLOW|O_CLOEXEC|O_PATH 0`, R: 4}, 1332 {C: `fstat 4 <ptr>`, R: syscall.Stat_t{}}, 1333 {C: `close 3`}, 1334 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 3}, 1335 {C: `openat 3 "rofs" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 5}, 1336 {C: `openat 5 "target" O_NOFOLLOW|O_CLOEXEC|O_PATH 0`, R: 6}, 1337 {C: `fstat 6 <ptr>`, R: syscall.Stat_t{}}, 1338 {C: `close 5`}, 1339 {C: `close 3`}, 1340 {C: `mount "/proc/self/fd/4" "/proc/self/fd/6" "" MS_BIND ""`}, 1341 {C: `close 6`}, 1342 {C: `close 4`}, 1343 }) 1344 } 1345 1346 // Change.Perform wants to bind mount a directory but the mount source isn't there and the parent is read-only. 1347 func (s *changeSuite) TestPerformDirectoryBindMountWithoutMountSourceAndReadOnlyBase(c *C) { 1348 defer s.as.MockUnrestrictedPaths("/")() // Treat test path as unrestricted. 1349 s.sys.InsertOsLstatResult(`lstat "/rofs"`, testutil.FileInfoDir) 1350 s.sys.InsertFault(`lstat "/rofs/source"`, syscall.ENOENT) 1351 s.sys.InsertOsLstatResult(`lstat "/target"`, testutil.FileInfoDir) 1352 s.sys.InsertFault(`mkdirat 3 "rofs" 0755`, syscall.EEXIST) 1353 s.sys.InsertFault(`mkdirat 4 "source" 0755`, syscall.EROFS) 1354 1355 chg := &update.Change{Action: update.Mount, Entry: osutil.MountEntry{Name: "/rofs/source", Dir: "/target", Options: []string{"bind"}}} 1356 synth, err := chg.Perform(s.as) 1357 c.Assert(err, ErrorMatches, `cannot operate on read-only filesystem at /rofs`) 1358 c.Assert(synth, HasLen, 0) 1359 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 1360 {C: `lstat "/target"`, R: testutil.FileInfoDir}, 1361 {C: `lstat "/rofs/source"`, E: syscall.ENOENT}, 1362 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 1363 {C: `mkdirat 3 "rofs" 0755`, E: syscall.EEXIST}, 1364 {C: `openat 3 "rofs" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4}, 1365 {C: `close 3`}, 1366 {C: `mkdirat 4 "source" 0755`, E: syscall.EROFS}, 1367 {C: `close 4`}, 1368 }) 1369 } 1370 1371 // Change.Perform wants to bind mount a directory but the mount source isn't there and the parent is read-only but this is for a layout. 1372 func (s *changeSuite) TestPerformDirectoryBindMountWithoutMountSourceAndReadOnlyBaseForLayout(c *C) { 1373 defer s.as.MockUnrestrictedPaths("/")() // Treat test path as unrestricted. 1374 s.sys.InsertOsLstatResult(`lstat "/target"`, testutil.FileInfoDir) 1375 s.sys.InsertFault(`lstat "/rofs/source"`, syscall.ENOENT) 1376 s.sys.InsertFault(`mkdirat 3 "rofs" 0755`, syscall.EEXIST) 1377 s.sys.InsertFault(`openat 3 "source" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, syscall.ENOENT, nil) // works on 2nd try 1378 s.sys.InsertFault(`mkdirat 4 "source" 0755`, syscall.EROFS, nil) // works on 2nd try 1379 s.sys.InsertReadDirResult(`readdir "/rofs"`, nil) // pretend /rofs is empty. 1380 s.sys.InsertFault(`lstat "/tmp/.snap/rofs"`, syscall.ENOENT) 1381 s.sys.InsertOsLstatResult(`lstat "/rofs"`, testutil.FileInfoDir) 1382 s.sys.InsertSysLstatResult(`lstat "/rofs" <ptr>`, syscall.Stat_t{Uid: 0, Gid: 0, Mode: 0755}) 1383 s.sys.InsertFstatResult(`fstat 4 <ptr>`, syscall.Stat_t{}) 1384 s.sys.InsertFstatResult(`fstat 5 <ptr>`, syscall.Stat_t{}) 1385 s.sys.InsertFstatResult(`fstat 7 <ptr>`, syscall.Stat_t{}) 1386 s.sys.InsertFstatResult(`fstat 6 <ptr>`, syscall.Stat_t{}) 1387 s.sys.InsertFstatfsResult(`fstatfs 6 <ptr>`, syscall.Statfs_t{}) 1388 1389 chg := &update.Change{Action: update.Mount, Entry: osutil.MountEntry{Name: "/rofs/source", Dir: "/target", Options: []string{"bind", "x-snapd.origin=layout"}}} 1390 synth, err := chg.Perform(s.as) 1391 c.Assert(err, IsNil) 1392 c.Check(synth, DeepEquals, []*update.Change{ 1393 {Action: update.Mount, Entry: osutil.MountEntry{ 1394 Name: "tmpfs", Dir: "/rofs", Type: "tmpfs", 1395 Options: []string{"x-snapd.synthetic", "x-snapd.needed-by=/rofs/source", "mode=0755", "uid=0", "gid=0"}}, 1396 }, 1397 }) 1398 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 1399 // sniff mount target and source 1400 {C: `lstat "/target"`, R: testutil.FileInfoDir}, 1401 {C: `lstat "/rofs/source"`, E: syscall.ENOENT}, 1402 1403 // /rofs/source is missing, create it 1404 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 1405 {C: `mkdirat 3 "rofs" 0755`, E: syscall.EEXIST}, 1406 {C: `openat 3 "rofs" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4}, 1407 {C: `close 3`}, 1408 {C: `mkdirat 4 "source" 0755`, E: syscall.EROFS}, 1409 {C: `close 4`}, 1410 1411 // error /rofs is a read-only filesystem, create a mimic 1412 {C: `lstat "/rofs" <ptr>`, R: syscall.Stat_t{Mode: 0755}}, 1413 {C: `readdir "/rofs"`, R: []os.FileInfo(nil)}, 1414 {C: `lstat "/tmp/.snap/rofs"`, E: syscall.ENOENT}, 1415 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 1416 {C: `mkdirat 3 "tmp" 0755`}, 1417 {C: `openat 3 "tmp" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4}, 1418 {C: `fchown 4 0 0`}, 1419 {C: `mkdirat 4 ".snap" 0755`}, 1420 {C: `openat 4 ".snap" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 5}, 1421 {C: `fchown 5 0 0`}, 1422 {C: `close 4`}, 1423 {C: `close 3`}, 1424 {C: `mkdirat 5 "rofs" 0755`}, 1425 {C: `openat 5 "rofs" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 1426 {C: `fchown 3 0 0`}, 1427 {C: `close 3`}, 1428 {C: `close 5`}, 1429 {C: `lstat "/rofs"`, R: testutil.FileInfoDir}, 1430 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 3}, 1431 {C: `openat 3 "rofs" O_NOFOLLOW|O_CLOEXEC|O_PATH 0`, R: 4}, 1432 {C: `fstat 4 <ptr>`, R: syscall.Stat_t{}}, 1433 {C: `close 3`}, 1434 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 3}, 1435 {C: `openat 3 "tmp" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 5}, 1436 {C: `openat 5 ".snap" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 6}, 1437 {C: `openat 6 "rofs" O_NOFOLLOW|O_CLOEXEC|O_PATH 0`, R: 7}, 1438 {C: `fstat 7 <ptr>`, R: syscall.Stat_t{}}, 1439 {C: `close 6`}, 1440 {C: `close 5`}, 1441 {C: `close 3`}, 1442 {C: `mount "/proc/self/fd/4" "/proc/self/fd/7" "" MS_BIND|MS_REC ""`}, 1443 {C: `close 7`}, 1444 {C: `close 4`}, 1445 {C: `lstat "/rofs"`, R: testutil.FileInfoDir}, 1446 {C: `mount "tmpfs" "/rofs" "tmpfs" 0 "mode=0755,uid=0,gid=0"`}, 1447 {C: `mount "none" "/tmp/.snap/rofs" "" MS_REC|MS_PRIVATE ""`}, 1448 {C: `unmount "/tmp/.snap/rofs" UMOUNT_NOFOLLOW|MNT_DETACH`}, 1449 1450 // Perform clean up after the unmount operation. 1451 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 3}, 1452 {C: `openat 3 "tmp" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 4}, 1453 {C: `openat 4 ".snap" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 5}, 1454 {C: `openat 5 "rofs" O_NOFOLLOW|O_CLOEXEC|O_PATH 0`, R: 6}, 1455 {C: `fstat 6 <ptr>`, R: syscall.Stat_t{}}, 1456 {C: `close 5`}, 1457 {C: `close 4`}, 1458 {C: `close 3`}, 1459 {C: `fstatfs 6 <ptr>`, R: syscall.Statfs_t{}}, 1460 {C: `remove "/tmp/.snap/rofs"`}, 1461 {C: `close 6`}, 1462 1463 // /rofs/source was missing (we checked earlier), create it 1464 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 1465 {C: `mkdirat 3 "rofs" 0755`, E: syscall.EEXIST}, 1466 {C: `openat 3 "rofs" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4}, 1467 {C: `close 3`}, 1468 {C: `mkdirat 4 "source" 0755`}, 1469 {C: `openat 4 "source" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 1470 {C: `fchown 3 0 0`}, 1471 {C: `close 3`}, 1472 {C: `close 4`}, 1473 1474 // bind mount /rofs/source -> /target 1475 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 3}, 1476 {C: `openat 3 "rofs" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 4}, 1477 {C: `openat 4 "source" O_NOFOLLOW|O_CLOEXEC|O_PATH 0`, R: 5}, 1478 {C: `fstat 5 <ptr>`, R: syscall.Stat_t{}}, 1479 {C: `close 4`}, 1480 {C: `close 3`}, 1481 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 3}, 1482 {C: `openat 3 "target" O_NOFOLLOW|O_CLOEXEC|O_PATH 0`, R: 4}, 1483 {C: `fstat 4 <ptr>`, R: syscall.Stat_t{}}, 1484 {C: `close 3`}, 1485 {C: `mount "/proc/self/fd/5" "/proc/self/fd/4" "" MS_BIND ""`}, 1486 {C: `close 4`}, 1487 {C: `close 5`}, 1488 }) 1489 } 1490 1491 // Change.Perform wants to bind mount a directory but there's a symlink in mount point. 1492 func (s *changeSuite) TestPerformDirectoryBindMountWithSymlinkInMountPoint(c *C) { 1493 s.sys.InsertOsLstatResult(`lstat "/target"`, testutil.FileInfoSymlink) 1494 chg := &update.Change{Action: update.Mount, Entry: osutil.MountEntry{Name: "/source", Dir: "/target", Options: []string{"bind"}}} 1495 synth, err := chg.Perform(s.as) 1496 c.Assert(err, ErrorMatches, `cannot use "/target" as mount point: not a directory`) 1497 c.Assert(synth, HasLen, 0) 1498 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 1499 {C: `lstat "/target"`, R: testutil.FileInfoSymlink}, 1500 }) 1501 } 1502 1503 // Change.Perform wants to bind mount a directory but there's a file in mount mount. 1504 func (s *changeSuite) TestPerformDirectoryBindMountWithFileInMountPoint(c *C) { 1505 s.sys.InsertOsLstatResult(`lstat "/target"`, testutil.FileInfoFile) 1506 chg := &update.Change{Action: update.Mount, Entry: osutil.MountEntry{Name: "/source", Dir: "/target", Options: []string{"bind"}}} 1507 synth, err := chg.Perform(s.as) 1508 c.Assert(err, ErrorMatches, `cannot use "/target" as mount point: not a directory`) 1509 c.Assert(synth, HasLen, 0) 1510 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 1511 {C: `lstat "/target"`, R: testutil.FileInfoFile}, 1512 }) 1513 } 1514 1515 // Change.Perform wants to bind mount a directory but there's a symlink in source. 1516 func (s *changeSuite) TestPerformDirectoryBindMountWithSymlinkInMountSource(c *C) { 1517 s.sys.InsertOsLstatResult(`lstat "/target"`, testutil.FileInfoDir) 1518 s.sys.InsertOsLstatResult(`lstat "/source"`, testutil.FileInfoSymlink) 1519 chg := &update.Change{Action: update.Mount, Entry: osutil.MountEntry{Name: "/source", Dir: "/target", Options: []string{"bind"}}} 1520 synth, err := chg.Perform(s.as) 1521 c.Assert(err, ErrorMatches, `cannot use "/source" as bind-mount source: not a directory`) 1522 c.Assert(synth, HasLen, 0) 1523 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 1524 {C: `lstat "/target"`, R: testutil.FileInfoDir}, 1525 {C: `lstat "/source"`, R: testutil.FileInfoSymlink}, 1526 }) 1527 } 1528 1529 // Change.Perform wants to bind mount a directory but there's a file in source. 1530 func (s *changeSuite) TestPerformDirectoryBindMountWithFileInMountSource(c *C) { 1531 s.sys.InsertOsLstatResult(`lstat "/target"`, testutil.FileInfoDir) 1532 s.sys.InsertOsLstatResult(`lstat "/source"`, testutil.FileInfoFile) 1533 chg := &update.Change{Action: update.Mount, Entry: osutil.MountEntry{Name: "/source", Dir: "/target", Options: []string{"bind"}}} 1534 synth, err := chg.Perform(s.as) 1535 c.Assert(err, ErrorMatches, `cannot use "/source" as bind-mount source: not a directory`) 1536 c.Assert(synth, HasLen, 0) 1537 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 1538 {C: `lstat "/target"`, R: testutil.FileInfoDir}, 1539 {C: `lstat "/source"`, R: testutil.FileInfoFile}, 1540 }) 1541 } 1542 1543 // Change.Perform wants to unmount a directory bind mount. 1544 func (s *changeSuite) TestPerformDirectoryBindUnmount(c *C) { 1545 s.sys.InsertFstatResult(`fstat 4 <ptr>`, syscall.Stat_t{}) 1546 s.sys.InsertFstatfsResult(`fstatfs 4 <ptr>`, syscall.Statfs_t{}) 1547 chg := &update.Change{Action: update.Unmount, Entry: osutil.MountEntry{Name: "/source", Dir: "/target", Options: []string{"bind"}}} 1548 synth, err := chg.Perform(s.as) 1549 c.Assert(err, IsNil) 1550 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 1551 {C: `unmount "/target" UMOUNT_NOFOLLOW`}, 1552 1553 // Perform clean up after the unmount operation. 1554 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 3}, 1555 {C: `openat 3 "target" O_NOFOLLOW|O_CLOEXEC|O_PATH 0`, R: 4}, 1556 {C: `fstat 4 <ptr>`, R: syscall.Stat_t{}}, 1557 {C: `close 3`}, 1558 {C: `fstatfs 4 <ptr>`, R: syscall.Statfs_t{}}, 1559 {C: `remove "/target"`}, 1560 {C: `close 4`}, 1561 }) 1562 c.Assert(synth, HasLen, 0) 1563 } 1564 1565 // Change.Perform wants to unmount a directory bind mount but it fails. 1566 func (s *changeSuite) TestPerformDirectoryBindUnmountError(c *C) { 1567 s.sys.InsertFault(`unmount "/target" UMOUNT_NOFOLLOW`, errTesting) 1568 chg := &update.Change{Action: update.Unmount, Entry: osutil.MountEntry{Name: "/source", Dir: "/target", Options: []string{"bind"}}} 1569 synth, err := chg.Perform(s.as) 1570 c.Assert(err, Equals, errTesting) 1571 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 1572 {C: `unmount "/target" UMOUNT_NOFOLLOW`, E: errTesting}, 1573 }) 1574 c.Assert(synth, HasLen, 0) 1575 } 1576 1577 // ######################################### 1578 // Topic: bind-mounting and unmounting files 1579 // ######################################### 1580 1581 // Change.Perform wants to bind mount a file but the target cannot be stat'ed. 1582 func (s *changeSuite) TestPerformFileBindMountTargetLstatError(c *C) { 1583 s.sys.InsertFault(`lstat "/target"`, errTesting) 1584 chg := &update.Change{Action: update.Mount, Entry: osutil.MountEntry{Name: "/source", Dir: "/target", Options: []string{"bind", "x-snapd.kind=file"}}} 1585 synth, err := chg.Perform(s.as) 1586 c.Assert(err, ErrorMatches, `cannot inspect "/target": testing`) 1587 c.Assert(synth, HasLen, 0) 1588 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 1589 {C: `lstat "/target"`, E: errTesting}, 1590 }) 1591 } 1592 1593 // Change.Perform wants to bind mount a file but the source cannot be stat'ed. 1594 func (s *changeSuite) TestPerformFileBindMountSourceLstatError(c *C) { 1595 s.sys.InsertOsLstatResult(`lstat "/target"`, testutil.FileInfoFile) 1596 s.sys.InsertFault(`lstat "/source"`, errTesting) 1597 chg := &update.Change{Action: update.Mount, Entry: osutil.MountEntry{Name: "/source", Dir: "/target", Options: []string{"bind", "x-snapd.kind=file"}}} 1598 synth, err := chg.Perform(s.as) 1599 c.Assert(err, ErrorMatches, `cannot inspect "/source": testing`) 1600 c.Assert(synth, HasLen, 0) 1601 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 1602 {C: `lstat "/target"`, R: testutil.FileInfoFile}, 1603 {C: `lstat "/source"`, E: errTesting}, 1604 }) 1605 } 1606 1607 // Change.Perform wants to bind mount a file. 1608 func (s *changeSuite) TestPerformFileBindMount(c *C) { 1609 s.sys.InsertOsLstatResult(`lstat "/source"`, testutil.FileInfoFile) 1610 s.sys.InsertOsLstatResult(`lstat "/target"`, testutil.FileInfoFile) 1611 s.sys.InsertFstatResult(`fstat 4 <ptr>`, syscall.Stat_t{}) 1612 s.sys.InsertFstatResult(`fstat 5 <ptr>`, syscall.Stat_t{}) 1613 chg := &update.Change{Action: update.Mount, Entry: osutil.MountEntry{Name: "/source", Dir: "/target", Options: []string{"bind", "x-snapd.kind=file"}}} 1614 synth, err := chg.Perform(s.as) 1615 c.Assert(err, IsNil) 1616 c.Assert(synth, HasLen, 0) 1617 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 1618 {C: `lstat "/target"`, R: testutil.FileInfoFile}, 1619 {C: `lstat "/source"`, R: testutil.FileInfoFile}, 1620 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 3}, 1621 {C: `openat 3 "source" O_NOFOLLOW|O_CLOEXEC|O_PATH 0`, R: 4}, 1622 {C: `fstat 4 <ptr>`, R: syscall.Stat_t{}}, 1623 {C: `close 3`}, 1624 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 3}, 1625 {C: `openat 3 "target" O_NOFOLLOW|O_CLOEXEC|O_PATH 0`, R: 5}, 1626 {C: `fstat 5 <ptr>`, R: syscall.Stat_t{}}, 1627 {C: `close 3`}, 1628 {C: `mount "/proc/self/fd/4" "/proc/self/fd/5" "" MS_BIND ""`}, 1629 {C: `close 5`}, 1630 {C: `close 4`}, 1631 }) 1632 } 1633 1634 // Change.Perform wants to bind mount a file but it fails. 1635 func (s *changeSuite) TestPerformFileBindMountWithError(c *C) { 1636 s.sys.InsertOsLstatResult(`lstat "/target"`, testutil.FileInfoFile) 1637 s.sys.InsertOsLstatResult(`lstat "/source"`, testutil.FileInfoFile) 1638 s.sys.InsertFstatResult(`fstat 4 <ptr>`, syscall.Stat_t{}) 1639 s.sys.InsertFstatResult(`fstat 5 <ptr>`, syscall.Stat_t{}) 1640 s.sys.InsertFault(`mount "/proc/self/fd/4" "/proc/self/fd/5" "" MS_BIND ""`, errTesting) 1641 chg := &update.Change{Action: update.Mount, Entry: osutil.MountEntry{Name: "/source", Dir: "/target", Options: []string{"bind", "x-snapd.kind=file"}}} 1642 synth, err := chg.Perform(s.as) 1643 c.Assert(err, Equals, errTesting) 1644 c.Assert(synth, HasLen, 0) 1645 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 1646 {C: `lstat "/target"`, R: testutil.FileInfoFile}, 1647 {C: `lstat "/source"`, R: testutil.FileInfoFile}, 1648 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 3}, 1649 {C: `openat 3 "source" O_NOFOLLOW|O_CLOEXEC|O_PATH 0`, R: 4}, 1650 {C: `fstat 4 <ptr>`, R: syscall.Stat_t{}}, 1651 {C: `close 3`}, 1652 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 3}, 1653 {C: `openat 3 "target" O_NOFOLLOW|O_CLOEXEC|O_PATH 0`, R: 5}, 1654 {C: `fstat 5 <ptr>`, R: syscall.Stat_t{}}, 1655 {C: `close 3`}, 1656 {C: `mount "/proc/self/fd/4" "/proc/self/fd/5" "" MS_BIND ""`, E: errTesting}, 1657 {C: `close 5`}, 1658 {C: `close 4`}, 1659 }) 1660 } 1661 1662 // Change.Perform wants to bind mount a file but the mount point isn't there. 1663 func (s *changeSuite) TestPerformFileBindMountWithoutMountPoint(c *C) { 1664 defer s.as.MockUnrestrictedPaths("/")() // Treat test path as unrestricted. 1665 s.sys.InsertOsLstatResult(`lstat "/source"`, testutil.FileInfoFile) 1666 s.sys.InsertFault(`lstat "/target"`, syscall.ENOENT) 1667 s.sys.InsertFstatResult(`fstat 4 <ptr>`, syscall.Stat_t{}) 1668 s.sys.InsertFstatResult(`fstat 5 <ptr>`, syscall.Stat_t{}) 1669 chg := &update.Change{Action: update.Mount, Entry: osutil.MountEntry{Name: "/source", Dir: "/target", Options: []string{"bind", "x-snapd.kind=file"}}} 1670 synth, err := chg.Perform(s.as) 1671 c.Assert(err, IsNil) 1672 c.Assert(synth, HasLen, 0) 1673 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 1674 {C: `lstat "/target"`, E: syscall.ENOENT}, 1675 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 1676 {C: `openat 3 "target" O_NOFOLLOW|O_CLOEXEC|O_CREAT|O_EXCL 0755`, R: 4}, 1677 {C: `fchown 4 0 0`}, 1678 {C: `close 4`}, 1679 {C: `close 3`}, 1680 {C: `lstat "/source"`, R: testutil.FileInfoFile}, 1681 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 3}, 1682 {C: `openat 3 "source" O_NOFOLLOW|O_CLOEXEC|O_PATH 0`, R: 4}, 1683 {C: `fstat 4 <ptr>`, R: syscall.Stat_t{}}, 1684 {C: `close 3`}, 1685 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 3}, 1686 {C: `openat 3 "target" O_NOFOLLOW|O_CLOEXEC|O_PATH 0`, R: 5}, 1687 {C: `fstat 5 <ptr>`, R: syscall.Stat_t{}}, 1688 {C: `close 3`}, 1689 {C: `mount "/proc/self/fd/4" "/proc/self/fd/5" "" MS_BIND ""`}, 1690 {C: `close 5`}, 1691 {C: `close 4`}, 1692 }) 1693 } 1694 1695 // Change.Perform wants to create a directory bind mount but the mount point isn't there and cannot be created. 1696 func (s *changeSuite) TestPerformFileBindMountWithoutMountPointWithErrors(c *C) { 1697 defer s.as.MockUnrestrictedPaths("/")() // Treat test path as unrestricted. 1698 s.sys.InsertFault(`lstat "/target"`, syscall.ENOENT) 1699 s.sys.InsertFault(`openat 3 "target" O_NOFOLLOW|O_CLOEXEC|O_CREAT|O_EXCL 0755`, errTesting) 1700 chg := &update.Change{Action: update.Mount, Entry: osutil.MountEntry{Name: "/source", Dir: "/target", Options: []string{"bind", "x-snapd.kind=file"}}} 1701 synth, err := chg.Perform(s.as) 1702 c.Assert(err, ErrorMatches, `cannot open file "/target": testing`) 1703 c.Assert(synth, HasLen, 0) 1704 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 1705 {C: `lstat "/target"`, E: syscall.ENOENT}, 1706 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 1707 {C: `openat 3 "target" O_NOFOLLOW|O_CLOEXEC|O_CREAT|O_EXCL 0755`, E: errTesting}, 1708 {C: `close 3`}, 1709 }) 1710 } 1711 1712 // Change.Perform wants to bind mount a file but the mount source isn't there. 1713 func (s *changeSuite) TestPerformFileBindMountWithoutMountSource(c *C) { 1714 defer s.as.MockUnrestrictedPaths("/")() // Treat test path as unrestricted. 1715 s.sys.InsertFault(`lstat "/source"`, syscall.ENOENT) 1716 s.sys.InsertOsLstatResult(`lstat "/target"`, testutil.FileInfoFile) 1717 s.sys.InsertFstatResult(`fstat 4 <ptr>`, syscall.Stat_t{}) 1718 s.sys.InsertFstatResult(`fstat 5 <ptr>`, syscall.Stat_t{}) 1719 chg := &update.Change{Action: update.Mount, Entry: osutil.MountEntry{Name: "/source", Dir: "/target", Options: []string{"bind", "x-snapd.kind=file"}}} 1720 synth, err := chg.Perform(s.as) 1721 c.Assert(err, IsNil) 1722 c.Assert(synth, HasLen, 0) 1723 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 1724 {C: `lstat "/target"`, R: testutil.FileInfoFile}, 1725 {C: `lstat "/source"`, E: syscall.ENOENT}, 1726 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 1727 {C: `openat 3 "source" O_NOFOLLOW|O_CLOEXEC|O_CREAT|O_EXCL 0755`, R: 4}, 1728 {C: `fchown 4 0 0`}, 1729 {C: `close 4`}, 1730 {C: `close 3`}, 1731 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 3}, 1732 {C: `openat 3 "source" O_NOFOLLOW|O_CLOEXEC|O_PATH 0`, R: 4}, 1733 {C: `fstat 4 <ptr>`, R: syscall.Stat_t{}}, 1734 {C: `close 3`}, 1735 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 3}, 1736 {C: `openat 3 "target" O_NOFOLLOW|O_CLOEXEC|O_PATH 0`, R: 5}, 1737 {C: `fstat 5 <ptr>`, R: syscall.Stat_t{}}, 1738 {C: `close 3`}, 1739 {C: `mount "/proc/self/fd/4" "/proc/self/fd/5" "" MS_BIND ""`}, 1740 {C: `close 5`}, 1741 {C: `close 4`}, 1742 }) 1743 } 1744 1745 // Change.Perform wants to create a file bind mount but the mount source isn't there and cannot be created. 1746 func (s *changeSuite) TestPerformFileBindMountWithoutMountSourceWithErrors(c *C) { 1747 defer s.as.MockUnrestrictedPaths("/")() // Treat test path as unrestricted. 1748 s.sys.InsertFault(`lstat "/source"`, syscall.ENOENT) 1749 s.sys.InsertFault(`openat 3 "source" O_NOFOLLOW|O_CLOEXEC|O_CREAT|O_EXCL 0755`, errTesting) 1750 s.sys.InsertOsLstatResult(`lstat "/target"`, testutil.FileInfoFile) 1751 chg := &update.Change{Action: update.Mount, Entry: osutil.MountEntry{Name: "/source", Dir: "/target", Options: []string{"bind", "x-snapd.kind=file"}}} 1752 synth, err := chg.Perform(s.as) 1753 c.Assert(err, ErrorMatches, `cannot open file "/source": testing`) 1754 c.Assert(synth, HasLen, 0) 1755 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 1756 {C: `lstat "/target"`, R: testutil.FileInfoFile}, 1757 {C: `lstat "/source"`, E: syscall.ENOENT}, 1758 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 1759 {C: `openat 3 "source" O_NOFOLLOW|O_CLOEXEC|O_CREAT|O_EXCL 0755`, E: errTesting}, 1760 {C: `close 3`}, 1761 }) 1762 } 1763 1764 // Change.Perform wants to bind mount a file but the mount point isn't there and the parent is read-only. 1765 func (s *changeSuite) TestPerformFileBindMountWithoutMountPointAndReadOnlyBase(c *C) { 1766 defer s.as.MockUnrestrictedPaths("/")() // Treat test path as unrestricted. 1767 s.sys.InsertFault(`lstat "/rofs/target"`, syscall.ENOENT) 1768 s.sys.InsertFault(`mkdirat 3 "rofs" 0755`, syscall.EEXIST) 1769 s.sys.InsertFault(`openat 4 "target" O_NOFOLLOW|O_CLOEXEC|O_CREAT|O_EXCL 0755`, syscall.EROFS, nil) // works on 2nd try 1770 s.sys.InsertSysLstatResult(`lstat "/rofs" <ptr>`, syscall.Stat_t{Uid: 0, Gid: 0, Mode: 0755}) 1771 s.sys.InsertReadDirResult(`readdir "/rofs"`, nil) // pretend /rofs is empty. 1772 s.sys.InsertFault(`lstat "/tmp/.snap/rofs"`, syscall.ENOENT) 1773 s.sys.InsertOsLstatResult(`lstat "/rofs"`, testutil.FileInfoDir) 1774 s.sys.InsertOsLstatResult(`lstat "/source"`, testutil.FileInfoFile) 1775 s.sys.InsertFstatResult(`fstat 4 <ptr>`, syscall.Stat_t{}) 1776 s.sys.InsertFstatResult(`fstat 6 <ptr>`, syscall.Stat_t{}) 1777 s.sys.InsertFstatResult(`fstat 7 <ptr>`, syscall.Stat_t{}) 1778 s.sys.InsertFstatResult(`fstat 6 <ptr>`, syscall.Stat_t{}) 1779 s.sys.InsertFstatfsResult(`fstatfs 6 <ptr>`, syscall.Statfs_t{}) 1780 1781 chg := &update.Change{Action: update.Mount, Entry: osutil.MountEntry{Name: "/source", Dir: "/rofs/target", Options: []string{"bind", "x-snapd.kind=file"}}} 1782 synth, err := chg.Perform(s.as) 1783 c.Assert(err, IsNil) 1784 c.Assert(synth, DeepEquals, []*update.Change{ 1785 {Action: update.Mount, Entry: osutil.MountEntry{ 1786 Name: "tmpfs", Dir: "/rofs", Type: "tmpfs", 1787 Options: []string{"x-snapd.synthetic", "x-snapd.needed-by=/rofs/target", "mode=0755", "uid=0", "gid=0"}}, 1788 }, 1789 }) 1790 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 1791 // sniff mount target 1792 {C: `lstat "/rofs/target"`, E: syscall.ENOENT}, 1793 1794 // /rofs/target is missing, create it 1795 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 1796 {C: `mkdirat 3 "rofs" 0755`, E: syscall.EEXIST}, 1797 {C: `openat 3 "rofs" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4}, 1798 {C: `close 3`}, 1799 {C: `openat 4 "target" O_NOFOLLOW|O_CLOEXEC|O_CREAT|O_EXCL 0755`, E: syscall.EROFS}, 1800 {C: `close 4`}, 1801 1802 // error, read only filesystem, create a mimic 1803 {C: `lstat "/rofs" <ptr>`, R: syscall.Stat_t{Mode: 0755}}, 1804 {C: `readdir "/rofs"`, R: []os.FileInfo(nil)}, 1805 {C: `lstat "/tmp/.snap/rofs"`, E: syscall.ENOENT}, 1806 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 1807 {C: `mkdirat 3 "tmp" 0755`}, 1808 {C: `openat 3 "tmp" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4}, 1809 {C: `fchown 4 0 0`}, 1810 {C: `mkdirat 4 ".snap" 0755`}, 1811 {C: `openat 4 ".snap" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 5}, 1812 {C: `fchown 5 0 0`}, 1813 {C: `close 4`}, 1814 {C: `close 3`}, 1815 {C: `mkdirat 5 "rofs" 0755`}, 1816 {C: `openat 5 "rofs" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 1817 {C: `fchown 3 0 0`}, 1818 {C: `close 3`}, 1819 {C: `close 5`}, 1820 {C: `lstat "/rofs"`, R: testutil.FileInfoDir}, 1821 1822 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 3}, 1823 {C: `openat 3 "rofs" O_NOFOLLOW|O_CLOEXEC|O_PATH 0`, R: 4}, 1824 {C: `fstat 4 <ptr>`, R: syscall.Stat_t{}}, 1825 {C: `close 3`}, 1826 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 3}, 1827 {C: `openat 3 "tmp" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 5}, 1828 {C: `openat 5 ".snap" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 6}, 1829 {C: `openat 6 "rofs" O_NOFOLLOW|O_CLOEXEC|O_PATH 0`, R: 7}, 1830 {C: `fstat 7 <ptr>`, R: syscall.Stat_t{}}, 1831 {C: `close 6`}, 1832 {C: `close 5`}, 1833 {C: `close 3`}, 1834 {C: `mount "/proc/self/fd/4" "/proc/self/fd/7" "" MS_BIND|MS_REC ""`}, 1835 {C: `close 7`}, 1836 {C: `close 4`}, 1837 1838 {C: `lstat "/rofs"`, R: testutil.FileInfoDir}, 1839 {C: `mount "tmpfs" "/rofs" "tmpfs" 0 "mode=0755,uid=0,gid=0"`}, 1840 {C: `mount "none" "/tmp/.snap/rofs" "" MS_REC|MS_PRIVATE ""`}, 1841 {C: `unmount "/tmp/.snap/rofs" UMOUNT_NOFOLLOW|MNT_DETACH`}, 1842 1843 // Perform clean up after the unmount operation. 1844 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 3}, 1845 {C: `openat 3 "tmp" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 4}, 1846 {C: `openat 4 ".snap" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 5}, 1847 {C: `openat 5 "rofs" O_NOFOLLOW|O_CLOEXEC|O_PATH 0`, R: 6}, 1848 {C: `fstat 6 <ptr>`, R: syscall.Stat_t{}}, 1849 {C: `close 5`}, 1850 {C: `close 4`}, 1851 {C: `close 3`}, 1852 {C: `fstatfs 6 <ptr>`, R: syscall.Statfs_t{}}, 1853 {C: `remove "/tmp/.snap/rofs"`}, 1854 {C: `close 6`}, 1855 1856 // mimic ready, re-try initial mkdir 1857 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 1858 {C: `mkdirat 3 "rofs" 0755`, E: syscall.EEXIST}, 1859 {C: `openat 3 "rofs" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4}, 1860 {C: `close 3`}, 1861 {C: `openat 4 "target" O_NOFOLLOW|O_CLOEXEC|O_CREAT|O_EXCL 0755`, R: 3}, 1862 {C: `fchown 3 0 0`}, 1863 {C: `close 3`}, 1864 {C: `close 4`}, 1865 1866 // sniff mount source 1867 {C: `lstat "/source"`, R: testutil.FileInfoFile}, 1868 1869 // mount the filesystem 1870 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 3}, 1871 {C: `openat 3 "source" O_NOFOLLOW|O_CLOEXEC|O_PATH 0`, R: 4}, 1872 {C: `fstat 4 <ptr>`, R: syscall.Stat_t{}}, 1873 {C: `close 3`}, 1874 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 3}, 1875 {C: `openat 3 "rofs" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 5}, 1876 {C: `openat 5 "target" O_NOFOLLOW|O_CLOEXEC|O_PATH 0`, R: 6}, 1877 {C: `fstat 6 <ptr>`, R: syscall.Stat_t{}}, 1878 {C: `close 5`}, 1879 {C: `close 3`}, 1880 {C: `mount "/proc/self/fd/4" "/proc/self/fd/6" "" MS_BIND ""`}, 1881 {C: `close 6`}, 1882 {C: `close 4`}, 1883 }) 1884 } 1885 1886 // Change.Perform wants to bind mount a file but there's a symlink in mount point. 1887 func (s *changeSuite) TestPerformFileBindMountWithSymlinkInMountPoint(c *C) { 1888 s.sys.InsertOsLstatResult(`lstat "/target"`, testutil.FileInfoSymlink) 1889 chg := &update.Change{Action: update.Mount, Entry: osutil.MountEntry{Name: "/source", Dir: "/target", Options: []string{"bind", "x-snapd.kind=file"}}} 1890 synth, err := chg.Perform(s.as) 1891 c.Assert(err, ErrorMatches, `cannot use "/target" as mount point: not a regular file`) 1892 c.Assert(synth, HasLen, 0) 1893 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 1894 {C: `lstat "/target"`, R: testutil.FileInfoSymlink}, 1895 }) 1896 } 1897 1898 // Change.Perform wants to bind mount a file but there's a directory in mount point. 1899 func (s *changeSuite) TestPerformBindMountFileWithDirectoryInMountPoint(c *C) { 1900 s.sys.InsertOsLstatResult(`lstat "/target"`, testutil.FileInfoDir) 1901 chg := &update.Change{Action: update.Mount, Entry: osutil.MountEntry{Name: "/source", Dir: "/target", Options: []string{"bind", "x-snapd.kind=file"}}} 1902 synth, err := chg.Perform(s.as) 1903 c.Assert(err, ErrorMatches, `cannot use "/target" as mount point: not a regular file`) 1904 c.Assert(synth, HasLen, 0) 1905 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 1906 {C: `lstat "/target"`, R: testutil.FileInfoDir}, 1907 }) 1908 } 1909 1910 // Change.Perform wants to bind mount a file but there's a symlink in source. 1911 func (s *changeSuite) TestPerformFileBindMountWithSymlinkInMountSource(c *C) { 1912 s.sys.InsertOsLstatResult(`lstat "/target"`, testutil.FileInfoFile) 1913 s.sys.InsertOsLstatResult(`lstat "/source"`, testutil.FileInfoSymlink) 1914 chg := &update.Change{Action: update.Mount, Entry: osutil.MountEntry{Name: "/source", Dir: "/target", Options: []string{"bind", "x-snapd.kind=file"}}} 1915 synth, err := chg.Perform(s.as) 1916 c.Assert(err, ErrorMatches, `cannot use "/source" as bind-mount source: not a regular file`) 1917 c.Assert(synth, HasLen, 0) 1918 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 1919 {C: `lstat "/target"`, R: testutil.FileInfoFile}, 1920 {C: `lstat "/source"`, R: testutil.FileInfoSymlink}, 1921 }) 1922 } 1923 1924 // Change.Perform wants to bind mount a file but there's a directory in source. 1925 func (s *changeSuite) TestPerformFileBindMountWithDirectoryInMountSource(c *C) { 1926 s.sys.InsertOsLstatResult(`lstat "/target"`, testutil.FileInfoFile) 1927 s.sys.InsertOsLstatResult(`lstat "/source"`, testutil.FileInfoDir) 1928 chg := &update.Change{Action: update.Mount, Entry: osutil.MountEntry{Name: "/source", Dir: "/target", Options: []string{"bind", "x-snapd.kind=file"}}} 1929 synth, err := chg.Perform(s.as) 1930 c.Assert(err, ErrorMatches, `cannot use "/source" as bind-mount source: not a regular file`) 1931 c.Assert(synth, HasLen, 0) 1932 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 1933 {C: `lstat "/target"`, R: testutil.FileInfoFile}, 1934 {C: `lstat "/source"`, R: testutil.FileInfoDir}, 1935 }) 1936 } 1937 1938 // Change.Perform wants to unmount a file bind mount made on empty squashfs placeholder. 1939 func (s *changeSuite) TestPerformFileBindUnmountOnSquashfs(c *C) { 1940 s.sys.InsertFstatfsResult(`fstatfs 4 <ptr>`, syscall.Statfs_t{Type: update.SquashfsMagic}) 1941 s.sys.InsertFstatResult(`fstat 4 <ptr>`, syscall.Stat_t{Size: 0}) 1942 chg := &update.Change{Action: update.Unmount, Entry: osutil.MountEntry{Name: "/source", Dir: "/target", Options: []string{"bind", "x-snapd.kind=file"}}} 1943 synth, err := chg.Perform(s.as) 1944 c.Assert(err, IsNil) 1945 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 1946 {C: `unmount "/target" UMOUNT_NOFOLLOW`}, 1947 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 3}, 1948 {C: `openat 3 "target" O_NOFOLLOW|O_CLOEXEC|O_PATH 0`, R: 4}, 1949 {C: `fstat 4 <ptr>`, R: syscall.Stat_t{}}, 1950 {C: `close 3`}, 1951 {C: `fstatfs 4 <ptr>`, R: syscall.Statfs_t{Type: update.SquashfsMagic}}, 1952 {C: `close 4`}, 1953 }) 1954 c.Assert(synth, HasLen, 0) 1955 } 1956 1957 // Change.Perform wants to unmount a file bind mount made on non-empty ext4 placeholder. 1958 func (s *changeSuite) TestPerformFileBindUnmountOnExt4NonEmpty(c *C) { 1959 s.sys.InsertFstatfsResult(`fstatfs 4 <ptr>`, syscall.Statfs_t{Type: update.Ext4Magic}) 1960 s.sys.InsertFstatResult(`fstat 4 <ptr>`, syscall.Stat_t{Size: 1}) 1961 chg := &update.Change{Action: update.Unmount, Entry: osutil.MountEntry{Name: "/source", Dir: "/target", Options: []string{"bind", "x-snapd.kind=file"}}} 1962 synth, err := chg.Perform(s.as) 1963 c.Assert(err, IsNil) 1964 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 1965 {C: `unmount "/target" UMOUNT_NOFOLLOW`}, 1966 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 3}, 1967 {C: `openat 3 "target" O_NOFOLLOW|O_CLOEXEC|O_PATH 0`, R: 4}, 1968 {C: `fstat 4 <ptr>`, R: syscall.Stat_t{Size: 1}}, 1969 {C: `close 3`}, 1970 {C: `fstatfs 4 <ptr>`, R: syscall.Statfs_t{Type: update.Ext4Magic}}, 1971 {C: `fstat 4 <ptr>`, R: syscall.Stat_t{Size: 1}}, 1972 {C: `close 4`}, 1973 }) 1974 c.Assert(synth, HasLen, 0) 1975 } 1976 1977 // Change.Perform wants to unmount a file bind mount made on empty tmpfs placeholder. 1978 func (s *changeSuite) TestPerformFileBindUnmountOnTmpfsEmpty(c *C) { 1979 s.sys.InsertFstatfsResult(`fstatfs 4 <ptr>`, syscall.Statfs_t{Type: update.TmpfsMagic}) 1980 s.sys.InsertFstatResult(`fstat 4 <ptr>`, syscall.Stat_t{Size: 0}) 1981 chg := &update.Change{Action: update.Unmount, Entry: osutil.MountEntry{Name: "/source", Dir: "/target", Options: []string{"bind", "x-snapd.kind=file"}}} 1982 synth, err := chg.Perform(s.as) 1983 c.Assert(err, IsNil) 1984 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 1985 {C: `unmount "/target" UMOUNT_NOFOLLOW`}, 1986 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 3}, 1987 {C: `openat 3 "target" O_NOFOLLOW|O_CLOEXEC|O_PATH 0`, R: 4}, 1988 {C: `fstat 4 <ptr>`, R: syscall.Stat_t{Size: 0}}, 1989 {C: `close 3`}, 1990 {C: `fstatfs 4 <ptr>`, R: syscall.Statfs_t{Type: update.TmpfsMagic}}, 1991 {C: `fstat 4 <ptr>`, R: syscall.Stat_t{Size: 0}}, 1992 {C: `remove "/target"`}, 1993 {C: `close 4`}, 1994 }) 1995 c.Assert(synth, HasLen, 0) 1996 } 1997 1998 // Change.Perform wants to unmount a file bind mount made on empty tmpfs placeholder but it is busy!. 1999 func (s *changeSuite) TestPerformFileBindUnmountOnTmpfsEmptyButBusy(c *C) { 2000 restore := osutil.MockMountInfo("") 2001 defer restore() 2002 s.sys.InsertFstatfsResult(`fstatfs 4 <ptr>`, syscall.Statfs_t{Type: update.TmpfsMagic}) 2003 s.sys.InsertFstatResult(`fstat 4 <ptr>`, syscall.Stat_t{Size: 0}) 2004 s.sys.InsertFault(`remove "/target"`, syscall.EBUSY) 2005 chg := &update.Change{Action: update.Unmount, Entry: osutil.MountEntry{Name: "/source", Dir: "/target", Options: []string{"bind", "x-snapd.kind=file"}}} 2006 synth, err := chg.Perform(s.as) 2007 c.Assert(err, IsNil) 2008 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 2009 {C: `unmount "/target" UMOUNT_NOFOLLOW`}, 2010 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 3}, 2011 {C: `openat 3 "target" O_NOFOLLOW|O_CLOEXEC|O_PATH 0`, R: 4}, 2012 {C: `fstat 4 <ptr>`, R: syscall.Stat_t{Size: 0}}, 2013 {C: `close 3`}, 2014 {C: `fstatfs 4 <ptr>`, R: syscall.Statfs_t{Type: update.TmpfsMagic}}, 2015 {C: `fstat 4 <ptr>`, R: syscall.Stat_t{Size: 0}}, 2016 {C: `remove "/target"`, E: syscall.EBUSY}, 2017 {C: `close 4`}, 2018 }) 2019 c.Assert(synth, HasLen, 0) 2020 } 2021 2022 // Change.Perform wants to unmount a file bind mount but it fails. 2023 func (s *changeSuite) TestPerformFileBindUnmountError(c *C) { 2024 s.sys.InsertFault(`unmount "/target" UMOUNT_NOFOLLOW`, errTesting) 2025 chg := &update.Change{Action: update.Unmount, Entry: osutil.MountEntry{Name: "/source", Dir: "/target", Options: []string{"bind", "x-snapd.kind=file"}}} 2026 synth, err := chg.Perform(s.as) 2027 c.Assert(err, Equals, errTesting) 2028 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 2029 {C: `unmount "/target" UMOUNT_NOFOLLOW`, E: errTesting}, 2030 }) 2031 c.Assert(synth, HasLen, 0) 2032 } 2033 2034 // ############################################################# 2035 // Topic: handling mounts with the x-snapd.ignore-missing option 2036 // ############################################################# 2037 2038 func (s *changeSuite) TestPerformMountWithIgnoredMissingMountSource(c *C) { 2039 s.sys.InsertFault(`lstat "/source"`, syscall.ENOENT) 2040 s.sys.InsertOsLstatResult(`lstat "/target"`, testutil.FileInfoDir) 2041 chg := &update.Change{Action: update.Mount, Entry: osutil.MountEntry{Name: "/source", Dir: "/target", Options: []string{"bind", "x-snapd.ignore-missing"}}} 2042 synth, err := chg.Perform(s.as) 2043 c.Assert(err, Equals, update.ErrIgnoredMissingMount) 2044 c.Assert(synth, HasLen, 0) 2045 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 2046 {C: `lstat "/target"`, R: testutil.FileInfoDir}, 2047 {C: `lstat "/source"`, E: syscall.ENOENT}, 2048 }) 2049 } 2050 2051 func (s *changeSuite) TestPerformMountWithIgnoredMissingMountPoint(c *C) { 2052 s.sys.InsertFault(`lstat "/target"`, syscall.ENOENT) 2053 chg := &update.Change{Action: update.Mount, Entry: osutil.MountEntry{Name: "/source", Dir: "/target", Options: []string{"bind", "x-snapd.ignore-missing"}}} 2054 synth, err := chg.Perform(s.as) 2055 c.Assert(err, Equals, update.ErrIgnoredMissingMount) 2056 c.Assert(synth, HasLen, 0) 2057 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 2058 {C: `lstat "/target"`, E: syscall.ENOENT}, 2059 }) 2060 } 2061 2062 // ######################## 2063 // Topic: creating symlinks 2064 // ######################## 2065 2066 // Change.Perform wants to create a symlink but name cannot be stat'ed. 2067 func (s *changeSuite) TestPerformCreateSymlinkNameLstatError(c *C) { 2068 s.sys.InsertFault(`lstat "/name"`, errTesting) 2069 chg := &update.Change{Action: update.Mount, Entry: osutil.MountEntry{Name: "unused", Dir: "/name", Options: []string{"x-snapd.kind=symlink", "x-snapd.symlink=/oldname"}}} 2070 synth, err := chg.Perform(s.as) 2071 c.Assert(err, ErrorMatches, `cannot inspect "/name": testing`) 2072 c.Assert(synth, HasLen, 0) 2073 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 2074 {C: `lstat "/name"`, E: errTesting}, 2075 }) 2076 } 2077 2078 // Change.Perform wants to create a symlink. 2079 func (s *changeSuite) TestPerformCreateSymlink(c *C) { 2080 defer s.as.MockUnrestrictedPaths("/")() // Treat test path as unrestricted. 2081 s.sys.InsertFault(`lstat "/name"`, syscall.ENOENT) 2082 chg := &update.Change{Action: update.Mount, Entry: osutil.MountEntry{Name: "unused", Dir: "/name", Options: []string{"x-snapd.kind=symlink", "x-snapd.symlink=/oldname"}}} 2083 synth, err := chg.Perform(s.as) 2084 c.Assert(err, IsNil) 2085 c.Assert(synth, HasLen, 0) 2086 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 2087 {C: `lstat "/name"`, E: syscall.ENOENT}, 2088 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 2089 {C: `symlinkat "/oldname" 3 "name"`}, 2090 {C: `close 3`}, 2091 }) 2092 } 2093 2094 // Change.Perform wants to create a symlink but it fails. 2095 func (s *changeSuite) TestPerformCreateSymlinkWithError(c *C) { 2096 defer s.as.MockUnrestrictedPaths("/")() // Treat test path as unrestricted. 2097 s.sys.InsertFault(`lstat "/name"`, syscall.ENOENT) 2098 s.sys.InsertFault(`symlinkat "/oldname" 3 "name"`, errTesting) 2099 chg := &update.Change{Action: update.Mount, Entry: osutil.MountEntry{Name: "unused", Dir: "/name", Options: []string{"x-snapd.kind=symlink", "x-snapd.symlink=/oldname"}}} 2100 synth, err := chg.Perform(s.as) 2101 c.Assert(err, ErrorMatches, `cannot create symlink "/name": testing`) 2102 c.Assert(synth, HasLen, 0) 2103 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 2104 {C: `lstat "/name"`, E: syscall.ENOENT}, 2105 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 2106 {C: `symlinkat "/oldname" 3 "name"`, E: errTesting}, 2107 {C: `close 3`}, 2108 }) 2109 } 2110 2111 // Change.Perform wants to create a symlink but the target is empty. 2112 func (s *changeSuite) TestPerformCreateSymlinkWithNoTargetError(c *C) { 2113 s.sys.InsertFault(`lstat "/name"`, syscall.ENOENT) 2114 chg := &update.Change{Action: update.Mount, Entry: osutil.MountEntry{Name: "unused", Dir: "/name", Options: []string{"x-snapd.kind=symlink", "x-snapd.symlink="}}} 2115 synth, err := chg.Perform(s.as) 2116 c.Assert(err, ErrorMatches, `cannot create symlink with empty target: "/name"`) 2117 c.Assert(synth, HasLen, 0) 2118 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 2119 {C: `lstat "/name"`, E: syscall.ENOENT}, 2120 }) 2121 } 2122 2123 // Change.Perform wants to create a symlink but the base directory isn't there. 2124 func (s *changeSuite) TestPerformCreateSymlinkWithoutBaseDir(c *C) { 2125 defer s.as.MockUnrestrictedPaths("/")() // Treat test path as unrestricted. 2126 s.sys.InsertFault(`lstat "/base/name"`, syscall.ENOENT) 2127 chg := &update.Change{Action: update.Mount, Entry: osutil.MountEntry{Name: "unused", Dir: "/base/name", Options: []string{"x-snapd.kind=symlink", "x-snapd.symlink=/oldname"}}} 2128 synth, err := chg.Perform(s.as) 2129 c.Assert(err, IsNil) 2130 c.Assert(synth, HasLen, 0) 2131 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 2132 {C: `lstat "/base/name"`, E: syscall.ENOENT}, 2133 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 2134 {C: `mkdirat 3 "base" 0755`}, 2135 {C: `openat 3 "base" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4}, 2136 {C: `fchown 4 0 0`}, 2137 {C: `close 3`}, 2138 {C: `symlinkat "/oldname" 4 "name"`}, 2139 {C: `close 4`}, 2140 }) 2141 } 2142 2143 // Change.Perform wants to create a symlink but the base directory isn't there and cannot be created. 2144 func (s *changeSuite) TestPerformCreateSymlinkWithoutBaseDirWithErrors(c *C) { 2145 defer s.as.MockUnrestrictedPaths("/")() // Treat test path as unrestricted. 2146 s.sys.InsertFault(`lstat "/base/name"`, syscall.ENOENT) 2147 s.sys.InsertFault(`mkdirat 3 "base" 0755`, errTesting) 2148 chg := &update.Change{Action: update.Mount, Entry: osutil.MountEntry{Name: "unused", Dir: "/base/name", Options: []string{"x-snapd.kind=symlink", "x-snapd.symlink=/oldname"}}} 2149 synth, err := chg.Perform(s.as) 2150 c.Assert(err, ErrorMatches, `cannot create directory "/base": testing`) 2151 c.Assert(synth, HasLen, 0) 2152 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 2153 {C: `lstat "/base/name"`, E: syscall.ENOENT}, 2154 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 2155 {C: `mkdirat 3 "base" 0755`, E: errTesting}, 2156 {C: `close 3`}, 2157 }) 2158 } 2159 2160 // Change.Perform wants to create a symlink but the base directory isn't there and the parent is read-only. 2161 func (s *changeSuite) TestPerformCreateSymlinkWithoutBaseDirAndReadOnlyBase(c *C) { 2162 defer s.as.MockUnrestrictedPaths("/")() // Treat test path as unrestricted. 2163 s.sys.InsertFault(`lstat "/rofs/name"`, syscall.ENOENT) 2164 s.sys.InsertFault(`mkdirat 3 "rofs" 0755`, syscall.EEXIST) 2165 s.sys.InsertFault(`symlinkat "/oldname" 4 "name"`, syscall.EROFS, nil) // works on 2nd try 2166 s.sys.InsertSysLstatResult(`lstat "/rofs" <ptr>`, syscall.Stat_t{Uid: 0, Gid: 0, Mode: 0755}) 2167 s.sys.InsertReadDirResult(`readdir "/rofs"`, nil) // pretend /rofs is empty. 2168 s.sys.InsertFault(`lstat "/tmp/.snap/rofs"`, syscall.ENOENT) 2169 s.sys.InsertOsLstatResult(`lstat "/rofs"`, testutil.FileInfoDir) 2170 s.sys.InsertFstatResult(`fstat 4 <ptr>`, syscall.Stat_t{}) 2171 s.sys.InsertFstatResult(`fstat 7 <ptr>`, syscall.Stat_t{}) 2172 s.sys.InsertFstatResult(`fstat 6 <ptr>`, syscall.Stat_t{}) 2173 s.sys.InsertFstatfsResult(`fstatfs 6 <ptr>`, syscall.Statfs_t{}) 2174 2175 chg := &update.Change{Action: update.Mount, Entry: osutil.MountEntry{Name: "unused", Dir: "/rofs/name", Options: []string{"x-snapd.kind=symlink", "x-snapd.symlink=/oldname"}}} 2176 synth, err := chg.Perform(s.as) 2177 c.Assert(err, IsNil) 2178 c.Assert(synth, DeepEquals, []*update.Change{ 2179 {Action: update.Mount, Entry: osutil.MountEntry{ 2180 Name: "tmpfs", Dir: "/rofs", Type: "tmpfs", 2181 Options: []string{"x-snapd.synthetic", "x-snapd.needed-by=/rofs/name", "mode=0755", "uid=0", "gid=0"}}, 2182 }, 2183 }) 2184 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 2185 // sniff symlink name 2186 {C: `lstat "/rofs/name"`, E: syscall.ENOENT}, 2187 2188 // create base name (/rofs) 2189 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 2190 {C: `mkdirat 3 "rofs" 0755`, E: syscall.EEXIST}, 2191 {C: `openat 3 "rofs" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4}, 2192 {C: `close 3`}, 2193 // create symlink 2194 {C: `symlinkat "/oldname" 4 "name"`, E: syscall.EROFS}, 2195 {C: `close 4`}, 2196 2197 // error, read only filesystem, create a mimic 2198 {C: `lstat "/rofs" <ptr>`, R: syscall.Stat_t{Mode: 0755}}, 2199 {C: `readdir "/rofs"`, R: []os.FileInfo(nil)}, 2200 {C: `lstat "/tmp/.snap/rofs"`, E: syscall.ENOENT}, 2201 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 2202 {C: `mkdirat 3 "tmp" 0755`}, 2203 {C: `openat 3 "tmp" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4}, 2204 {C: `fchown 4 0 0`}, 2205 {C: `mkdirat 4 ".snap" 0755`}, 2206 {C: `openat 4 ".snap" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 5}, 2207 {C: `fchown 5 0 0`}, 2208 {C: `close 4`}, 2209 {C: `close 3`}, 2210 {C: `mkdirat 5 "rofs" 0755`}, 2211 {C: `openat 5 "rofs" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 2212 {C: `fchown 3 0 0`}, 2213 {C: `close 3`}, 2214 {C: `close 5`}, 2215 {C: `lstat "/rofs"`, R: testutil.FileInfoDir}, 2216 2217 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 3}, 2218 {C: `openat 3 "rofs" O_NOFOLLOW|O_CLOEXEC|O_PATH 0`, R: 4}, 2219 {C: `fstat 4 <ptr>`, R: syscall.Stat_t{}}, 2220 {C: `close 3`}, 2221 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 3}, 2222 {C: `openat 3 "tmp" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 5}, 2223 {C: `openat 5 ".snap" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 6}, 2224 {C: `openat 6 "rofs" O_NOFOLLOW|O_CLOEXEC|O_PATH 0`, R: 7}, 2225 {C: `fstat 7 <ptr>`, R: syscall.Stat_t{}}, 2226 {C: `close 6`}, 2227 {C: `close 5`}, 2228 {C: `close 3`}, 2229 {C: `mount "/proc/self/fd/4" "/proc/self/fd/7" "" MS_BIND|MS_REC ""`}, 2230 {C: `close 7`}, 2231 {C: `close 4`}, 2232 2233 {C: `lstat "/rofs"`, R: testutil.FileInfoDir}, 2234 {C: `mount "tmpfs" "/rofs" "tmpfs" 0 "mode=0755,uid=0,gid=0"`}, 2235 {C: `mount "none" "/tmp/.snap/rofs" "" MS_REC|MS_PRIVATE ""`}, 2236 {C: `unmount "/tmp/.snap/rofs" UMOUNT_NOFOLLOW|MNT_DETACH`}, 2237 2238 // Perform clean up after the unmount operation. 2239 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 3}, 2240 {C: `openat 3 "tmp" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 4}, 2241 {C: `openat 4 ".snap" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 5}, 2242 {C: `openat 5 "rofs" O_NOFOLLOW|O_CLOEXEC|O_PATH 0`, R: 6}, 2243 {C: `fstat 6 <ptr>`, R: syscall.Stat_t{}}, 2244 {C: `close 5`}, 2245 {C: `close 4`}, 2246 {C: `close 3`}, 2247 {C: `fstatfs 6 <ptr>`, R: syscall.Statfs_t{}}, 2248 {C: `remove "/tmp/.snap/rofs"`}, 2249 {C: `close 6`}, 2250 2251 // mimic ready, re-try initial base mkdir 2252 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 2253 {C: `mkdirat 3 "rofs" 0755`, E: syscall.EEXIST}, 2254 {C: `openat 3 "rofs" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4}, 2255 {C: `close 3`}, 2256 // create symlink 2257 {C: `symlinkat "/oldname" 4 "name"`}, 2258 {C: `close 4`}, 2259 }) 2260 } 2261 2262 // Change.Perform wants to create a symlink but there's a file in the way. 2263 func (s *changeSuite) TestPerformCreateSymlinkWithFileInTheWay(c *C) { 2264 s.sys.InsertOsLstatResult(`lstat "/name"`, testutil.FileInfoFile) 2265 chg := &update.Change{Action: update.Mount, Entry: osutil.MountEntry{Name: "unused", Dir: "/name", Options: []string{"x-snapd.kind=symlink", "x-snapd.symlink=/oldname"}}} 2266 synth, err := chg.Perform(s.as) 2267 c.Assert(err, ErrorMatches, `cannot create symlink in "/name": existing file in the way`) 2268 c.Assert(synth, HasLen, 0) 2269 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 2270 {C: `lstat "/name"`, R: testutil.FileInfoFile}, 2271 }) 2272 } 2273 2274 // Change.Perform wants to create a symlink but a correct symlink is already present. 2275 func (s *changeSuite) TestPerformCreateSymlinkWithGoodSymlinkPresent(c *C) { 2276 defer s.as.MockUnrestrictedPaths("/")() // Treat test path as unrestricted. 2277 s.sys.InsertOsLstatResult(`lstat "/name"`, testutil.FileInfoSymlink) 2278 s.sys.InsertFault(`symlinkat "/oldname" 3 "name"`, syscall.EEXIST) 2279 s.sys.InsertFstatResult(`fstat 4 <ptr>`, syscall.Stat_t{Mode: syscall.S_IFLNK}) 2280 s.sys.InsertReadlinkatResult(`readlinkat 4 "" <ptr>`, "/oldname") 2281 chg := &update.Change{Action: update.Mount, Entry: osutil.MountEntry{Name: "unused", Dir: "/name", Options: []string{"x-snapd.kind=symlink", "x-snapd.symlink=/oldname"}}} 2282 synth, err := chg.Perform(s.as) 2283 c.Assert(err, IsNil) 2284 c.Assert(synth, HasLen, 0) 2285 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 2286 {C: `lstat "/name"`, R: testutil.FileInfoSymlink}, 2287 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 2288 {C: `symlinkat "/oldname" 3 "name"`, E: syscall.EEXIST}, 2289 {C: `openat 3 "name" O_NOFOLLOW|O_CLOEXEC|O_PATH 0`, R: 4}, 2290 {C: `fstat 4 <ptr>`, R: syscall.Stat_t{Mode: syscall.S_IFLNK}}, 2291 {C: `readlinkat 4 "" <ptr>`, R: "/oldname"}, 2292 {C: `close 4`}, 2293 {C: `close 3`}, 2294 }) 2295 } 2296 2297 // Change.Perform wants to create a symlink but a incorrect symlink is already present. 2298 func (s *changeSuite) TestPerformCreateSymlinkWithBadSymlinkPresent(c *C) { 2299 defer s.as.MockUnrestrictedPaths("/")() // Treat test path as unrestricted. 2300 s.sys.InsertOsLstatResult(`lstat "/name"`, testutil.FileInfoSymlink) 2301 s.sys.InsertFault(`symlinkat "/oldname" 3 "name"`, syscall.EEXIST) 2302 s.sys.InsertFstatResult(`fstat 4 <ptr>`, syscall.Stat_t{Mode: syscall.S_IFLNK}) 2303 s.sys.InsertReadlinkatResult(`readlinkat 4 "" <ptr>`, "/evil") 2304 chg := &update.Change{Action: update.Mount, Entry: osutil.MountEntry{Name: "unused", Dir: "/name", Options: []string{"x-snapd.kind=symlink", "x-snapd.symlink=/oldname"}}} 2305 synth, err := chg.Perform(s.as) 2306 c.Assert(err, ErrorMatches, `cannot create symbolic link "/name": existing symbolic link in the way`) 2307 c.Assert(synth, HasLen, 0) 2308 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 2309 {C: `lstat "/name"`, R: testutil.FileInfoSymlink}, 2310 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 2311 {C: `symlinkat "/oldname" 3 "name"`, E: syscall.EEXIST}, 2312 {C: `openat 3 "name" O_NOFOLLOW|O_CLOEXEC|O_PATH 0`, R: 4}, 2313 {C: `fstat 4 <ptr>`, R: syscall.Stat_t{Mode: syscall.S_IFLNK}}, 2314 {C: `readlinkat 4 "" <ptr>`, R: "/evil"}, 2315 {C: `close 4`}, 2316 {C: `close 3`}, 2317 }) 2318 } 2319 2320 func (s *changeSuite) TestPerformRemoveSymlink(c *C) { 2321 chg := &update.Change{Action: update.Unmount, Entry: osutil.MountEntry{Name: "unused", Dir: "/name", Options: []string{"x-snapd.kind=symlink", "x-snapd.symlink=/oldname"}}} 2322 synth, err := chg.Perform(s.as) 2323 c.Assert(err, IsNil) 2324 c.Assert(synth, HasLen, 0) 2325 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 2326 {C: `remove "/name"`}, 2327 }) 2328 } 2329 2330 // Change.Perform wants to create a symlink in /etc and the write is made private. 2331 func (s *changeSuite) TestPerformCreateSymlinkWithAvoidedTrespassing(c *C) { 2332 defer s.as.MockUnrestrictedPaths("/tmp/")() // Allow writing to /tmp 2333 2334 s.sys.InsertFault(`lstat "/etc/demo.conf"`, syscall.ENOENT) 2335 s.sys.InsertFstatfsResult(`fstatfs 3 <ptr>`, syscall.Statfs_t{Type: update.SquashfsMagic}) 2336 s.sys.InsertFstatResult(`fstat 3 <ptr>`, syscall.Stat_t{}) 2337 s.sys.InsertFault(`mkdirat 3 "etc" 0755`, syscall.EEXIST) 2338 s.sys.InsertFstatfsResult(`fstatfs 4 <ptr>`, 2339 // On 1st call ext4, on 2nd call tmpfs 2340 syscall.Statfs_t{Type: update.Ext4Magic}, 2341 syscall.Statfs_t{Type: update.TmpfsMagic}) 2342 s.sys.InsertFstatResult(`fstat 4 <ptr>`, syscall.Stat_t{}) 2343 s.sys.InsertSysLstatResult(`lstat "/etc" <ptr>`, syscall.Stat_t{Mode: 0755}) 2344 otherConf := testutil.FakeFileInfo("other.conf", 0755) 2345 s.sys.InsertReadDirResult(`readdir "/etc"`, []os.FileInfo{otherConf}) 2346 s.sys.InsertFault(`lstat "/tmp/.snap/etc"`, syscall.ENOENT) 2347 s.sys.InsertFault(`lstat "/tmp/.snap/etc/other.conf"`, syscall.ENOENT) 2348 s.sys.InsertOsLstatResult(`lstat "/etc"`, testutil.FileInfoDir) 2349 s.sys.InsertOsLstatResult(`lstat "/etc/other.conf"`, otherConf) 2350 s.sys.InsertFault(`mkdirat 3 "tmp" 0755`, syscall.EEXIST) 2351 s.sys.InsertFstatResult(`fstat 5 <ptr>`, syscall.Stat_t{Mode: syscall.S_IFREG}) 2352 s.sys.InsertFstatResult(`fstat 4 <ptr>`, syscall.Stat_t{Mode: syscall.S_IFDIR}) 2353 s.sys.InsertFstatResult(`fstat 7 <ptr>`, syscall.Stat_t{Mode: syscall.S_IFDIR}) 2354 s.sys.InsertFstatResult(`fstat 6 <ptr>`, syscall.Stat_t{}) 2355 s.sys.InsertFstatfsResult(`fstatfs 6 <ptr>`, syscall.Statfs_t{}) 2356 2357 // This is the change we want to perform: 2358 // put a layout symlink at /etc/demo.conf -> /oldname 2359 chg := &update.Change{Action: update.Mount, Entry: osutil.MountEntry{Name: "unused", Dir: "/etc/demo.conf", Options: []string{"x-snapd.kind=symlink", "x-snapd.symlink=/oldname"}}} 2360 synth, err := chg.Perform(s.as) 2361 c.Check(err, IsNil) 2362 c.Check(synth, HasLen, 2) 2363 // We have created some synthetic change (made /etc a new tmpfs and re-populate it) 2364 c.Assert(synth[0], DeepEquals, &update.Change{ 2365 Entry: osutil.MountEntry{Name: "tmpfs", Dir: "/etc", Type: "tmpfs", Options: []string{"x-snapd.synthetic", "x-snapd.needed-by=/etc/demo.conf", "mode=0755", "uid=0", "gid=0"}}, 2366 Action: "mount"}) 2367 c.Assert(synth[1], DeepEquals, &update.Change{ 2368 Entry: osutil.MountEntry{Name: "/etc/other.conf", Dir: "/etc/other.conf", Options: []string{"bind", "x-snapd.kind=file", "x-snapd.synthetic", "x-snapd.needed-by=/etc/demo.conf"}}, 2369 Action: "mount"}) 2370 2371 // And this is exactly how we made that happen: 2372 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 2373 // Attempt to construct a symlink /etc/demo.conf -> /oldname. 2374 // This stops as soon as we notice that /etc is an ext4 filesystem. 2375 // To avoid writing to it directly we need a writable mimic. 2376 {C: `lstat "/etc/demo.conf"`, E: syscall.ENOENT}, 2377 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 2378 {C: `fstatfs 3 <ptr>`, R: syscall.Statfs_t{Type: update.SquashfsMagic}}, 2379 {C: `fstat 3 <ptr>`, R: syscall.Stat_t{}}, 2380 {C: `mkdirat 3 "etc" 0755`, E: syscall.EEXIST}, 2381 {C: `openat 3 "etc" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4}, 2382 {C: `close 3`}, 2383 {C: `fstatfs 4 <ptr>`, R: syscall.Statfs_t{Type: update.Ext4Magic}}, 2384 {C: `fstat 4 <ptr>`, R: syscall.Stat_t{Mode: 0x4000}}, 2385 {C: `close 4`}, 2386 2387 // Create a writable mimic over /etc, scan the contents of /etc first. 2388 // For convenience we pretend that /etc is empty. The mimic 2389 // replicates /etc in /tmp/.snap/etc for subsequent re-construction. 2390 {C: `lstat "/etc" <ptr>`, R: syscall.Stat_t{Mode: 0755}}, 2391 {C: `readdir "/etc"`, R: []os.FileInfo{otherConf}}, 2392 {C: `lstat "/tmp/.snap/etc"`, E: syscall.ENOENT}, 2393 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 2394 {C: `mkdirat 3 "tmp" 0755`, E: syscall.EEXIST}, 2395 {C: `openat 3 "tmp" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4}, 2396 {C: `mkdirat 4 ".snap" 0755`}, 2397 {C: `openat 4 ".snap" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 5}, 2398 {C: `fchown 5 0 0`}, 2399 {C: `close 4`}, 2400 {C: `close 3`}, 2401 {C: `mkdirat 5 "etc" 0755`}, 2402 {C: `openat 5 "etc" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 2403 {C: `fchown 3 0 0`}, 2404 {C: `close 3`}, 2405 {C: `close 5`}, 2406 2407 // Prepare a secure bind mount operation /etc -> /tmp/.snap/etc 2408 {C: `lstat "/etc"`, R: testutil.FileInfoDir}, 2409 2410 // Open an O_PATH descriptor to /etc. We need this as a source of a 2411 // secure bind mount operation. We also ensure that the descriptor 2412 // refers to a directory. 2413 // NOTE: we keep fd 4 open for subsequent use. 2414 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 3}, 2415 {C: `openat 3 "etc" O_NOFOLLOW|O_CLOEXEC|O_PATH 0`, R: 4}, 2416 {C: `fstat 4 <ptr>`, R: syscall.Stat_t{Mode: syscall.S_IFDIR}}, 2417 {C: `close 3`}, 2418 2419 // Open an O_PATH descriptor to /tmp/.snap/etc. We need this as a 2420 // target of a secure bind mount operation. We also ensure that the 2421 // descriptor refers to a directory. 2422 // NOTE: we keep fd 7 open for subsequent use. 2423 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 3}, 2424 {C: `openat 3 "tmp" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 5}, 2425 {C: `openat 5 ".snap" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 6}, 2426 {C: `openat 6 "etc" O_NOFOLLOW|O_CLOEXEC|O_PATH 0`, R: 7}, 2427 {C: `fstat 7 <ptr>`, R: syscall.Stat_t{Mode: syscall.S_IFDIR}}, 2428 {C: `close 6`}, 2429 {C: `close 5`}, 2430 {C: `close 3`}, 2431 2432 // Perform the secure bind mount operation /etc -> /tmp/.snap/etc 2433 // and release the two associated file descriptors. 2434 {C: `mount "/proc/self/fd/4" "/proc/self/fd/7" "" MS_BIND|MS_REC ""`}, 2435 {C: `close 7`}, 2436 {C: `close 4`}, 2437 2438 // Mount a tmpfs over /etc, re-constructing the original mode and 2439 // ownership. Bind mount each original file over and detach the copy 2440 // of /etc we had in /tmp/.snap/etc. 2441 2442 {C: `lstat "/etc"`, R: testutil.FileInfoDir}, 2443 {C: `mount "tmpfs" "/etc" "tmpfs" 0 "mode=0755,uid=0,gid=0"`}, 2444 // Here we restore the contents of /etc: here it's just one file - other.conf 2445 {C: `lstat "/etc/other.conf"`, R: otherConf}, 2446 {C: `lstat "/tmp/.snap/etc/other.conf"`, E: syscall.ENOENT}, 2447 2448 // Create /tmp/.snap/etc/other.conf as an empty file. 2449 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 2450 {C: `mkdirat 3 "tmp" 0755`, E: syscall.EEXIST}, 2451 {C: `openat 3 "tmp" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4}, 2452 {C: `mkdirat 4 ".snap" 0755`}, 2453 {C: `openat 4 ".snap" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 5}, 2454 {C: `fchown 5 0 0`}, 2455 {C: `mkdirat 5 "etc" 0755`}, 2456 {C: `openat 5 "etc" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 6}, 2457 {C: `fchown 6 0 0`}, 2458 {C: `close 5`}, 2459 {C: `close 4`}, 2460 {C: `close 3`}, 2461 // NOTE: This is without O_DIRECTORY and with O_CREAT|O_EXCL, 2462 // we are creating an empty file for the subsequent bind mount. 2463 {C: `openat 6 "other.conf" O_NOFOLLOW|O_CLOEXEC|O_CREAT|O_EXCL 0755`, R: 3}, 2464 {C: `fchown 3 0 0`}, 2465 {C: `close 3`}, 2466 {C: `close 6`}, 2467 2468 // Open O_PATH to /tmp/.snap/etc/other.conf 2469 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 3}, 2470 {C: `openat 3 "tmp" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 4}, 2471 {C: `openat 4 ".snap" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 5}, 2472 {C: `openat 5 "etc" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 6}, 2473 {C: `openat 6 "other.conf" O_NOFOLLOW|O_CLOEXEC|O_PATH 0`, R: 7}, 2474 {C: `fstat 7 <ptr>`, R: syscall.Stat_t{Mode: syscall.S_IFDIR}}, 2475 {C: `close 6`}, 2476 {C: `close 5`}, 2477 {C: `close 4`}, 2478 {C: `close 3`}, 2479 2480 // Open O_PATH to /etc/other.conf 2481 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 3}, 2482 {C: `openat 3 "etc" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 4}, 2483 {C: `openat 4 "other.conf" O_NOFOLLOW|O_CLOEXEC|O_PATH 0`, R: 5}, 2484 {C: `fstat 5 <ptr>`, R: syscall.Stat_t{Mode: syscall.S_IFREG}}, 2485 {C: `close 4`}, 2486 {C: `close 3`}, 2487 2488 // Restore the /etc/other.conf file with a secure bind mount. 2489 {C: `mount "/proc/self/fd/7" "/proc/self/fd/5" "" MS_BIND ""`}, 2490 {C: `close 5`}, 2491 {C: `close 7`}, 2492 2493 // We're done restoring now. 2494 {C: `mount "none" "/tmp/.snap/etc" "" MS_REC|MS_PRIVATE ""`}, 2495 {C: `unmount "/tmp/.snap/etc" UMOUNT_NOFOLLOW|MNT_DETACH`}, 2496 2497 // Perform clean up after the unmount operation. 2498 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 3}, 2499 {C: `openat 3 "tmp" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 4}, 2500 {C: `openat 4 ".snap" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 5}, 2501 {C: `openat 5 "etc" O_NOFOLLOW|O_CLOEXEC|O_PATH 0`, R: 6}, 2502 {C: `fstat 6 <ptr>`, R: syscall.Stat_t{}}, 2503 {C: `close 5`}, 2504 {C: `close 4`}, 2505 {C: `close 3`}, 2506 {C: `fstatfs 6 <ptr>`, R: syscall.Statfs_t{}}, 2507 {C: `remove "/tmp/.snap/etc"`}, 2508 {C: `close 6`}, 2509 2510 // The mimic is now complete and subsequent writes to /etc are private 2511 // to the mount namespace of the process. 2512 2513 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 3}, 2514 {C: `fstatfs 3 <ptr>`, R: syscall.Statfs_t{Type: update.SquashfsMagic}}, 2515 {C: `fstat 3 <ptr>`, R: syscall.Stat_t{}}, 2516 {C: `mkdirat 3 "etc" 0755`, E: syscall.EEXIST}, 2517 {C: `openat 3 "etc" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY 0`, R: 4}, 2518 {C: `close 3`}, 2519 {C: `fstatfs 4 <ptr>`, R: syscall.Statfs_t{Type: update.TmpfsMagic}}, 2520 {C: `fstat 4 <ptr>`, R: syscall.Stat_t{Mode: 0x4000}}, 2521 {C: `symlinkat "/oldname" 4 "demo.conf"`}, 2522 {C: `close 4`}, 2523 }) 2524 } 2525 2526 // Change.Perform wants to remove a directory which is a bind mount of ext4 from onto squashfs. 2527 func (s *changeSuite) TestPerformRmdirOnExt4OnSquashfs(c *C) { 2528 defer s.as.MockUnrestrictedPaths("/tmp/")() // Allow writing to /tmp 2529 2530 s.sys.InsertFstatResult(`fstat 4 <ptr>`, syscall.Stat_t{}) 2531 // Pretend that /root is an ext4 bind mount from somewhere. 2532 s.sys.InsertFstatfsResult(`fstatfs 4 <ptr>`, syscall.Statfs_t{Type: update.Ext4Magic}) 2533 // Pretend that removing /root returns EROFS (it really can!). 2534 s.sys.InsertFault(`remove "/root"`, syscall.EROFS) 2535 2536 // This is the change we want to perform: 2537 // - unmount a layout from /root 2538 chg := &update.Change{Action: update.Unmount, Entry: osutil.MountEntry{Name: "unused", Dir: "/root", Options: []string{"x-snapd.origin=layout"}}} 2539 synth, err := chg.Perform(s.as) 2540 // The change succeeded even though we were unable to remove the /root 2541 // directory because it is backed by a squashfs, which is not modelled by 2542 // this test but is modelled by the integration test. 2543 c.Check(err, IsNil) 2544 c.Check(synth, HasLen, 0) 2545 2546 // And this is exactly how we made that happen: 2547 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 2548 {C: `unmount "/root" UMOUNT_NOFOLLOW`}, 2549 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 3}, 2550 {C: `openat 3 "root" O_NOFOLLOW|O_CLOEXEC|O_PATH 0`, R: 4}, 2551 {C: `fstat 4 <ptr>`, R: syscall.Stat_t{}}, 2552 {C: `close 3`}, 2553 {C: `fstatfs 4 <ptr>`, R: syscall.Statfs_t{Type: update.Ext4Magic}}, 2554 {C: `remove "/root"`, E: syscall.EROFS}, 2555 {C: `close 4`}, 2556 }) 2557 } 2558 2559 // ########### 2560 // Topic: misc 2561 // ########### 2562 2563 // Change.Perform handles unknown actions. 2564 func (s *changeSuite) TestPerformUnknownAction(c *C) { 2565 chg := &update.Change{Action: update.Action("42")} 2566 synth, err := chg.Perform(s.as) 2567 c.Assert(err, ErrorMatches, `cannot process mount change: unknown action: .*`) 2568 c.Assert(synth, HasLen, 0) 2569 c.Assert(s.sys.RCalls(), HasLen, 0) 2570 } 2571 2572 // Change.Perform wants to keep a mount entry unchanged. 2573 func (s *changeSuite) TestPerformKeep(c *C) { 2574 chg := &update.Change{Action: update.Keep} 2575 synth, err := chg.Perform(s.as) 2576 c.Assert(err, IsNil) 2577 c.Assert(synth, HasLen, 0) 2578 c.Assert(s.sys.RCalls(), HasLen, 0) 2579 } 2580 2581 // ############################################ 2582 // Topic: change history tracked in Assumptions 2583 // ############################################ 2584 2585 func (s *changeSuite) TestPerformedChangesAreTracked(c *C) { 2586 s.sys.InsertOsLstatResult(`lstat "/target"`, testutil.FileInfoDir) 2587 c.Assert(s.as.PastChanges(), HasLen, 0) 2588 2589 chg := &update.Change{Action: update.Mount, Entry: osutil.MountEntry{Name: "device", Dir: "/target", Type: "type"}} 2590 _, err := chg.Perform(s.as) 2591 c.Assert(err, IsNil) 2592 c.Assert(s.as.PastChanges(), DeepEquals, []*update.Change{ 2593 {Action: update.Mount, Entry: osutil.MountEntry{Name: "device", Dir: "/target", Type: "type"}}, 2594 }) 2595 2596 s.sys.InsertFstatResult(`fstat 4 <ptr>`, syscall.Stat_t{}) 2597 s.sys.InsertFstatfsResult(`fstatfs 4 <ptr>`, syscall.Statfs_t{}) 2598 chg = &update.Change{Action: update.Unmount, Entry: osutil.MountEntry{Name: "device", Dir: "/target", Type: "type"}} 2599 _, err = chg.Perform(s.as) 2600 c.Assert(err, IsNil) 2601 2602 chg = &update.Change{Action: update.Keep, Entry: osutil.MountEntry{Name: "tmpfs", Dir: "/target", Type: "tmpfs"}} 2603 _, err = chg.Perform(s.as) 2604 c.Assert(err, IsNil) 2605 c.Assert(s.as.PastChanges(), DeepEquals, []*update.Change{ 2606 // past changes stack in order. 2607 {Action: update.Mount, Entry: osutil.MountEntry{Name: "device", Dir: "/target", Type: "type"}}, 2608 {Action: update.Unmount, Entry: osutil.MountEntry{Name: "device", Dir: "/target", Type: "type"}}, 2609 {Action: update.Keep, Entry: osutil.MountEntry{Name: "tmpfs", Dir: "/target", Type: "tmpfs"}}, 2610 }) 2611 } 2612 2613 func (s *changeSuite) TestComplexPropagatingChanges(c *C) { 2614 // This problem is more subtle. It is a variant of the regression test 2615 // implemented in tests/regression/lp-1831010. Here, we have four directories: 2616 // 2617 // - $SNAP/a 2618 // - $SNAP/b 2619 // - $SNAP/b/c 2620 // - $SNAP/d 2621 // 2622 // but snapd's mount profile contains only two entries: 2623 // 2624 // 1) recursive-bind $SNAP/a -> $SNAP/b/c (ie, mount --rbind $SNAP/a $SNAP/b/c) 2625 // 2) recursive-bind $SNAP/b -> $SNAP/d (ie, mount --rbind $SNAP/b $SNAP/d) 2626 // 2627 // Both mount operations are performed under a substrate that is MS_SHARED. 2628 // Therefore, due to the rules that decide upon propagation of bind mounts 2629 // the propagation of the new mount entries is also shared. This is 2630 // documented in section 5b of 2631 // https://www.kernel.org/doc/Documentation/filesystems/sharedsubtree.txt. 2632 // 2633 // Interactive experimentation shows that the following three mount points exist 2634 // after this operation, as illustrated by findmnt: 2635 // 2636 // TARGET SOURCE FSTYPE OPTIONS 2637 // ... 2638 // └─/snap/test-snapd-layout/x1 /dev/loop1 squashfs ro,nodev,relatime 2639 // ├─/snap/test-snapd-layout/x1/b/c /dev/loop1[/a] squashfs ro,nodev,relatime 2640 // └─/snap/test-snapd-layout/x1/d /dev/loop1[/b] squashfs ro,nodev,relatime 2641 // └─/snap/test-snapd-layout/x1/d/c /dev/loop1[/a] squashfs ro,nodev,relatime 2642 // 2643 // Note that after the first mount operation only one mount point is created, namely 2644 // $SNAP/a -> $SNAP/b/c. The second recursive bind mount not only creates 2645 // $SNAP/b -> $SNAP/d, but also replicates $SNAP/a -> $SNAP/b/c as 2646 // $SNAP/a -> $SNAP/d/c. 2647 // 2648 // The test will simulate a refresh of the snap from revision x1 to revision 2649 // x2. When this happens the mount profile associated with x1 must be undone 2650 // and the mount profile associated with x2 must be constructed. Because 2651 // ordering matters, let's first consider the order of construction of x1 2652 // itself. Starting from nothing, apply x1 as follows: 2653 x1 := &osutil.MountProfile{ 2654 Entries: []osutil.MountEntry{ 2655 {Name: "/snap/app/x1/a", Dir: "/snap/app/x1/b/c", Type: "none", Options: []string{"rbind", "rw", "x-snapd.origin=layout"}}, 2656 {Name: "/snap/app/x1/b", Dir: "/snap/app/x1/d", Type: "none", Options: []string{"rbind", "rw", "x-snapd.origin=layout"}}, 2657 }, 2658 } 2659 changes := update.NeededChanges(&osutil.MountProfile{}, x1) 2660 c.Assert(changes, DeepEquals, []*update.Change{ 2661 {Action: update.Mount, Entry: osutil.MountEntry{Name: "/snap/app/x1/a", Dir: "/snap/app/x1/b/c", Type: "none", Options: []string{"rbind", "rw", "x-snapd.origin=layout"}}}, 2662 {Action: update.Mount, Entry: osutil.MountEntry{Name: "/snap/app/x1/b", Dir: "/snap/app/x1/d", Type: "none", Options: []string{"rbind", "rw", "x-snapd.origin=layout"}}}, 2663 }) 2664 // We can see that x1 is constructed in alphabetical order, first recursively 2665 // bind mount at $SNAP/a the directory $SNAP/b/c, second recursively bind 2666 // mount at $SNAP/b the directory $SNAP/d. 2667 x2 := &osutil.MountProfile{ 2668 Entries: []osutil.MountEntry{ 2669 {Name: "/snap/app/x2/a", Dir: "/snap/app/x2/b/c", Type: "none", Options: []string{"rbind", "rw", "x-snapd.origin=layout"}}, 2670 {Name: "/snap/app/x2/b", Dir: "/snap/app/x2/d", Type: "none", Options: []string{"rbind", "rw", "x-snapd.origin=layout"}}, 2671 }, 2672 } 2673 // When we are asked to refresh to revision x2, using the same layout, we 2674 // simply undo x1 and then create x2, which apart from the difference in 2675 // revision name, is exactly the same. The undo code, however, does not take 2676 // the replicated mount point under consideration and therefore attempts to 2677 // detach "x1/d", which normally fails with EBUSY. To counter this, the 2678 // unmount operation first switches the mount point to recursive private 2679 // propagation, before actually unmounting it. This ensures that propagation 2680 // doesn't self-conflict, simply because there isn't any left. 2681 changes = update.NeededChanges(x1, x2) 2682 c.Assert(changes, DeepEquals, []*update.Change{ 2683 {Action: update.Unmount, Entry: osutil.MountEntry{Name: "/snap/app/x1/b", Dir: "/snap/app/x1/d", Type: "none", Options: []string{"rbind", "rw", "x-snapd.origin=layout", "x-snapd.detach"}}}, 2684 {Action: update.Unmount, Entry: osutil.MountEntry{Name: "/snap/app/x1/a", Dir: "/snap/app/x1/b/c", Type: "none", Options: []string{"rbind", "rw", "x-snapd.origin=layout", "x-snapd.detach"}}}, 2685 {Action: update.Mount, Entry: osutil.MountEntry{Name: "/snap/app/x2/a", Dir: "/snap/app/x2/b/c", Type: "none", Options: []string{"rbind", "rw", "x-snapd.origin=layout"}}}, 2686 {Action: update.Mount, Entry: osutil.MountEntry{Name: "/snap/app/x2/b", Dir: "/snap/app/x2/d", Type: "none", Options: []string{"rbind", "rw", "x-snapd.origin=layout"}}}, 2687 }) 2688 } 2689 2690 func (s *changeSuite) TestUnmountFailsWithEINVALAndUnmounted(c *C) { 2691 // We wanted to unmount /target, which failed with EINVAL. 2692 // Because /target is no longer mounted, we consume the error and carry on. 2693 restore := osutil.MockMountInfo("") 2694 defer restore() 2695 s.sys.InsertFault(`unmount "/target" UMOUNT_NOFOLLOW`, syscall.EINVAL) 2696 s.sys.InsertFstatResult(`fstat 4 <ptr>`, syscall.Stat_t{}) 2697 s.sys.InsertFstatfsResult(`fstatfs 4 <ptr>`, syscall.Statfs_t{}) 2698 chg := &update.Change{Action: update.Unmount, Entry: osutil.MountEntry{Name: "tmpfs", Dir: "/target", Type: "tmpfs"}} 2699 _, err := chg.Perform(s.as) 2700 c.Assert(err, IsNil) 2701 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 2702 {C: `unmount "/target" UMOUNT_NOFOLLOW`, E: syscall.EINVAL}, 2703 {C: `open "/" O_NOFOLLOW|O_CLOEXEC|O_DIRECTORY|O_PATH 0`, R: 3}, 2704 {C: `openat 3 "target" O_NOFOLLOW|O_CLOEXEC|O_PATH 0`, R: 4}, 2705 {C: `fstat 4 <ptr>`, R: syscall.Stat_t{}}, 2706 {C: `close 3`}, 2707 {C: `fstatfs 4 <ptr>`, R: syscall.Statfs_t{}}, 2708 {C: `remove "/target"`}, 2709 {C: `close 4`}, 2710 }) 2711 } 2712 2713 func (s *changeSuite) TestUnmountFailsWithEINVALButStillMounted(c *C) { 2714 // We wanted to unmount /target, which failed with EINVAL. 2715 // Because /target is still mounted, we propagate the error. 2716 restore := osutil.MockMountInfo("132 28 0:82 / /target rw,relatime shared:74 - tmpfs tmpfs rw") 2717 defer restore() 2718 s.sys.InsertFault(`unmount "/target" UMOUNT_NOFOLLOW`, syscall.EINVAL) 2719 chg := &update.Change{Action: update.Unmount, Entry: osutil.MountEntry{Name: "tmpfs", Dir: "/target", Type: "tmpfs"}} 2720 _, err := chg.Perform(s.as) 2721 c.Assert(err, Equals, syscall.EINVAL) 2722 c.Assert(s.sys.RCalls(), testutil.SyscallsEqual, []testutil.CallResultError{ 2723 {C: `unmount "/target" UMOUNT_NOFOLLOW`, E: syscall.EINVAL}, 2724 }) 2725 }