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