github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/libdokan/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 package libdokan 6 7 import ( 8 "sync" 9 "time" 10 11 "github.com/keybase/client/go/kbfs/data" 12 "github.com/keybase/client/go/kbfs/dokan" 13 "github.com/keybase/client/go/kbfs/libfs" 14 "github.com/keybase/client/go/kbfs/libkbfs" 15 "github.com/keybase/client/go/kbfs/tlf" 16 "github.com/keybase/client/go/kbfs/tlfhandle" 17 "github.com/keybase/client/go/libkb" 18 "golang.org/x/net/context" 19 ) 20 21 // TLF represents the root directory of a TLF. It wraps a lazy-loaded 22 // Dir. 23 type TLF struct { 24 refcount refcount 25 26 folder *Folder 27 28 dirLock sync.RWMutex 29 dir *Dir 30 31 emptyFile 32 } 33 34 func newTLF(fl *FolderList, h *tlfhandle.Handle, 35 name tlf.PreferredName) *TLF { 36 folder := newFolder(fl, h, name) 37 tlf := &TLF{ 38 folder: folder, 39 } 40 tlf.refcount.Increase() 41 return tlf 42 } 43 44 func (tlf *TLF) getStoredDir() *Dir { 45 tlf.dirLock.RLock() 46 defer tlf.dirLock.RUnlock() 47 return tlf.dir 48 } 49 50 func (tlf *TLF) loadDirHelper(ctx context.Context, info string, 51 mode libkbfs.ErrorModeType, branch data.BranchName, filterErr bool) ( 52 dir *Dir, exitEarly bool, err error) { 53 dir = tlf.getStoredDir() 54 if dir != nil { 55 return dir, false, nil 56 } 57 58 tlf.dirLock.Lock() 59 defer tlf.dirLock.Unlock() 60 // Need to check for nilness again to avoid racing with other 61 // calls to loadDir(). 62 if tlf.dir != nil { 63 return tlf.dir, false, nil 64 } 65 66 name := tlf.folder.name() 67 68 tlf.folder.fs.log.CDebugf(ctx, "Loading root directory for folder %s "+ 69 "(type: %s, filter error: %t) for %s", 70 name, tlf.folder.list.tlfType, filterErr, info) 71 defer func() { 72 if filterErr { 73 exitEarly, err = libfs.FilterTLFEarlyExitError(ctx, err, tlf.folder.fs.log, name) 74 } 75 76 tlf.folder.reportErr(ctx, mode, err) 77 }() 78 79 handle, err := tlf.folder.resolve(ctx) 80 if err != nil { 81 return nil, false, err 82 } 83 84 if branch == data.MasterBranch { 85 conflictBranch, isLocalConflictBranch := 86 data.MakeConflictBranchName(handle) 87 if isLocalConflictBranch { 88 branch = conflictBranch 89 } 90 } 91 92 var rootNode libkbfs.Node 93 if filterErr { 94 rootNode, _, err = tlf.folder.fs.config.KBFSOps().GetRootNode( 95 ctx, handle, branch) 96 if err != nil { 97 return nil, false, err 98 } 99 // If not fake an empty directory. 100 if rootNode == nil { 101 return nil, false, libfs.TlfDoesNotExist{} 102 } 103 } else { 104 rootNode, _, err = tlf.folder.fs.config.KBFSOps().GetOrCreateRootNode( 105 ctx, handle, branch) 106 if err != nil { 107 return nil, false, err 108 } 109 } 110 111 err = tlf.folder.setFolderBranch(rootNode.GetFolderBranch()) 112 if err != nil { 113 return nil, false, err 114 } 115 116 tlf.folder.nodes[rootNode.GetID()] = tlf 117 tlf.dir = newDir(tlf.folder, rootNode, string(name), nil) 118 // TLFs should be cached. 119 tlf.dir.refcount.Increase() 120 tlf.folder.lockedAddNode(rootNode, tlf.dir) 121 122 return tlf.dir, false, nil 123 } 124 125 func (tlf *TLF) loadDir(ctx context.Context, info string) (*Dir, error) { 126 dir, _, err := tlf.loadDirHelper( 127 ctx, info, libkbfs.WriteMode, data.MasterBranch, false) 128 return dir, err 129 } 130 131 // loadDirAllowNonexistent loads a TLF if it's not already loaded. If 132 // the TLF doesn't yet exist, it still returns a nil error and 133 // indicates that the calling function should pretend it's an empty 134 // folder. 135 func (tlf *TLF) loadDirAllowNonexistent(ctx context.Context, info string) ( 136 *Dir, bool, error) { 137 return tlf.loadDirHelper( 138 ctx, info, libkbfs.ReadMode, data.MasterBranch, true) 139 } 140 141 func (tlf *TLF) loadArchivedDir( 142 ctx context.Context, info string, branch data.BranchName) ( 143 *Dir, bool, error) { 144 // Always filter errors for archive TLF directories, so that we 145 // don't try to initialize them. 146 return tlf.loadDirHelper(ctx, info, libkbfs.ReadMode, branch, true) 147 } 148 149 // SetFileTime sets mtime for FSOs (File and Dir). 150 func (tlf *TLF) SetFileTime(ctx context.Context, fi *dokan.FileInfo, creation time.Time, lastAccess time.Time, lastWrite time.Time) (err error) { 151 tlf.folder.fs.logEnter(ctx, "TLF SetFileTime") 152 153 dir, err := tlf.loadDir(ctx, "TLF SetFileTime") 154 if err != nil { 155 return err 156 } 157 return dir.SetFileTime(ctx, fi, creation, lastAccess, lastWrite) 158 } 159 160 // SetFileAttributes for Dokan. 161 func (tlf *TLF) SetFileAttributes(ctx context.Context, fi *dokan.FileInfo, fileAttributes dokan.FileAttribute) error { 162 tlf.folder.fs.logEnter(ctx, "TLF SetFileAttributes") 163 dir, err := tlf.loadDir(ctx, "TLF SetFileAttributes") 164 if err != nil { 165 return err 166 } 167 return dir.SetFileAttributes(ctx, fi, fileAttributes) 168 } 169 170 // GetFileInformation for dokan. 171 func (tlf *TLF) GetFileInformation(ctx context.Context, fi *dokan.FileInfo) (st *dokan.Stat, err error) { 172 dir := tlf.getStoredDir() 173 if dir == nil { 174 return defaultDirectoryInformation() 175 } 176 177 return dir.GetFileInformation(ctx, fi) 178 } 179 180 // open tries to open a file. 181 func (tlf *TLF) open(ctx context.Context, oc *openContext, path []string) ( 182 dokan.File, dokan.CreateStatus, error) { 183 if len(path) == 0 { 184 if err := oc.ReturningDirAllowed(); err != nil { 185 return nil, 0, err 186 } 187 tlf.refcount.Increase() 188 return tlf, dokan.ExistingDir, nil 189 } 190 191 mode := libkbfs.ReadMode 192 if oc.isCreation() { 193 mode = libkbfs.WriteMode 194 } 195 // If it is a creation then we need the dir for real. 196 dir, exitEarly, err := 197 tlf.loadDirHelper( 198 ctx, "open", mode, data.MasterBranch, !oc.isCreation()) 199 if err != nil { 200 return nil, 0, err 201 } 202 if exitEarly { 203 specialNode := handleTLFSpecialFile(lastStr(path), tlf.folder) 204 if specialNode != nil { 205 return specialNode, dokan.ExistingFile, nil 206 } 207 208 return nil, 0, dokan.ErrObjectNameNotFound 209 } 210 211 branch, isArchivedBranch := libfs.BranchNameFromArchiveRefDir(path[0]) 212 if isArchivedBranch { 213 archivedTLF := newTLF( 214 tlf.folder.list, tlf.folder.h, tlf.folder.hPreferredName) 215 _, _, err := archivedTLF.loadArchivedDir(ctx, "open", branch) 216 if err != nil { 217 return nil, 0, err 218 } 219 return archivedTLF.open(ctx, oc, path[1:]) 220 } 221 222 linkTarget, isArchivedTimeLink, err := libfs.LinkTargetFromTimeString( 223 ctx, tlf.folder.fs.config, tlf.folder.h, path[0]) 224 if err != nil { 225 return nil, 0, err 226 } 227 if isArchivedTimeLink { 228 if len(path) == 1 && oc.isOpenReparsePoint() { 229 // TODO handle dir/non-dir here, semantics? 230 return &Alias{canon: linkTarget}, dokan.ExistingDir, nil 231 } 232 path[0] = linkTarget 233 return tlf.open(ctx, oc, path) 234 } 235 236 _, isRelTimeLink, err := libfs.FileDataFromRelativeTimeString( 237 ctx, tlf.folder.fs.config, tlf.folder.h, path[0]) 238 if err != nil { 239 return nil, 0, err 240 } 241 if isRelTimeLink { 242 return NewArchiveRelTimeFile(tlf.folder.fs, tlf.folder.h, path[0]), 243 dokan.ExistingFile, nil 244 } 245 246 return dir.open(ctx, oc, path) 247 } 248 249 // FindFiles does readdir for dokan. 250 func (tlf *TLF) FindFiles(ctx context.Context, fi *dokan.FileInfo, pattern string, callback func(*dokan.NamedStat) error) (err error) { 251 tlf.folder.fs.logEnter(ctx, "TLF FindFiles") 252 dir, exitEarly, err := tlf.loadDirAllowNonexistent(ctx, "FindFiles") 253 if err != nil { 254 return errToDokan(err) 255 } 256 if exitEarly { 257 return dokan.ErrObjectNameNotFound 258 } 259 return dir.FindFiles(ctx, fi, pattern, callback) 260 } 261 262 // CanDeleteDirectory - return just nil because tlfs 263 // can always be removed from favorites. 264 func (tlf *TLF) CanDeleteDirectory(ctx context.Context, fi *dokan.FileInfo) (err error) { 265 return nil 266 } 267 268 // Cleanup - forget references, perform deletions etc. 269 func (tlf *TLF) Cleanup(ctx context.Context, fi *dokan.FileInfo) { 270 var err error 271 if fi != nil && fi.IsDeleteOnClose() { 272 tlf.folder.handleMu.Lock() 273 fav := tlf.folder.h.ToFavorite() 274 tlf.folder.handleMu.Unlock() 275 tlf.folder.fs.vlog.CLogf( 276 ctx, libkb.VLog1, "TLF Removing favorite %q", fav.Name) 277 defer func() { 278 tlf.folder.reportErr(ctx, libkbfs.WriteMode, err) 279 }() 280 err = tlf.folder.fs.config.KBFSOps().DeleteFavorite(ctx, fav) 281 } 282 283 if tlf.refcount.Decrease() { 284 dir := tlf.getStoredDir() 285 if dir == nil { 286 return 287 } 288 dir.Cleanup(ctx, fi) 289 } 290 }