/
githubmirror
/
client
Обзор
Документация
Войти
/
githubmirror
/
client
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
go/kbfs/libfuse/dir.go
984 строки
29 KB
zoom-ua
modernize error handling with errors.As/Is (#29350)
25 июн 2026, 17:41
Не верифицирован
25 июн 2026, 17:41
e09e254
Код
Авторство
О чём код?
// Copyright 2016 Keybase Inc. All rights reserved. // Use of this source code is governed by a BSD // license that can be found in the LICENSE file. // //go:build !windows package libfuse import ( "context" "errors" "fmt" "math" "os" "strings" "sync" "syscall" "time" "bazil.org/fuse" "bazil.org/fuse/fs" "github.com/keybase/client/go/kbfs/data" "github.com/keybase/client/go/kbfs/idutil" "github.com/keybase/client/go/kbfs/libcontext" "github.com/keybase/client/go/kbfs/libfs" "github.com/keybase/client/go/kbfs/libkbfs" "github.com/keybase/client/go/kbfs/sysutils" "github.com/keybase/client/go/kbfs/tlf" "github.com/keybase/client/go/kbfs/tlfhandle" "github.com/keybase/client/go/libkb" ) // Folder represents the info shared among all nodes of a KBFS // top-level folder. type Folder struct { fs *FS list *FolderList handleMu sync.RWMutex h *tlfhandle.Handle hPreferredName tlf.PreferredName folderBranchMu sync.Mutex folderBranch data.FolderBranch // Protects the nodes map. nodesMu sync.Mutex // Map KBFS nodes to FUSE nodes, to be able to handle multiple // lookups and incoming change notifications. A node is present // here if the kernel holds a reference to it. // // If we ever support hardlinks, this would need refcounts. // // Children must call folder.forgetChildLocked on receiving the // FUSE Forget request. nodes map[libkbfs.NodeID]fs.Node // Protects the updateChan. updateMu sync.Mutex // updateChan is non-nil when the user disables updates via the // file system. Sending a struct{}{} on this channel will unpause // the updates. updateChan chan<- struct{} quarantine bool } func newFolder(ctx context.Context, fl *FolderList, h *tlfhandle.Handle, hPreferredName tlf.PreferredName, ) *Folder { f := &Folder{ fs: fl.fs, list: fl, h: h, hPreferredName: hPreferredName, nodes: map[libkbfs.NodeID]fs.Node{}, quarantine: !libkbfs.IsOnlyWriterInNonTeamTlf(ctx, fl.fs.config.KBPKI(), h), } return f } func (f *Folder) name() tlf.CanonicalName { f.handleMu.RLock() defer f.handleMu.RUnlock() return tlf.CanonicalName(f.hPreferredName) } func (f *Folder) processError(ctx context.Context, mode libkbfs.ErrorModeType, err error, ) error { if err == nil { f.fs.errVlog.CLogf(ctx, libkb.VLog1, "Request complete") return nil } f.fs.config.Reporter().ReportErr(ctx, f.name(), f.list.tlfType, mode, err) // We just log the error as debug, rather than error, because it // might just indicate an expected error such as an ENOENT. // // TODO: Classify errors and escalate the logging level of the // important ones. f.fs.errLog.CDebugf(ctx, err.Error()) return filterError(err) } func (f *Folder) setFolderBranch(folderBranch data.FolderBranch) error { f.folderBranchMu.Lock() defer f.folderBranchMu.Unlock() // TODO unregister all at unmount err := f.list.fs.config.Notifier().RegisterForChanges( []data.FolderBranch{folderBranch}, f) if err != nil { return err } f.folderBranch = folderBranch return nil } func (f *Folder) unsetFolderBranch(ctx context.Context) { f.folderBranchMu.Lock() defer f.folderBranchMu.Unlock() if f.folderBranch == (data.FolderBranch{}) { // Wasn't set. return } err := f.list.fs.config.Notifier().UnregisterFromChanges([]data.FolderBranch{f.folderBranch}, f) if err != nil { f.fs.log.Info("cannot unregister change notifier for folder %q: %v", f.name(), err) } f.folderBranch = data.FolderBranch{} } func (f *Folder) getFolderBranch() data.FolderBranch { f.folderBranchMu.Lock() defer f.folderBranchMu.Unlock() return f.folderBranch } // forgetNode forgets a formerly active child with basename name. func (f *Folder) forgetNode(node libkbfs.Node) { f.nodesMu.Lock() defer f.nodesMu.Unlock() delete(f.nodes, node.GetID()) if len(f.nodes) == 0 { ctx := libcontext.BackgroundContextWithCancellationDelayer() defer func() { err := libcontext.CleanupCancellationDelayer(ctx) if err != nil { f.fs.log.CDebugf(ctx, "Coudn't cleanup ctx: %+v", err) } }() f.unsetFolderBranch(ctx) f.list.forgetFolder(string(f.name())) } } var _ libkbfs.Observer = (*Folder)(nil) func (f *Folder) resolve(ctx context.Context) (*tlfhandle.Handle, error) { if f.h.TlfID() == tlf.NullID { // If the handle doesn't have a TLF ID yet, fetch it now. handle, err := tlfhandle.ParseHandlePreferred( ctx, f.fs.config.KBPKI(), f.fs.config.MDOps(), f.fs.config, string(f.hPreferredName), f.h.Type()) if err != nil { return nil, err } // Update the handle. f.TlfHandleChange(ctx, handle) return handle, nil } // In case there were any unresolved assertions, try them again on // the first load. Otherwise, since we haven't subscribed to // updates yet for this folder, we might have missed a name // change. handle, err := f.h.ResolveAgain( ctx, f.fs.config.KBPKI(), f.fs.config.MDOps(), f.fs.config) if err != nil { return nil, err } eq, err := f.h.Equals(f.fs.config.Codec(), *handle) if err != nil { return nil, err } if !eq { // Make sure the name changes in the folder and the folder list f.TlfHandleChange(ctx, handle) } return handle, nil } // invalidateNodeDataRange notifies the kernel to invalidate cached data for node. // // The arguments follow KBFS semantics: // // - Len > 0: "bytes Off..Off+Len were mutated" // - Len == 0: "new file Len is Off" // // For comparison, the FUSE semantics are: // // - Len < 0: "forget data in range Off..infinity" // - Len > 0: "forget data in range Off..Off+Len" func (f *Folder) invalidateNodeDataRange(node fs.Node, write libkbfs.WriteRange) error { if file, ok := node.(*File); ok { file.eiCache.destroy() } off := int64(write.Off) size := int64(write.Len) if write.Off > math.MaxInt64 || write.Len > math.MaxInt64 { // out of bounds, just invalidate all data off = 0 size = -1 } if write.Len == 0 { // truncate, invalidate all data in the now-lost tail size = -1 } // Off=0 Len=0 is the same as calling InvalidateNodeDataAttr; we // can just let that go through InvalidateNodeDataRange. return f.fs.fuse.InvalidateNodeDataRange(node, off, size) } // LocalChange is called for changes originating within in this process. func (f *Folder) LocalChange(ctx context.Context, node libkbfs.Node, write libkbfs.WriteRange) { if !f.fs.conn.Protocol().HasInvalidate() { // OSXFUSE 2.x does not support notifications return } if origin, ok := ctx.Value(libfs.CtxAppIDKey).(*FS); ok && origin == f.fs { return } // Handle in the background because we shouldn't lock during the // notification. f.fs.queueNotification(func() { f.localChangeInvalidate(ctx, node, write) }) } func (f *Folder) localChangeInvalidate(ctx context.Context, node libkbfs.Node, write libkbfs.WriteRange, ) { f.nodesMu.Lock() n, ok := f.nodes[node.GetID()] f.nodesMu.Unlock() if !ok { return } if err := f.invalidateNodeDataRange(n, write); err != nil && !errors.Is(err, fuse.ErrNotCached) { // TODO we have no mechanism to do anything about this f.fs.log.CErrorf(ctx, "FUSE invalidate error: %v", err) } } // BatchChanges is called for changes originating anywhere, including // other hosts. func (f *Folder) BatchChanges( ctx context.Context, changes []libkbfs.NodeChange, _ []libkbfs.NodeID, ) { if !f.fs.conn.Protocol().HasInvalidate() { // OSXFUSE 2.x does not support notifications return } if origin, ok := ctx.Value(libfs.CtxAppIDKey).(*FS); ok && origin == f.fs { return } if v := ctx.Value(libkbfs.CtxBackgroundSyncKey); v != nil { return } // Handle in the background because we shouldn't lock during the // notification. f.fs.queueNotification(func() { f.batchChangesInvalidate(ctx, changes) }) } func (f *Folder) batchChangesInvalidate(ctx context.Context, changes []libkbfs.NodeChange, ) { for _, v := range changes { f.nodesMu.Lock() n, ok := f.nodes[v.Node.GetID()] f.nodesMu.Unlock() if !ok { continue } switch { case len(v.DirUpdated) > 0: // invalidate potentially cached Readdir contents if err := f.fs.fuse.InvalidateNodeData(n); err != nil && !errors.Is(err, fuse.ErrNotCached) { // TODO we have no mechanism to do anything about this f.fs.log.CErrorf(ctx, "FUSE invalidate error: %v", err) } for _, name := range v.DirUpdated { // invalidate the dentry cache if err := f.fs.fuse.InvalidateEntry(n, name.Plaintext()); err != nil && !errors.Is(err, fuse.ErrNotCached) { // TODO we have no mechanism to do anything about this f.fs.log.CErrorf(ctx, "FUSE invalidate error: %v", err) } } case len(v.FileUpdated) > 0: for _, write := range v.FileUpdated { if err := f.invalidateNodeDataRange(n, write); err != nil && !errors.Is(err, fuse.ErrNotCached) { // TODO we have no mechanism to do anything about this f.fs.log.CErrorf(ctx, "FUSE invalidate error: %v", err) } } default: if file, ok := n.(*File); ok { file.eiCache.destroy() } // just the attributes if err := f.fs.fuse.InvalidateNodeAttr(n); err != nil && !errors.Is(err, fuse.ErrNotCached) { // TODO we have no mechanism to do anything about this f.fs.log.CErrorf(ctx, "FUSE invalidate error: %v", err) } } } } // TlfHandleChange is called when the name of a folder changes. // Note that newHandle may be nil. Then the handle in the folder is used. // This is used on e.g. logout/login. func (f *Folder) TlfHandleChange(ctx context.Context, newHandle *tlfhandle.Handle, ) { f.fs.log.CDebugf(ctx, "TlfHandleChange called on %q", canonicalNameIfNotNil(newHandle)) // Handle in the background because we shouldn't lock during the // notification f.fs.queueNotification(func() { f.tlfHandleChangeInvalidate(context.Background(), newHandle) }) } func canonicalNameIfNotNil(h *tlfhandle.Handle) string { if h == nil { return "(nil)" } return string(h.GetCanonicalName()) } func (f *Folder) tlfHandleChangeInvalidate(ctx context.Context, newHandle *tlfhandle.Handle, ) { session, err := idutil.GetCurrentSessionIfPossible( ctx, f.fs.config.KBPKI(), f.list.tlfType == tlf.Public) // Here we get an error, but there is little that can be done. // session will be empty in the error case in which case we will default to the // canonical format. if err != nil { f.fs.log.CDebugf(ctx, "tlfHandleChangeInvalidate: GetCurrentUserInfoIfPossible failed: %v", err) } oldName, newName := func() (tlf.PreferredName, tlf.PreferredName) { f.handleMu.Lock() defer f.handleMu.Unlock() oldName := f.hPreferredName if newHandle != nil { f.h = newHandle } f.hPreferredName = f.h.GetPreferredFormat(session.Name) return oldName, f.hPreferredName }() if oldName != newName { f.list.updateTlfName(ctx, string(oldName), string(newName)) } } func (f *Folder) writePermMode(ctx context.Context, node libkbfs.Node, original os.FileMode, ) (os.FileMode, error) { f.handleMu.RLock() defer f.handleMu.RUnlock() return libfs.WritePermMode( ctx, node, original, f.fs.config.KBPKI(), f.fs.config, f.h) } // fillAttrWithUIDAndWritePerm sets attributes based on the entry info, and // pops in correct UID and write permissions. It only handles fields common to // all entryinfo types. func (f *Folder) fillAttrWithUIDAndWritePerm( ctx context.Context, node libkbfs.Node, ei *data.EntryInfo, a *fuse.Attr, ) (err error) { a.Valid = 1 * time.Minute node.FillCacheDuration(&a.Valid) a.Size = ei.Size a.Blocks = getNumBlocksFromSize(ei.Size) a.Mtime = time.Unix(0, ei.Mtime) a.Ctime = time.Unix(0, ei.Ctime) a.Uid = uint32(os.Getuid()) //nolint:gosec // G115: UID values are bounded by system limits if a.Mode, err = f.writePermMode(ctx, node, a.Mode); err != nil { return err } return nil } func (f *Folder) isWriter(ctx context.Context) (bool, error) { f.handleMu.RLock() defer f.handleMu.RUnlock() return libfs.IsWriter(ctx, f.fs.config.KBPKI(), f.fs.config, f.h) } func (f *Folder) access(ctx context.Context, r *fuse.AccessRequest) error { if int(r.Uid) != os.Getuid() && // Finder likes to use UID 0 for some operations. osxfuse already allows // ACCESS and GETXATTR requests from root to go through. This allows root // in ACCESS handler. See KBFS-1733 for more details. int(r.Uid) != 0 { // short path: not accessible by anybody other than root or the user who // executed the kbfsfuse process. return fuse.EPERM } if r.Mask&0o2 == 0 { // For directory, we only check for the w bit. return nil } iw, err := f.isWriter(ctx) if err != nil { return nil } if !iw { return fuse.EPERM } return nil } func (f *Folder) openFileCount() int64 { f.nodesMu.Lock() defer f.nodesMu.Unlock() count := int64(len(f.nodes)) if count > 0 { // The root node itself should only be counted by the folder // list, not here. count-- } return count } // TODO: Expire TLF nodes periodically. See // https://keybase.atlassian.net/browse/KBFS-59 . // DirInterface gathers all the interfaces a Dir or something that // wraps a Dir should implement. type DirInterface interface { fs.Node fs.NodeAccesser fs.NodeRequestLookuper fs.NodeCreater fs.NodeMkdirer fs.NodeSymlinker fs.NodeRenamer fs.NodeRemover fs.Handle fs.HandleReadDirAller fs.NodeForgetter fs.NodeSetattrer fs.NodeFsyncer fs.NodeGetxattrer fs.NodeSetxattrer } // Dir represents a subdirectory of a KBFS top-level folder (including // the TLF root directory itself). type Dir struct { folder *Folder node libkbfs.Node inode uint64 XattrHandler } func newDirWithInode(folder *Folder, node libkbfs.Node, inode uint64) *Dir { d := &Dir{ folder: folder, node: node, inode: inode, } if folder.quarantine { d.XattrHandler = NewQuarantineXattrHandler(node, folder) } else { d.XattrHandler = NoXattrHandler{} } return d } func newDir(folder *Folder, node libkbfs.Node) *Dir { return newDirWithInode(folder, node, folder.fs.assignInode()) } var _ DirInterface = (*Dir)(nil) // Access implements the fs.NodeAccesser interface for File. See comment for // File.Access for more details. func (d *Dir) Access(ctx context.Context, r *fuse.AccessRequest) (err error) { ctx = d.folder.fs.config.MaybeStartTrace( ctx, "Dir.Access", d.node.GetBasename().String()) defer func() { d.folder.fs.config.MaybeFinishTrace(ctx, err) }() return d.folder.access(ctx, r) } // Attr implements the fs.Node interface for Dir. func (d *Dir) Attr(ctx context.Context, a *fuse.Attr) (err error) { ctx = d.folder.fs.config.MaybeStartTrace( ctx, "Dir.Attr", d.node.GetBasename().String()) defer func() { d.folder.fs.config.MaybeFinishTrace(ctx, err) }() d.folder.fs.vlog.CLogf(ctx, libkb.VLog1, "Dir Attr") defer func() { err = d.folder.processError(ctx, libkbfs.ReadMode, err) }() // This fits in situation 1 as described in libkbfs/delayed_cancellation.go err = libcontext.EnableDelayedCancellationWithGracePeriod( ctx, d.folder.fs.config.DelayedCancellationGracePeriod()) if err != nil { return err } return d.attr(ctx, a) } func (d *Dir) attr(ctx context.Context, a *fuse.Attr) (err error) { de, err := d.folder.fs.config.KBFSOps().Stat(ctx, d.node) if err != nil { if isNoSuchNameError(err) { return fuse.ESTALE } return err } if err = d.folder.fillAttrWithUIDAndWritePerm( ctx, d.node, &de, a); err != nil { return err } a.Mode |= os.ModeDir | 0o500 a.Inode = d.inode return nil } func (d *Dir) makeFile(node libkbfs.Node) (file *File) { file = &File{ folder: d.folder, node: node, inode: d.folder.fs.assignInode(), } if d.folder.quarantine { file.XattrHandler = NewQuarantineXattrHandler(node, d.folder) } else { file.XattrHandler = NoXattrHandler{} } return file } // Lookup implements the fs.NodeRequestLookuper interface for Dir. func (d *Dir) Lookup(ctx context.Context, req *fuse.LookupRequest, resp *fuse.LookupResponse) (node fs.Node, err error) { namePPS := d.node.ChildName(req.Name) ctx = d.folder.fs.config.MaybeStartTrace(ctx, "Dir.Lookup", fmt.Sprintf("%s %s", d.node.GetBasename(), namePPS)) defer func() { d.folder.fs.config.MaybeFinishTrace(ctx, err) }() d.folder.fs.vlog.CLogf(ctx, libkb.VLog1, "Dir Lookup %s", namePPS) defer func() { err = d.folder.processError(ctx, libkbfs.ReadMode, err) }() // This fits in situation 1 as described in libkbfs/delayed_cancellation.go err = libcontext.EnableDelayedCancellationWithGracePeriod( ctx, d.folder.fs.config.DelayedCancellationGracePeriod()) if err != nil { return nil, err } specialNode := handleTLFSpecialFile( req.Name, d.folder, &resp.EntryValid) if specialNode != nil { return specialNode, nil } // Check if this is a per-file metainformation file, if so // return the corresponding SpecialReadFile. if strings.HasPrefix(req.Name, libfs.FileInfoPrefix) { name := req.Name[len(libfs.FileInfoPrefix):] return NewFileInfoFile(d.folder.fs, d.node, name, &resp.EntryValid), nil } newNode, de, err := d.folder.fs.config.KBFSOps().Lookup( ctx, d.node, namePPS) if err != nil { if _, ok := err.(idutil.NoSuchNameError); ok { return nil, fuse.ENOENT } return nil, err } // No libkbfs calls after this point! d.folder.nodesMu.Lock() defer d.folder.nodesMu.Unlock() // newNode can be nil even without errors when the KBFS direntry // is of a type that doesn't get its own node (is fully contained // in the directory); Symlink does this. if newNode != nil { if n, ok := d.folder.nodes[newNode.GetID()]; ok { return n, nil } newNode.FillCacheDuration(&resp.EntryValid) } switch de.Type { default: return nil, fmt.Errorf("unhandled entry type: %v", de.Type) case data.File, data.Exec: child := d.makeFile(newNode) d.folder.nodes[newNode.GetID()] = child return child, nil case data.Dir: child := newDir(d.folder, newNode) d.folder.nodes[newNode.GetID()] = child return child, nil case data.Sym: // Give each symlink instance a unique inode. We don't get // enough information about remote renames of syminks to be // able to attach a constant inode to a given symlink. child := &Symlink{ parent: d, name: req.Name, inode: d.folder.fs.assignInode(), } // A Symlink is never included in Folder.nodes, as it doesn't // have a libkbfs.Node to keep track of renames. return child, nil } } func getEXCLFromCreateRequest(req *fuse.CreateRequest) libkbfs.Excl { return libkbfs.Excl(req.Flags&fuse.OpenExclusive == fuse.OpenExclusive) } // Create implements the fs.NodeCreater interface for Dir. func (d *Dir) Create(ctx context.Context, req *fuse.CreateRequest, resp *fuse.CreateResponse) (node fs.Node, handle fs.Handle, err error) { namePPS := d.node.ChildName(req.Name) ctx = d.folder.fs.config.MaybeStartTrace(ctx, "Dir.Create", fmt.Sprintf("%s %s", d.node.GetBasename(), namePPS)) defer func() { d.folder.fs.config.MaybeFinishTrace(ctx, err) }() d.folder.fs.vlog.CLogf(ctx, libkb.VLog1, "Dir Create %s", namePPS) defer func() { err = d.folder.processError(ctx, libkbfs.WriteMode, err) }() isExec := (req.Mode.Perm() & 0o100) != 0 excl := getEXCLFromCreateRequest(req) newNode, ei, err := d.folder.fs.config.KBFSOps().CreateFile( ctx, d.node, namePPS, isExec, excl) if err != nil { return nil, nil, err } child := d.makeFile(newNode) // Create is normally followed an Attr call. Fuse uses the same context for // them. If the context is cancelled after the Create call enters the // critical portion, and grace period has passed before Attr happens, the // Attr can result in EINTR which application does not expect. This caches // the EntryInfo for the created node and allows the subsequent Attr call to // use the cached EntryInfo instead of relying on a new Stat call. if reqID, ok := ctx.Value(CtxIDKey).(string); ok { child.eiCache.set(reqID, ei) } d.folder.nodesMu.Lock() d.folder.nodes[newNode.GetID()] = child d.folder.nodesMu.Unlock() return child, child, nil } // Mkdir implements the fs.NodeMkdirer interface for Dir. func (d *Dir) Mkdir(ctx context.Context, req *fuse.MkdirRequest) ( node fs.Node, err error, ) { namePPS := d.node.ChildName(req.Name) ctx = d.folder.fs.config.MaybeStartTrace(ctx, "Dir.Mkdir", fmt.Sprintf("%s %s", d.node.GetBasename(), namePPS)) defer func() { d.folder.fs.config.MaybeFinishTrace(ctx, err) }() d.folder.fs.vlog.CLogf(ctx, libkb.VLog1, "Dir Mkdir %s", namePPS) defer func() { err = d.folder.processError(ctx, libkbfs.WriteMode, err) }() // This fits in situation 1 as described in libkbfs/delayed_cancellation.go err = libcontext.EnableDelayedCancellationWithGracePeriod( ctx, d.folder.fs.config.DelayedCancellationGracePeriod()) if err != nil { return nil, err } newNode, _, err := d.folder.fs.config.KBFSOps().CreateDir( ctx, d.node, namePPS) if err != nil { return nil, err } child := newDir(d.folder, newNode) d.folder.nodesMu.Lock() d.folder.nodes[newNode.GetID()] = child d.folder.nodesMu.Unlock() return child, nil } // Symlink implements the fs.NodeSymlinker interface for Dir. func (d *Dir) Symlink(ctx context.Context, req *fuse.SymlinkRequest) ( node fs.Node, err error, ) { namePPS := d.node.ChildName(req.NewName) targetPPS := d.node.ChildName(req.Target) ctx = d.folder.fs.config.MaybeStartTrace(ctx, "Dir.Symlink", fmt.Sprintf("%s %s -> %s", d.node.GetBasename(), namePPS, targetPPS)) defer func() { d.folder.fs.config.MaybeFinishTrace(ctx, err) }() d.folder.fs.vlog.CLogf( ctx, libkb.VLog1, "Dir Symlink %s -> %s", namePPS, req.Target) defer func() { err = d.folder.processError(ctx, libkbfs.WriteMode, err) }() // This fits in situation 1 as described in libkbfs/delayed_cancellation.go err = libcontext.EnableDelayedCancellationWithGracePeriod( ctx, d.folder.fs.config.DelayedCancellationGracePeriod()) if err != nil { return nil, err } if _, err := d.folder.fs.config.KBFSOps().CreateLink( ctx, d.node, namePPS, targetPPS); err != nil { return nil, err } child := &Symlink{ parent: d, name: req.NewName, inode: d.folder.fs.assignInode(), } return child, nil } var _ fs.NodeLinker = (*Dir)(nil) // Link implements the fs.NodeLinker interface for Dir. func (d *Dir) Link( _ context.Context, _ *fuse.LinkRequest, _ fs.Node, ) (fs.Node, error) { return nil, fuse.ENOTSUP } // Rename implements the fs.NodeRenamer interface for Dir. func (d *Dir) Rename(ctx context.Context, req *fuse.RenameRequest, newDir fs.Node, ) (err error) { oldNamePPS := d.node.ChildName(req.OldName) // We need to log the new name before we have the new node, so // just obfuscate it with the old node for now, it's the best we // can do. newNameLoggingPPS := d.node.ChildName(req.NewName) ctx = d.folder.fs.config.MaybeStartTrace(ctx, "Dir.Rename", fmt.Sprintf("%s %s -> %s", d.node.GetBasename(), oldNamePPS, newNameLoggingPPS)) defer func() { d.folder.fs.config.MaybeFinishTrace(ctx, err) }() d.folder.fs.vlog.CLogf( ctx, libkb.VLog1, "Dir Rename %s -> %s", oldNamePPS, newNameLoggingPPS) defer func() { err = d.folder.processError(ctx, libkbfs.WriteMode, err) }() var realNewDir *Dir switch newDir := newDir.(type) { case *Dir: realNewDir = newDir case *TLF: var err error realNewDir, err = newDir.loadDir(ctx) if err != nil { return err } case *FolderList, *Root: // This normally wouldn't happen since a presumably pre-check on // destination permissions would have failed. But in case it happens, it // should be a EACCES according to rename() man page. return fuse.Errno(syscall.EACCES) default: // This shouldn't happen unless we add other nodes. EIO is not in the error // codes listed in rename(), but there doesn't seem to be any suitable // error code listed for this situation either. return fuse.Errno(syscall.EIO) } err = d.folder.fs.config.KBFSOps().Rename(ctx, d.node, oldNamePPS, realNewDir.node, realNewDir.node.ChildName(req.NewName)) switch e := err.(type) { case nil: return nil case libkbfs.RenameAcrossDirsError: var execPathErr error e.ApplicationExecPath, execPathErr = sysutils.GetExecPathFromPID(req.Pid) if execPathErr != nil { d.folder.fs.log.CDebugf(ctx, "Dir Rename: getting exec path for PID %d error: %v", req.Pid, execPathErr) } return e default: return err } } // Remove implements the fs.NodeRemover interface for Dir. func (d *Dir) Remove(ctx context.Context, req *fuse.RemoveRequest) (err error) { namePPS := d.node.ChildName(req.Name) ctx = d.folder.fs.config.MaybeStartTrace(ctx, "Dir.Remove", fmt.Sprintf("%s %s", d.node.GetBasename(), namePPS)) defer func() { d.folder.fs.config.MaybeFinishTrace(ctx, err) }() d.folder.fs.vlog.CLogf(ctx, libkb.VLog1, "Dir Remove %s", namePPS) defer func() { err = d.folder.processError(ctx, libkbfs.WriteMode, err) }() // This fits in situation 1 as described in libkbfs/delayed_cancellation.go err = libcontext.EnableDelayedCancellationWithGracePeriod( ctx, d.folder.fs.config.DelayedCancellationGracePeriod()) if err != nil { return err } // node will be removed from Folder.nodes, if it is there in the // first place, by its Forget if req.Dir { err = d.folder.fs.config.KBFSOps().RemoveDir(ctx, d.node, namePPS) } else { err = d.folder.fs.config.KBFSOps().RemoveEntry(ctx, d.node, namePPS) } if err != nil { return err } return nil } // ReadDirAll implements the fs.NodeReadDirAller interface for Dir. func (d *Dir) ReadDirAll(ctx context.Context) (res []fuse.Dirent, err error) { ctx = d.folder.fs.config.MaybeStartTrace( ctx, "Dir.ReadDirAll", d.node.GetBasename().String()) defer func() { d.folder.fs.config.MaybeFinishTrace(ctx, err) }() d.folder.fs.vlog.CLogf(ctx, libkb.VLog1, "Dir ReadDirAll") defer func() { err = d.folder.processError(ctx, libkbfs.ReadMode, err) }() children, err := d.folder.fs.config.KBFSOps().GetDirChildren(ctx, d.node) if err != nil { return nil, err } for name, ei := range children { fde := fuse.Dirent{ Name: name.Plaintext(), // Technically we should be setting the inode here, but // since we don't have a proper node for each of these // entries yet we can't generate one, because we don't // have anywhere to save it. So bazil.org/fuse will // generate a random one for each entry, but doesn't store // it anywhere, so it's safe. } switch ei.Type { case data.File, data.Exec: fde.Type = fuse.DT_File case data.Dir: fde.Type = fuse.DT_Dir case data.Sym: fde.Type = fuse.DT_Link } res = append(res, fde) } d.folder.fs.vlog.CLogf(ctx, libkb.VLog1, "Returning %d entries", len(res)) return res, nil } // Forget kernel reference to this node. func (d *Dir) Forget() { d.folder.forgetNode(d.node) } // Setattr implements the fs.NodeSetattrer interface for Dir. func (d *Dir) Setattr(ctx context.Context, req *fuse.SetattrRequest, resp *fuse.SetattrResponse) (err error) { valid := req.Valid ctx = d.folder.fs.config.MaybeStartTrace(ctx, "Dir.Setattr", fmt.Sprintf("%s %s", d.node.GetBasename(), valid)) defer func() { d.folder.fs.config.MaybeFinishTrace(ctx, err) }() d.folder.fs.vlog.CLogf(ctx, libkb.VLog1, "Dir SetAttr %s", valid) defer func() { err = d.folder.processError(ctx, libkbfs.WriteMode, err) }() if valid.Mode() { // You can't set the mode on KBFS directories, but we don't // want to return EPERM because that unnecessarily fails some // applications like unzip. Instead ignore it, print a debug // message, and advertise this behavior on the // "understand_kbfs" doc online. d.folder.fs.vlog.CLogf( ctx, libkb.VLog1, "Ignoring unsupported attempt to set "+ "the mode on a directory") valid &^= fuse.SetattrMode } if valid.Mtime() { err := d.folder.fs.config.KBFSOps().SetMtime( ctx, d.node, &req.Mtime) if err != nil { return err } valid &^= fuse.SetattrMtime | fuse.SetattrMtimeNow } // KBFS has no concept of persistent atime; explicitly don't handle it valid &^= fuse.SetattrAtime | fuse.SetattrAtimeNow // things we don't need to explicitly handle valid &^= fuse.SetattrLockOwner | fuse.SetattrHandle if valid.Uid() || valid.Gid() { // You can't set the UID/GID on KBFS directories, but we don't // want to return ENOSYS because that causes scary warnings on // some programs like mv. Instead ignore it, print a debug // message, and advertise this behavior on the // "understand_kbfs" doc online. d.folder.fs.vlog.CLogf( ctx, libkb.VLog1, "Ignoring unsupported attempt to set "+ "the UID/GID on a directory") valid &^= fuse.SetattrUid | fuse.SetattrGid } if valid != 0 { // don't let an unhandled operation slip by without error d.folder.fs.log.CInfof(ctx, "Setattr did not handle %v", valid) return fuse.ENOSYS } // Something in Linux kernel *requires* directories to provide // attributes here, where it was just an optimization for files. return d.attr(ctx, &resp.Attr) } // Fsync implements the fs.NodeFsyncer interface for Dir. func (d *Dir) Fsync(ctx context.Context, req *fuse.FsyncRequest) (err error) { ctx = d.folder.fs.config.MaybeStartTrace( ctx, "Dir.Fsync", d.node.GetBasename().String()) defer func() { d.folder.fs.config.MaybeFinishTrace(ctx, err) }() d.folder.fs.vlog.CLogf(ctx, libkb.VLog1, "Dir Fsync") defer func() { err = d.folder.processError(ctx, libkbfs.WriteMode, err) }() // This fits in situation 1 as described in libkbfs/delayed_cancellation.go err = libcontext.EnableDelayedCancellationWithGracePeriod( ctx, d.folder.fs.config.DelayedCancellationGracePeriod()) if err != nil { return err } return d.folder.fs.config.KBFSOps().SyncAll(ctx, d.node.GetFolderBranch()) } // isNoSuchNameError checks for libkbfs.NoSuchNameError. func isNoSuchNameError(err error) bool { var nsne idutil.NoSuchNameError return errors.As(err, &nsne) }