github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/cmd/snap-update-ns/change.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 21 22 import ( 23 "errors" 24 "fmt" 25 "os" 26 "path/filepath" 27 "sort" 28 "strings" 29 "syscall" 30 31 "github.com/snapcore/snapd/features" 32 "github.com/snapcore/snapd/logger" 33 "github.com/snapcore/snapd/osutil" 34 "github.com/snapcore/snapd/osutil/mount" 35 ) 36 37 // Action represents a mount action (mount, remount, unmount, etc). 38 type Action string 39 40 const ( 41 // Keep indicates that a given mount entry should be kept as-is. 42 Keep Action = "keep" 43 // Mount represents an action that results in mounting something somewhere. 44 Mount Action = "mount" 45 // Unmount represents an action that results in unmounting something from somewhere. 46 Unmount Action = "unmount" 47 // Remount when needed 48 ) 49 50 var ( 51 // ErrIgnoredMissingMount is returned when a mount entry has 52 // been marked with x-snapd.ignore-missing, and the mount 53 // source or target do not exist. 54 ErrIgnoredMissingMount = errors.New("mount source or target are missing") 55 ) 56 57 // Change describes a change to the mount table (action and the entry to act on). 58 type Change struct { 59 Entry osutil.MountEntry 60 Action Action 61 } 62 63 // String formats mount change to a human-readable line. 64 func (c Change) String() string { 65 return fmt.Sprintf("%s (%s)", c.Action, c.Entry) 66 } 67 68 // changePerform is Change.Perform that can be mocked for testing. 69 var changePerform func(*Change, *Assumptions) ([]*Change, error) 70 71 // mimicRequired provides information if an error warrants a writable mimic. 72 // 73 // The returned path is the location where a mimic should be constructed. 74 func mimicRequired(err error) (needsMimic bool, path string) { 75 switch err.(type) { 76 case *ReadOnlyFsError: 77 rofsErr := err.(*ReadOnlyFsError) 78 return true, rofsErr.Path 79 case *TrespassingError: 80 tErr := err.(*TrespassingError) 81 return true, tErr.ViolatedPath 82 } 83 return false, "" 84 } 85 86 func (c *Change) createPath(path string, pokeHoles bool, as *Assumptions) ([]*Change, error) { 87 // If we've been asked to create a missing path, and the mount 88 // entry uses the ignore-missing option, return an error. 89 if c.Entry.XSnapdIgnoreMissing() { 90 return nil, ErrIgnoredMissingMount 91 } 92 93 var err error 94 var changes []*Change 95 96 // In case we need to create something, some constants. 97 const ( 98 mode = 0755 99 uid = 0 100 gid = 0 101 ) 102 103 // If the element doesn't exist we can attempt to create it. We will 104 // create the parent directory and then the final element relative to it. 105 // The traversed space may be writable so we just try to create things 106 // first. 107 kind := c.Entry.XSnapdKind() 108 109 // TODO: re-factor this, if possible, with inspection and preemptive 110 // creation after the current release ships. This should be possible but 111 // will affect tests heavily (churn, not safe before release). 112 rs := as.RestrictionsFor(path) 113 switch kind { 114 case "": 115 err = MkdirAll(path, mode, uid, gid, rs) 116 case "file": 117 err = MkfileAll(path, mode, uid, gid, rs) 118 case "symlink": 119 err = MksymlinkAll(path, mode, uid, gid, c.Entry.XSnapdSymlink(), rs) 120 } 121 if needsMimic, mimicPath := mimicRequired(err); needsMimic && pokeHoles { 122 // If the error can be recovered by using a writable mimic 123 // then construct one and try again. 124 logger.Debugf("need to create writable mimic needed to create path %q (original error: %v)", path, err) 125 changes, err = createWritableMimic(mimicPath, path, as) 126 if err != nil { 127 err = fmt.Errorf("cannot create writable mimic over %q: %s", mimicPath, err) 128 } else { 129 // Try once again. Note that we care *just* about the error. We have already 130 // performed the hole poking and thus additional changes must be nil. 131 _, err = c.createPath(path, false, as) 132 } 133 } 134 return changes, err 135 } 136 137 func (c *Change) ensureTarget(as *Assumptions) ([]*Change, error) { 138 var changes []*Change 139 140 kind := c.Entry.XSnapdKind() 141 path := c.Entry.Dir 142 143 // We use lstat to ensure that we don't follow a symlink in case one was 144 // set up by the snap. Note that at the time this is run, all the snap's 145 // processes are frozen but if the path is a directory controlled by the 146 // user (typically in /home) then we may still race with user processes 147 // that change it. 148 fi, err := osLstat(path) 149 150 if err == nil { 151 // If the element already exists we just need to ensure it is of 152 // the correct type. The desired type depends on the kind of entry 153 // we are working with. 154 switch kind { 155 case "": 156 if !fi.Mode().IsDir() { 157 err = fmt.Errorf("cannot use %q as mount point: not a directory", path) 158 } 159 case "file": 160 if !fi.Mode().IsRegular() { 161 err = fmt.Errorf("cannot use %q as mount point: not a regular file", path) 162 } 163 case "symlink": 164 if fi.Mode()&os.ModeSymlink == os.ModeSymlink { 165 // Create path verifies the symlink or fails if it is not what we wanted. 166 _, err = c.createPath(path, false, as) 167 } else { 168 err = fmt.Errorf("cannot create symlink in %q: existing file in the way", path) 169 } 170 } 171 } else if os.IsNotExist(err) { 172 changes, err = c.createPath(path, true, as) 173 } else { 174 // If we cannot inspect the element let's just bail out. 175 err = fmt.Errorf("cannot inspect %q: %v", path, err) 176 } 177 return changes, err 178 } 179 180 func (c *Change) ensureSource(as *Assumptions) ([]*Change, error) { 181 var changes []*Change 182 183 // We only have to do ensure bind mount source exists. 184 // This also rules out symlinks. 185 flags, _ := osutil.MountOptsToCommonFlags(c.Entry.Options) 186 if flags&syscall.MS_BIND == 0 { 187 return nil, nil 188 } 189 190 kind := c.Entry.XSnapdKind() 191 path := c.Entry.Name 192 fi, err := osLstat(path) 193 194 if err == nil { 195 // If the element already exists we just need to ensure it is of 196 // the correct type. The desired type depends on the kind of entry 197 // we are working with. 198 switch kind { 199 case "": 200 if !fi.Mode().IsDir() { 201 err = fmt.Errorf("cannot use %q as bind-mount source: not a directory", path) 202 } 203 case "file": 204 if !fi.Mode().IsRegular() { 205 err = fmt.Errorf("cannot use %q as bind-mount source: not a regular file", path) 206 } 207 } 208 } else if os.IsNotExist(err) { 209 // NOTE: This createPath is using pokeHoles, to make read-only places 210 // writable, but only for layouts and not for other (typically content 211 // sharing) mount entries. 212 // 213 // This is done because the changes made with pokeHoles=true are only 214 // visible in this current mount namespace and are not generally 215 // visible from other snaps because they inhabit different namespaces. 216 // 217 // In other words, changes made here are only observable by the single 218 // snap they apply to. As such they are useless for content sharing but 219 // very much useful to layouts. 220 pokeHoles := c.Entry.XSnapdOrigin() == "layout" 221 changes, err = c.createPath(path, pokeHoles, as) 222 } else { 223 // If we cannot inspect the element let's just bail out. 224 err = fmt.Errorf("cannot inspect %q: %v", path, err) 225 } 226 227 return changes, err 228 } 229 230 // changePerformImpl is the real implementation of Change.Perform 231 func changePerformImpl(c *Change, as *Assumptions) (changes []*Change, err error) { 232 if c.Action == Mount { 233 var changesSource, changesTarget []*Change 234 // We may be asked to bind mount a file, bind mount a directory, mount 235 // a filesystem over a directory, or create a symlink (which is abusing 236 // the "mount" concept slightly). That actual operation is performed in 237 // c.lowLevelPerform. Here we just set the stage to make that possible. 238 // 239 // As a result of this ensure call we may need to make the medium writable 240 // and that's why we may return more changes as a result of performing this 241 // one. 242 changesTarget, err = c.ensureTarget(as) 243 // NOTE: we are collecting changes even if things fail. This is so that 244 // upper layers can perform undo correctly. 245 changes = append(changes, changesTarget...) 246 if err != nil { 247 return changes, err 248 } 249 250 // At this time we can be sure that the target element (for files and 251 // directories) exists and is of the right type or that it (for 252 // symlinks) doesn't exist but the parent directory does. 253 // This property holds as long as we don't interact with locations that 254 // are under the control of regular (non-snap) processes that are not 255 // suspended and may be racing with us. 256 changesSource, err = c.ensureSource(as) 257 // NOTE: we are collecting changes even if things fail. This is so that 258 // upper layers can perform undo correctly. 259 changes = append(changes, changesSource...) 260 if err != nil { 261 return changes, err 262 } 263 } 264 265 // Perform the underlying mount / unmount / unlink call. 266 err = c.lowLevelPerform(as) 267 return changes, err 268 } 269 270 func init() { 271 changePerform = changePerformImpl 272 } 273 274 // Perform executes the desired mount or unmount change using system calls. 275 // Filesystems that depend on helper programs or multiple independent calls to 276 // the kernel (--make-shared, for example) are unsupported. 277 // 278 // Perform may synthesize *additional* changes that were necessary to perform 279 // this change (such as mounted tmpfs or overlayfs). 280 func (c *Change) Perform(as *Assumptions) ([]*Change, error) { 281 return changePerform(c, as) 282 } 283 284 // lowLevelPerform is simple bridge from Change to mount / unmount syscall. 285 func (c *Change) lowLevelPerform(as *Assumptions) error { 286 var err error 287 switch c.Action { 288 case Mount: 289 kind := c.Entry.XSnapdKind() 290 switch kind { 291 case "symlink": 292 // symlinks are handled in createInode directly, nothing to do here. 293 case "", "file": 294 flags, unparsed := osutil.MountOptsToCommonFlags(c.Entry.Options) 295 // Split the mount flags from the event propagation changes. 296 // Those have to be applied separately. 297 const propagationMask = syscall.MS_SHARED | syscall.MS_SLAVE | syscall.MS_PRIVATE | syscall.MS_UNBINDABLE 298 maskedFlagsRecursive := flags & syscall.MS_REC 299 maskedFlagsPropagation := flags & propagationMask 300 maskedFlagsNotPropagationNotRecursive := flags & ^(propagationMask | syscall.MS_REC) 301 302 var flagsForMount uintptr 303 if flags&syscall.MS_BIND == syscall.MS_BIND { 304 // bind / rbind mount 305 flagsForMount = uintptr(maskedFlagsNotPropagationNotRecursive | maskedFlagsRecursive) 306 err = BindMount(c.Entry.Name, c.Entry.Dir, uint(flagsForMount)) 307 } else { 308 // normal mount, not bind / rbind, not propagation change 309 flagsForMount = uintptr(maskedFlagsNotPropagationNotRecursive) 310 err = sysMount(c.Entry.Name, c.Entry.Dir, c.Entry.Type, uintptr(flagsForMount), strings.Join(unparsed, ",")) 311 } 312 mountOpts, unknownFlags := mount.MountFlagsToOpts(int(flagsForMount)) 313 if unknownFlags != 0 { 314 mountOpts = append(mountOpts, fmt.Sprintf("%#x", unknownFlags)) 315 } 316 logger.Debugf("mount name:%q dir:%q type:%q opts:%s unparsed:%q (error: %v)", 317 c.Entry.Name, c.Entry.Dir, c.Entry.Type, strings.Join(mountOpts, "|"), strings.Join(unparsed, ","), err) 318 if err == nil && maskedFlagsPropagation != 0 { 319 // now change mount propagation (shared/rshared, private/rprivate, 320 // slave/rslave, unbindable/runbindable). 321 flagsForMount := uintptr(maskedFlagsPropagation | maskedFlagsRecursive) 322 mountOpts, unknownFlags := mount.MountFlagsToOpts(int(flagsForMount)) 323 if unknownFlags != 0 { 324 mountOpts = append(mountOpts, fmt.Sprintf("%#x", unknownFlags)) 325 } 326 err = sysMount("none", c.Entry.Dir, "", flagsForMount, "") 327 logger.Debugf("mount name:%q dir:%q type:%q opts:%s unparsed:%q (error: %v)", 328 "none", c.Entry.Dir, "", strings.Join(mountOpts, "|"), strings.Join(unparsed, ","), err) 329 } 330 if err == nil { 331 as.AddChange(c) 332 } 333 } 334 return err 335 case Unmount: 336 kind := c.Entry.XSnapdKind() 337 switch kind { 338 case "symlink": 339 err = osRemove(c.Entry.Dir) 340 logger.Debugf("remove %q (error: %v)", c.Entry.Dir, err) 341 case "", "file": 342 // Detach the mount point instead of unmounting it if requested. 343 flags := umountNoFollow 344 if c.Entry.XSnapdDetach() { 345 flags |= syscall.MNT_DETACH 346 // If we are detaching something then before performing the actual detach 347 // switch the entire hierarchy to private event propagation (that is, 348 // none). This works around a bit of peculiar kernel behavior when the 349 // kernel reports EBUSY during a detach operation, because the changes 350 // propagate in a way that conflicts with itself. This is also documented 351 // in umount(2). 352 err = sysMount("none", c.Entry.Dir, "", syscall.MS_REC|syscall.MS_PRIVATE, "") 353 logger.Debugf("mount --make-rprivate %q (error: %v)", c.Entry.Dir, err) 354 } 355 356 // Perform the raw unmount operation. 357 if err == nil { 358 err = sysUnmount(c.Entry.Dir, flags) 359 umountOpts, unknownFlags := mount.UnmountFlagsToOpts(flags) 360 if unknownFlags != 0 { 361 umountOpts = append(umountOpts, fmt.Sprintf("%#x", unknownFlags)) 362 } 363 logger.Debugf("umount %q %s (error: %v)", c.Entry.Dir, strings.Join(umountOpts, "|"), err) 364 if err == syscall.EINVAL { 365 // We attempted to unmount but got an EINVAL, one of the 366 // possibilities and the only one unless we provided wrong 367 // flags, is that the mount no longer exists. 368 // 369 // We can verify that now by scanning mountinfo: 370 entries, _ := osutil.LoadMountInfo() 371 for _, entry := range entries { 372 if entry.MountDir == c.Entry.Dir { 373 // Mount point still exists, EINVAL was unexpected. 374 return err 375 } 376 } 377 // We didn't find a mount point at the location we tried to 378 // unmount. The EINVAL we observed indicates that the mount 379 // profile no longer agrees with reality. The mount point 380 // no longer exists. As such, consume the error and carry on. 381 logger.Debugf("ignoring EINVAL from unmount, %q is not mounted", c.Entry.Dir) 382 err = nil 383 } 384 if err != nil { 385 return err 386 } 387 } 388 if err == nil { 389 as.AddChange(c) 390 } 391 392 // Open a path of the file we are considering the removal of. 393 path := c.Entry.Dir 394 var fd int 395 fd, err = OpenPath(path) 396 // If the place does not exist anymore, we are done. 397 if os.IsNotExist(err) { 398 return nil 399 } 400 if err != nil { 401 return err 402 } 403 defer sysClose(fd) 404 405 // Don't attempt to remove anything from squashfs. 406 // Note that this is not a perfect check and we also handle EROFS below. 407 var statfsBuf syscall.Statfs_t 408 err = sysFstatfs(fd, &statfsBuf) 409 if err != nil { 410 return err 411 } 412 if statfsBuf.Type == SquashfsMagic { 413 return nil 414 } 415 416 if kind == "file" { 417 // Don't attempt to remove non-empty files since they cannot be 418 // the placeholders we created. 419 var statBuf syscall.Stat_t 420 err = sysFstat(fd, &statBuf) 421 if err != nil { 422 return err 423 } 424 if statBuf.Size != 0 { 425 return nil 426 } 427 } 428 429 // Remove the file or directory while using the full path. There's 430 // no way to avoid a race here since there's no way to unlink a 431 // file solely by file descriptor. 432 err = osRemove(path) 433 logger.Debugf("remove %q (error: %v)", path, err) 434 // Unpack the low-level error that osRemove wraps into PathError. 435 if packed, ok := err.(*os.PathError); ok { 436 err = packed.Err 437 } 438 if err == syscall.EROFS { 439 // If the underlying medium is read-only then ignore the error. 440 // Instead of checking up front we just try to remove because 441 // of https://bugs.launchpad.net/snapd/+bug/1867752 which showed us 442 // two important properties: 443 // 1) inside containers we cannot detect squashfs reliably and 444 // will always see FUSE instead. The problem is that there's no 445 // indication as to what is really mounted via statfs(2) and 446 // we would have to deduce that from mountinfo, trusting 447 // that fuse.<name> is not spoofed (as in, the name is not 448 // spoofed). 449 // 2) rmdir of a bind mount (from a normal writable filesystem like ext4) 450 // over a read-only filesystem also yields EROFS without any indication 451 // that this is to be expected. 452 logger.Debugf("cannot remove a mount point on read-only filesystem %q", path) 453 return nil 454 } 455 // If we were removing a directory but it was not empty then just 456 // ignore the error. This is the equivalent of the non-empty file 457 // check we do above. See rmdir(2) for explanation why we accept 458 // more than one errno value. 459 if kind == "" && (err == syscall.ENOTEMPTY || err == syscall.EEXIST) { 460 return nil 461 } 462 if features.RobustMountNamespaceUpdates.IsEnabled() { 463 // FIXME: This should not be necessary. It is necessary because 464 // mimic construction code is not considering all layouts in tandem 465 // and doesn't know enough about base file system to construct 466 // mimics in the order that would prevent them from nesting. 467 // 468 // By ignoring EBUSY here and by continuing to tear down the mimic 469 // tmpfs entirely (without any reuse) we guarantee that at the end 470 // of the day the nested mimic case is entirely removed. 471 // 472 // In an ideal world we would model this better and could do 473 // without this edge case. 474 if (kind == "" || kind == "file") && err == syscall.EBUSY { 475 logger.Debugf("cannot remove busy mount point %q", path) 476 return nil 477 } 478 } 479 } 480 return err 481 case Keep: 482 as.AddChange(c) 483 return nil 484 } 485 return fmt.Errorf("cannot process mount change: unknown action: %q", c.Action) 486 } 487 488 // neededChangesOld is the real implementation of NeededChanges 489 // This function is used when RobustMountNamespaceUpdate is not enabled. 490 func neededChangesOld(currentProfile, desiredProfile *osutil.MountProfile) []*Change { 491 // Copy both profiles as we will want to mutate them. 492 current := make([]osutil.MountEntry, len(currentProfile.Entries)) 493 copy(current, currentProfile.Entries) 494 desired := make([]osutil.MountEntry, len(desiredProfile.Entries)) 495 copy(desired, desiredProfile.Entries) 496 497 // Clean the directory part of both profiles. This is done so that we can 498 // easily test if a given directory is a subdirectory with 499 // strings.HasPrefix coupled with an extra slash character. 500 for i := range current { 501 current[i].Dir = filepath.Clean(current[i].Dir) 502 } 503 for i := range desired { 504 desired[i].Dir = filepath.Clean(desired[i].Dir) 505 } 506 507 // Sort both lists by directory name with implicit trailing slash. 508 sort.Sort(byOriginAndMagicDir(current)) 509 sort.Sort(byOriginAndMagicDir(desired)) 510 511 // Construct a desired directory map. 512 desiredMap := make(map[string]*osutil.MountEntry) 513 for i := range desired { 514 desiredMap[desired[i].Dir] = &desired[i] 515 } 516 517 // Indexed by mount point path. 518 reuse := make(map[string]bool) 519 // Indexed by entry ID 520 desiredIDs := make(map[string]bool) 521 var skipDir string 522 523 // Collect the IDs of desired changes. 524 // We need that below to keep implicit changes from the current profile. 525 for i := range desired { 526 desiredIDs[desired[i].XSnapdEntryID()] = true 527 } 528 529 // Compute reusable entries: those which are equal in current and desired and which 530 // are not prefixed by another entry that changed. 531 for i := range current { 532 dir := current[i].Dir 533 if skipDir != "" && strings.HasPrefix(dir, skipDir) { 534 logger.Debugf("skipping entry %q", current[i]) 535 continue 536 } 537 skipDir = "" // reset skip prefix as it no longer applies 538 539 // Reuse synthetic entries if their needed-by entry is desired. 540 // Synthetic entries cannot exist on their own and always couple to a 541 // non-synthetic entry. 542 543 // NOTE: Synthetic changes have a special purpose. 544 // 545 // They are a "shadow" of mount events that occurred to allow one of 546 // the desired mount entries to be possible. The changes have only one 547 // goal: tell snap-update-ns how those mount events can be undone in 548 // case they are no longer needed. The actual changes may have been 549 // different and may have involved steps not represented as synthetic 550 // mount entires as long as those synthetic entries can be undone to 551 // reverse the effect. In reality each non-tmpfs synthetic entry was 552 // constructed using a temporary bind mount that contained the original 553 // mount entries of a directory that was hidden with a tmpfs, but this 554 // fact was lost. 555 if current[i].XSnapdSynthetic() && desiredIDs[current[i].XSnapdNeededBy()] { 556 logger.Debugf("reusing synthetic entry %q", current[i]) 557 reuse[dir] = true 558 continue 559 } 560 561 // Reuse entries that are desired and identical in the current profile. 562 if entry, ok := desiredMap[dir]; ok && current[i].Equal(entry) { 563 logger.Debugf("reusing unchanged entry %q", current[i]) 564 reuse[dir] = true 565 continue 566 } 567 568 skipDir = strings.TrimSuffix(dir, "/") + "/" 569 } 570 571 logger.Debugf("desiredIDs: %v", desiredIDs) 572 logger.Debugf("reuse: %v", reuse) 573 574 // We are now ready to compute the necessary mount changes. 575 var changes []*Change 576 577 // Unmount entries not reused in reverse to handle children before their parent. 578 for i := len(current) - 1; i >= 0; i-- { 579 if reuse[current[i].Dir] { 580 changes = append(changes, &Change{Action: Keep, Entry: current[i]}) 581 } else { 582 var entry osutil.MountEntry = current[i] 583 entry.Options = append([]string(nil), entry.Options...) 584 // If the mount entry can potentially host nested mount points then detach 585 // rather than unmount, since detach will always succeed. 586 shouldDetach := entry.Type == "tmpfs" || entry.OptBool("bind") || entry.OptBool("rbind") 587 if shouldDetach && !entry.XSnapdDetach() { 588 entry.Options = append(entry.Options, osutil.XSnapdDetach()) 589 } 590 changes = append(changes, &Change{Action: Unmount, Entry: entry}) 591 } 592 } 593 594 // Mount desired entries not reused. 595 for i := range desired { 596 if !reuse[desired[i].Dir] { 597 changes = append(changes, &Change{Action: Mount, Entry: desired[i]}) 598 } 599 } 600 601 return changes 602 } 603 604 // neededChangesNew is the real implementation of NeededChanges 605 // This function is used when RobustMountNamespaceUpdate is enabled. 606 func neededChangesNew(currentProfile, desiredProfile *osutil.MountProfile) []*Change { 607 // Copy both profiles as we will want to mutate them. 608 current := make([]osutil.MountEntry, len(currentProfile.Entries)) 609 copy(current, currentProfile.Entries) 610 desired := make([]osutil.MountEntry, len(desiredProfile.Entries)) 611 copy(desired, desiredProfile.Entries) 612 613 // Clean the directory part of both profiles. This is done so that we can 614 // easily test if a given directory is a subdirectory with 615 // strings.HasPrefix coupled with an extra slash character. 616 for i := range current { 617 current[i].Dir = filepath.Clean(current[i].Dir) 618 } 619 for i := range desired { 620 desired[i].Dir = filepath.Clean(desired[i].Dir) 621 } 622 623 // Sort both lists by directory name with implicit trailing slash. 624 sort.Sort(byOriginAndMagicDir(current)) 625 sort.Sort(byOriginAndMagicDir(desired)) 626 627 // We are now ready to compute the necessary mount changes. 628 var changes []*Change 629 630 // Unmount entries in reverse order, so that the most nested element is 631 // always processed first. 632 for i := len(current) - 1; i >= 0; i-- { 633 var entry osutil.MountEntry = current[i] 634 entry.Options = append([]string(nil), entry.Options...) 635 switch { 636 case entry.XSnapdSynthetic() && entry.Type == "tmpfs": 637 // Synthetic changes are rooted under a tmpfs, detach that tmpfs to 638 // remove them all. 639 if !entry.XSnapdDetach() { 640 entry.Options = append(entry.Options, osutil.XSnapdDetach()) 641 } 642 case entry.XSnapdSynthetic(): 643 // Consume all other syn ethic entries without emitting either a 644 // mount, unmount or keep change. This relies on the fact that all 645 // synthetic mounts are created by a mimic underneath a tmpfs that 646 // is detached, as coded above. 647 continue 648 case entry.OptBool("rbind") || entry.Type == "tmpfs": 649 // Recursive bind mounts and non-mimic tmpfs mounts need to be 650 // detached because they can contain other mount points that can 651 // otherwise propagate in a self-conflicting way. 652 if !entry.XSnapdDetach() { 653 entry.Options = append(entry.Options, osutil.XSnapdDetach()) 654 } 655 case entry.OptBool("bind") && entry.XSnapdKind() == "file": 656 // Bind mounted files are detached. If a bind mounted file open or 657 // mapped into a process as a library, then attempting to unmount 658 // it will result in EBUSY. 659 // 660 // This can happen when a snap has a service, for example one using 661 // a library mounted via a bind mount and an absent content 662 // connection. Subsequent connection of the content connection will 663 // trigger re-population of the mount namespace, which will start 664 // by tearing down the existing file bind-mount. To prevent this, 665 // detach the mount instead. 666 if !entry.XSnapdDetach() { 667 entry.Options = append(entry.Options, osutil.XSnapdDetach()) 668 } 669 } 670 // Unmount all changes that were not eliminated. 671 changes = append(changes, &Change{Action: Unmount, Entry: entry}) 672 } 673 674 // Mount desired entries. 675 for i := range desired { 676 changes = append(changes, &Change{Action: Mount, Entry: desired[i]}) 677 } 678 679 return changes 680 } 681 682 // NeededChanges computes the changes required to change current to desired mount entries. 683 // 684 // The algorithm differs depending on the value of the robust mount namespace 685 // updates feature flag. If the flag is enabled then the current profile is 686 // entirely undone and the desired profile is constructed from scratch. 687 // 688 // If the flag is disabled then a diff-like operation on the mount profile is 689 // computed. Some of the mount entries from the current profile may be reused. 690 // The diff approach doesn't function correctly in cases of nested mimics. 691 var NeededChanges = func(current, desired *osutil.MountProfile) []*Change { 692 if features.RobustMountNamespaceUpdates.IsEnabled() { 693 return neededChangesNew(current, desired) 694 } 695 return neededChangesOld(current, desired) 696 }