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