github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/kbfs/libfs/fs.go (about) 1 // Copyright 2017 Keybase Inc. All rights reserved. 2 // Use of this source code is governed by a BSD 3 // license that can be found in the LICENSE file. 4 5 package libfs 6 7 import ( 8 "bytes" 9 "context" 10 "crypto/rand" 11 "encoding/base64" 12 "fmt" 13 "net/http" 14 "os" 15 "path" 16 "strings" 17 "sync" 18 "time" 19 20 "github.com/keybase/client/go/kbfs/data" 21 "github.com/keybase/client/go/kbfs/idutil" 22 "github.com/keybase/client/go/kbfs/libkbfs" 23 "github.com/keybase/client/go/kbfs/tlfhandle" 24 "github.com/keybase/client/go/logger" 25 "github.com/keybase/client/go/protocol/keybase1" 26 "github.com/pkg/errors" 27 billy "gopkg.in/src-d/go-billy.v4" 28 ) 29 30 // FSEventType is FS event type. 31 type FSEventType int 32 33 const ( 34 _ FSEventType = iota 35 // FSEventLock indicates Lock method has been called. 36 FSEventLock 37 // FSEventUnlock indicates Unlock method has been called. 38 FSEventUnlock 39 ) 40 41 // FSEvent is the type for events sent into the events channel passed into 42 // NewFS. 43 type FSEvent struct { 44 EventType FSEventType 45 File *File 46 Done <-chan struct{} 47 } 48 49 type fsInner struct { 50 config libkbfs.Config 51 root libkbfs.Node 52 rootInfo data.EntryInfo 53 h *tlfhandle.Handle 54 subdir string 55 uniqID string 56 log logger.Logger 57 deferLog logger.Logger 58 priority keybase1.MDPriority 59 empty bool 60 // lockNamespace is the prefix used by any *File generated by this *FS when 61 // they need to generate a lockID. By default, we use a canonical unix path 62 // of the root of this FS as lock namespace. But one can call 63 // SetLockNamespace to set it explicitly, which can be any bytes. When 64 // Chroot is called, a slash ('/') followed by the changed subpath are 65 // appended to the existing lockNamespace to form the new one. Note that 66 // this is a naive append without and path clean. 67 lockNamespace []byte 68 69 eventsLock sync.RWMutex // nolint 70 events map[chan<- FSEvent]bool 71 } 72 73 // FS is a wrapper around a KBFS subdirectory that implements the 74 // billy.Filesystem interface. It uses forward-slash separated paths. 75 // It may return errors wrapped with the `github.com/pkg/errors` 76 // package. 77 type FS struct { 78 // Yes, storing ctx in a struct is a mortal sin, but the 79 // billy.Filesystem interface doesn't give us a way to accept ctxs 80 // any other way. 81 ctx context.Context 82 *fsInner 83 } 84 85 var _ billy.Filesystem = (*FS)(nil) 86 87 const ( 88 maxSymlinkLevels = 40 // same as Linux 89 ) 90 91 func followSymlink(parentPath, link string) (newPath string, err error) { 92 if path.IsAbs(link) { 93 return "", errors.Errorf("Can't follow absolute link: %s", link) 94 } 95 96 newPath = path.Clean(path.Join(parentPath, link)) 97 if strings.HasPrefix(newPath, "..") { 98 return "", errors.Errorf( 99 "Cannot follow symlink out of chroot: %s", newPath) 100 } 101 102 return newPath, nil 103 } 104 105 func pathForLogging( 106 ctx context.Context, config libkbfs.Config, root libkbfs.Node, 107 filename string) string { 108 if root == nil || root.Obfuscator() == nil { 109 return filename 110 } 111 112 if filename == "." { 113 return "" 114 } 115 116 parts := strings.Split(filename, "/") 117 if len(parts) == 0 { 118 return "" 119 } 120 n := root 121 ret := "" 122 for i := 0; i < len(parts)-1; i++ { 123 p := parts[i] 124 childName := n.ChildName(p) 125 ret = path.Join(ret, childName.String()) 126 nextNode, _, err := config.KBFSOps().Lookup(ctx, n, childName) 127 // If filename is a path that includes a symlink, we can get a nil node 128 // here. So just move on if nextNode == nil. In the very rare case of 129 // an entry that gets a duplicated obfuscated name within a directory, 130 // this could give the wrong answer. But it's not worth it at this time 131 // to follow the symlnk for logging. 132 if err != nil || nextNode == nil { 133 // Just keep using the parent node to obfuscate. 134 continue 135 } 136 n = nextNode 137 } 138 ret = path.Join(ret, n.ChildName(parts[len(parts)-1]).String()) 139 return ret 140 } 141 142 // ErrNotADirectory is returned when a non-final path element exists but is not 143 // a directory. 144 type ErrNotADirectory struct { 145 Name string 146 } 147 148 func (e ErrNotADirectory) Error() string { 149 return fmt.Sprintf("%s is not a directory", e.Name) 150 } 151 152 type accessType int 153 154 const ( 155 readwrite accessType = iota 156 // readwriteNoCreate creates a read-write file system, but doesn't 157 // create the TLF if it doesn't already exists. Instead, it 158 // creates an empty file system. 159 readwriteNoCreate 160 readonly 161 ) 162 163 func newFS(ctx context.Context, config libkbfs.Config, 164 tlfHandle *tlfhandle.Handle, branch data.BranchName, subdir string, 165 uniqID string, priority keybase1.MDPriority, unwrap bool, 166 atype accessType) (*FS, error) { 167 rootNodeGetter := config.KBFSOps().GetOrCreateRootNode 168 if branch != data.MasterBranch || atype != readwrite { 169 rootNodeGetter = config.KBFSOps().GetRootNode 170 } 171 172 rootNode, ei, err := rootNodeGetter(ctx, tlfHandle, branch) 173 if err != nil { 174 return nil, err 175 } 176 log := config.MakeLogger("") 177 if rootNode == nil { 178 if len(subdir) > 0 { 179 return nil, errors.New("Subdir doesn't exist in empty TLF") 180 } 181 log.CDebugf(ctx, 182 "Returning empty FS for empty TLF=%s", tlfHandle.GetCanonicalName()) 183 return &FS{ 184 ctx: ctx, 185 fsInner: &fsInner{ 186 config: config, 187 empty: true, 188 h: tlfHandle, 189 uniqID: uniqID, 190 log: log, 191 deferLog: log.CloneWithAddedDepth(1), 192 }, 193 }, nil 194 } 195 196 if unwrap { 197 rootNode = rootNode.Unwrap() 198 } 199 if subdir != "" { 200 subdir = path.Clean(subdir) 201 } 202 203 if atype == readonly { 204 rootNode = libkbfs.ReadonlyNode{Node: rootNode} 205 } 206 n := rootNode 207 208 // Look up the subdir's root. 209 var parts []string 210 if len(subdir) > 0 { 211 parts = strings.Split(subdir, "/") 212 } 213 // Loop while we follow symlinks. 214 outer: 215 for { 216 for i, p := range parts { 217 n, ei, err = config.KBFSOps().Lookup(ctx, n, n.ChildName(p)) 218 if err != nil { 219 return nil, err 220 } 221 switch ei.Type { 222 case data.Dir: 223 continue 224 case data.Sym: 225 parentParts := parts[:i] 226 newPath, err := followSymlink( 227 path.Join(parentParts...), ei.SymPath) 228 if err != nil { 229 return nil, err 230 } 231 newParts := strings.Split(newPath, "/") 232 newParts = append(newParts, parts[i+1:]...) 233 // Fix subdir so we'll get the correct default lock namespace. 234 oldSubdir := subdir 235 subdir = path.Join(newParts...) 236 config.MakeLogger("").CDebugf(ctx, "Expanding symlink: %s->%s", 237 pathForLogging(ctx, config, rootNode, oldSubdir), 238 pathForLogging(ctx, config, rootNode, subdir)) 239 parts = newParts 240 n = rootNode 241 continue outer 242 default: 243 return nil, ErrNotADirectory{Name: path.Join(parts[:i+1]...)} 244 } 245 } 246 // Successfully looked up all directories. 247 break 248 } 249 250 log.CDebugf(ctx, "Made new FS for TLF=%s, subdir=%s", 251 tlfHandle.GetCanonicalName(), 252 pathForLogging(ctx, config, rootNode, subdir)) 253 254 // Use the canonical unix path for default locking namespace, as this needs 255 // to be the same across all platforms. 256 unixFullPath := path.Join("/keybase", tlfHandle.Type().String(), subdir) 257 258 return &FS{ 259 ctx: ctx, 260 fsInner: &fsInner{ 261 config: config, 262 root: n, 263 rootInfo: ei, 264 h: tlfHandle, 265 subdir: subdir, 266 uniqID: uniqID, 267 log: log, 268 deferLog: log.CloneWithAddedDepth(1), 269 lockNamespace: []byte(unixFullPath), 270 priority: priority, 271 events: make(map[chan<- FSEvent]bool), 272 }, 273 }, nil 274 } 275 276 // NewUnwrappedFS returns a new FS instance, chroot'd to the given TLF 277 // and subdir within that TLF, but all the nodes are unwrapped. 278 // `subdir` must exist, and point to a directory, before this function 279 // is called. `uniqID` needs to uniquely identify this instance among 280 // all users of this TLF globally; for example, a device ID combined 281 // with a local tempfile name is recommended. 282 func NewUnwrappedFS(ctx context.Context, config libkbfs.Config, 283 tlfHandle *tlfhandle.Handle, branch data.BranchName, subdir string, 284 uniqID string, priority keybase1.MDPriority) (*FS, error) { 285 return newFS( 286 ctx, config, tlfHandle, branch, subdir, uniqID, priority, true, 287 readwrite) 288 } 289 290 // NewReadonlyFS returns a new FS instance, chroot'd to the given TLF 291 // and subdir within that TLF, but all the nodes are read-only. 292 // `subdir` must exist, and point to a directory, before this function 293 // is called. `uniqID` needs to uniquely identify this instance among 294 // all users of this TLF globally; for example, a device ID combined 295 // with a local tempfile name is recommended. 296 // 297 // Note that this should only be used for subdirectories that will 298 // never be accessed in read-write mode by this process, because the 299 // nodes created via this FS might stay read-only in the libkbfs 300 // NodeCache for a while. 301 func NewReadonlyFS(ctx context.Context, config libkbfs.Config, 302 tlfHandle *tlfhandle.Handle, branch data.BranchName, subdir string, 303 uniqID string, priority keybase1.MDPriority) (*FS, error) { 304 return newFS( 305 ctx, config, tlfHandle, branch, subdir, uniqID, priority, false, 306 readonly) 307 } 308 309 // NewFSIfExists returns a new FS instance, chroot'd to the given TLF 310 // and subdir within that TLF, but only if the TLF already exists. 311 // `subdir` must exist, and point to a directory, before this function 312 // is called. `uniqID` needs to uniquely identify this instance among 313 // all users of this TLF globally; for example, a device ID combined 314 // with a local tempfile name is recommended. 315 // 316 // If the TLF hasn't been initialized yet, this will return an FS that 317 // is always empty. `IsEmpty()` will tell if you this is happening. 318 // If there's a need to re-check the TLF, a new FS must be 319 // constructed. 320 func NewFSIfExists(ctx context.Context, config libkbfs.Config, 321 tlfHandle *tlfhandle.Handle, branch data.BranchName, subdir string, 322 uniqID string, priority keybase1.MDPriority) (*FS, error) { 323 return newFS( 324 ctx, config, tlfHandle, branch, subdir, uniqID, priority, false, 325 readwriteNoCreate) 326 } 327 328 // NewFS returns a new FS instance, chroot'd to the given TLF and 329 // subdir within that TLF. `subdir` must exist, and point to a 330 // directory, before this function is called. `uniqID` needs to 331 // uniquely identify this instance among all users of this TLF 332 // globally; for example, a device ID combined with a local tempfile 333 // name is recommended. 334 func NewFS(ctx context.Context, config libkbfs.Config, 335 tlfHandle *tlfhandle.Handle, branch data.BranchName, subdir string, 336 uniqID string, priority keybase1.MDPriority) (*FS, error) { 337 return newFS( 338 ctx, config, tlfHandle, branch, subdir, uniqID, priority, false, 339 readwrite) 340 } 341 342 // PathForLogging returns the obfuscated path for the given filename. 343 func (fs *FS) PathForLogging(filename string) string { 344 return pathForLogging(fs.ctx, fs.config, fs.root, filename) 345 } 346 347 // lookupOrCreateEntryNoFollow looks up the entry for a file in a 348 // given parent node. If the entry is a symlink, it will return a nil 349 // Node and a nil error. If the entry doesn't exist and O_CREATE is 350 // set in `flag`, it will create the entry as a file. 351 func (fs *FS) lookupOrCreateEntryNoFollow( 352 dir libkbfs.Node, filename data.PathPartString, flag int, 353 perm os.FileMode) (libkbfs.Node, data.EntryInfo, error) { 354 n, ei, err := fs.config.KBFSOps().Lookup(fs.ctx, dir, filename) 355 switch errors.Cause(err).(type) { 356 case idutil.NoSuchNameError: 357 // The file doesn't exist yet; create if requested 358 if flag&os.O_CREATE == 0 { 359 return nil, data.EntryInfo{}, err 360 } 361 fs.log.CDebugf( 362 fs.ctx, "Creating %s since it doesn't exist yet", filename) 363 excl := libkbfs.NoExcl 364 if flag&os.O_EXCL != 0 { 365 excl = libkbfs.WithExcl 366 } 367 isExec := (perm & 0100) != 0 368 n, ei, err = fs.config.KBFSOps().CreateFile( 369 fs.ctx, dir, filename, isExec, excl) 370 switch errors.Cause(err).(type) { 371 case data.NameExistsError: 372 // Someone made it already; recurse to try the lookup again. 373 fs.log.CDebugf( 374 fs.ctx, "Attempting lookup again after failed create") 375 return fs.lookupOrCreateEntryNoFollow(dir, filename, flag, perm) 376 case nil: 377 return n, ei, nil 378 default: 379 return nil, data.EntryInfo{}, err 380 } 381 case nil: 382 // If we were supposed to have exclusively-created this file, 383 // we must fail. 384 if flag&os.O_CREATE != 0 && flag&os.O_EXCL != 0 { 385 return nil, data.EntryInfo{}, errors.Wrap(os.ErrExist, 386 "Exclusive create failed because the file exists") 387 } 388 389 if ei.Type == data.Sym { 390 // The caller must retry if desired. 391 return nil, ei, nil 392 } 393 394 return n, ei, nil 395 default: 396 return nil, data.EntryInfo{}, err 397 } 398 } 399 400 // lookupParentWithDepth looks up the parent node of the given 401 // filename. It follows symlinks in the path, but doesn't resolve the 402 // final base name. If `exitEarly` is true, it returns on the first 403 // not-found error and `base` will contain the subpath of filename not 404 // yet found. 405 func (fs *FS) lookupParentWithDepth( 406 filename string, exitEarly bool, depth int) ( 407 parent libkbfs.Node, parentDir, base string, err error) { 408 parts := strings.Split(filename, "/") 409 n := fs.root 410 // Iterate through each of the parent directories of the file, but 411 // not the file itself. 412 for i := 0; i < len(parts)-1; i++ { 413 p := parts[i] 414 nextNode, ei, err := fs.config.KBFSOps().Lookup( 415 fs.ctx, n, n.ChildName(p)) 416 switch errors.Cause(err).(type) { 417 case idutil.NoSuchNameError: 418 if exitEarly { 419 parentDir = path.Join(parts[:i]...) 420 base = path.Join(parts[i:]...) 421 return n, parentDir, base, nil 422 } 423 return nil, "", "", err 424 case nil: 425 n = nextNode 426 default: 427 return nil, "", "", err 428 } 429 430 switch ei.Type { 431 case data.Sym: 432 if depth == maxSymlinkLevels { 433 return nil, "", "", errors.New("Too many levels of symlinks") 434 } 435 parentDir = path.Join(parts[:i]...) 436 newPath, err := followSymlink(parentDir, ei.SymPath) 437 if err != nil { 438 return nil, "", "", err 439 } 440 newPathPlusRemainder := append([]string{newPath}, parts[i+1:]...) 441 return fs.lookupParentWithDepth( 442 path.Join(newPathPlusRemainder...), exitEarly, depth+1) 443 case data.Dir: 444 continue 445 default: 446 return nil, "", "", ErrNotADirectory{Name: path.Join(parts[:i+1]...)} 447 } 448 } 449 450 parentDir = path.Join(parts[:len(parts)-1]...) 451 base = parts[len(parts)-1] 452 return n, parentDir, base, nil 453 } 454 455 func (fs *FS) lookupParent(filename string) ( 456 parent libkbfs.Node, parentDir, base string, err error) { 457 return fs.lookupParentWithDepth(filename, false, 0) 458 } 459 460 // lookupOrCreateEntry looks up the entry for a filename, following 461 // symlinks in the path (including if the final entry is a symlink). 462 // If the entry doesn't exist an O_CREATE is set in `flag`, it will 463 // create the entry as a file. 464 func (fs *FS) lookupOrCreateEntry( 465 filename string, flag int, perm os.FileMode) ( 466 n libkbfs.Node, ei data.EntryInfo, err error) { 467 // Shortcut the case where there's nothing to look up. 468 if filename == "" || filename == "/" || filename == "." { 469 return fs.root, fs.rootInfo, nil 470 } 471 filename = strings.TrimPrefix(filename, "/") 472 473 for i := 0; i < maxSymlinkLevels; i++ { 474 var parentDir, fName string 475 n, parentDir, fName, err = fs.lookupParent(filename) 476 if err != nil { 477 return nil, data.EntryInfo{}, err 478 } 479 480 n, ei, err := fs.lookupOrCreateEntryNoFollow( 481 n, n.ChildName(fName), flag, perm) 482 if err != nil { 483 return nil, data.EntryInfo{}, err 484 } 485 486 if ei.Type != data.Sym { 487 return n, ei, nil 488 } 489 fs.log.CDebugf(fs.ctx, "Following symlink=%s from dir=%s", 490 fs.PathForLogging(ei.SymPath), fs.PathForLogging(parentDir)) 491 filename, err = followSymlink(parentDir, ei.SymPath) 492 if err != nil { 493 return nil, data.EntryInfo{}, err 494 } 495 } 496 return nil, data.EntryInfo{}, errors.New("Too many levels of symlinks") 497 } 498 499 func translateErr(err error) error { 500 switch errors.Cause(err).(type) { 501 case idutil.NoSuchNameError, ErrNotADirectory: 502 return os.ErrNotExist 503 case libkbfs.TlfAccessError, tlfhandle.ReadAccessError: 504 return os.ErrPermission 505 case libkbfs.NotDirError, libkbfs.NotFileError: 506 return os.ErrInvalid 507 case data.NameExistsError: 508 return os.ErrExist 509 default: 510 return err 511 } 512 } 513 514 func (fs *FS) mkdirAll(filename string, perm os.FileMode) (err error) { 515 defer func() { 516 err = translateErr(err) 517 }() 518 519 if filename == "/" || filename == "" || filename == "." { 520 return nil 521 } 522 523 n, _, leftover, err := fs.lookupParentWithDepth(filename, true, 0) 524 if err != nil { 525 return err 526 } 527 528 parts := strings.Split(leftover, "/") 529 // Make all necessary dirs. 530 for _, p := range parts { 531 child, _, err := fs.config.KBFSOps().CreateDir( 532 fs.ctx, n, n.ChildName(p)) 533 switch errors.Cause(err).(type) { 534 case data.NameExistsError: 535 // The child directory already exists. 536 case tlfhandle.WriteAccessError, libkbfs.WriteToReadonlyNodeError: 537 // If the child already exists, this doesn't matter. 538 var lookupErr error 539 child, _, lookupErr = fs.config.KBFSOps().Lookup( 540 fs.ctx, n, n.ChildName(p)) 541 if lookupErr != nil { 542 return err 543 } 544 case nil: 545 default: 546 return err 547 } 548 n = child 549 } 550 551 return nil 552 } 553 554 func (fs *FS) ensureParentDir(filename string) error { 555 err := fs.mkdirAll(path.Dir(filename), 0755) 556 if err != nil && !os.IsExist(err) { 557 switch errors.Cause(err).(type) { 558 case tlfhandle.WriteAccessError, libkbfs.WriteToReadonlyNodeError: 559 // We're not allowed to create any of the parent 560 // directories automatically, so give back a proper 561 // isNotExist error. 562 fs.log.CDebugf(fs.ctx, "ensureParentDir: "+ 563 "can't mkdir all due to permission error %+v", err) 564 return os.ErrNotExist 565 default: 566 return err 567 } 568 } 569 return nil 570 } 571 572 type onFsEmpty bool 573 574 const ( 575 onFsEmptyErrNotExist onFsEmpty = true 576 onFsEmptyErrNotSupported onFsEmpty = false 577 ) 578 579 // chooseErrorIfEmpty checks if fs is empty, and returns an error if it is. 580 // Based on onFsEmpty, it returns either os.ErrNotExist or a custom error. This 581 // is useful for operations like Stat and allows caller to treat lookups in an 582 // empty FS as not exist, as they should. 583 func (fs *FS) chooseErrorIfEmpty(onFsEmpty onFsEmpty) error { 584 if fs.empty && onFsEmpty == onFsEmptyErrNotExist { 585 return os.ErrNotExist 586 } else if fs.empty { 587 return errors.New("Not supported for an empty TLF") 588 } 589 return nil 590 } 591 592 // OpenFile implements the billy.Filesystem interface for FS. 593 func (fs *FS) OpenFile(filename string, flag int, perm os.FileMode) ( 594 f billy.File, err error) { 595 fs.log.CDebugf( 596 fs.ctx, "OpenFile %s, flag=%d, perm=%o", 597 fs.PathForLogging(filename), flag, perm) 598 defer func() { 599 fs.deferLog.CDebugf(fs.ctx, "OpenFile done: %+v", err) 600 err = translateErr(err) 601 }() 602 603 if err := fs.chooseErrorIfEmpty(flag&os.O_CREATE == 0); err != nil { 604 return nil, err 605 } 606 607 err = fs.ensureParentDir(filename) 608 if err != nil { 609 return nil, err 610 } 611 612 n, ei, err := fs.lookupOrCreateEntry(filename, flag, perm) 613 if err != nil { 614 return nil, err 615 } 616 617 // Make sure this is a file. 618 if !ei.Type.IsFile() { 619 return nil, errors.Errorf("%s is not a file", filename) 620 } 621 622 if flag&os.O_TRUNC != 0 { 623 err := fs.config.KBFSOps().Truncate(fs.ctx, n, 0) 624 if err != nil { 625 return nil, err 626 } 627 } 628 629 offset := int64(0) 630 if flag&os.O_APPEND != 0 { 631 if ei.Size >= uint64(1<<63) { 632 return nil, errors.New("offset too large") 633 } 634 offset = int64(ei.Size) 635 } 636 637 return &File{ 638 fs: fs, 639 filename: filename, 640 node: n, 641 readOnly: flag == os.O_RDONLY, 642 offset: offset, 643 }, nil 644 } 645 646 // Create implements the billy.Filesystem interface for FS. 647 func (fs *FS) Create(filename string) (billy.File, error) { 648 return fs.OpenFile(filename, os.O_CREATE, 0600) 649 } 650 651 // Open implements the billy.Filesystem interface for FS. 652 func (fs *FS) Open(filename string) (billy.File, error) { 653 return fs.OpenFile(filename, os.O_RDONLY, 0600) 654 } 655 656 func (fs *FS) makeFileInfo( 657 ei data.EntryInfo, node libkbfs.Node, name string) os.FileInfo { 658 if IsFastModeEnabled(fs.ctx) { 659 return &FileInfoFast{ 660 name: name, 661 ei: ei, 662 } 663 } 664 return &FileInfo{ 665 fs: fs, 666 ei: ei, 667 node: node, 668 name: name, 669 } 670 } 671 672 // Stat implements the billy.Filesystem interface for FS. 673 func (fs *FS) Stat(filename string) (fi os.FileInfo, err error) { 674 fs.log.CDebugf(fs.ctx, "Stat %s", fs.PathForLogging(filename)) 675 defer func() { 676 fs.deferLog.CDebugf(fs.ctx, "Stat done: %+v", err) 677 err = translateErr(err) 678 }() 679 680 if fs.empty && (filename == "" || filename == ".") { 681 // We can't just uncondionally use FileInfoFast here as that'd result 682 // in WritePerm unset for non-existent TLFs. 683 return fs.makeFileInfo(data.EntryInfo{ 684 Type: data.Dir, 685 }, nil, filename), nil 686 } else if err := fs.chooseErrorIfEmpty(onFsEmptyErrNotExist); err != nil { 687 return nil, err 688 } 689 690 n, ei, err := fs.lookupOrCreateEntry(filename, os.O_RDONLY, 0) 691 if err != nil { 692 return nil, err 693 } 694 695 return fs.makeFileInfo(ei, n, n.GetBasename().Plaintext()), nil 696 } 697 698 // Rename implements the billy.Filesystem interface for FS. 699 func (fs *FS) Rename(oldpath, newpath string) (err error) { 700 fs.log.CDebugf(fs.ctx, "Rename %s -> %s", 701 fs.PathForLogging(oldpath), fs.PathForLogging(newpath)) 702 defer func() { 703 fs.deferLog.CDebugf(fs.ctx, "Rename done: %+v", err) 704 err = translateErr(err) 705 }() 706 707 if err := fs.chooseErrorIfEmpty(onFsEmptyErrNotSupported); err != nil { 708 return err 709 } 710 711 err = fs.mkdirAll(path.Dir(newpath), 0755) 712 if err != nil && !os.IsExist(err) { 713 return err 714 } 715 716 oldParent, _, oldBase, err := fs.lookupParent(oldpath) 717 if err != nil { 718 return err 719 } 720 721 newParent, _, newBase, err := fs.lookupParent(newpath) 722 if err != nil { 723 return err 724 } 725 726 return fs.config.KBFSOps().Rename( 727 fs.ctx, oldParent, oldParent.ChildName(oldBase), newParent, 728 newParent.ChildName(newBase)) 729 } 730 731 // Remove implements the billy.Filesystem interface for FS. 732 func (fs *FS) Remove(filename string) (err error) { 733 fs.log.CDebugf(fs.ctx, "Remove %s", fs.PathForLogging(filename)) 734 defer func() { 735 fs.deferLog.CDebugf(fs.ctx, "Remove done: %+v", err) 736 err = translateErr(err) 737 }() 738 739 if err := fs.chooseErrorIfEmpty(onFsEmptyErrNotSupported); err != nil { 740 return err 741 } 742 743 parent, _, base, err := fs.lookupParent(filename) 744 if err != nil { 745 return err 746 } 747 748 basePart := parent.ChildName(base) 749 _, ei, err := fs.config.KBFSOps().Lookup(fs.ctx, parent, basePart) 750 if err != nil { 751 return err 752 } 753 754 if ei.Type == data.Dir { 755 return fs.config.KBFSOps().RemoveDir(fs.ctx, parent, basePart) 756 } 757 return fs.config.KBFSOps().RemoveEntry(fs.ctx, parent, basePart) 758 } 759 760 // Join implements the billy.Filesystem interface for FS. 761 func (fs *FS) Join(elem ...string) string { 762 return path.Clean(path.Join(elem...)) 763 } 764 765 // TempFile implements the billy.Filesystem interface for FS. 766 func (fs *FS) TempFile(dir, prefix string) (billy.File, error) { 767 if err := fs.chooseErrorIfEmpty(onFsEmptyErrNotSupported); err != nil { 768 return nil, err 769 } 770 771 // We'd have to turn off journaling to support TempFile perfectly, 772 // but the given uniq ID and a random number should be good 773 // enough. Especially since most users will end up renaming the 774 // temp file before journal flushing even happens. 775 b := make([]byte, 8) 776 _, err := rand.Read(b) 777 if err != nil { 778 return nil, err 779 } 780 suffix := fs.uniqID + "-" + base64.URLEncoding.EncodeToString(b) 781 return fs.OpenFile(path.Join(dir, prefix+suffix), 782 os.O_CREATE|os.O_EXCL, 0600) 783 } 784 785 func (fs *FS) readDir(n libkbfs.Node) (fis []os.FileInfo, err error) { 786 children, err := fs.config.KBFSOps().GetDirChildren(fs.ctx, n) 787 if err != nil { 788 return nil, err 789 } 790 791 fis = make([]os.FileInfo, 0, len(children)) 792 for name, ei := range children { 793 var child libkbfs.Node 794 if !IsFastModeEnabled(fs.ctx) { // node is not used in FileInfoFast 795 child, _, err = fs.config.KBFSOps().Lookup(fs.ctx, n, name) 796 if err != nil { 797 return nil, err 798 } 799 } 800 801 fis = append(fis, fs.makeFileInfo(ei, child, name.Plaintext())) 802 } 803 return fis, nil 804 } 805 806 // ReadDir implements the billy.Filesystem interface for FS. 807 func (fs *FS) ReadDir(p string) (fis []os.FileInfo, err error) { 808 fs.log.CDebugf(fs.ctx, "ReadDir %s", fs.PathForLogging(p)) 809 defer func() { 810 fs.deferLog.CDebugf(fs.ctx, "ReadDir done: %+v", err) 811 err = translateErr(err) 812 }() 813 814 if fs.empty && (p == "" || p == "." || p == "/") { 815 return nil, nil 816 } else if err := fs.chooseErrorIfEmpty(onFsEmptyErrNotExist); err != nil { 817 return nil, err 818 } 819 820 n, _, err := fs.lookupOrCreateEntry(p, os.O_RDONLY, 0) 821 if err != nil { 822 return nil, err 823 } 824 return fs.readDir(n) 825 } 826 827 // MkdirAll implements the billy.Filesystem interface for FS. 828 func (fs *FS) MkdirAll(filename string, perm os.FileMode) (err error) { 829 fs.log.CDebugf(fs.ctx, "MkdirAll %s", fs.PathForLogging(filename)) 830 defer func() { 831 fs.deferLog.CDebugf(fs.ctx, "MkdirAll done: %+v", err) 832 }() 833 834 if err := fs.chooseErrorIfEmpty(onFsEmptyErrNotSupported); err != nil { 835 return err 836 } 837 838 return fs.mkdirAll(filename, perm) 839 } 840 841 // Lstat implements the billy.Filesystem interface for FS. 842 func (fs *FS) Lstat(filename string) (fi os.FileInfo, err error) { 843 fs.log.CDebugf(fs.ctx, "Lstat %s", fs.PathForLogging(filename)) 844 defer func() { 845 fs.deferLog.CDebugf(fs.ctx, "Lstat done: %+v", err) 846 err = translateErr(err) 847 }() 848 849 if fs.empty && (filename == "" || filename == ".") { 850 // We can't just uncondionally use FileInfoFast here as that'd result 851 // in WritePerm unset for non-existent TLFs. 852 return fs.makeFileInfo(data.EntryInfo{ 853 Type: data.Dir, 854 }, nil, filename), nil 855 } else if err := fs.chooseErrorIfEmpty(onFsEmptyErrNotExist); err != nil { 856 return nil, err 857 } 858 859 n, _, base, err := fs.lookupParent(filename) 860 if err != nil { 861 return nil, err 862 } 863 864 if base == "" { 865 ei, err := fs.config.KBFSOps().Stat(fs.ctx, n) 866 if err != nil { 867 return nil, err 868 } 869 return fs.makeFileInfo(ei, n, ""), nil 870 } 871 872 n, ei, err := fs.config.KBFSOps().Lookup(fs.ctx, n, n.ChildName(base)) 873 if err != nil { 874 return nil, err 875 } 876 877 return fs.makeFileInfo(ei, n, base), nil 878 } 879 880 // Symlink implements the billy.Filesystem interface for FS. 881 func (fs *FS) Symlink(target, link string) (err error) { 882 fs.log.CDebugf(fs.ctx, "Symlink target=%s link=%s", 883 fs.PathForLogging(target), fs.root.ChildName(link)) 884 defer func() { 885 fs.deferLog.CDebugf(fs.ctx, "Symlink done: %+v", err) 886 err = translateErr(err) 887 }() 888 889 if err := fs.chooseErrorIfEmpty(onFsEmptyErrNotSupported); err != nil { 890 return err 891 } 892 893 err = fs.ensureParentDir(link) 894 if err != nil { 895 return err 896 } 897 898 n, _, base, err := fs.lookupParent(link) 899 if err != nil { 900 return err 901 } 902 903 _, err = fs.config.KBFSOps().CreateLink( 904 fs.ctx, n, n.ChildName(base), n.ChildName(target)) 905 return err 906 } 907 908 // Readlink implements the billy.Filesystem interface for FS. 909 func (fs *FS) Readlink(link string) (target string, err error) { 910 fs.log.CDebugf(fs.ctx, "Readlink %s", fs.PathForLogging(link)) 911 defer func() { 912 fs.deferLog.CDebugf(fs.ctx, "Readlink done: %+v", err) 913 err = translateErr(err) 914 }() 915 916 if err := fs.chooseErrorIfEmpty(onFsEmptyErrNotExist); err != nil { 917 return "", err 918 } 919 920 n, _, base, err := fs.lookupParent(link) 921 if err != nil { 922 return "", err 923 } 924 925 _, ei, err := fs.config.KBFSOps().Lookup(fs.ctx, n, n.ChildName(base)) 926 if err != nil { 927 return "", err 928 } 929 930 if ei.Type != data.Sym { 931 return "", errors.Errorf("%s is not a symlink", link) 932 } 933 return ei.SymPath, nil 934 } 935 936 // Chmod implements the billy.Filesystem interface for FS. 937 func (fs *FS) Chmod(name string, mode os.FileMode) (err error) { 938 fs.log.CDebugf(fs.ctx, "Chmod %s %s", fs.PathForLogging(name), mode) 939 defer func() { 940 fs.deferLog.CDebugf(fs.ctx, "Chmod done: %+v", err) 941 err = translateErr(err) 942 }() 943 944 if err := fs.chooseErrorIfEmpty(onFsEmptyErrNotSupported); err != nil { 945 return err 946 } 947 948 n, _, err := fs.lookupOrCreateEntry(name, os.O_RDONLY, 0) 949 if err != nil { 950 return err 951 } 952 953 isExec := (mode & 0100) != 0 954 return fs.config.KBFSOps().SetEx(fs.ctx, n, isExec) 955 } 956 957 // Lchown implements the billy.Filesystem interface for FS. 958 func (fs *FS) Lchown(name string, uid, gid int) error { 959 // KBFS doesn't support ownership changes. 960 fs.log.CDebugf(fs.ctx, "Ignoring Lchown %s %d %d", 961 fs.PathForLogging(name), uid, gid) 962 return nil 963 } 964 965 // Chown implements the billy.Filesystem interface for FS. 966 func (fs *FS) Chown(name string, uid, gid int) error { 967 // KBFS doesn't support ownership changes. 968 fs.log.CDebugf(fs.ctx, "Ignoring Chown %s %d %d", 969 fs.PathForLogging(name), uid, gid) 970 return nil 971 } 972 973 // Chtimes implements the billy.Filesystem interface for FS. 974 func (fs *FS) Chtimes(name string, atime time.Time, mtime time.Time) ( 975 err error) { 976 fs.log.CDebugf(fs.ctx, "Chtimes %s mtime=%s; ignoring atime=%s", 977 fs.PathForLogging(name), mtime, atime) 978 defer func() { 979 fs.deferLog.CDebugf(fs.ctx, "Chtimes done: %+v", err) 980 err = translateErr(err) 981 }() 982 983 if err := fs.chooseErrorIfEmpty(onFsEmptyErrNotSupported); err != nil { 984 return err 985 } 986 987 n, _, err := fs.lookupOrCreateEntry(name, os.O_RDONLY, 0) 988 if err != nil { 989 return err 990 } 991 992 return fs.config.KBFSOps().SetMtime(fs.ctx, n, &mtime) 993 } 994 995 // ChrootAsLibFS returns a *FS whose root is p. 996 func (fs *FS) ChrootAsLibFS(p string) (newFS *FS, err error) { 997 fs.log.CDebugf(fs.ctx, "Chroot %s", fs.PathForLogging(p)) 998 defer func() { 999 fs.deferLog.CDebugf(fs.ctx, "Chroot done: %+v", err) 1000 err = translateErr(err) 1001 }() 1002 1003 if p == "" || p == "." { 1004 return fs, nil 1005 } 1006 1007 if err := fs.chooseErrorIfEmpty(onFsEmptyErrNotExist); err != nil { 1008 return nil, err 1009 } 1010 1011 // lookupOrCreateEntry doesn't handle "..", so we don't have to 1012 // worry about someone trying to break out of the jail since this 1013 // lookup will fail. 1014 n, ei, err := fs.lookupOrCreateEntry(p, os.O_RDONLY, 0) 1015 if err != nil { 1016 return nil, err 1017 } 1018 1019 return &FS{ 1020 ctx: fs.ctx, 1021 fsInner: &fsInner{ 1022 config: fs.config, 1023 root: n, 1024 rootInfo: ei, 1025 h: fs.h, 1026 subdir: path.Clean(path.Join(fs.subdir, p)), 1027 uniqID: fs.uniqID, 1028 log: fs.log, 1029 deferLog: fs.deferLog, 1030 1031 // Original lock namespace plus '/' plus the subdir. 1032 lockNamespace: bytes.Join( 1033 [][]byte{fs.lockNamespace, []byte(p)}, []byte{'/'}), 1034 priority: fs.priority, 1035 events: make(map[chan<- FSEvent]bool), 1036 }, 1037 }, nil 1038 } 1039 1040 // Chroot implements the billy.Filesystem interface for FS. 1041 func (fs *FS) Chroot(p string) (newFS billy.Filesystem, err error) { 1042 return fs.ChrootAsLibFS(p) 1043 } 1044 1045 // Root implements the billy.Filesystem interface for FS. 1046 func (fs *FS) Root() string { 1047 return path.Join(fs.h.GetCanonicalPath(), fs.subdir) 1048 } 1049 1050 // SyncAll syncs any outstanding buffered writes to the KBFS journal. 1051 func (fs *FS) SyncAll() error { 1052 if err := fs.chooseErrorIfEmpty(onFsEmptyErrNotSupported); err != nil { 1053 return err 1054 } 1055 return fs.config.KBFSOps().SyncAll(fs.ctx, fs.root.GetFolderBranch()) 1056 } 1057 1058 // Config returns the underlying Config object of this FS. 1059 func (fs *FS) Config() libkbfs.Config { 1060 return fs.config 1061 } 1062 1063 // SetLockNamespace sets the namespace used in locking. 1064 func (fs *FS) SetLockNamespace(lockNamespace []byte) { 1065 fs.lockNamespace = make([]byte, len(lockNamespace)) 1066 copy(fs.lockNamespace, lockNamespace) 1067 } 1068 1069 // GetLockNamespace returns the namespace used in locking. 1070 func (fs *FS) GetLockNamespace() (lockNamespace []byte) { 1071 return fs.lockNamespace 1072 } 1073 1074 // SubscribeToEvents causes *File objects constructed from this *FS to send 1075 // events to the channel at beginning of Lock and Unlock. The send is done 1076 // blockingly so caller needs to drain the channel properly or make it buffered 1077 // with enough size. 1078 func (fs *FS) SubscribeToEvents(ch chan<- FSEvent) { 1079 fs.eventsLock.Lock() 1080 defer fs.eventsLock.Unlock() 1081 fs.events[ch] = true 1082 } 1083 1084 // UnsubscribeToEvents stops *File objects constructed from this *FS from 1085 // sending events to ch. It also closes ch. 1086 func (fs *FS) UnsubscribeToEvents(ch chan<- FSEvent) { 1087 fs.eventsLock.Lock() 1088 defer fs.eventsLock.Unlock() 1089 delete(fs.events, ch) 1090 close(ch) 1091 } 1092 1093 func (fs *FS) sendEvents(e FSEvent) { 1094 fs.eventsLock.RLock() 1095 defer fs.eventsLock.RUnlock() 1096 for ch := range fs.events { 1097 ch <- e 1098 } 1099 } 1100 1101 // WithContext returns a *FS based on fs, with its ctx replaced with ctx. 1102 func (fs *FS) WithContext(ctx context.Context) *FS { 1103 return &FS{ 1104 ctx: ctx, 1105 fsInner: fs.fsInner, 1106 } 1107 } 1108 1109 // ToHTTPFileSystem calls fs.WithCtx with ctx to create a *FS with the new ctx, 1110 // and returns a wrapper around it that satisfies the http.FileSystem 1111 // interface. 1112 func (fs *FS) ToHTTPFileSystem(ctx context.Context) http.FileSystem { 1113 return httpFileSystem{fs: fs.WithContext(ctx)} 1114 } 1115 1116 // RootNode returns the Node of the root directory of this FS. 1117 func (fs *FS) RootNode() libkbfs.Node { 1118 return fs.root 1119 } 1120 1121 // Handle returns the TLF handle corresponding to this FS. 1122 func (fs *FS) Handle() *tlfhandle.Handle { 1123 return fs.h 1124 } 1125 1126 type folderHandleChangeObserver func() 1127 1128 func (folderHandleChangeObserver) LocalChange( 1129 context.Context, libkbfs.Node, libkbfs.WriteRange) { 1130 } 1131 func (folderHandleChangeObserver) BatchChanges( 1132 context.Context, []libkbfs.NodeChange, []libkbfs.NodeID) { 1133 } 1134 func (o folderHandleChangeObserver) TlfHandleChange( 1135 context.Context, *tlfhandle.Handle) { 1136 o() 1137 } 1138 1139 // SubscribeToObsolete returns a channel that will be closed when this *FS 1140 // reaches obsolescence, meaning if user of this object caches it for long term 1141 // use, it should invalide this entry and create a new one using NewFS. 1142 func (fs *FS) SubscribeToObsolete() (<-chan struct{}, error) { 1143 if err := fs.chooseErrorIfEmpty(onFsEmptyErrNotSupported); err != nil { 1144 return nil, err 1145 } 1146 1147 c := make(chan struct{}) 1148 var once sync.Once 1149 onHandleChange := folderHandleChangeObserver( 1150 func() { once.Do(func() { close(c) }) }) 1151 if err := fs.config.Notifier().RegisterForChanges( 1152 []data.FolderBranch{fs.root.GetFolderBranch()}, 1153 onHandleChange); err != nil { 1154 return nil, err 1155 } 1156 return c, nil 1157 } 1158 1159 // IsEmpty returns true if this is a faked-out empty TLF. 1160 func (fs *FS) IsEmpty() bool { 1161 return fs.empty 1162 }