gitee.com/mysnapcore/mysnapd@v0.1.0/cmd/snap-update-ns/utils.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 "fmt" 24 "io/ioutil" 25 "os" 26 "path/filepath" 27 "strings" 28 "syscall" 29 30 "gitee.com/mysnapcore/mysnapd/logger" 31 "gitee.com/mysnapcore/mysnapd/osutil" 32 "gitee.com/mysnapcore/mysnapd/osutil/sys" 33 "gitee.com/mysnapcore/mysnapd/strutil" 34 ) 35 36 // not available through syscall 37 const ( 38 umountNoFollow = 8 39 // StReadOnly is the equivalent of ST_RDONLY 40 StReadOnly = 1 41 // SquashfsMagic is the equivalent of SQUASHFS_MAGIC 42 SquashfsMagic = 0x73717368 43 // Ext4Magic is the equivalent of EXT4_SUPER_MAGIC 44 //nolint:deadcode 45 Ext4Magic = 0xef53 46 // TmpfsMagic is the equivalent of TMPFS_MAGIC 47 TmpfsMagic = 0x01021994 48 ) 49 50 // For mocking everything during testing. 51 var ( 52 osLstat = os.Lstat 53 osReadlink = os.Readlink 54 osRemove = os.Remove 55 56 sysClose = syscall.Close 57 sysMkdirat = syscall.Mkdirat 58 sysMount = syscall.Mount 59 sysOpen = syscall.Open 60 sysOpenat = syscall.Openat 61 sysUnmount = syscall.Unmount 62 sysFchown = sys.Fchown 63 sysFstat = syscall.Fstat 64 sysFstatfs = syscall.Fstatfs 65 sysSymlinkat = osutil.Symlinkat 66 sysReadlinkat = osutil.Readlinkat 67 sysFchdir = syscall.Fchdir 68 sysLstat = syscall.Lstat 69 70 ioutilReadDir = ioutil.ReadDir 71 ) 72 73 // ReadOnlyFsError is an error encapsulating encountered EROFS. 74 type ReadOnlyFsError struct { 75 Path string 76 } 77 78 func (e *ReadOnlyFsError) Error() string { 79 return fmt.Sprintf("cannot operate on read-only filesystem at %s", e.Path) 80 } 81 82 // OpenPath creates a path file descriptor for the given 83 // path, making sure no components are symbolic links. 84 // 85 // The file descriptor is opened using the O_PATH, O_NOFOLLOW, 86 // and O_CLOEXEC flags. 87 func OpenPath(path string) (int, error) { 88 iter, err := strutil.NewPathIterator(path) 89 if err != nil { 90 return -1, fmt.Errorf("cannot open path: %s", err) 91 } 92 if !filepath.IsAbs(iter.Path()) { 93 return -1, fmt.Errorf("path %v is not absolute", iter.Path()) 94 } 95 iter.Next() // Advance iterator to '/' 96 // We use the following flags to open: 97 // O_PATH: we don't intend to use the fd for IO 98 // O_NOFOLLOW: don't follow symlinks 99 // O_DIRECTORY: we expect to find directories (except for the leaf) 100 // O_CLOEXEC: don't leak file descriptors over exec() boundaries 101 openFlags := sys.O_PATH | syscall.O_NOFOLLOW | syscall.O_DIRECTORY | syscall.O_CLOEXEC 102 fd, err := sysOpen("/", openFlags, 0) 103 if err != nil { 104 return -1, err 105 } 106 for iter.Next() { 107 // Ensure the parent file descriptor is closed 108 defer sysClose(fd) 109 if !strings.HasSuffix(iter.CurrentName(), "/") { 110 openFlags &^= syscall.O_DIRECTORY 111 } 112 fd, err = sysOpenat(fd, iter.CurrentCleanName(), openFlags, 0) 113 if err != nil { 114 return -1, err 115 } 116 } 117 118 var statBuf syscall.Stat_t 119 err = sysFstat(fd, &statBuf) 120 if err != nil { 121 sysClose(fd) 122 return -1, err 123 } 124 if statBuf.Mode&syscall.S_IFMT == syscall.S_IFLNK { 125 sysClose(fd) 126 return -1, fmt.Errorf("%q is a symbolic link", path) 127 } 128 return fd, nil 129 } 130 131 // syscallMode returns the syscall-specific mode bits from Go's portable mode bits. 132 // This is a copy of the same helper in Go's os package. 133 func syscallMode(i os.FileMode) (o uint32) { 134 o |= uint32(i.Perm()) 135 if i&os.ModeSetuid != 0 { 136 o |= syscall.S_ISUID 137 } 138 if i&os.ModeSetgid != 0 { 139 o |= syscall.S_ISGID 140 } 141 if i&os.ModeSticky != 0 { 142 o |= syscall.S_ISVTX 143 } 144 // No mapping for Go's ModeTemporary (plan9 only). 145 return o 146 } 147 148 // MkPrefix creates all the missing directories in a given base path and 149 // returns the file descriptor to the leaf directory as well as the restricted 150 // flag. This function is a base for secure variants of mkdir, touch and 151 // symlink. None of the traversed directories can be symbolic links. 152 func MkPrefix(base string, perm os.FileMode, uid sys.UserID, gid sys.GroupID, rs *Restrictions) (int, error) { 153 iter, err := strutil.NewPathIterator(base) 154 if err != nil { 155 // TODO: Reword the error and adjust the tests. 156 return -1, fmt.Errorf("cannot split unclean path %q", base) 157 } 158 if !filepath.IsAbs(iter.Path()) { 159 return -1, fmt.Errorf("path %v is not absolute", iter.Path()) 160 } 161 iter.Next() // Advance iterator to '/' 162 163 const openFlags = syscall.O_NOFOLLOW | syscall.O_CLOEXEC | syscall.O_DIRECTORY 164 // Open the root directory and start there. 165 // 166 // We don't have to check for possible trespassing on / here because we are 167 // going to check for it in sec.MkDir call below which verifies that 168 // trespassing restrictions are not violated. 169 fd, err := sysOpen("/", openFlags, 0) 170 if err != nil { 171 return -1, fmt.Errorf("cannot open root directory: %v", err) 172 } 173 for iter.Next() { 174 // Keep closing the previous descriptor as we go, so that we have the 175 // last one handy from the MkDir below. 176 defer sysClose(fd) 177 fd, err = MkDir(fd, iter.CurrentBase(), iter.CurrentCleanName(), perm, uid, gid, rs) 178 if err != nil { 179 return -1, err 180 } 181 } 182 183 return fd, nil 184 } 185 186 // MkDir creates a directory with a given name. 187 // 188 // The directory is represented with a file descriptor and its name (for 189 // convenience). This function is meant to be used to construct subsequent 190 // elements of some path. The return value contains the newly created file 191 // descriptor for the new directory or -1 on error. 192 func MkDir(dirFd int, dirName string, name string, perm os.FileMode, uid sys.UserID, gid sys.GroupID, rs *Restrictions) (int, error) { 193 if err := rs.Check(dirFd, dirName); err != nil { 194 return -1, err 195 } 196 197 made := true 198 const openFlags = syscall.O_NOFOLLOW | syscall.O_CLOEXEC | syscall.O_DIRECTORY 199 200 if err := sysMkdirat(dirFd, name, syscallMode(perm)); err != nil { 201 switch err { 202 case syscall.EEXIST: 203 made = false 204 case syscall.EROFS: 205 // Treat EROFS specially: this is a hint that we have to poke a 206 // hole using tmpfs. The path below is the location where we 207 // need to poke the hole. 208 return -1, &ReadOnlyFsError{Path: dirName} 209 default: 210 return -1, fmt.Errorf("cannot create directory %q: %v", filepath.Join(dirName, name), err) 211 } 212 } 213 newFd, err := sysOpenat(dirFd, name, openFlags, 0) 214 if err != nil { 215 return -1, fmt.Errorf("cannot open directory %q: %v", filepath.Join(dirName, name), err) 216 } 217 if made { 218 // Chown each segment that we made. 219 if err := sysFchown(newFd, uid, gid); err != nil { 220 // Close the FD we opened if we fail here since the caller will get 221 // an error and won't assume responsibility for the FD. 222 sysClose(newFd) 223 return -1, fmt.Errorf("cannot chown directory %q to %d.%d: %v", filepath.Join(dirName, name), uid, gid, err) 224 } 225 // As soon as we find a place that is safe to write we can switch off 226 // the restricted mode (and thus any subsequent checks). This is 227 // because we only allow "writing" to read-only filesystems where 228 // writes fail with EROFS or to a tmpfs that snapd has privately 229 // mounted inside the per-snap mount namespace. As soon as we start 230 // walking over such tmpfs any subsequent children are either read- 231 // only bind mounts from $SNAP, other tmpfs'es (e.g. one explicitly 232 // constructed for a layout) or writable places that are bind-mounted 233 // from $SNAP_DATA or similar. 234 rs.Lift() 235 } 236 return newFd, err 237 } 238 239 // MkFile creates a file with a given name. 240 // 241 // The directory is represented with a file descriptor and its name (for 242 // convenience). This function is meant to be used to create the leaf file as 243 // a preparation for a mount point. Existing files are reused without errors. 244 // Newly created files have the specified mode and ownership. 245 func MkFile(dirFd int, dirName string, name string, perm os.FileMode, uid sys.UserID, gid sys.GroupID, rs *Restrictions) error { 246 if err := rs.Check(dirFd, dirName); err != nil { 247 return err 248 } 249 250 made := true 251 // NOTE: Tests don't show O_RDONLY as has a value of 0 and is not 252 // translated to textual form. It is added here for explicitness. 253 const openFlags = syscall.O_NOFOLLOW | syscall.O_CLOEXEC | syscall.O_RDONLY 254 255 // Open the final path segment as a file. Try to create the file (so that 256 // we know if we need to chown it) but fall back to just opening an 257 // existing one. 258 259 newFd, err := sysOpenat(dirFd, name, openFlags|syscall.O_CREAT|syscall.O_EXCL, syscallMode(perm)) 260 if err != nil { 261 switch err { 262 case syscall.EEXIST: 263 // If the file exists then just open it without O_CREAT and O_EXCL 264 newFd, err = sysOpenat(dirFd, name, openFlags, 0) 265 if err != nil { 266 return fmt.Errorf("cannot open file %q: %v", filepath.Join(dirName, name), err) 267 } 268 made = false 269 case syscall.EROFS: 270 // Treat EROFS specially: this is a hint that we have to poke a 271 // hole using tmpfs. The path below is the location where we 272 // need to poke the hole. 273 return &ReadOnlyFsError{Path: dirName} 274 default: 275 return fmt.Errorf("cannot open file %q: %v", filepath.Join(dirName, name), err) 276 } 277 } 278 defer sysClose(newFd) 279 280 if made { 281 // Chown the file if we made it. 282 if err := sysFchown(newFd, uid, gid); err != nil { 283 return fmt.Errorf("cannot chown file %q to %d.%d: %v", filepath.Join(dirName, name), uid, gid, err) 284 } 285 } 286 287 return nil 288 } 289 290 // MkSymlink creates a symlink with a given name. 291 // 292 // The directory is represented with a file descriptor and its name (for 293 // convenience). This function is meant to be used to create the leaf symlink. 294 // Existing and identical symlinks are reused without errors. 295 func MkSymlink(dirFd int, dirName string, name string, oldname string, rs *Restrictions) error { 296 if err := rs.Check(dirFd, dirName); err != nil { 297 return err 298 } 299 300 // Create the final path segment as a symlink. 301 if err := sysSymlinkat(oldname, dirFd, name); err != nil { 302 switch err { 303 case syscall.EEXIST: 304 var objFd int 305 // If the file exists then just open it for examination. 306 // Maybe it's the symlink we were hoping to create. 307 objFd, err = sysOpenat(dirFd, name, syscall.O_CLOEXEC|sys.O_PATH|syscall.O_NOFOLLOW, 0) 308 if err != nil { 309 return fmt.Errorf("cannot open existing file %q: %v", filepath.Join(dirName, name), err) 310 } 311 defer sysClose(objFd) 312 var statBuf syscall.Stat_t 313 err = sysFstat(objFd, &statBuf) 314 if err != nil { 315 return fmt.Errorf("cannot inspect existing file %q: %v", filepath.Join(dirName, name), err) 316 } 317 if statBuf.Mode&syscall.S_IFMT != syscall.S_IFLNK { 318 return fmt.Errorf("cannot create symbolic link %q: existing file in the way", filepath.Join(dirName, name)) 319 } 320 var n int 321 buf := make([]byte, len(oldname)+2) 322 n, err = sysReadlinkat(objFd, "", buf) 323 if err != nil { 324 return fmt.Errorf("cannot read symbolic link %q: %v", filepath.Join(dirName, name), err) 325 } 326 if string(buf[:n]) != oldname { 327 return fmt.Errorf("cannot create symbolic link %q: existing symbolic link in the way", filepath.Join(dirName, name)) 328 } 329 return nil 330 case syscall.EROFS: 331 // Treat EROFS specially: this is a hint that we have to poke a 332 // hole using tmpfs. The path below is the location where we 333 // need to poke the hole. 334 return &ReadOnlyFsError{Path: dirName} 335 default: 336 return fmt.Errorf("cannot create symlink %q: %v", filepath.Join(dirName, name), err) 337 } 338 } 339 340 return nil 341 } 342 343 // MkdirAll is the secure variant of os.MkdirAll. 344 // 345 // Unlike the regular version this implementation does not follow any symbolic 346 // links. At all times the new directory segment is created using mkdirat(2) 347 // while holding an open file descriptor to the parent directory. 348 // 349 // The only handled error is mkdirat(2) that fails with EEXIST. All other 350 // errors are fatal but there is no attempt to undo anything that was created. 351 // 352 // The uid and gid are used for the fchown(2) system call which is performed 353 // after each segment is created and opened. The special value -1 may be used 354 // to request that ownership is not changed. 355 func MkdirAll(path string, perm os.FileMode, uid sys.UserID, gid sys.GroupID, rs *Restrictions) error { 356 if path != filepath.Clean(path) { 357 // TODO: Reword the error and adjust the tests. 358 return fmt.Errorf("cannot split unclean path %q", path) 359 } 360 // Only support absolute paths to avoid bugs in snap-confine when 361 // called from anywhere. 362 if !filepath.IsAbs(path) { 363 return fmt.Errorf("cannot create directory with relative path: %q", path) 364 } 365 base, name := filepath.Split(path) 366 base = filepath.Clean(base) // Needed to chomp the trailing slash. 367 368 // Create the prefix. 369 dirFd, err := MkPrefix(base, perm, uid, gid, rs) 370 if err != nil { 371 return err 372 } 373 defer sysClose(dirFd) 374 375 if name != "" { 376 // Create the leaf as a directory. 377 leafFd, err := MkDir(dirFd, base, name, perm, uid, gid, rs) 378 if err != nil { 379 return err 380 } 381 defer sysClose(leafFd) 382 } 383 384 return nil 385 } 386 387 // MkfileAll is a secure implementation of "mkdir -p $(dirname $1) && touch $1". 388 // 389 // This function is like MkdirAll but it creates an empty file instead of 390 // a directory for the final path component. Each created directory component 391 // is chowned to the desired user and group. 392 func MkfileAll(path string, perm os.FileMode, uid sys.UserID, gid sys.GroupID, rs *Restrictions) error { 393 if path != filepath.Clean(path) { 394 // TODO: Reword the error and adjust the tests. 395 return fmt.Errorf("cannot split unclean path %q", path) 396 } 397 // Only support absolute paths to avoid bugs in snap-confine when 398 // called from anywhere. 399 if !filepath.IsAbs(path) { 400 return fmt.Errorf("cannot create file with relative path: %q", path) 401 } 402 // Only support file names, not directory names. 403 if strings.HasSuffix(path, "/") { 404 return fmt.Errorf("cannot create non-file path: %q", path) 405 } 406 base, name := filepath.Split(path) 407 base = filepath.Clean(base) // Needed to chomp the trailing slash. 408 409 // Create the prefix. 410 dirFd, err := MkPrefix(base, perm, uid, gid, rs) 411 if err != nil { 412 return err 413 } 414 defer sysClose(dirFd) 415 416 if name != "" { 417 // Create the leaf as a file. 418 err = MkFile(dirFd, base, name, perm, uid, gid, rs) 419 } 420 return err 421 } 422 423 // MksymlinkAll is a secure implementation of "ln -s". 424 func MksymlinkAll(path string, perm os.FileMode, uid sys.UserID, gid sys.GroupID, oldname string, rs *Restrictions) error { 425 if path != filepath.Clean(path) { 426 // TODO: Reword the error and adjust the tests. 427 return fmt.Errorf("cannot split unclean path %q", path) 428 } 429 // Only support absolute paths to avoid bugs in snap-confine when 430 // called from anywhere. 431 if !filepath.IsAbs(path) { 432 return fmt.Errorf("cannot create symlink with relative path: %q", path) 433 } 434 // Only support file names, not directory names. 435 if strings.HasSuffix(path, "/") { 436 return fmt.Errorf("cannot create non-file path: %q", path) 437 } 438 if oldname == "" { 439 return fmt.Errorf("cannot create symlink with empty target: %q", path) 440 } 441 442 base, name := filepath.Split(path) 443 base = filepath.Clean(base) // Needed to chomp the trailing slash. 444 445 // Create the prefix. 446 dirFd, err := MkPrefix(base, perm, uid, gid, rs) 447 if err != nil { 448 return err 449 } 450 defer sysClose(dirFd) 451 452 if name != "" { 453 // Create the leaf as a symlink. 454 err = MkSymlink(dirFd, base, name, oldname, rs) 455 } 456 return err 457 } 458 459 // planWritableMimic plans how to transform a given directory from read-only to writable. 460 // 461 // The algorithm is designed to be universally reversible so that it can be 462 // always de-constructed back to the original directory. The original directory 463 // is hidden by tmpfs and a subset of things that were present there originally 464 // is bind mounted back on top of empty directories or empty files. Symlinks 465 // are re-created directly. Devices and all other elements are not supported 466 // because they are forbidden in snaps for which this function is designed to 467 // be used with. Since the original directory is hidden the algorithm relies on 468 // a temporary directory where the original is bind-mounted during the 469 // progression of the algorithm. 470 func planWritableMimic(dir, neededBy string) ([]*Change, error) { 471 // We need a place for "safe keeping" of what is present in the original 472 // directory as we are about to attach a tmpfs there, which will hide 473 // everything inside. 474 logger.Debugf("create-writable-mimic %q", dir) 475 safeKeepingDir := filepath.Join("/tmp/.snap/", dir) 476 477 var changes []*Change 478 479 // Stat the original directory to know which mode and ownership to 480 // replicate on top of the tmpfs we are about to create below. 481 var sb syscall.Stat_t 482 if err := sysLstat(dir, &sb); err != nil { 483 return nil, err 484 } 485 486 // Bind mount the original directory elsewhere for safe-keeping. 487 changes = append(changes, &Change{ 488 Action: Mount, Entry: osutil.MountEntry{ 489 // NOTE: Here we recursively bind because we realized that not 490 // doing so doesn't work on core devices which use bind mounts 491 // extensively to construct writable spaces in /etc and /var and 492 // elsewhere. 493 // 494 // All directories present in the original are also recursively 495 // bind mounted back to their original location. To unmount this 496 // contraption we use MNT_DETACH which frees us from having to 497 // enumerate the mount table, unmount all the things (starting 498 // with most nested). 499 // 500 // The undo logic handles rbind mounts and adds x-snapd.unbind 501 // flag to them, which in turns translates to MNT_DETACH on 502 // umount2(2) system call. 503 Name: dir, Dir: safeKeepingDir, Options: []string{"rbind"}}, 504 }) 505 506 // Mount tmpfs over the original directory, hiding its contents. 507 // The mounted tmpfs will mimic the mode and ownership of the original 508 // directory. 509 changes = append(changes, &Change{ 510 Action: Mount, Entry: osutil.MountEntry{ 511 Name: "tmpfs", Dir: dir, Type: "tmpfs", 512 Options: []string{ 513 osutil.XSnapdSynthetic(), 514 osutil.XSnapdNeededBy(neededBy), 515 fmt.Sprintf("mode=%#o", sb.Mode&07777), 516 fmt.Sprintf("uid=%d", sb.Uid), 517 fmt.Sprintf("gid=%d", sb.Gid), 518 }, 519 }, 520 }) 521 // Iterate over the items in the original directory (nothing is mounted _yet_). 522 entries, err := ioutilReadDir(dir) 523 if err != nil { 524 return nil, err 525 } 526 for _, fi := range entries { 527 ch := &Change{Action: Mount, Entry: osutil.MountEntry{ 528 Name: filepath.Join(safeKeepingDir, fi.Name()), 529 Dir: filepath.Join(dir, fi.Name()), 530 }} 531 // Bind mount each element from the safe-keeping directory into the 532 // tmpfs. Our Change.Perform() engine can create the missing 533 // directories automatically so we don't bother creating those. 534 m := fi.Mode() 535 switch { 536 case m.IsDir(): 537 ch.Entry.Options = []string{"rbind"} 538 case m.IsRegular(): 539 ch.Entry.Options = []string{"bind", osutil.XSnapdKindFile()} 540 case m&os.ModeSymlink != 0: 541 if target, err := osReadlink(filepath.Join(dir, fi.Name())); err == nil { 542 ch.Entry.Options = []string{osutil.XSnapdKindSymlink(), osutil.XSnapdSymlink(target)} 543 } else { 544 continue 545 } 546 default: 547 logger.Noticef("skipping unsupported file %s", fi) 548 continue 549 } 550 ch.Entry.Options = append(ch.Entry.Options, osutil.XSnapdSynthetic()) 551 ch.Entry.Options = append(ch.Entry.Options, osutil.XSnapdNeededBy(neededBy)) 552 changes = append(changes, ch) 553 } 554 // Finally unbind the safe-keeping directory as we don't need it anymore. 555 changes = append(changes, &Change{ 556 Action: Unmount, Entry: osutil.MountEntry{Name: "none", Dir: safeKeepingDir, Options: []string{osutil.XSnapdDetach()}}, 557 }) 558 return changes, nil 559 } 560 561 // FatalError is an error that we cannot correct. 562 type FatalError struct { 563 error 564 } 565 566 // execWritableMimic executes the plan for a writable mimic. 567 // The result is a transformed mount namespace and a set of fake mount changes 568 // that only exist in order to undo the plan. 569 // 570 // Certain assumptions are made about the plan, it must closely resemble that 571 // created by planWritableMimic, in particular the sequence must look like this: 572 // 573 // - bind a directory aside into safekeeping location 574 // - cover the original with tmpfs 575 // - bind mount something from safekeeping location to an empty file or 576 // directory in the tmpfs; this step can repeat any number of times 577 // - unbind the safekeeping location 578 // 579 // Apart from merely executing the plan a fake plan is returned for undo. The 580 // undo plan skips the following elements as compared to the original plan: 581 // 582 // - the initial bind mount that constructs the safekeeping directory is gone 583 // - the final unmount that removes the safekeeping directory 584 // - the source of each of the bind mounts that re-populate tmpfs. 585 // 586 // In the event of a failure the undo plan is executed and an error is 587 // returned. If the undo plan fails the function returns a FatalError as it 588 // cannot fix the system from an inconsistent state. 589 func execWritableMimic(plan []*Change, as *Assumptions) ([]*Change, error) { 590 undoChanges := make([]*Change, 0, len(plan)-2) 591 for i, change := range plan { 592 if _, err := change.Perform(as); err != nil { 593 // Drat, we failed! Let's undo everything according to our own undo 594 // plan, by following it in reverse order. 595 596 recoveryUndoChanges := make([]*Change, 0, len(undoChanges)+1) 597 if i > 0 { 598 // The undo plan doesn't contain the entry for the initial bind 599 // mount of the safe keeping directory but we have already 600 // performed it. For this recovery phase we need to insert that 601 // in front of the undo plan manually. 602 recoveryUndoChanges = append(recoveryUndoChanges, plan[0]) 603 } 604 recoveryUndoChanges = append(recoveryUndoChanges, undoChanges...) 605 606 for j := len(recoveryUndoChanges) - 1; j >= 0; j-- { 607 recoveryUndoChange := recoveryUndoChanges[j] 608 // All the changes mount something, we need to reverse that. 609 // The "undo plan" is "a plan that can be undone" not "the plan 610 // for how to undo" so we need to flip the actions. 611 recoveryUndoChange.Action = Unmount 612 if recoveryUndoChange.Entry.OptBool("rbind") { 613 recoveryUndoChange.Entry.Options = append(recoveryUndoChange.Entry.Options, osutil.XSnapdDetach()) 614 } 615 if _, err2 := recoveryUndoChange.Perform(as); err2 != nil { 616 // Drat, we failed when trying to recover from an error. 617 // We cannot do anything at this stage. 618 return nil, &FatalError{error: fmt.Errorf("cannot undo change %q while recovering from earlier error %v: %v", recoveryUndoChange, err, err2)} 619 } 620 } 621 return nil, err 622 } 623 if i == 0 || i == len(plan)-1 { 624 // Don't represent the initial and final changes in the undo plan. 625 // The initial change is the safe-keeping bind mount, the final 626 // change is the safe-keeping unmount. 627 continue 628 } 629 if change.Entry.XSnapdKind() == "symlink" { 630 // Don't represent symlinks in the undo plan. They are removed when 631 // the tmpfs is unmounted. 632 continue 633 634 } 635 // Store an undo change for the change we just performed. 636 undoOpts := change.Entry.Options 637 if change.Entry.OptBool("rbind") { 638 undoOpts = make([]string, 0, len(change.Entry.Options)+1) 639 undoOpts = append(undoOpts, change.Entry.Options...) 640 undoOpts = append(undoOpts, "x-snapd.detach") 641 } 642 undoChange := &Change{ 643 Action: Mount, 644 Entry: osutil.MountEntry{Dir: change.Entry.Dir, Name: change.Entry.Name, Type: change.Entry.Type, Options: undoOpts}, 645 } 646 // Because of the use of a temporary bind mount (aka the safe-keeping 647 // directory) we cannot represent bind mounts fully (the temporary bind 648 // mount is unmounted as the last stage of this process). For that 649 // reason let's hide the original location and overwrite it so to 650 // appear as if the directory was a bind mount over itself. This is not 651 // fully true (it is a bind mount from the old self to the new empty 652 // directory or file in the same path, with the tmpfs in place already) 653 // but this is closer to the truth and more in line with the idea that 654 // this is just a plan for undoing the operation. 655 if undoChange.Entry.OptBool("bind") || undoChange.Entry.OptBool("rbind") { 656 undoChange.Entry.Name = undoChange.Entry.Dir 657 } 658 undoChanges = append(undoChanges, undoChange) 659 } 660 return undoChanges, nil 661 } 662 663 func createWritableMimic(dir, neededBy string, as *Assumptions) ([]*Change, error) { 664 plan, err := planWritableMimic(dir, neededBy) 665 if err != nil { 666 return nil, err 667 } 668 changes, err := execWritableMimic(plan, as) 669 if err != nil { 670 return nil, err 671 } 672 return changes, nil 673 }