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