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