github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/libdokan/folderlist.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/dokan" 12 "github.com/keybase/client/go/kbfs/favorites" 13 "github.com/keybase/client/go/kbfs/idutil" 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 kbname "github.com/keybase/client/go/kbun" 18 "github.com/keybase/client/go/libkb" 19 "github.com/pkg/errors" 20 "golang.org/x/net/context" 21 ) 22 23 type fileOpener interface { 24 open(ctx context.Context, oc *openContext, path []string) ( 25 f dokan.File, cst dokan.CreateStatus, err error) 26 dokan.File 27 } 28 29 // FolderList is a node that can list all of the logged-in user's 30 // favorite top-level folders, on either a public or private basis. 31 type FolderList struct { 32 emptyFile 33 fs *FS 34 tlfType tlf.Type 35 36 mu sync.Mutex 37 folders map[string]fileOpener 38 aliasCache map[string]string 39 } 40 41 // GetFileInformation for dokan. 42 func (*FolderList) GetFileInformation(context.Context, *dokan.FileInfo) (*dokan.Stat, error) { 43 return defaultDirectoryInformation() 44 } 45 46 func (fl *FolderList) reportErr(ctx context.Context, 47 mode libkbfs.ErrorModeType, tlfName tlf.CanonicalName, err error, cancelFn func()) { 48 if cancelFn != nil { 49 defer cancelFn() 50 } 51 if err == nil { 52 fl.fs.vlog.CLogf(ctx, libkb.VLog1, "Request complete") 53 return 54 } 55 56 fl.fs.config.Reporter().ReportErr(ctx, tlfName, fl.tlfType, mode, err) 57 // We just log the error as debug, rather than error, because it 58 // might just indicate an expected error such as an ENOENT. 59 // 60 // TODO: Classify errors and escalate the logging level of the 61 // important ones. 62 fl.fs.log.CDebugf(ctx, err.Error()) 63 64 } 65 66 // open tries to open the correct thing. Following aliases and deferring to 67 // Dir.open as necessary. 68 func (fl *FolderList) open(ctx context.Context, oc *openContext, path []string) (f dokan.File, cst dokan.CreateStatus, err error) { 69 fl.fs.vlog.CLogf(ctx, libkb.VLog1, "FL Lookup %#v type=%s upper=%v", 70 path, fl.tlfType, oc.isUppercasePath) 71 if len(path) == 0 { 72 return oc.returnDirNoCleanup(fl) 73 } 74 75 defer func() { 76 fl.reportErr(ctx, libkbfs.ReadMode, tlf.CanonicalName(path[0]), err, nil) 77 }() 78 79 // TODO: A simple lower-casing is not good enough - see CORE-2967 80 // However libkbfs does this too in tlf_handle.go... 81 // The case of possible redirections will be ok, so we only need 82 // to do this initially. 83 if c := lowerTranslateCandidate(oc, path[0]); c != "" { 84 path[0] = c 85 } 86 87 for oc.reduceRedirectionsLeft() { 88 name := path[0] 89 90 if name == "desktop.ini" || name == "DESKTOP.INI" { 91 fl.fs.vlog.CLogf(ctx, libkb.VLog1, "FL Lookup ignoring desktop.ini") 92 return nil, 0, dokan.ErrObjectNameNotFound 93 } 94 95 var aliasTarget string 96 fl.mu.Lock() 97 child, ok := fl.folders[name] 98 if !ok { 99 aliasTarget = fl.aliasCache[name] 100 } 101 fl.mu.Unlock() 102 103 if ok { 104 fl.fs.vlog.CLogf( 105 ctx, libkb.VLog1, "FL Lookup recursing to child %q", name) 106 return child.open(ctx, oc, path[1:]) 107 } 108 109 if len(path) == 1 && isNewFolderName(name) { 110 if !oc.isCreateDirectory() { 111 return nil, 0, dokan.ErrObjectNameNotFound 112 } 113 fl.fs.vlog.CLogf( 114 ctx, libkb.VLog1, "FL Lookup creating EmptyFolder for Explorer") 115 e := &EmptyFolder{} 116 fl.lockedAddChild(name, e) 117 return e, dokan.NewDir, nil 118 } 119 120 if aliasTarget != "" { 121 fl.fs.vlog.CLogf( 122 ctx, libkb.VLog1, "FL Lookup aliasCache hit: %q -> %q", 123 name, aliasTarget) 124 if len(path) == 1 && oc.isOpenReparsePoint() { 125 // TODO handle dir/non-dir here, semantics? 126 return &Alias{canon: aliasTarget}, dokan.ExistingDir, nil 127 } 128 path[0] = aliasTarget 129 continue 130 } 131 132 h, err := tlfhandle.ParseHandlePreferredQuick( 133 ctx, fl.fs.config.KBPKI(), fl.fs.config, name, fl.tlfType) 134 fl.fs.vlog.CLogf( 135 ctx, libkb.VLog1, "FL Lookup continuing -> %v,%v", h, err) 136 switch e := errors.Cause(err).(type) { 137 case nil: 138 // no error 139 140 case idutil.TlfNameNotCanonical: 141 // Only permit Aliases to targets that contain no errors. 142 aliasTarget = e.NameToTry 143 fl.fs.vlog.CLogf( 144 ctx, libkb.VLog1, "FL Lookup set alias: %q -> %q", 145 name, aliasTarget) 146 if !fl.isValidAliasTarget(ctx, aliasTarget) { 147 fl.fs.vlog.CLogf( 148 ctx, libkb.VLog1, 149 "FL Refusing alias to non-valid target %q", aliasTarget) 150 return nil, 0, dokan.ErrObjectNameNotFound 151 } 152 fl.mu.Lock() 153 fl.aliasCache[name] = aliasTarget 154 fl.mu.Unlock() 155 156 if len(path) == 1 && oc.isOpenReparsePoint() { 157 // TODO handle dir/non-dir here, semantics? 158 fl.fs.vlog.CLogf( 159 ctx, libkb.VLog1, "FL Lookup ret alias, oc: %#v", 160 oc.CreateData) 161 return &Alias{canon: aliasTarget}, dokan.ExistingDir, nil 162 } 163 path[0] = aliasTarget 164 continue 165 166 case idutil.NoSuchNameError, idutil.BadTLFNameError, 167 tlf.NoSuchUserError, idutil.NoSuchUserError: 168 return nil, 0, dokan.ErrObjectNameNotFound 169 170 default: 171 // Some other error. 172 return nil, 0, err 173 } 174 175 fl.fs.vlog.CLogf(ctx, libkb.VLog1, "FL Lookup adding new child") 176 session, err := idutil.GetCurrentSessionIfPossible(ctx, fl.fs.config.KBPKI(), h.Type() == tlf.Public) 177 if err != nil { 178 return nil, 0, err 179 } 180 child = newTLF(fl, h, h.GetPreferredFormat(session.Name)) 181 fl.lockedAddChild(name, child) 182 return child.open(ctx, oc, path[1:]) 183 } 184 return nil, 0, dokan.ErrObjectNameNotFound 185 } 186 187 func (fl *FolderList) forgetFolder(folderName string) { 188 fl.mu.Lock() 189 defer fl.mu.Unlock() 190 delete(fl.folders, folderName) 191 } 192 193 // FindFiles for dokan. 194 func (fl *FolderList) FindFiles(ctx context.Context, fi *dokan.FileInfo, ignored string, callback func(*dokan.NamedStat) error) (err error) { 195 fl.fs.logEnter(ctx, "FL FindFiles") 196 defer func() { fl.fs.reportErr(ctx, libkbfs.ReadMode, err) }() 197 198 session, err := fl.fs.config.KBPKI().GetCurrentSession(ctx) 199 isLoggedIn := err == nil 200 201 var favs []favorites.Folder 202 if isLoggedIn { 203 favs, err = fl.fs.config.KBFSOps().GetFavorites(ctx) 204 if err != nil { 205 return err 206 } 207 } 208 var ns dokan.NamedStat 209 ns.FileAttributes = dokan.FileAttributeDirectory 210 empty := true 211 for _, fav := range favs { 212 if fav.Type != fl.tlfType { 213 continue 214 } 215 pname, err := tlf.CanonicalToPreferredName(session.Name, 216 tlf.CanonicalName(fav.Name)) 217 if err != nil { 218 fl.fs.log.CErrorf(ctx, "CanonicalToPreferredName: %q %v", fav.Name, err) 219 continue 220 } 221 empty = false 222 ns.Name = string(pname) 223 err = callback(&ns) 224 if err != nil { 225 return err 226 } 227 } 228 if empty { 229 return dokan.ErrObjectNameNotFound 230 } 231 return nil 232 } 233 234 func (fl *FolderList) isValidAliasTarget(ctx context.Context, nameToTry string) bool { 235 return tlfhandle.CheckHandleOffline(ctx, nameToTry, fl.tlfType) == nil 236 } 237 238 func (fl *FolderList) lockedAddChild(name string, val fileOpener) { 239 fl.mu.Lock() 240 fl.folders[name] = val 241 fl.mu.Unlock() 242 } 243 244 func (fl *FolderList) updateTlfName(ctx context.Context, oldName string, 245 newName string) { 246 fl.mu.Lock() 247 defer fl.mu.Unlock() 248 tlf, ok := fl.folders[oldName] 249 if !ok { 250 return 251 } 252 253 fl.fs.log.CDebugf(ctx, "Folder name updated: %s -> %s", oldName, newName) 254 delete(fl.folders, oldName) 255 fl.folders[newName] = tlf 256 // TODO: invalidate kernel cache for this name? (Make sure to 257 // do so outside of the lock!) 258 } 259 260 func (fl *FolderList) clearAliasCache() { 261 fl.mu.Lock() 262 fl.aliasCache = map[string]string{} 263 fl.mu.Unlock() 264 } 265 266 func clearFolderListCacheLoop(ctx context.Context, r *Root) { 267 t := time.NewTicker(time.Hour) 268 for { 269 select { 270 case <-ctx.Done(): 271 return 272 case <-t.C: 273 } 274 r.private.clearAliasCache() 275 r.public.clearAliasCache() 276 } 277 } 278 279 // update things after user changed. 280 func (fl *FolderList) userChanged(ctx context.Context, _, newUser kbname.NormalizedUsername) { 281 var fs []*Folder 282 func() { 283 fl.mu.Lock() 284 defer fl.mu.Unlock() 285 for _, tlf := range fl.folders { 286 if tlf, ok := tlf.(*TLF); ok { 287 fs = append(fs, tlf.folder) 288 } 289 } 290 }() 291 for _, f := range fs { 292 f.TlfHandleChange(ctx, nil) 293 } 294 if newUser != kbname.NormalizedUsername("") { 295 fl.fs.config.KBFSOps().ForceFastForward(ctx) 296 } 297 }