github.com/anonymouse64/snapd@v0.0.0-20210824153203-04c4c42d842d/gadget/mountedfilesystem.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2019-2020 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 gadget 21 22 import ( 23 "bytes" 24 "crypto" 25 _ "crypto/sha1" 26 "fmt" 27 "io" 28 "io/ioutil" 29 "os" 30 "path/filepath" 31 "sort" 32 "strings" 33 34 "github.com/snapcore/snapd/logger" 35 "github.com/snapcore/snapd/osutil" 36 "github.com/snapcore/snapd/strutil" 37 ) 38 39 func checkSourceIsDir(src string) error { 40 if !osutil.IsDirectory(src) { 41 if strings.HasSuffix(src, "/") { 42 return fmt.Errorf("cannot specify trailing / for a source which is not a directory") 43 } 44 return fmt.Errorf("source is not a directory") 45 } 46 return nil 47 } 48 49 func checkContent(content *ResolvedContent) error { 50 if content.ResolvedSource == "" { 51 return fmt.Errorf("internal error: source cannot be unset") 52 } 53 if content.Target == "" { 54 return fmt.Errorf("internal error: target cannot be unset") 55 } 56 return nil 57 } 58 59 func observe(observer ContentObserver, op ContentOperation, ps *LaidOutStructure, root, dst string, data *ContentChange) (ContentChangeAction, error) { 60 if observer == nil { 61 return ChangeApply, nil 62 } 63 relativeTarget := dst 64 if strings.HasPrefix(dst, root) { 65 // target path isn't really relative, make it so now 66 relative, err := filepath.Rel(root, dst) 67 if err != nil { 68 return ChangeAbort, err 69 } 70 relativeTarget = relative 71 } 72 return observer.Observe(op, ps, root, relativeTarget, data) 73 } 74 75 // TODO: MountedFilesystemWriter should not be exported 76 77 // MountedFilesystemWriter assists in writing contents of a structure to a 78 // mounted filesystem. 79 type MountedFilesystemWriter struct { 80 ps *LaidOutStructure 81 observer ContentObserver 82 } 83 84 // NewMountedFilesystemWriter returns a writer capable of writing provided 85 // structure, with content of the structure stored in the given root directory. 86 func NewMountedFilesystemWriter(ps *LaidOutStructure, observer ContentObserver) (*MountedFilesystemWriter, error) { 87 if ps == nil { 88 return nil, fmt.Errorf("internal error: *LaidOutStructure is nil") 89 } 90 if !ps.HasFilesystem() { 91 return nil, fmt.Errorf("structure %v has no filesystem", ps) 92 } 93 fw := &MountedFilesystemWriter{ 94 ps: ps, 95 observer: observer, 96 } 97 return fw, nil 98 } 99 100 func mapPreserve(dstDir string, preserve []string) ([]string, error) { 101 preserveInDst := make([]string, len(preserve)) 102 for i, p := range preserve { 103 inDst := filepath.Join(dstDir, p) 104 105 if osutil.IsDirectory(inDst) { 106 return nil, fmt.Errorf("preserved entry %q cannot be a directory", p) 107 } 108 109 preserveInDst[i] = inDst 110 } 111 sort.Strings(preserveInDst) 112 113 return preserveInDst, nil 114 } 115 116 // Write writes structure data into provided directory. All existing files are 117 // overwritten, unless their paths, relative to target directory, are listed in 118 // the preserve list. Permission bits and ownership of updated entries is not 119 // preserved. 120 func (m *MountedFilesystemWriter) Write(whereDir string, preserve []string) error { 121 if whereDir == "" { 122 return fmt.Errorf("internal error: destination directory cannot be unset") 123 } 124 125 // TODO:UC20: preserve managed boot assets 126 preserveInDst, err := mapPreserve(whereDir, preserve) 127 if err != nil { 128 return fmt.Errorf("cannot map preserve entries for destination %q: %v", whereDir, err) 129 } 130 131 for _, c := range m.ps.ResolvedContent { 132 if err := m.writeVolumeContent(whereDir, &c, preserveInDst); err != nil { 133 return fmt.Errorf("cannot write filesystem content of %s: %v", c, err) 134 } 135 } 136 return nil 137 } 138 139 // writeDirectory copies the source directory, or its contents under target 140 // location dst. Follows rsync like semantics, that is: 141 // /foo/ -> /bar - writes contents of foo under /bar 142 // /foo -> /bar - writes foo and its subtree under /bar 143 func (m *MountedFilesystemWriter) writeDirectory(volumeRoot, src, dst string, preserveInDst []string) error { 144 hasDirSourceSlash := strings.HasSuffix(src, "/") 145 146 if err := checkSourceIsDir(src); err != nil { 147 return err 148 } 149 150 if !hasDirSourceSlash { 151 // /foo -> /bar (write foo and subtree) 152 dst = filepath.Join(dst, filepath.Base(src)) 153 } 154 155 fis, err := ioutil.ReadDir(src) 156 if err != nil { 157 return fmt.Errorf("cannot list directory entries: %v", err) 158 } 159 160 for _, fi := range fis { 161 pSrc := filepath.Join(src, fi.Name()) 162 pDst := filepath.Join(dst, fi.Name()) 163 164 write := m.observedWriteFileOrSymlink 165 if fi.IsDir() { 166 if err := os.MkdirAll(pDst, 0755); err != nil { 167 return fmt.Errorf("cannot create directory prefix: %v", err) 168 } 169 170 write = m.writeDirectory 171 pSrc += "/" 172 } 173 if err := write(volumeRoot, pSrc, pDst, preserveInDst); err != nil { 174 return err 175 } 176 } 177 178 return nil 179 } 180 181 func (m *MountedFilesystemWriter) observedWriteFileOrSymlink(volumeRoot, src, dst string, preserveInDst []string) error { 182 if strings.HasSuffix(dst, "/") { 183 // write to directory 184 dst = filepath.Join(dst, filepath.Base(src)) 185 } 186 187 data := &ContentChange{ 188 // we are writing a new thing 189 Before: "", 190 // with content in this file 191 After: src, 192 } 193 act, err := observe(m.observer, ContentWrite, m.ps, volumeRoot, dst, data) 194 if err != nil { 195 return fmt.Errorf("cannot observe file write: %v", err) 196 } 197 if act == ChangeIgnore { 198 return nil 199 } 200 return writeFileOrSymlink(src, dst, preserveInDst) 201 } 202 203 // writeFileOrSymlink writes the source file or a symlink at given location or 204 // under given directory. Follows rsync like semantics, that is: 205 // /foo -> /bar/ - writes foo as /bar/foo 206 // /foo -> /bar - writes foo as /bar 207 // The destination location is overwritten. 208 func writeFileOrSymlink(src, dst string, preserveInDst []string) error { 209 if strings.HasSuffix(dst, "/") { 210 // write to directory 211 dst = filepath.Join(dst, filepath.Base(src)) 212 } 213 214 if osutil.FileExists(dst) && strutil.SortedListContains(preserveInDst, dst) { 215 // entry shall be preserved 216 return nil 217 } 218 219 if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil { 220 return fmt.Errorf("cannot create prefix directory: %v", err) 221 } 222 223 if osutil.IsSymlink(src) { 224 // recreate the symlinks as they are 225 to, err := os.Readlink(src) 226 if err != nil { 227 return fmt.Errorf("cannot read symlink: %v", err) 228 } 229 if err := os.Symlink(to, dst); err != nil { 230 return fmt.Errorf("cannot write a symlink: %v", err) 231 } 232 } else { 233 // TODO try to preserve ownership and permission bits 234 235 // do not follow sylimks, dst is a reflection of the src which 236 // is a file 237 if err := osutil.AtomicWriteFileCopy(dst, src, 0); err != nil { 238 return fmt.Errorf("cannot copy %s: %v", src, err) 239 } 240 } 241 return nil 242 } 243 244 func (m *MountedFilesystemWriter) writeVolumeContent(volumeRoot string, content *ResolvedContent, preserveInDst []string) error { 245 if err := checkContent(content); err != nil { 246 return err 247 } 248 realTarget := filepath.Join(volumeRoot, content.Target) 249 250 // filepath trims the trailing /, restore if needed 251 if strings.HasSuffix(content.Target, "/") { 252 realTarget += "/" 253 } 254 255 if osutil.IsDirectory(content.ResolvedSource) || strings.HasSuffix(content.ResolvedSource, "/") { 256 // write a directory 257 return m.writeDirectory(volumeRoot, content.ResolvedSource, realTarget, preserveInDst) 258 } else { 259 // write a file 260 return m.observedWriteFileOrSymlink(volumeRoot, content.ResolvedSource, realTarget, preserveInDst) 261 } 262 } 263 264 func newStampFile(stamp string) (*osutil.AtomicFile, error) { 265 if err := os.MkdirAll(filepath.Dir(stamp), 0755); err != nil { 266 return nil, fmt.Errorf("cannot create stamp file prefix: %v", err) 267 } 268 return osutil.NewAtomicFile(stamp, 0644, 0, osutil.NoChown, osutil.NoChown) 269 } 270 271 func makeStamp(stamp string) error { 272 f, err := newStampFile(stamp) 273 if err != nil { 274 return err 275 } 276 return f.Commit() 277 } 278 279 type mountLookupFunc func(ps *LaidOutStructure) (string, error) 280 281 // mountedFilesystemUpdater assists in applying updates to a mounted filesystem. 282 // 283 // The update process is composed of 2 main passes, and an optional rollback: 284 // 285 // 1) backup, where update data and current data is analyzed to identify 286 // identical content, stamp files are created for entries that are to be 287 // preserved, modified or otherwise touched by the update, that is, for existing 288 // files that would be created/overwritten, ones that are explicitly listed as 289 // preserved, or directories to be written to 290 // 291 // 2) update, where update data is written to the target location 292 // 293 // 3) rollback (optional), where update data is rolled back and replaced with 294 // backup copies of files, newly created directories are removed 295 type mountedFilesystemUpdater struct { 296 *MountedFilesystemWriter 297 backupDir string 298 mountPoint string 299 updateObserver ContentObserver 300 } 301 302 // newMountedFilesystemUpdater returns an updater for given filesystem 303 // structure, with structure content coming from provided root directory. The 304 // mount is located by calling a mount lookup helper. The backup directory 305 // contains backup state information for use during rollback. 306 func newMountedFilesystemUpdater(ps *LaidOutStructure, backupDir string, mountLookup mountLookupFunc, observer ContentObserver) (*mountedFilesystemUpdater, error) { 307 // avoid passing observer, writes will not be observed 308 fw, err := NewMountedFilesystemWriter(ps, nil) 309 if err != nil { 310 return nil, err 311 } 312 if mountLookup == nil { 313 return nil, fmt.Errorf("internal error: mount lookup helper must be provided") 314 } 315 if backupDir == "" { 316 return nil, fmt.Errorf("internal error: backup directory must not be unset") 317 } 318 mount, err := mountLookup(ps) 319 if err != nil { 320 return nil, fmt.Errorf("cannot find mount location of structure %v: %v", ps, err) 321 } 322 323 fu := &mountedFilesystemUpdater{ 324 MountedFilesystemWriter: fw, 325 backupDir: backupDir, 326 mountPoint: mount, 327 updateObserver: observer, 328 } 329 return fu, nil 330 } 331 332 func fsStructBackupPath(backupDir string, ps *LaidOutStructure) string { 333 return filepath.Join(backupDir, fmt.Sprintf("struct-%v", ps.Index)) 334 } 335 336 // entryDestPaths resolves destination and backup paths for given 337 // source/target combination. Backup location is within provided 338 // backup directory or empty if directory was not provided. 339 func (f *mountedFilesystemUpdater) entryDestPaths(dstRoot, source, target, backupDir string) (dstPath, backupPath string) { 340 dstBasePath := target 341 if strings.HasSuffix(target, "/") { 342 // write to a directory 343 dstBasePath = filepath.Join(dstBasePath, filepath.Base(source)) 344 } 345 dstPath = filepath.Join(dstRoot, dstBasePath) 346 347 if backupDir != "" { 348 backupPath = filepath.Join(backupDir, dstBasePath) 349 } 350 351 return dstPath, backupPath 352 } 353 354 // Update applies an update to a mounted filesystem. The caller must have 355 // executed a Backup() before, to prepare a data set for rollback purpose. 356 func (f *mountedFilesystemUpdater) Update() error { 357 preserveInDst, err := mapPreserve(f.mountPoint, f.ps.Update.Preserve) 358 if err != nil { 359 return fmt.Errorf("cannot map preserve entries for mount location %q: %v", f.mountPoint, err) 360 } 361 362 backupRoot := fsStructBackupPath(f.backupDir, f.ps) 363 364 skipped := 0 365 for _, c := range f.ps.ResolvedContent { 366 if err := f.updateVolumeContent(f.mountPoint, &c, preserveInDst, backupRoot); err != nil { 367 if err == ErrNoUpdate { 368 skipped++ 369 continue 370 } 371 return fmt.Errorf("cannot update content: %v", err) 372 } 373 } 374 375 if skipped == len(f.ps.ResolvedContent) { 376 return ErrNoUpdate 377 } 378 379 return nil 380 } 381 382 func (f *mountedFilesystemUpdater) sourceDirectoryEntries(srcPath string) ([]os.FileInfo, error) { 383 if err := checkSourceIsDir(srcPath); err != nil { 384 return nil, err 385 } 386 387 // TODO: enable support for symlinks when needed 388 if osutil.IsSymlink(srcPath) { 389 return nil, fmt.Errorf("source is a symbolic link") 390 } 391 392 return ioutil.ReadDir(srcPath) 393 } 394 395 // targetInSourceDir resolves the actual target for given source directory name 396 // and target specification. 397 // source: /foo/bar/ target: /baz => /bar/ (contents of /foo/bar/ under /baz) 398 // source: /foo/bar target: /baz => /bar/bar (directory /foo/bar under /baz, contents under /baz/bar) 399 func targetForSourceDir(source, target string) string { 400 if strings.HasSuffix(source, "/") { 401 // contents of source directory land under target 402 return target 403 } 404 // source directory lands under target 405 return filepath.Join(target, filepath.Base(source)) 406 } 407 408 func (f *mountedFilesystemUpdater) updateDirectory(dstRoot, source, target string, preserveInDst []string, backupDir string) error { 409 fis, err := f.sourceDirectoryEntries(source) 410 if err != nil { 411 return fmt.Errorf("cannot list source directory %q: %v", source, err) 412 } 413 414 target = targetForSourceDir(source, target) 415 416 // create current target directory if needed 417 if err := os.MkdirAll(filepath.Join(dstRoot, target), 0755); err != nil { 418 return fmt.Errorf("cannot write directory: %v", err) 419 } 420 // and write the content of source to target 421 skipped := 0 422 for _, fi := range fis { 423 pSrc := filepath.Join(source, fi.Name()) 424 pDst := filepath.Join(target, fi.Name()) 425 426 update := f.updateOrSkipFile 427 if fi.IsDir() { 428 // continue updating contents of the directory rather 429 // than the directory itself 430 pSrc += "/" 431 pDst += "/" 432 update = f.updateDirectory 433 } 434 if err := update(dstRoot, pSrc, pDst, preserveInDst, backupDir); err != nil { 435 if err == ErrNoUpdate { 436 skipped++ 437 continue 438 } 439 return err 440 } 441 } 442 443 if skipped == len(fis) { 444 return ErrNoUpdate 445 } 446 447 return nil 448 } 449 450 func (f *mountedFilesystemUpdater) updateOrSkipFile(dstRoot, source, target string, preserveInDst []string, backupDir string) error { 451 dstPath, backupPath := f.entryDestPaths(dstRoot, source, target, backupDir) 452 backupName := backupPath + ".backup" 453 sameStamp := backupPath + ".same" 454 preserveStamp := backupPath + ".preserve" 455 ignoreStamp := backupPath + ".ignore" 456 457 // TODO: enable support for symlinks when needed 458 if osutil.IsSymlink(source) { 459 return fmt.Errorf("cannot update file %s: symbolic links are not supported", source) 460 } 461 462 if osutil.FileExists(ignoreStamp) { 463 // explicitly ignored by request of the observer 464 return ErrNoUpdate 465 } 466 467 if osutil.FileExists(dstPath) { 468 if strutil.SortedListContains(preserveInDst, dstPath) || osutil.FileExists(preserveStamp) { 469 // file is to be preserved 470 return ErrNoUpdate 471 } 472 if osutil.FileExists(sameStamp) { 473 // file is the same as current copy 474 return ErrNoUpdate 475 } 476 if !osutil.FileExists(backupName) { 477 // not preserved & different than the update, error out 478 // as there is no backup 479 return fmt.Errorf("missing backup file %q for %v", backupName, target) 480 } 481 } 482 483 return writeFileOrSymlink(source, dstPath, preserveInDst) 484 } 485 486 func (f *mountedFilesystemUpdater) updateVolumeContent(volumeRoot string, content *ResolvedContent, preserveInDst []string, backupDir string) error { 487 if err := checkContent(content); err != nil { 488 return err 489 } 490 491 if osutil.IsDirectory(content.ResolvedSource) || strings.HasSuffix(content.ResolvedSource, "/") { 492 // TODO: pass both Unresolved and resolved Source (unresolved for better error reporting) 493 return f.updateDirectory(volumeRoot, content.ResolvedSource, content.Target, preserveInDst, backupDir) 494 } else { 495 // TODO: pass both Unresolved and resolved Source (unresolved for better error reporting) 496 return f.updateOrSkipFile(volumeRoot, content.ResolvedSource, content.Target, preserveInDst, backupDir) 497 } 498 } 499 500 // Backup analyzes a mounted filesystem and prepares a rollback state should the 501 // update be applied. The content of the filesystem is processed, files and 502 // directories that would be modified by the update are backed up, while 503 // identical/preserved files may be stamped to improve the later step of update 504 // process. 505 // 506 // The backup directory structure mirrors the structure of destination 507 // location. Given the following destination structure: 508 // 509 // foo 510 // ├── a 511 // ├── b 512 // ├── bar 513 // │ ├── baz 514 // │ │ └── d 515 // │ └── z 516 // ├── c 517 // └── d 518 // 519 // The structure of backup looks like this: 520 // 521 // foo-backup 522 // ├── a.backup <-- backup copy of ./a 523 // ├── bar 524 // │ ├── baz 525 // │ │ └── d.backup <-- backup copy of ./bar/baz/d 526 // │ └── baz.backup <-- stamp indicating ./bar/baz existed before the update 527 // ├── bar.backup <-- stamp indicating ./bar existed before the update 528 // ├── b.same <-- stamp indicating ./b is identical to the update data 529 // ├── c.ignore <-- stamp indicating change to ./c was requested to be ignored 530 // └── d.preserve <-- stamp indicating ./d is to be preserved 531 // 532 func (f *mountedFilesystemUpdater) Backup() error { 533 backupRoot := fsStructBackupPath(f.backupDir, f.ps) 534 535 if err := os.MkdirAll(backupRoot, 0755); err != nil { 536 return fmt.Errorf("cannot create backup directory: %v", err) 537 } 538 539 preserveInDst, err := mapPreserve(f.mountPoint, f.ps.Update.Preserve) 540 if err != nil { 541 return fmt.Errorf("cannot map preserve entries for mount location %q: %v", f.mountPoint, err) 542 } 543 544 for _, c := range f.ps.ResolvedContent { 545 if err := f.backupVolumeContent(f.mountPoint, &c, preserveInDst, backupRoot); err != nil { 546 return fmt.Errorf("cannot backup content: %v", err) 547 } 548 } 549 550 return nil 551 } 552 553 func (f *mountedFilesystemUpdater) backupOrCheckpointDirectory(dstRoot, source, target string, preserveInDst []string, backupDir string) error { 554 fis, err := f.sourceDirectoryEntries(source) 555 if err != nil { 556 return fmt.Errorf("cannot backup directory %q: %v", source, err) 557 } 558 559 target = targetForSourceDir(source, target) 560 561 for _, fi := range fis { 562 pSrc := filepath.Join(source, fi.Name()) 563 pDst := filepath.Join(target, fi.Name()) 564 565 backup := f.observedBackupOrCheckpointFile 566 if fi.IsDir() { 567 // continue backing up the contents of the directory 568 // rather than the directory itself 569 pSrc += "/" 570 pDst += "/" 571 backup = f.backupOrCheckpointDirectory 572 } 573 if err := f.checkpointPrefix(dstRoot, pDst, backupDir); err != nil { 574 return err 575 } 576 if err := backup(dstRoot, pSrc, pDst, preserveInDst, backupDir); err != nil { 577 return err 578 } 579 } 580 581 return nil 582 } 583 584 // checkpointPrefix creates stamps for each part of the destination prefix that exists 585 func (f *mountedFilesystemUpdater) checkpointPrefix(dstRoot, target string, backupDir string) error { 586 // check how much of the prefix needs to be created 587 for prefix := filepath.Dir(target); prefix != "." && prefix != "/"; prefix = filepath.Dir(prefix) { 588 prefixDst, prefixBackupBase := f.entryDestPaths(dstRoot, "", prefix, backupDir) 589 590 // TODO: enable support for symlinks when needed 591 if osutil.IsSymlink(prefixDst) { 592 return fmt.Errorf("cannot create a checkpoint for directory %v: symbolic links are not supported", prefix) 593 } 594 595 prefixBackupName := prefixBackupBase + ".backup" 596 if osutil.FileExists(prefixBackupName) { 597 continue 598 } 599 if !osutil.IsDirectory(prefixDst) { 600 // does not exist now, will be created on the fly and 601 // removed during rollback 602 continue 603 } 604 if err := os.MkdirAll(filepath.Dir(prefixBackupName), 0755); err != nil { 605 return fmt.Errorf("cannot create backup prefix: %v", err) 606 } 607 if err := makeStamp(prefixBackupName); err != nil { 608 return fmt.Errorf("cannot create a checkpoint for directory: %v", err) 609 } 610 } 611 return nil 612 } 613 614 func (f *mountedFilesystemUpdater) ignoreChange(change *ContentChange, backupPath string) error { 615 preserveStamp := backupPath + ".preserve" 616 backupName := backupPath + ".backup" 617 sameStamp := backupPath + ".same" 618 ignoreStamp := backupPath + ".ignore" 619 if err := makeStamp(ignoreStamp); err != nil { 620 return fmt.Errorf("cannot create a checkpoint file: %v", err) 621 } 622 for _, name := range []string{backupName, sameStamp, preserveStamp} { 623 if err := os.Remove(name); err != nil && !os.IsNotExist(err) { 624 return fmt.Errorf("cannot remove existing stamp file: %v", err) 625 } 626 } 627 return nil 628 } 629 630 func (f *mountedFilesystemUpdater) observedBackupOrCheckpointFile(dstRoot, source, target string, preserveInDst []string, backupDir string) error { 631 change, err := f.backupOrCheckpointFile(dstRoot, source, target, preserveInDst, backupDir) 632 if err != nil { 633 return err 634 } 635 if change != nil { 636 dstPath, backupPath := f.entryDestPaths(dstRoot, source, target, backupDir) 637 act, err := observe(f.updateObserver, ContentUpdate, f.ps, f.mountPoint, dstPath, change) 638 if err != nil { 639 return fmt.Errorf("cannot observe pending file write %v\n", err) 640 } 641 if act == ChangeIgnore { 642 // observer asked for the change to be ignored 643 if err := f.ignoreChange(change, backupPath); err != nil { 644 return fmt.Errorf("cannot ignore content change: %v", err) 645 } 646 } 647 } 648 return nil 649 } 650 651 // backupOrCheckpointFile analyzes a given source file from the gadget and a 652 // target location under the provided destination root directory. When both 653 // files are identical, creates a stamp that allows the update to skip the file. 654 // When content of the new file is different, a backup of the original file is 655 // created. Returns a content change if a file will be written by the update 656 // pass. 657 func (f *mountedFilesystemUpdater) backupOrCheckpointFile(dstRoot, source, target string, preserveInDst []string, backupDir string) (change *ContentChange, err error) { 658 dstPath, backupPath := f.entryDestPaths(dstRoot, source, target, backupDir) 659 660 backupName := backupPath + ".backup" 661 sameStamp := backupPath + ".same" 662 preserveStamp := backupPath + ".preserve" 663 ignoreStamp := backupPath + ".ignore" 664 665 changeWithBackup := &ContentChange{ 666 // content of the new data 667 After: source, 668 // this is the original data that was present before the 669 // update 670 Before: backupName, 671 } 672 changeNewFile := &ContentChange{ 673 // content of the new data 674 After: source, 675 } 676 677 if osutil.FileExists(ignoreStamp) { 678 // observer already requested the change to the target location 679 // to be ignored 680 return nil, nil 681 } 682 683 // TODO: enable support for symlinks when needed 684 if osutil.IsSymlink(dstPath) { 685 return nil, fmt.Errorf("cannot backup file %s: symbolic links are not supported", target) 686 } 687 688 if !osutil.FileExists(dstPath) { 689 // destination does not exist and will be created when writing 690 // the udpate, no need for backup 691 return changeNewFile, nil 692 } 693 // destination file exists beyond this point 694 695 if osutil.FileExists(backupName) { 696 // file already checked and backed up 697 return changeWithBackup, nil 698 } 699 if osutil.FileExists(sameStamp) { 700 // file already checked, same as the update, move on 701 return nil, nil 702 } 703 // TODO: correctly identify new files that were written by a partially 704 // executed update pass 705 706 if strutil.SortedListContains(preserveInDst, dstPath) { 707 if osutil.FileExists(preserveStamp) { 708 // already stamped 709 return nil, nil 710 } 711 // make a stamp 712 if err := makeStamp(preserveStamp); err != nil { 713 return nil, fmt.Errorf("cannot create preserve stamp: %v", err) 714 } 715 return nil, nil 716 } 717 718 // try to find out whether the update and the existing file are 719 // identical 720 721 orig, err := os.Open(dstPath) 722 if err != nil { 723 return nil, fmt.Errorf("cannot open destination file: %v", err) 724 } 725 726 // backup of the original content 727 backup, err := newStampFile(backupName) 728 if err != nil { 729 return nil, fmt.Errorf("cannot create backup file: %v", err) 730 } 731 // becomes a backup copy or a noop if canceled 732 defer backup.Commit() 733 734 // checksum the original data while it's being copied 735 origHash := crypto.SHA1.New() 736 htr := io.TeeReader(orig, origHash) 737 738 _, err = io.Copy(backup, htr) 739 if err != nil { 740 backup.Cancel() 741 return nil, fmt.Errorf("cannot backup original file: %v", err) 742 } 743 744 // digest of the update 745 updateDigest, _, err := osutil.FileDigest(source, crypto.SHA1) 746 if err != nil { 747 backup.Cancel() 748 return nil, fmt.Errorf("cannot checksum update file: %v", err) 749 } 750 // digest of the currently present data 751 origDigest := origHash.Sum(nil) 752 753 // TODO: look into comparing the streams directly 754 if bytes.Equal(origDigest, updateDigest) { 755 // mark that files are identical and update can be skipped, no 756 // backup is needed 757 if err := makeStamp(sameStamp); err != nil { 758 return nil, fmt.Errorf("cannot create a checkpoint file: %v", err) 759 } 760 761 // makes the deferred commit a noop 762 backup.Cancel() 763 return nil, nil 764 } 765 766 // update will overwrite existing file, a backup copy is created on 767 // Commit() 768 return changeWithBackup, nil 769 } 770 771 func (f *mountedFilesystemUpdater) backupVolumeContent(volumeRoot string, content *ResolvedContent, preserveInDst []string, backupDir string) error { 772 if err := checkContent(content); err != nil { 773 return err 774 } 775 776 if err := f.checkpointPrefix(volumeRoot, content.Target, backupDir); err != nil { 777 return err 778 } 779 if osutil.IsDirectory(content.ResolvedSource) || strings.HasSuffix(content.ResolvedSource, "/") { 780 // backup directory contents 781 // TODO: pass both Unresolved and resolved Source (unresolved for better error reporting) 782 return f.backupOrCheckpointDirectory(volumeRoot, content.ResolvedSource, content.Target, preserveInDst, backupDir) 783 } else { 784 // backup a file 785 return f.observedBackupOrCheckpointFile(volumeRoot, content.ResolvedSource, content.Target, preserveInDst, backupDir) 786 } 787 } 788 789 // Rollback attempts to revert changes done by the update step, using state 790 // information collected during backup phase. Files that were modified by the 791 // update are stored from their backup copies, newly added directories are 792 // removed. 793 func (f *mountedFilesystemUpdater) Rollback() error { 794 backupRoot := fsStructBackupPath(f.backupDir, f.ps) 795 796 preserveInDst, err := mapPreserve(f.mountPoint, f.ps.Update.Preserve) 797 if err != nil { 798 return fmt.Errorf("cannot map preserve entries for mount location %q: %v", f.mountPoint, err) 799 } 800 801 for _, c := range f.ps.ResolvedContent { 802 if err := f.rollbackVolumeContent(f.mountPoint, &c, preserveInDst, backupRoot); err != nil { 803 return fmt.Errorf("cannot rollback content: %v", err) 804 } 805 } 806 return nil 807 } 808 809 func (f *mountedFilesystemUpdater) rollbackPrefix(dstRoot, target string, backupDir string) error { 810 for prefix := filepath.Dir(target); prefix != "/" && prefix != "."; prefix = filepath.Dir(prefix) { 811 prefixDstPath, prefixBackupPath := f.entryDestPaths(dstRoot, "", prefix, backupDir) 812 if !osutil.FileExists(prefixBackupPath + ".backup") { 813 // try remove 814 if err := os.Remove(prefixDstPath); err != nil { 815 logger.Noticef("cannot remove gadget directory %q: %v", prefix, err) 816 } 817 } 818 } 819 return nil 820 } 821 822 func (f *mountedFilesystemUpdater) rollbackDirectory(dstRoot, source, target string, preserveInDst []string, backupDir string) error { 823 fis, err := f.sourceDirectoryEntries(source) 824 if err != nil { 825 return fmt.Errorf("cannot rollback directory %q: %v", source, err) 826 } 827 828 target = targetForSourceDir(source, target) 829 830 for _, fi := range fis { 831 pSrc := filepath.Join(source, fi.Name()) 832 pDst := filepath.Join(target, fi.Name()) 833 834 rollback := f.rollbackFile 835 if fi.IsDir() { 836 // continue rolling back the contents of the directory 837 // rather than the directory itself 838 rollback = f.rollbackDirectory 839 pSrc += "/" 840 pDst += "/" 841 } 842 if err := rollback(dstRoot, pSrc, pDst, preserveInDst, backupDir); err != nil { 843 return err 844 } 845 if err := f.rollbackPrefix(dstRoot, pDst, backupDir); err != nil { 846 return err 847 } 848 } 849 850 return nil 851 } 852 853 func (f *mountedFilesystemUpdater) rollbackFile(dstRoot, source, target string, preserveInDst []string, backupDir string) error { 854 dstPath, backupPath := f.entryDestPaths(dstRoot, source, target, backupDir) 855 856 backupName := backupPath + ".backup" 857 sameStamp := backupPath + ".same" 858 preserveStamp := backupPath + ".preserve" 859 ignoreStamp := backupPath + ".ignore" 860 861 if strutil.SortedListContains(preserveInDst, dstPath) && osutil.FileExists(preserveStamp) { 862 // file was preserved at original location by being 863 // explicitly listed 864 return nil 865 } 866 if osutil.FileExists(sameStamp) { 867 // contents are the same as original, do nothing 868 return nil 869 } 870 if osutil.FileExists(ignoreStamp) { 871 // observer requested the changes to the target to be ignored 872 // previously 873 return nil 874 } 875 876 data := &ContentChange{ 877 After: source, 878 // original content was in the backup file 879 Before: backupName, 880 } 881 882 if osutil.FileExists(backupName) { 883 // restore backup -> destination 884 if err := writeFileOrSymlink(backupName, dstPath, nil); err != nil { 885 return err 886 } 887 } else { 888 if err := os.Remove(dstPath); err != nil && !os.IsNotExist(err) { 889 return fmt.Errorf("cannot remove written update: %v", err) 890 } 891 // since it's a new file, there was no original content 892 data.Before = "" 893 } 894 // avoid passing source path during rollback, the file has been restored 895 // to the disk already 896 _, err := observe(f.updateObserver, ContentRollback, f.ps, f.mountPoint, dstPath, data) 897 if err != nil { 898 return fmt.Errorf("cannot observe pending file rollback %v\n", err) 899 } 900 901 return nil 902 } 903 904 func (f *mountedFilesystemUpdater) rollbackVolumeContent(volumeRoot string, content *ResolvedContent, preserveInDst []string, backupDir string) error { 905 if err := checkContent(content); err != nil { 906 return err 907 } 908 909 var err error 910 if osutil.IsDirectory(content.ResolvedSource) || strings.HasSuffix(content.ResolvedSource, "/") { 911 // rollback directory 912 err = f.rollbackDirectory(volumeRoot, content.ResolvedSource, content.Target, preserveInDst, backupDir) 913 } else { 914 // rollback file 915 err = f.rollbackFile(volumeRoot, content.ResolvedSource, content.Target, preserveInDst, backupDir) 916 } 917 if err != nil { 918 return err 919 } 920 921 return f.rollbackPrefix(volumeRoot, content.Target, backupDir) 922 }