github.com/hanwen/go-fuse@v1.0.0/unionfs/unionfs.go (about) 1 // Copyright 2016 the Go-FUSE Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package unionfs 6 7 import ( 8 "crypto/md5" 9 "fmt" 10 "log" 11 "os" 12 "path" 13 "path/filepath" 14 "strings" 15 "sync" 16 "syscall" 17 "time" 18 19 "github.com/hanwen/go-fuse/fuse" 20 "github.com/hanwen/go-fuse/fuse/nodefs" 21 "github.com/hanwen/go-fuse/fuse/pathfs" 22 ) 23 24 func filePathHash(path string) string { 25 dir, base := filepath.Split(path) 26 27 h := md5.New() 28 h.Write([]byte(dir)) 29 return fmt.Sprintf("%x-%s", h.Sum(nil)[:8], base) 30 } 31 32 /* 33 34 UnionFs implements a user-space union file system, which is 35 stateless but efficient even if the writable branch is on NFS. 36 37 38 Assumptions: 39 40 * It uses a list of branches, the first of which (index 0) is thought 41 to be writable, and the rest read-only. 42 43 * It assumes that the number of deleted files is small relative to 44 the total tree size. 45 46 47 Implementation notes. 48 49 * It overlays arbitrary writable FileSystems with any number of 50 readonly FileSystems. 51 52 * Deleting a file will put a file named 53 /DELETIONS/HASH-OF-FULL-FILENAME into the writable overlay, 54 containing the full filename itself. 55 56 This is optimized for NFS usage: we want to minimize the number of 57 NFS operations, which are slow. By putting all whiteouts in one 58 place, we can cheaply fetch the list of all deleted files. Even 59 without caching on our side, the kernel's negative dentry cache can 60 answer is-deleted queries quickly. 61 62 */ 63 type unionFS struct { 64 pathfs.FileSystem 65 66 // The same, but as interfaces. 67 fileSystems []pathfs.FileSystem 68 69 // A file-existence cache. 70 deletionCache *dirCache 71 72 // A file -> branch cache. 73 branchCache *TimedCache 74 75 // Map of files to hide. 76 hiddenFiles map[string]bool 77 78 options *UnionFsOptions 79 nodeFs *pathfs.PathNodeFs 80 } 81 82 type UnionFsOptions struct { 83 BranchCacheTTL time.Duration 84 DeletionCacheTTL time.Duration 85 DeletionDirName string 86 HiddenFiles []string 87 } 88 89 const ( 90 _DROP_CACHE = ".drop_cache" 91 ) 92 93 func NewUnionFs(fileSystems []pathfs.FileSystem, options UnionFsOptions) (pathfs.FileSystem, error) { 94 g := &unionFS{ 95 options: &options, 96 fileSystems: fileSystems, 97 FileSystem: pathfs.NewDefaultFileSystem(), 98 } 99 100 writable := g.fileSystems[0] 101 code := g.createDeletionStore() 102 if !code.Ok() { 103 return nil, fmt.Errorf("could not create deletion path %v: %v", options.DeletionDirName, code) 104 } 105 106 g.deletionCache = newDirCache(writable, options.DeletionDirName, options.DeletionCacheTTL) 107 g.branchCache = NewTimedCache( 108 func(n string) (interface{}, bool) { return g.getBranchAttrNoCache(n), true }, 109 options.BranchCacheTTL) 110 111 g.hiddenFiles = make(map[string]bool) 112 for _, name := range options.HiddenFiles { 113 g.hiddenFiles[name] = true 114 } 115 116 return g, nil 117 } 118 119 func (fs *unionFS) OnMount(nodeFs *pathfs.PathNodeFs) { 120 fs.nodeFs = nodeFs 121 } 122 123 //////////////// 124 // Deal with all the caches. 125 126 // The isDeleted() method tells us if a path has a marker in the deletion store. 127 // It may return an error code if the store could not be accessed. 128 func (fs *unionFS) isDeleted(name string) (deleted bool, code fuse.Status) { 129 marker := fs.deletionPath(name) 130 haveCache, found := fs.deletionCache.HasEntry(filepath.Base(marker)) 131 if haveCache { 132 return found, fuse.OK 133 } 134 135 _, code = fs.fileSystems[0].GetAttr(marker, nil) 136 137 if code == fuse.OK { 138 return true, code 139 } 140 if code == fuse.ENOENT { 141 return false, fuse.OK 142 } 143 144 log.Printf("error accessing deletion marker %s: %v", marker, code) 145 return false, fuse.Status(syscall.EROFS) 146 } 147 148 func (fs *unionFS) createDeletionStore() (code fuse.Status) { 149 writable := fs.fileSystems[0] 150 fi, code := writable.GetAttr(fs.options.DeletionDirName, nil) 151 if code == fuse.ENOENT { 152 code = writable.Mkdir(fs.options.DeletionDirName, 0755, nil) 153 if code.Ok() { 154 fi, code = writable.GetAttr(fs.options.DeletionDirName, nil) 155 } 156 } 157 158 if !code.Ok() || !fi.IsDir() { 159 code = fuse.Status(syscall.EROFS) 160 } 161 162 return code 163 } 164 165 func (fs *unionFS) getBranch(name string) branchResult { 166 name = stripSlash(name) 167 r := fs.branchCache.Get(name) 168 return r.(branchResult) 169 } 170 171 func (fs *unionFS) setBranch(name string, r branchResult) { 172 if !r.valid() { 173 log.Panicf("entry %q setting illegal branchResult %v", name, r) 174 } 175 fs.branchCache.Set(name, r) 176 } 177 178 type branchResult struct { 179 attr *fuse.Attr 180 code fuse.Status 181 branch int 182 } 183 184 func (r *branchResult) valid() bool { 185 return (r.branch >= 0 && r.attr != nil && r.code.Ok()) || 186 (r.branch < 0 && r.attr == nil && !r.code.Ok()) 187 } 188 189 func (fs branchResult) String() string { 190 return fmt.Sprintf("{%v %v branch %d}", fs.attr, fs.code, fs.branch) 191 } 192 193 func (fs *unionFS) getBranchAttrNoCache(name string) branchResult { 194 name = stripSlash(name) 195 196 parent, base := path.Split(name) 197 parent = stripSlash(parent) 198 199 parentBranch := 0 200 if base != "" { 201 parentBranch = fs.getBranch(parent).branch 202 } 203 for i, fs := range fs.fileSystems { 204 if i < parentBranch { 205 continue 206 } 207 208 a, s := fs.GetAttr(name, nil) 209 if s.Ok() { 210 if i > 0 { 211 // Needed to make hardlinks work. 212 a.Ino = 0 213 } 214 return branchResult{ 215 attr: a, 216 code: s, 217 branch: i, 218 } 219 } else { 220 if s != fuse.ENOENT { 221 log.Printf("getattr: %v: Got error %v from branch %v", name, s, i) 222 } 223 } 224 } 225 return branchResult{nil, fuse.ENOENT, -1} 226 } 227 228 //////////////// 229 // Deletion. 230 231 func (fs *unionFS) deletionPath(name string) string { 232 return filepath.Join(fs.options.DeletionDirName, filePathHash(name)) 233 } 234 235 func (fs *unionFS) removeDeletion(name string) { 236 marker := fs.deletionPath(name) 237 238 // os.Remove tries to be smart and issues a Remove() and 239 // Rmdir() sequentially. We want to skip the 2nd system call, 240 // so use syscall.Unlink() directly. 241 242 code := fs.fileSystems[0].Unlink(marker, nil) 243 if !code.Ok() && code != fuse.ENOENT { 244 log.Printf("error unlinking %s: %v", marker, code) 245 } 246 247 // Update in-memory cache as last step, so we avoid caching a 248 // state from before the storage update. 249 fs.deletionCache.RemoveEntry(path.Base(marker)) 250 } 251 252 func (fs *unionFS) putDeletion(name string) (code fuse.Status) { 253 code = fs.createDeletionStore() 254 if !code.Ok() { 255 return code 256 } 257 258 marker := fs.deletionPath(name) 259 260 // Is there a WriteStringToFileOrDie ? 261 writable := fs.fileSystems[0] 262 fi, code := writable.GetAttr(marker, nil) 263 if code.Ok() && fi.Size == uint64(len(name)) { 264 return fuse.OK 265 } 266 267 var f nodefs.File 268 if code == fuse.ENOENT { 269 f, code = writable.Create(marker, uint32(os.O_TRUNC|os.O_WRONLY), 0644, nil) 270 } else { 271 writable.Chmod(marker, 0644, nil) 272 f, code = writable.Open(marker, uint32(os.O_TRUNC|os.O_WRONLY), nil) 273 } 274 if !code.Ok() { 275 log.Printf("could not create deletion file %v: %v", marker, code) 276 return fuse.EPERM 277 } 278 defer f.Release() 279 defer f.Flush() 280 n, code := f.Write([]byte(name), 0) 281 if int(n) != len(name) || !code.Ok() { 282 panic(fmt.Sprintf("Error for writing %v: %v, %v (exp %v) %v", name, marker, n, len(name), code)) 283 } 284 285 // Update the in-memory deletion cache as the last step, 286 // to ensure that the new state stays in memory 287 fs.deletionCache.AddEntry(path.Base(marker)) 288 289 return fuse.OK 290 } 291 292 //////////////// 293 // Promotion. 294 295 func (fs *unionFS) Promote(name string, srcResult branchResult, context *fuse.Context) (code fuse.Status) { 296 writable := fs.fileSystems[0] 297 sourceFs := fs.fileSystems[srcResult.branch] 298 299 // Promote directories. 300 fs.promoteDirsTo(name) 301 302 if srcResult.attr.IsRegular() { 303 code = pathfs.CopyFile(sourceFs, writable, name, name, context) 304 305 if code.Ok() { 306 code = writable.Chmod(name, srcResult.attr.Mode&07777|0200, context) 307 } 308 if code.Ok() { 309 aTime := srcResult.attr.AccessTime() 310 mTime := srcResult.attr.ModTime() 311 code = writable.Utimens(name, &aTime, &mTime, context) 312 } 313 314 files := fs.nodeFs.AllFiles(name, 0) 315 for _, fileWrapper := range files { 316 if !code.Ok() { 317 break 318 } 319 var uf *unionFsFile 320 f := fileWrapper.File 321 for f != nil { 322 ok := false 323 uf, ok = f.(*unionFsFile) 324 if ok { 325 break 326 } 327 f = f.InnerFile() 328 } 329 if uf == nil { 330 panic("no unionFsFile found inside") 331 } 332 333 if uf.layer > 0 { 334 uf.layer = 0 335 f := uf.File 336 uf.File, code = fs.fileSystems[0].Open(name, fileWrapper.OpenFlags, context) 337 f.Flush() 338 f.Release() 339 } 340 } 341 } else if srcResult.attr.IsSymlink() { 342 link := "" 343 link, code = sourceFs.Readlink(name, context) 344 if !code.Ok() { 345 log.Println("can't read link in source fs", name) 346 } else { 347 code = writable.Symlink(link, name, context) 348 } 349 } else if srcResult.attr.IsDir() { 350 code = writable.Mkdir(name, srcResult.attr.Mode&07777|0200, context) 351 } else { 352 log.Println("Unknown file type:", srcResult.attr) 353 return fuse.ENOSYS 354 } 355 356 if !code.Ok() { 357 fs.branchCache.GetFresh(name) 358 return code 359 } else { 360 r := fs.getBranch(name) 361 r.branch = 0 362 fs.setBranch(name, r) 363 } 364 365 return fuse.OK 366 } 367 368 //////////////////////////////////////////////////////////////// 369 // Below: implement interface for a FileSystem. 370 371 func (fs *unionFS) Link(orig string, newName string, context *fuse.Context) (code fuse.Status) { 372 origResult := fs.getBranch(orig) 373 code = origResult.code 374 if code.Ok() && origResult.branch > 0 { 375 code = fs.Promote(orig, origResult, context) 376 } 377 if code.Ok() && origResult.branch > 0 { 378 // Hairy: for the link to be hooked up to the existing 379 // inode, PathNodeFs must see a client inode for the 380 // original. We force a refresh of the attribute (so 381 // the Ino is filled in.), and then force PathNodeFs 382 // to see the Inode number. 383 fs.branchCache.GetFresh(orig) 384 inode := fs.nodeFs.Node(orig) 385 var a fuse.Attr 386 inode.Node().GetAttr(&a, nil, nil) 387 } 388 if code.Ok() { 389 code = fs.promoteDirsTo(newName) 390 } 391 if code.Ok() { 392 code = fs.fileSystems[0].Link(orig, newName, context) 393 } 394 if code.Ok() { 395 fs.removeDeletion(newName) 396 fs.branchCache.GetFresh(newName) 397 } 398 return code 399 } 400 401 func (fs *unionFS) Rmdir(path string, context *fuse.Context) (code fuse.Status) { 402 r := fs.getBranch(path) 403 if r.code != fuse.OK { 404 return r.code 405 } 406 if !r.attr.IsDir() { 407 return fuse.Status(syscall.ENOTDIR) 408 } 409 410 stream, code := fs.OpenDir(path, context) 411 found := false 412 for _ = range stream { 413 found = true 414 } 415 if found { 416 return fuse.Status(syscall.ENOTEMPTY) 417 } 418 419 if r.branch > 0 { 420 code = fs.putDeletion(path) 421 return code 422 } 423 code = fs.fileSystems[0].Rmdir(path, context) 424 if code != fuse.OK { 425 return code 426 } 427 428 r = fs.branchCache.GetFresh(path).(branchResult) 429 if r.branch > 0 { 430 code = fs.putDeletion(path) 431 } 432 return code 433 } 434 435 func (fs *unionFS) Mkdir(path string, mode uint32, context *fuse.Context) (code fuse.Status) { 436 deleted, code := fs.isDeleted(path) 437 if !code.Ok() { 438 return code 439 } 440 441 if !deleted { 442 r := fs.getBranch(path) 443 if r.code != fuse.ENOENT { 444 return fuse.Status(syscall.EEXIST) 445 } 446 } 447 448 code = fs.promoteDirsTo(path) 449 if code.Ok() { 450 code = fs.fileSystems[0].Mkdir(path, mode, context) 451 } 452 if code.Ok() { 453 fs.removeDeletion(path) 454 attr := &fuse.Attr{ 455 Mode: fuse.S_IFDIR | mode, 456 } 457 fs.setBranch(path, branchResult{attr, fuse.OK, 0}) 458 } 459 460 var stream []fuse.DirEntry 461 stream, code = fs.OpenDir(path, context) 462 if code.Ok() { 463 // This shouldn't happen, but let's be safe. 464 for _, entry := range stream { 465 fs.putDeletion(filepath.Join(path, entry.Name)) 466 } 467 } 468 469 return code 470 } 471 472 func (fs *unionFS) Symlink(pointedTo string, linkName string, context *fuse.Context) (code fuse.Status) { 473 code = fs.promoteDirsTo(linkName) 474 if code.Ok() { 475 code = fs.fileSystems[0].Symlink(pointedTo, linkName, context) 476 } 477 if code.Ok() { 478 fs.removeDeletion(linkName) 479 fs.branchCache.GetFresh(linkName) 480 } 481 return code 482 } 483 484 func (fs *unionFS) Truncate(path string, size uint64, context *fuse.Context) (code fuse.Status) { 485 if path == _DROP_CACHE { 486 return fuse.OK 487 } 488 489 r := fs.getBranch(path) 490 if r.branch > 0 { 491 code = fs.Promote(path, r, context) 492 r.branch = 0 493 } 494 495 if code.Ok() { 496 code = fs.fileSystems[0].Truncate(path, size, context) 497 } 498 if code.Ok() { 499 newAttr := *r.attr 500 501 r.attr = &newAttr 502 r.attr.Size = size 503 now := time.Now() 504 r.attr.SetTimes(nil, &now, &now) 505 fs.setBranch(path, r) 506 } 507 return code 508 } 509 510 func (fs *unionFS) Utimens(name string, atime *time.Time, mtime *time.Time, context *fuse.Context) (code fuse.Status) { 511 name = stripSlash(name) 512 r := fs.getBranch(name) 513 514 code = r.code 515 if code.Ok() && r.branch > 0 { 516 code = fs.Promote(name, r, context) 517 r.branch = 0 518 } 519 if code.Ok() { 520 code = fs.fileSystems[0].Utimens(name, atime, mtime, context) 521 } 522 if code.Ok() { 523 now := time.Now() 524 newAttr := *r.attr 525 r.attr = &newAttr 526 r.attr.SetTimes(atime, mtime, &now) 527 fs.setBranch(name, r) 528 } 529 return code 530 } 531 532 func (fs *unionFS) Chown(name string, uid uint32, gid uint32, context *fuse.Context) (code fuse.Status) { 533 name = stripSlash(name) 534 r := fs.getBranch(name) 535 if r.attr == nil || r.code != fuse.OK { 536 return r.code 537 } 538 539 newAttr := *r.attr 540 r.attr = &newAttr 541 542 if os.Geteuid() != 0 { 543 return fuse.EPERM 544 } 545 546 if r.attr.Uid != uid || r.attr.Gid != gid { 547 if r.branch > 0 { 548 code := fs.Promote(name, r, context) 549 if code != fuse.OK { 550 return code 551 } 552 r.branch = 0 553 } 554 fs.fileSystems[0].Chown(name, uid, gid, context) 555 } 556 r.attr.Uid = uid 557 r.attr.Gid = gid 558 now := time.Now() 559 r.attr.SetTimes(nil, nil, &now) 560 fs.setBranch(name, r) 561 return fuse.OK 562 } 563 564 func (fs *unionFS) Chmod(name string, mode uint32, context *fuse.Context) (code fuse.Status) { 565 name = stripSlash(name) 566 r := fs.getBranch(name) 567 if r.attr == nil { 568 return r.code 569 } 570 newAttr := *r.attr 571 r.attr = &newAttr 572 if r.code != fuse.OK { 573 return r.code 574 } 575 576 permMask := uint32(07777) 577 578 // Always be writable. 579 oldMode := r.attr.Mode & permMask 580 581 if oldMode != mode { 582 if r.branch > 0 { 583 code := fs.Promote(name, r, context) 584 if code != fuse.OK { 585 return code 586 } 587 r.branch = 0 588 } 589 fs.fileSystems[0].Chmod(name, mode, context) 590 } 591 r.attr.Mode = (r.attr.Mode &^ permMask) | mode 592 now := time.Now() 593 r.attr.SetTimes(nil, nil, &now) 594 fs.setBranch(name, r) 595 return fuse.OK 596 } 597 598 func (fs *unionFS) Access(name string, mode uint32, context *fuse.Context) (code fuse.Status) { 599 // We always allow writing. 600 mode = mode &^ fuse.W_OK 601 if name == "" || name == _DROP_CACHE { 602 return fuse.OK 603 } 604 r := fs.getBranch(name) 605 if r.branch >= 0 { 606 return fs.fileSystems[r.branch].Access(name, mode, context) 607 } 608 return fuse.ENOENT 609 } 610 611 func (fs *unionFS) Unlink(name string, context *fuse.Context) (code fuse.Status) { 612 r := fs.getBranch(name) 613 if r.branch == 0 { 614 code = fs.fileSystems[0].Unlink(name, context) 615 if code != fuse.OK { 616 return code 617 } 618 r = fs.branchCache.GetFresh(name).(branchResult) 619 } 620 621 if r.branch > 0 { 622 // It would be nice to do the putDeletion async. 623 code = fs.putDeletion(name) 624 } 625 return code 626 } 627 628 func (fs *unionFS) Readlink(name string, context *fuse.Context) (out string, code fuse.Status) { 629 r := fs.getBranch(name) 630 if r.branch >= 0 { 631 return fs.fileSystems[r.branch].Readlink(name, context) 632 } 633 return "", fuse.ENOENT 634 } 635 636 func stripSlash(fn string) string { 637 return strings.TrimRight(fn, string(filepath.Separator)) 638 } 639 640 func (fs *unionFS) promoteDirsTo(filename string) fuse.Status { 641 dirName, _ := filepath.Split(filename) 642 dirName = stripSlash(dirName) 643 644 var todo []string 645 var results []branchResult 646 for dirName != "" { 647 r := fs.getBranch(dirName) 648 649 if !r.code.Ok() { 650 log.Println("path component does not exist", filename, dirName) 651 } 652 if !r.attr.IsDir() { 653 log.Println("path component is not a directory.", dirName, r) 654 return fuse.EPERM 655 } 656 if r.branch == 0 { 657 break 658 } 659 todo = append(todo, dirName) 660 results = append(results, r) 661 dirName, _ = filepath.Split(dirName) 662 dirName = stripSlash(dirName) 663 } 664 665 for i := range todo { 666 j := len(todo) - i - 1 667 d := todo[j] 668 r := results[j] 669 code := fs.fileSystems[0].Mkdir(d, r.attr.Mode&07777|0200, nil) 670 if code != fuse.OK { 671 log.Println("Error creating dir leading to path", d, code, fs.fileSystems[0]) 672 return fuse.EPERM 673 } 674 675 aTime := r.attr.AccessTime() 676 mTime := r.attr.ModTime() 677 fs.fileSystems[0].Utimens(d, &aTime, &mTime, nil) 678 r.branch = 0 679 fs.setBranch(d, r) 680 } 681 return fuse.OK 682 } 683 684 func (fs *unionFS) Create(name string, flags uint32, mode uint32, context *fuse.Context) (fuseFile nodefs.File, code fuse.Status) { 685 writable := fs.fileSystems[0] 686 687 code = fs.promoteDirsTo(name) 688 if code != fuse.OK { 689 return nil, code 690 } 691 fuseFile, code = writable.Create(name, flags, mode, context) 692 if code.Ok() { 693 fuseFile = fs.newUnionFsFile(fuseFile, 0) 694 fs.removeDeletion(name) 695 696 now := time.Now() 697 a := fuse.Attr{ 698 Mode: fuse.S_IFREG | mode, 699 } 700 a.SetTimes(nil, &now, &now) 701 fs.setBranch(name, branchResult{&a, fuse.OK, 0}) 702 } 703 return fuseFile, code 704 } 705 706 func (fs *unionFS) GetAttr(name string, context *fuse.Context) (a *fuse.Attr, s fuse.Status) { 707 _, hidden := fs.hiddenFiles[name] 708 if hidden { 709 return nil, fuse.ENOENT 710 } 711 if name == _DROP_CACHE { 712 return &fuse.Attr{ 713 Mode: fuse.S_IFREG | 0777, 714 }, fuse.OK 715 } 716 if name == fs.options.DeletionDirName { 717 return nil, fuse.ENOENT 718 } 719 isDel, s := fs.isDeleted(name) 720 if !s.Ok() { 721 return nil, s 722 } 723 724 if isDel { 725 return nil, fuse.ENOENT 726 } 727 r := fs.getBranch(name) 728 if r.branch < 0 { 729 return nil, fuse.ENOENT 730 } 731 fi := *r.attr 732 // Make everything appear writable. 733 fi.Mode |= 0200 734 return &fi, r.code 735 } 736 737 func (fs *unionFS) GetXAttr(name string, attr string, context *fuse.Context) ([]byte, fuse.Status) { 738 if name == _DROP_CACHE { 739 return nil, fuse.ENOATTR 740 } 741 r := fs.getBranch(name) 742 if r.branch >= 0 { 743 return fs.fileSystems[r.branch].GetXAttr(name, attr, context) 744 } 745 return nil, fuse.ENOENT 746 } 747 748 func (fs *unionFS) OpenDir(directory string, context *fuse.Context) (stream []fuse.DirEntry, status fuse.Status) { 749 dirBranch := fs.getBranch(directory) 750 if dirBranch.branch < 0 { 751 return nil, fuse.ENOENT 752 } 753 754 // We could try to use the cache, but we have a delay, so 755 // might as well get the fresh results async. 756 var wg sync.WaitGroup 757 var deletions map[string]struct{} 758 759 wg.Add(1) 760 go func() { 761 deletions = newDirnameMap(fs.fileSystems[0], fs.options.DeletionDirName) 762 wg.Done() 763 }() 764 765 entries := make([]map[string]uint32, len(fs.fileSystems)) 766 for i := range fs.fileSystems { 767 entries[i] = make(map[string]uint32) 768 } 769 770 statuses := make([]fuse.Status, len(fs.fileSystems)) 771 for i, l := range fs.fileSystems { 772 if i >= dirBranch.branch { 773 wg.Add(1) 774 go func(j int, pfs pathfs.FileSystem) { 775 ch, s := pfs.OpenDir(directory, context) 776 statuses[j] = s 777 for _, v := range ch { 778 entries[j][v.Name] = v.Mode 779 } 780 wg.Done() 781 }(i, l) 782 } 783 } 784 785 wg.Wait() 786 if deletions == nil { 787 _, code := fs.fileSystems[0].GetAttr(fs.options.DeletionDirName, context) 788 if code == fuse.ENOENT { 789 deletions = map[string]struct{}{} 790 } else { 791 return nil, fuse.Status(syscall.EROFS) 792 } 793 } 794 795 results := entries[0] 796 797 // TODO(hanwen): should we do anything with the return 798 // statuses? 799 for i, m := range entries { 800 if statuses[i] != fuse.OK { 801 continue 802 } 803 if i == 0 { 804 // We don't need to further process the first 805 // branch: it has no deleted files. 806 continue 807 } 808 for k, v := range m { 809 _, ok := results[k] 810 if ok { 811 continue 812 } 813 814 _, deleted := deletions[filePathHash(filepath.Join(directory, k))] 815 if !deleted { 816 results[k] = v 817 } 818 } 819 } 820 if directory == "" { 821 delete(results, fs.options.DeletionDirName) 822 for name, _ := range fs.hiddenFiles { 823 delete(results, name) 824 } 825 } 826 827 stream = make([]fuse.DirEntry, 0, len(results)) 828 for k, v := range results { 829 stream = append(stream, fuse.DirEntry{ 830 Name: k, 831 Mode: v, 832 }) 833 } 834 return stream, fuse.OK 835 } 836 837 // recursivePromote promotes path, and if a directory, everything 838 // below that directory. It returns a list of all promoted paths, in 839 // full, including the path itself. 840 func (fs *unionFS) recursivePromote(path string, pathResult branchResult, context *fuse.Context) (names []string, code fuse.Status) { 841 names = []string{} 842 if pathResult.branch > 0 { 843 code = fs.Promote(path, pathResult, context) 844 } 845 846 if code.Ok() { 847 names = append(names, path) 848 } 849 850 if code.Ok() && pathResult.attr != nil && pathResult.attr.IsDir() { 851 var stream []fuse.DirEntry 852 stream, code = fs.OpenDir(path, context) 853 for _, e := range stream { 854 if !code.Ok() { 855 break 856 } 857 subnames := []string{} 858 p := filepath.Join(path, e.Name) 859 r := fs.getBranch(p) 860 subnames, code = fs.recursivePromote(p, r, context) 861 names = append(names, subnames...) 862 } 863 } 864 865 if !code.Ok() { 866 names = nil 867 } 868 return names, code 869 } 870 871 func (fs *unionFS) renameDirectory(srcResult branchResult, srcDir string, dstDir string, context *fuse.Context) (code fuse.Status) { 872 names := []string{} 873 if code.Ok() { 874 names, code = fs.recursivePromote(srcDir, srcResult, context) 875 } 876 if code.Ok() { 877 code = fs.promoteDirsTo(dstDir) 878 } 879 880 if code.Ok() { 881 writable := fs.fileSystems[0] 882 code = writable.Rename(srcDir, dstDir, context) 883 } 884 885 if code.Ok() { 886 for _, srcName := range names { 887 relative := strings.TrimLeft(srcName[len(srcDir):], string(filepath.Separator)) 888 dst := filepath.Join(dstDir, relative) 889 fs.removeDeletion(dst) 890 891 srcResult := fs.getBranch(srcName) 892 srcResult.branch = 0 893 fs.setBranch(dst, srcResult) 894 895 srcResult = fs.branchCache.GetFresh(srcName).(branchResult) 896 if srcResult.branch > 0 { 897 code = fs.putDeletion(srcName) 898 } 899 } 900 } 901 return code 902 } 903 904 func (fs *unionFS) Rename(src string, dst string, context *fuse.Context) fuse.Status { 905 srcResult := fs.getBranch(src) 906 if !srcResult.code.Ok() { 907 return srcResult.code 908 } 909 910 if srcResult.attr.IsDir() { 911 return fs.renameDirectory(srcResult, src, dst, context) 912 } 913 914 if srcResult.branch > 0 { 915 if code := fs.Promote(src, srcResult, context); !code.Ok() { 916 return code 917 } 918 } 919 if code := fs.promoteDirsTo(dst); !code.Ok() { 920 return code 921 } 922 923 if code := fs.fileSystems[0].Rename(src, dst, context); !code.Ok() { 924 return code 925 } 926 927 fs.removeDeletion(dst) 928 // Rename is racy; avoid racing with unionFsFile.Release(). 929 fs.branchCache.DropEntry(dst) 930 931 srcResult = fs.branchCache.GetFresh(src).(branchResult) 932 if srcResult.branch > 0 { 933 return fs.putDeletion(src) 934 } 935 return fuse.OK 936 } 937 938 func (fs *unionFS) DropBranchCache(names []string) { 939 fs.branchCache.DropAll(names) 940 } 941 942 func (fs *unionFS) DropDeletionCache() { 943 fs.deletionCache.DropCache() 944 } 945 946 func (fs *unionFS) DropSubFsCaches() { 947 for _, fs := range fs.fileSystems { 948 a, code := fs.GetAttr(_DROP_CACHE, nil) 949 if code.Ok() && a.IsRegular() { 950 f, _ := fs.Open(_DROP_CACHE, uint32(os.O_WRONLY), nil) 951 if f != nil { 952 f.Flush() 953 f.Release() 954 } 955 } 956 } 957 } 958 959 func (fs *unionFS) Open(name string, flags uint32, context *fuse.Context) (fuseFile nodefs.File, status fuse.Status) { 960 if name == _DROP_CACHE { 961 if flags&fuse.O_ANYWRITE != 0 { 962 log.Println("Forced cache drop on", fs) 963 fs.DropBranchCache(nil) 964 fs.DropDeletionCache() 965 fs.DropSubFsCaches() 966 fs.nodeFs.ForgetClientInodes() 967 } 968 return nodefs.NewDevNullFile(), fuse.OK 969 } 970 r := fs.getBranch(name) 971 if r.branch < 0 { 972 // This should not happen, as a GetAttr() should have 973 // already verified existence. 974 log.Println("UnionFs: open of non-existent file:", name) 975 return nil, fuse.ENOENT 976 } 977 if flags&fuse.O_ANYWRITE != 0 && r.branch > 0 { 978 code := fs.Promote(name, r, context) 979 if code != fuse.OK { 980 return nil, code 981 } 982 r.branch = 0 983 now := time.Now() 984 r.attr.SetTimes(nil, &now, nil) 985 fs.setBranch(name, r) 986 } 987 fuseFile, status = fs.fileSystems[r.branch].Open(name, uint32(flags), context) 988 if fuseFile != nil { 989 fuseFile = fs.newUnionFsFile(fuseFile, r.branch) 990 } 991 return fuseFile, status 992 } 993 994 func (fs *unionFS) String() string { 995 names := []string{} 996 for _, fs := range fs.fileSystems { 997 names = append(names, fs.String()) 998 } 999 return fmt.Sprintf("UnionFs(%v)", names) 1000 } 1001 1002 func (fs *unionFS) StatFs(name string) *fuse.StatfsOut { 1003 return fs.fileSystems[0].StatFs("") 1004 } 1005 1006 type unionFsFile struct { 1007 nodefs.File 1008 ufs *unionFS 1009 node *nodefs.Inode 1010 layer int 1011 } 1012 1013 func (fs *unionFsFile) String() string { 1014 return fmt.Sprintf("unionFsFile(%s)", fs.File.String()) 1015 } 1016 1017 func (fs *unionFS) newUnionFsFile(f nodefs.File, branch int) *unionFsFile { 1018 return &unionFsFile{ 1019 File: f, 1020 ufs: fs, 1021 layer: branch, 1022 } 1023 } 1024 1025 func (fs *unionFsFile) InnerFile() (file nodefs.File) { 1026 return fs.File 1027 } 1028 1029 // We can't hook on Release. Release has no response, so it is not 1030 // ordered wrt any following calls. 1031 func (fs *unionFsFile) Flush() (code fuse.Status) { 1032 code = fs.File.Flush() 1033 path := fs.ufs.nodeFs.Path(fs.node) 1034 fs.ufs.branchCache.GetFresh(path) 1035 return code 1036 } 1037 1038 func (fs *unionFsFile) SetInode(node *nodefs.Inode) { 1039 fs.node = node 1040 } 1041 1042 func (fs *unionFsFile) GetAttr(out *fuse.Attr) fuse.Status { 1043 code := fs.File.GetAttr(out) 1044 if code.Ok() { 1045 out.Mode |= 0200 1046 } 1047 return code 1048 }