github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/libfuse/tlf.go (about) 1 // Copyright 2016 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 //go:build !windows 6 // +build !windows 7 8 package libfuse 9 10 import ( 11 "os" 12 "sync" 13 "time" 14 15 "bazil.org/fuse" 16 "bazil.org/fuse/fs" 17 "github.com/keybase/client/go/kbfs/data" 18 "github.com/keybase/client/go/kbfs/libfs" 19 "github.com/keybase/client/go/kbfs/libkbfs" 20 "github.com/keybase/client/go/kbfs/tlf" 21 "github.com/keybase/client/go/kbfs/tlfhandle" 22 "github.com/keybase/client/go/libkb" 23 "github.com/keybase/client/go/logger" 24 "golang.org/x/net/context" 25 ) 26 27 // TLF represents the root directory of a TLF. It wraps a lazy-loaded 28 // Dir. 29 type TLF struct { 30 folder *Folder 31 inode uint64 32 33 dirLock sync.RWMutex 34 dir *Dir 35 36 // We never set quarantine on TLF roots, so don't bother with getting a dir 37 // and calling the method on it. Instead, just use this dumb handler to 38 // always return fuse.ENOTSUP. 39 NoXattrHandler 40 } 41 42 func newTLF(ctx context.Context, fl *FolderList, h *tlfhandle.Handle, 43 name tlf.PreferredName) *TLF { 44 folder := newFolder(ctx, fl, h, name) 45 tlf := &TLF{ 46 folder: folder, 47 inode: fl.fs.assignInode(), 48 } 49 return tlf 50 } 51 52 var _ DirInterface = (*TLF)(nil) 53 54 func (tlf *TLF) getStoredDir() *Dir { 55 tlf.dirLock.RLock() 56 defer tlf.dirLock.RUnlock() 57 return tlf.dir 58 } 59 60 func (tlf *TLF) clearStoredDir() { 61 tlf.dirLock.Lock() 62 defer tlf.dirLock.Unlock() 63 tlf.dir = nil 64 } 65 66 func (tlf *TLF) log() logger.Logger { 67 return tlf.folder.fs.log 68 } 69 70 func (tlf *TLF) vlog() *libkb.VDebugLog { 71 return tlf.folder.fs.vlog 72 } 73 74 func (tlf *TLF) loadDirHelper( 75 ctx context.Context, mode libkbfs.ErrorModeType, branch data.BranchName, 76 filterErr bool) (dir *Dir, exitEarly bool, err error) { 77 dir = tlf.getStoredDir() 78 if dir != nil { 79 return dir, false, nil 80 } 81 82 tlf.dirLock.Lock() 83 defer tlf.dirLock.Unlock() 84 // Need to check for nilness again to avoid racing with other 85 // calls to loadDir(). 86 if tlf.dir != nil { 87 return tlf.dir, false, nil 88 } 89 90 tlf.log().CDebugf(ctx, "Loading root directory for folder %s "+ 91 "(type: %s, filter error: %t)", tlf.folder.name(), 92 tlf.folder.list.tlfType, filterErr) 93 defer func() { 94 if filterErr { 95 exitEarly, err = libfs.FilterTLFEarlyExitError(ctx, err, tlf.log(), tlf.folder.name()) 96 } 97 err = tlf.folder.processError(ctx, mode, err) 98 }() 99 100 handle, err := tlf.folder.resolve(ctx) 101 if err != nil { 102 return nil, false, err 103 } 104 105 if branch == data.MasterBranch { 106 conflictBranch, isLocalConflictBranch := 107 data.MakeConflictBranchName(handle) 108 if isLocalConflictBranch { 109 branch = conflictBranch 110 } 111 } 112 113 var rootNode libkbfs.Node 114 if filterErr { 115 rootNode, _, err = tlf.folder.fs.config.KBFSOps().GetRootNode( 116 ctx, handle, branch) 117 if err != nil { 118 return nil, false, err 119 } 120 // If not fake an empty directory. 121 if rootNode == nil { 122 return nil, false, libfs.TlfDoesNotExist{} 123 } 124 } else { 125 rootNode, _, err = tlf.folder.fs.config.KBFSOps().GetOrCreateRootNode( 126 ctx, handle, branch) 127 if err != nil { 128 return nil, false, err 129 } 130 } 131 132 err = tlf.folder.setFolderBranch(rootNode.GetFolderBranch()) 133 if err != nil { 134 return nil, false, err 135 } 136 137 tlf.folder.nodes[rootNode.GetID()] = tlf 138 tlf.dir = newDirWithInode(tlf.folder, rootNode, tlf.inode) 139 140 return tlf.dir, false, nil 141 } 142 143 func (tlf *TLF) loadDir(ctx context.Context) (*Dir, error) { 144 dir, _, err := tlf.loadDirHelper( 145 ctx, libkbfs.WriteMode, data.MasterBranch, false) 146 return dir, err 147 } 148 149 // loadDirAllowNonexistent loads a TLF if it's not already loaded. If 150 // the TLF doesn't yet exist, it still returns a nil error and 151 // indicates that the calling function should pretend it's an empty 152 // folder. 153 func (tlf *TLF) loadDirAllowNonexistent(ctx context.Context) ( 154 *Dir, bool, error) { 155 return tlf.loadDirHelper(ctx, libkbfs.ReadMode, data.MasterBranch, true) 156 } 157 158 func (tlf *TLF) loadArchivedDir( 159 ctx context.Context, branch data.BranchName) (*Dir, bool, error) { 160 // Always filter errors for archive TLF directories, so that we 161 // don't try to initialize them. 162 return tlf.loadDirHelper(ctx, libkbfs.ReadMode, branch, true) 163 } 164 165 // Access implements the fs.NodeAccesser interface for *TLF. 166 func (tlf *TLF) Access(ctx context.Context, r *fuse.AccessRequest) error { 167 return tlf.folder.access(ctx, r) 168 } 169 170 // Attr implements the fs.Node interface for TLF. 171 func (tlf *TLF) Attr(ctx context.Context, a *fuse.Attr) error { 172 dir := tlf.getStoredDir() 173 a.Inode = tlf.inode 174 if dir == nil { 175 tlf.vlog().CLogf( 176 ctx, libkb.VLog1, "Faking Attr for TLF %s", tlf.folder.name()) 177 // Have a low non-zero value for Valid to avoid being 178 // swamped with requests, while still not showing 179 // stale data for too long if we end up loading the 180 // dir. 181 a.Valid = 1 * time.Second 182 a.Mode = os.ModeDir | 0500 183 a.Uid = uint32(os.Getuid()) 184 return nil 185 } 186 187 return dir.Attr(ctx, a) 188 } 189 190 // tlfLoadAvoidingLookupNames specifies a set of directory entry names 191 // that should NOT cause a TLF to be fully loaded and identified on a 192 // lookup. If the directory is not yet loaded and one of these names 193 // are looked-up, then ENOENT will be returned automatically. This is 194 // to avoid unnecessary loading and tracker popups when listing the 195 // folder list directories. For example, when macOS finder opens 196 // `/keybase/private`, it looks up `.localized` in every TLF 197 // subdirectory to see if it should translate the TLF folder name or 198 // not, which can cause a tracker popup storm (see KBFS-2649). 199 var tlfLoadAvoidingLookupNames = map[string]bool{ 200 ".localized": true, 201 "Contents": true, 202 } 203 204 // Lookup implements the fs.NodeRequestLookuper interface for TLF. 205 func (tlf *TLF) Lookup(ctx context.Context, req *fuse.LookupRequest, resp *fuse.LookupResponse) (fs.Node, error) { 206 if tlfLoadAvoidingLookupNames[req.Name] { 207 dir := tlf.getStoredDir() 208 if dir == nil { 209 tlf.vlog().CLogf( 210 ctx, libkb.VLog1, "Avoiding TLF loading for name %s", req.Name) 211 return nil, fuse.ENOENT 212 } 213 return dir.Lookup(ctx, req, resp) 214 } 215 216 dir, exitEarly, err := tlf.loadDirAllowNonexistent(ctx) 217 if err != nil { 218 return nil, err 219 } 220 if exitEarly { 221 if node := handleTLFSpecialFile( 222 req.Name, tlf.folder, &resp.EntryValid); node != nil { 223 return node, nil 224 } 225 return nil, fuse.ENOENT 226 } 227 228 branch, isArchivedBranch := libfs.BranchNameFromArchiveRefDir(req.Name) 229 if isArchivedBranch { 230 archivedTLF := newTLF(ctx, 231 tlf.folder.list, tlf.folder.h, tlf.folder.hPreferredName) 232 _, _, err := archivedTLF.loadArchivedDir(ctx, branch) 233 if err != nil { 234 return nil, err 235 } 236 return archivedTLF, nil 237 } 238 239 linkTarget, isArchivedTimeLink, err := libfs.LinkTargetFromTimeString( 240 ctx, tlf.folder.fs.config, tlf.folder.h, req.Name) 241 if err != nil { 242 return nil, err 243 } 244 if isArchivedTimeLink { 245 return &Alias{ 246 realPath: linkTarget, 247 inode: 0, 248 }, nil 249 } 250 251 _, isRelTimeLink, err := libfs.FileDataFromRelativeTimeString( 252 ctx, tlf.folder.fs.config, tlf.folder.h, req.Name) 253 if err != nil { 254 return nil, err 255 } 256 if isRelTimeLink { 257 return NewArchiveRelTimeFile( 258 tlf.folder.fs, tlf.folder.h, req.Name, &resp.EntryValid), nil 259 } 260 261 return dir.Lookup(ctx, req, resp) 262 } 263 264 // Create implements the fs.NodeCreater interface for TLF. 265 func (tlf *TLF) Create(ctx context.Context, req *fuse.CreateRequest, resp *fuse.CreateResponse) (fs.Node, fs.Handle, error) { 266 dir, err := tlf.loadDir(ctx) 267 if err != nil { 268 return nil, nil, err 269 } 270 return dir.Create(ctx, req, resp) 271 } 272 273 // Mkdir implements the fs.NodeMkdirer interface for TLF. 274 func (tlf *TLF) Mkdir(ctx context.Context, req *fuse.MkdirRequest) (_ fs.Node, err error) { 275 dir, err := tlf.loadDir(ctx) 276 if err != nil { 277 return nil, err 278 } 279 return dir.Mkdir(ctx, req) 280 } 281 282 // Symlink implements the fs.NodeSymlinker interface for TLF. 283 func (tlf *TLF) Symlink(ctx context.Context, req *fuse.SymlinkRequest) ( 284 fs.Node, error) { 285 dir, err := tlf.loadDir(ctx) 286 if err != nil { 287 return nil, err 288 } 289 return dir.Symlink(ctx, req) 290 } 291 292 var _ fs.NodeLinker = (*TLF)(nil) 293 294 // Link implements the fs.NodeLinker interface for TLF. 295 func (tlf *TLF) Link( 296 _ context.Context, _ *fuse.LinkRequest, _ fs.Node) (fs.Node, error) { 297 return nil, fuse.ENOTSUP 298 } 299 300 // Rename implements the fs.NodeRenamer interface for TLF. 301 func (tlf *TLF) Rename(ctx context.Context, req *fuse.RenameRequest, 302 newDir fs.Node) error { 303 dir, err := tlf.loadDir(ctx) 304 if err != nil { 305 return err 306 } 307 return dir.Rename(ctx, req, newDir) 308 } 309 310 // Remove implements the fs.NodeRemover interface for TLF. 311 func (tlf *TLF) Remove(ctx context.Context, req *fuse.RemoveRequest) error { 312 dir, err := tlf.loadDir(ctx) 313 if err != nil { 314 return err 315 } 316 return dir.Remove(ctx, req) 317 } 318 319 // ReadDirAll implements the fs.NodeReadDirAller interface for TLF. 320 func (tlf *TLF) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) { 321 dir, exitEarly, err := tlf.loadDirAllowNonexistent(ctx) 322 if err != nil || exitEarly { 323 return nil, err 324 } 325 return dir.ReadDirAll(ctx) 326 } 327 328 // Forget kernel reference to this node. 329 func (tlf *TLF) Forget() { 330 dir := tlf.getStoredDir() 331 if dir != nil { 332 dir.Forget() 333 } else { 334 tlf.folder.list.forgetFolder(string(tlf.folder.name())) 335 } 336 } 337 338 // Setattr implements the fs.NodeSetattrer interface for TLF. 339 func (tlf *TLF) Setattr(ctx context.Context, req *fuse.SetattrRequest, resp *fuse.SetattrResponse) error { 340 dir, err := tlf.loadDir(ctx) 341 if err != nil { 342 return err 343 } 344 return dir.Setattr(ctx, req, resp) 345 } 346 347 // Fsync implements the fs.NodeFsyncer interface for TLF. 348 func (tlf *TLF) Fsync(ctx context.Context, req *fuse.FsyncRequest) (err error) { 349 dir := tlf.getStoredDir() 350 if dir == nil { 351 // The directory hasn't been loaded yet, so there's nothing to do. 352 return nil 353 } 354 355 return dir.Fsync(ctx, req) 356 } 357 358 var _ fs.Handle = (*TLF)(nil) 359 360 var _ fs.NodeOpener = (*TLF)(nil) 361 362 // Open implements the fs.NodeOpener interface for TLF. 363 func (tlf *TLF) Open(ctx context.Context, req *fuse.OpenRequest, 364 resp *fuse.OpenResponse) (fs.Handle, error) { 365 // Explicitly load the directory when a TLF is opened, because 366 // some OSX programs like ls have a bug that doesn't report errors 367 // on a ReadDirAll. 368 _, _, err := tlf.loadDirAllowNonexistent(ctx) 369 if err != nil { 370 return nil, err 371 } 372 return tlf, nil 373 } 374 375 func (tlf *TLF) openFileCount() int64 { 376 return tlf.folder.openFileCount() 377 }