github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/libfuse/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 //go:build !windows 6 // +build !windows 7 8 package libfuse 9 10 import ( 11 "fmt" 12 "os" 13 "strings" 14 "sync" 15 "syscall" 16 17 "bazil.org/fuse" 18 "bazil.org/fuse/fs" 19 "github.com/keybase/client/go/kbfs/favorites" 20 "github.com/keybase/client/go/kbfs/idutil" 21 "github.com/keybase/client/go/kbfs/libkbfs" 22 "github.com/keybase/client/go/kbfs/tlf" 23 "github.com/keybase/client/go/kbfs/tlfhandle" 24 kbname "github.com/keybase/client/go/kbun" 25 "github.com/keybase/client/go/libkb" 26 "github.com/pkg/errors" 27 "golang.org/x/net/context" 28 ) 29 30 // FolderList is a node that can list all of the logged-in user's 31 // favorite top-level folders, on either a public or private basis. 32 type FolderList struct { 33 fs *FS 34 // only accept public folders 35 tlfType tlf.Type 36 inode uint64 37 38 mu sync.Mutex 39 folders map[string]*TLF 40 } 41 42 var _ fs.NodeAccesser = (*FolderList)(nil) 43 44 // Access implements fs.NodeAccesser interface for *FolderList. 45 func (*FolderList) Access(ctx context.Context, r *fuse.AccessRequest) error { 46 if int(r.Uid) != os.Getuid() && 47 // Finder likes to use UID 0 for some operations. osxfuse already allows 48 // ACCESS and GETXATTR requests from root to go through. This allows root 49 // in ACCESS handler. See KBFS-1733 for more details. 50 int(r.Uid) != 0 { 51 // short path: not accessible by anybody other than root or the user who 52 // executed the kbfsfuse process. 53 return fuse.EPERM 54 } 55 56 if r.Mask&02 != 0 { 57 return fuse.EPERM 58 } 59 60 return nil 61 } 62 63 var _ fs.Node = (*FolderList)(nil) 64 65 // Attr implements the fs.Node interface. 66 func (fl *FolderList) Attr(ctx context.Context, a *fuse.Attr) error { 67 a.Mode = os.ModeDir | 0500 68 a.Uid = uint32(os.Getuid()) 69 a.Inode = fl.inode 70 return nil 71 } 72 73 var _ fs.NodeRequestLookuper = (*FolderList)(nil) 74 75 func (fl *FolderList) processError(ctx context.Context, 76 mode libkbfs.ErrorModeType, tlfName tlf.CanonicalName, err error) error { 77 if err == nil { 78 fl.fs.errLog.CDebugf(ctx, "Request complete") 79 return nil 80 } 81 82 fl.fs.config.Reporter().ReportErr(ctx, tlfName, fl.tlfType, mode, err) 83 // We just log the error as debug, rather than error, because it 84 // might just indicate an expected error such as an ENOENT. 85 // 86 // TODO: Classify errors and escalate the logging level of the 87 // important ones. 88 fl.fs.errLog.CDebugf(ctx, err.Error()) 89 return filterError(err) 90 } 91 92 // PathType returns PathType for this folder 93 func (fl *FolderList) PathType() tlfhandle.PathType { 94 switch fl.tlfType { 95 case tlf.Private: 96 return tlfhandle.PrivatePathType 97 case tlf.Public: 98 return tlfhandle.PublicPathType 99 case tlf.SingleTeam: 100 return tlfhandle.SingleTeamPathType 101 default: 102 panic(fmt.Sprintf("Unsupported tlf type: %s", fl.tlfType)) 103 } 104 } 105 106 // Create implements the fs.NodeCreater interface for FolderList. 107 func (fl *FolderList) Create(ctx context.Context, req *fuse.CreateRequest, resp *fuse.CreateResponse) (_ fs.Node, _ fs.Handle, err error) { 108 fl.fs.vlog.CLogf(ctx, libkb.VLog1, "FL Create") 109 tlfName := tlf.CanonicalName(req.Name) 110 defer func() { err = fl.processError(ctx, libkbfs.WriteMode, tlfName, err) }() 111 if strings.HasPrefix(req.Name, "._") { 112 // Quietly ignore writes to special macOS files, without 113 // triggering a notification. 114 return nil, nil, syscall.ENOENT 115 } 116 return nil, nil, libkbfs.NewWriteUnsupportedError(tlfhandle.BuildCanonicalPath(fl.PathType(), string(tlfName))) 117 } 118 119 // Mkdir implements the fs.NodeMkdirer interface for FolderList. 120 func (fl *FolderList) Mkdir(ctx context.Context, req *fuse.MkdirRequest) (_ fs.Node, err error) { 121 fl.fs.vlog.CLogf(ctx, libkb.VLog1, "FL Mkdir") 122 tlfName := tlf.CanonicalName(req.Name) 123 defer func() { err = fl.processError(ctx, libkbfs.WriteMode, tlfName, err) }() 124 return nil, libkbfs.NewWriteUnsupportedError(tlfhandle.BuildCanonicalPath(fl.PathType(), string(tlfName))) 125 } 126 127 // Lookup implements the fs.NodeRequestLookuper interface. 128 func (fl *FolderList) Lookup(ctx context.Context, req *fuse.LookupRequest, resp *fuse.LookupResponse) (node fs.Node, err error) { 129 fl.fs.vlog.CLogf(ctx, libkb.VLog1, "FL Lookup %s", req.Name) 130 defer func() { 131 err = fl.processError(ctx, libkbfs.ReadMode, 132 tlf.CanonicalName(req.Name), err) 133 }() 134 fl.mu.Lock() 135 defer fl.mu.Unlock() 136 137 specialNode := handleNonTLFSpecialFile( 138 req.Name, fl.fs, &resp.EntryValid) 139 if specialNode != nil { 140 return specialNode, nil 141 } 142 143 if child, ok := fl.folders[req.Name]; ok { 144 return child, nil 145 } 146 147 // Shortcut for dreaded extraneous OSX finder lookups 148 if strings.HasPrefix(req.Name, "._") { 149 return nil, fuse.ENOENT 150 } 151 152 h, err := tlfhandle.ParseHandlePreferredQuick( 153 ctx, fl.fs.config.KBPKI(), fl.fs.config, req.Name, fl.tlfType) 154 switch e := errors.Cause(err).(type) { 155 case nil: 156 // no error 157 158 case idutil.TlfNameNotCanonical: 159 // Only permit Aliases to targets that contain no errors. 160 if !fl.isValidAliasTarget(ctx, e.NameToTry) { 161 fl.fs.log.CDebugf(ctx, "FL Refusing alias to non-valid target %q", e.NameToTry) 162 return nil, fuse.ENOENT 163 } 164 // Non-canonical name. 165 n := &Alias{ 166 realPath: e.NameToTry, 167 inode: 0, 168 } 169 return n, nil 170 171 case idutil.NoSuchNameError, idutil.BadTLFNameError, 172 tlf.NoSuchUserError, idutil.NoSuchUserError: 173 // Invalid TLF. 174 return nil, fuse.ENOENT 175 176 default: 177 // Some other error. 178 return nil, err 179 } 180 181 session, err := idutil.GetCurrentSessionIfPossible( 182 ctx, fl.fs.config.KBPKI(), h.Type() == tlf.Public) 183 if err != nil { 184 return nil, err 185 } 186 child := newTLF(ctx, fl, h, h.GetPreferredFormat(session.Name)) 187 fl.folders[req.Name] = child 188 return child, nil 189 } 190 191 func (fl *FolderList) isValidAliasTarget(ctx context.Context, nameToTry string) bool { 192 return tlfhandle.CheckHandleOffline(ctx, nameToTry, fl.tlfType) == nil 193 } 194 195 func (fl *FolderList) forgetFolder(folderName string) { 196 fl.mu.Lock() 197 defer fl.mu.Unlock() 198 delete(fl.folders, folderName) 199 } 200 201 var _ fs.Handle = (*FolderList)(nil) 202 203 var _ fs.HandleReadDirAller = (*FolderList)(nil) 204 205 // ReadDirAll implements the ReadDirAll interface. 206 func (fl *FolderList) ReadDirAll(ctx context.Context) (res []fuse.Dirent, err error) { 207 fl.fs.vlog.CLogf(ctx, libkb.VLog1, "FL ReadDirAll") 208 defer func() { 209 err = fl.fs.processError(ctx, libkbfs.ReadMode, err) 210 }() 211 session, err := fl.fs.config.KBPKI().GetCurrentSession(ctx) 212 isLoggedIn := err == nil 213 214 var favs []favorites.Folder 215 if isLoggedIn { 216 favs, err = fl.fs.config.KBFSOps().GetFavorites(ctx) 217 if err != nil { 218 return nil, err 219 } 220 } 221 222 res = make([]fuse.Dirent, 0, len(favs)) 223 for _, fav := range favs { 224 if fav.Type != fl.tlfType { 225 continue 226 } 227 pname, err := tlf.CanonicalToPreferredName( 228 session.Name, tlf.CanonicalName(fav.Name)) 229 if err != nil { 230 fl.fs.log.Errorf("CanonicalToPreferredName: %q %v", fav.Name, err) 231 continue 232 } 233 res = append(res, fuse.Dirent{ 234 Type: fuse.DT_Dir, 235 Name: string(pname), 236 }) 237 } 238 return res, nil 239 } 240 241 var _ fs.NodeRemover = (*FolderList)(nil) 242 243 // Remove implements the fs.NodeRemover interface for FolderList. 244 func (fl *FolderList) Remove(ctx context.Context, req *fuse.RemoveRequest) (err error) { 245 fl.fs.vlog.CLogf(ctx, libkb.VLog1, "FolderList Remove %s", req.Name) 246 defer func() { err = fl.fs.processError(ctx, libkbfs.WriteMode, err) }() 247 248 h, err := tlfhandle.ParseHandlePreferredQuick( 249 ctx, fl.fs.config.KBPKI(), fl.fs.config, req.Name, fl.tlfType) 250 251 switch err := errors.Cause(err).(type) { 252 case nil: 253 func() { 254 fl.mu.Lock() 255 defer fl.mu.Unlock() 256 if tlf, ok := fl.folders[req.Name]; ok { 257 // Fake future attr calls for this TLF until the user 258 // actually opens the TLF again, because some OSes (*cough 259 // OS X cough*) like to do immediate lookup/attr calls 260 // right after doing a remove, which would otherwise end 261 // up re-adding the favorite. 262 tlf.clearStoredDir() 263 } 264 }() 265 266 // TODO how to handle closing down the folderbranchops 267 // object? Open files may still exist long after removing 268 // the favorite. 269 return fl.fs.config.KBFSOps().DeleteFavorite(ctx, h.ToFavorite()) 270 271 case idutil.TlfNameNotCanonical: 272 return nil 273 274 case idutil.NoSuchNameError, idutil.BadTLFNameError, 275 tlf.NoSuchUserError, idutil.NoSuchUserError: 276 // Invalid TLF. 277 return fuse.ENOENT 278 279 default: 280 return err 281 } 282 } 283 284 var _ fs.NodeSymlinker = (*FolderList)(nil) 285 286 // Symlink implements the fs.NodeSymlinker interface for FolderList. 287 func (fl *FolderList) Symlink( 288 _ context.Context, _ *fuse.SymlinkRequest) (fs.Node, error) { 289 return nil, fuse.ENOTSUP 290 } 291 292 var _ fs.NodeLinker = (*FolderList)(nil) 293 294 // Link implements the fs.NodeLinker interface for FolderList. 295 func (fl *FolderList) Link( 296 _ context.Context, _ *fuse.LinkRequest, _ fs.Node) (fs.Node, error) { 297 return nil, fuse.ENOTSUP 298 } 299 300 func (fl *FolderList) updateTlfName(ctx context.Context, oldName string, 301 newName string) { 302 ok := func() bool { 303 fl.mu.Lock() 304 defer fl.mu.Unlock() 305 tlf, ok := fl.folders[oldName] 306 if !ok { 307 return false 308 } 309 310 fl.fs.vlog.CLogf( 311 ctx, libkb.VLog1, "Folder name updated: %s -> %s", oldName, newName) 312 delete(fl.folders, oldName) 313 fl.folders[newName] = tlf 314 return true 315 }() 316 if !ok { 317 return 318 } 319 320 if err := fl.fs.fuse.InvalidateEntry(fl, oldName); err != nil { 321 // TODO we have no mechanism to do anything about this 322 fl.fs.log.CErrorf(ctx, "FUSE invalidate error for oldName=%s: %v", 323 oldName, err) 324 } 325 if err := fl.fs.fuse.InvalidateEntry(fl, newName); err != nil { 326 // TODO we have no mechanism to do anything about this 327 fl.fs.log.CErrorf(ctx, "FUSE invalidate error for newName=%s: %v", 328 newName, err) 329 } 330 } 331 332 // update things after user changed. 333 func (fl *FolderList) userChanged(ctx context.Context, _, newUser kbname.NormalizedUsername) { 334 var fs []*Folder 335 func() { 336 fl.mu.Lock() 337 defer fl.mu.Unlock() 338 for _, tlf := range fl.folders { 339 fs = append(fs, tlf.folder) 340 } 341 }() 342 for _, f := range fs { 343 f.TlfHandleChange(ctx, nil) 344 } 345 if newUser != kbname.NormalizedUsername("") { 346 fl.fs.config.KBFSOps().ForceFastForward(ctx) 347 } 348 } 349 350 func (fl *FolderList) openFileCount() (ret int64) { 351 fl.mu.Lock() 352 defer fl.mu.Unlock() 353 for _, tlf := range fl.folders { 354 ret += tlf.openFileCount() 355 } 356 return ret + int64(len(fl.folders)) 357 } 358 359 // Forget kernel reference to this node. 360 func (fl *FolderList) Forget() { 361 fl.fs.root.forgetFolderList(fl.tlfType) 362 }