github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/libfuse/fs.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 "net" 12 "net/http" 13 "net/http/pprof" 14 "os" 15 "strconv" 16 "strings" 17 "sync" 18 "syscall" 19 "time" 20 21 "bazil.org/fuse" 22 "bazil.org/fuse/fs" 23 "github.com/keybase/client/go/kbfs/idutil" 24 "github.com/keybase/client/go/kbfs/libcontext" 25 "github.com/keybase/client/go/kbfs/libfs" 26 "github.com/keybase/client/go/kbfs/libkbfs" 27 "github.com/keybase/client/go/kbfs/tlf" 28 "github.com/keybase/client/go/kbfs/tlfhandle" 29 kbname "github.com/keybase/client/go/kbun" 30 "github.com/keybase/client/go/libkb" 31 "github.com/keybase/client/go/logger" 32 keybase1 "github.com/keybase/client/go/protocol/keybase1" 33 "github.com/pkg/errors" 34 "golang.org/x/net/context" 35 "golang.org/x/net/trace" 36 ) 37 38 // FS implements the newfuse FS interface for KBFS. 39 type FS struct { 40 config libkbfs.Config 41 fuse *fs.Server 42 conn *fuse.Conn 43 log logger.Logger 44 errLog logger.Logger 45 vlog *libkb.VDebugLog 46 errVlog *libkb.VDebugLog 47 48 // Protects debugServerListener and debugServer.addr. 49 debugServerLock sync.Mutex 50 debugServerListener net.Listener 51 // An HTTP server used for debugging. Normally off unless 52 // turned on via enableDebugServer(). 53 debugServer *http.Server 54 55 notifications *libfs.FSNotifications 56 57 // remoteStatus is the current status of remote connections. 58 remoteStatus libfs.RemoteStatus 59 60 // this is like time.AfterFunc, except that in some tests this can be 61 // overridden to execute f without any delay. 62 execAfterDelay func(d time.Duration, f func()) 63 64 root *Root 65 66 platformParams PlatformParams 67 68 inodeLock sync.Mutex 69 nextInode uint64 70 } 71 72 func makeTraceHandler(renderFn func(http.ResponseWriter, *http.Request, bool)) func(http.ResponseWriter, *http.Request) { 73 return func(w http.ResponseWriter, req *http.Request) { 74 any, sensitive := trace.AuthRequest(req) 75 if !any { 76 http.Error(w, "not allowed", http.StatusUnauthorized) 77 return 78 } 79 w.Header().Set("Content-Type", "text/html; charset=utf-8") 80 renderFn(w, req, sensitive) 81 } 82 } 83 84 // NewFS creates an FS. Note that this isn't the only constructor; see 85 // makeFS in libfuse/mount_test.go. 86 func NewFS(config libkbfs.Config, conn *fuse.Conn, debug bool, 87 platformParams PlatformParams) *FS { 88 log := config.MakeLogger("kbfsfuse") 89 // We need extra depth for errors, so that we can report the line 90 // number for the caller of processError, not processError itself. 91 errLog := log.CloneWithAddedDepth(1) 92 if debug { 93 // Turn on debugging. TODO: allow a proper log file and 94 // style to be specified. 95 log.Configure("", true, "") 96 errLog.Configure("", true, "") 97 } 98 99 serveMux := http.NewServeMux() 100 101 // Replicate the default endpoints from pprof's init function. 102 serveMux.HandleFunc("/debug/pprof/", pprof.Index) 103 serveMux.HandleFunc("/debug/pprof/cmdline", pprof.Cmdline) 104 serveMux.HandleFunc("/debug/pprof/profile", pprof.Profile) 105 serveMux.HandleFunc("/debug/pprof/symbol", pprof.Symbol) 106 serveMux.HandleFunc("/debug/pprof/trace", pprof.Trace) 107 108 // Replicate the default endpoints from net/trace's init function. 109 serveMux.HandleFunc("/debug/requests", makeTraceHandler(func(w http.ResponseWriter, req *http.Request, sensitive bool) { 110 trace.Render(w, req, sensitive) 111 })) 112 serveMux.HandleFunc("/debug/events", makeTraceHandler(trace.RenderEvents)) 113 114 // Leave Addr blank to be set in enableDebugServer() and 115 // disableDebugServer(). 116 debugServer := &http.Server{ 117 Handler: serveMux, 118 ReadTimeout: 10 * time.Second, 119 WriteTimeout: 10 * time.Second, 120 } 121 122 fs := &FS{ 123 config: config, 124 conn: conn, 125 log: log, 126 errLog: errLog, 127 vlog: config.MakeVLogger(log), 128 errVlog: config.MakeVLogger(errLog), 129 debugServer: debugServer, 130 notifications: libfs.NewFSNotifications(log), 131 root: NewRoot(), 132 platformParams: platformParams, 133 nextInode: 2, // root is 1 134 } 135 fs.root.private = &FolderList{ 136 fs: fs, 137 tlfType: tlf.Private, 138 folders: make(map[string]*TLF), 139 inode: fs.assignInode(), 140 } 141 fs.root.public = &FolderList{ 142 fs: fs, 143 tlfType: tlf.Public, 144 folders: make(map[string]*TLF), 145 inode: fs.assignInode(), 146 } 147 fs.root.team = &FolderList{ 148 fs: fs, 149 tlfType: tlf.SingleTeam, 150 folders: make(map[string]*TLF), 151 inode: fs.assignInode(), 152 } 153 fs.execAfterDelay = func(d time.Duration, f func()) { 154 time.AfterFunc(d, f) 155 } 156 return fs 157 } 158 159 func (f *FS) assignInode() uint64 { 160 f.inodeLock.Lock() 161 defer f.inodeLock.Unlock() 162 next := f.nextInode 163 f.nextInode++ 164 return next 165 } 166 167 // tcpKeepAliveListener is copied from net/http/server.go, since it is 168 // used in http.(*Server).ListenAndServe() which we want to emulate in 169 // enableDebugServer. 170 type tcpKeepAliveListener struct { 171 *net.TCPListener 172 } 173 174 func (tkal tcpKeepAliveListener) Accept() (c net.Conn, err error) { 175 tc, err := tkal.AcceptTCP() 176 if err != nil { 177 return nil, err 178 } 179 err = tc.SetKeepAlive(true) 180 if err != nil { 181 return nil, err 182 } 183 err = tc.SetKeepAlivePeriod(3 * time.Minute) 184 if err != nil { 185 return nil, err 186 } 187 return tc, nil 188 } 189 190 func (f *FS) enableDebugServer(ctx context.Context, port uint16) error { 191 f.debugServerLock.Lock() 192 defer f.debugServerLock.Unlock() 193 194 // Note that f.debugServer may be nil if f was created via 195 // makeFS. But in that case we shouldn't be calling this 196 // function then anyway. 197 if f.debugServer.Addr != "" { 198 return errors.Errorf("Debug server already enabled at %s", 199 f.debugServer.Addr) 200 } 201 202 addr := net.JoinHostPort("localhost", 203 strconv.FormatUint(uint64(port), 10)) 204 f.log.CDebugf(ctx, "Enabling debug http server at %s", addr) 205 206 // Do Listen and Serve separately so we can catch errors with 207 // the port (e.g. "port already in use") and return it. 208 listener, err := net.Listen("tcp", addr) 209 if err != nil { 210 f.log.CDebugf(ctx, "Got error when listening on %s: %+v", 211 addr, err) 212 return err 213 } 214 215 f.debugServer.Addr = addr 216 f.debugServerListener = 217 tcpKeepAliveListener{listener.(*net.TCPListener)} 218 219 // This seems racy because the spawned goroutine may be 220 // scheduled to run after disableDebugServer is called. But 221 // that's okay since Serve will error out immediately after 222 // f.debugServerListener.Close() is called. 223 go func(server *http.Server, listener net.Listener) { 224 err := server.Serve(listener) 225 f.log.Debug("Debug http server ended with %+v", err) 226 }(f.debugServer, f.debugServerListener) 227 228 // TODO: Perhaps enable turning tracing on and off 229 // independently from the debug server. 230 f.config.SetTraceOptions(true) 231 232 return nil 233 } 234 235 func (f *FS) disableDebugServer(ctx context.Context) error { 236 f.debugServerLock.Lock() 237 defer f.debugServerLock.Unlock() 238 239 // Note that f.debugServer may be nil if f was created via 240 // makeFS. But in that case we shouldn't be calling this 241 // function then anyway. 242 if f.debugServer.Addr == "" { 243 return errors.New("Debug server already disabled") 244 } 245 246 f.log.CDebugf(ctx, "Disabling debug http server at %s", 247 f.debugServer.Addr) 248 // TODO: Use f.debugServer.Close() or f.debugServer.Shutdown() 249 // when we switch to go 1.8. 250 err := f.debugServerListener.Close() 251 f.log.CDebugf(ctx, "Debug http server shutdown with %+v", err) 252 253 // Assume the close succeeds in stopping the server, even if 254 // it returns an error. 255 f.debugServer.Addr = "" 256 f.debugServerListener = nil 257 258 f.config.SetTraceOptions(false) 259 260 return err 261 } 262 263 // SetFuseConn sets fuse connection for this FS. 264 func (f *FS) SetFuseConn(fuse *fs.Server, conn *fuse.Conn) { 265 f.fuse = fuse 266 f.conn = conn 267 } 268 269 // NotificationGroupWait - wait on the notification group. 270 func (f *FS) NotificationGroupWait() { 271 f.notifications.Wait() 272 } 273 274 func (f *FS) queueNotification(fn func()) { 275 f.notifications.QueueNotification(fn) 276 } 277 278 // LaunchNotificationProcessor launches the notification processor. 279 func (f *FS) LaunchNotificationProcessor(ctx context.Context) { 280 f.notifications.LaunchProcessor(ctx) 281 } 282 283 // WithContext adds app- and request-specific values to the context. 284 // libkbfs.NewContextWithCancellationDelayer is called before returning the 285 // context to ensure the cancellation is controllable. 286 // 287 // It is called by FUSE for normal runs, but may be called explicitly in other 288 // settings, such as tests. 289 func (f *FS) WithContext(ctx context.Context) context.Context { 290 id, errRandomReqID := libkbfs.MakeRandomRequestID() 291 if errRandomReqID != nil { 292 f.log.Errorf("Couldn't make request ID: %v", errRandomReqID) 293 } 294 295 // context.WithDeadline uses clock from `time` package, so we are not using 296 // f.config.Clock() here 297 start := time.Now() 298 ctx, err := libcontext.NewContextWithCancellationDelayer( 299 libcontext.NewContextReplayable(ctx, func(ctx context.Context) context.Context { 300 ctx = context.WithValue(ctx, libfs.CtxAppIDKey, f) 301 logTags := make(logger.CtxLogTags) 302 logTags[CtxIDKey] = CtxOpID 303 ctx = logger.NewContextWithLogTags(ctx, logTags) 304 305 if errRandomReqID == nil { 306 // Add a unique ID to this context, identifying a particular 307 // request. 308 ctx = context.WithValue(ctx, CtxIDKey, id) 309 } 310 311 if libkb.RuntimeGroup() == keybase1.RuntimeGroup_DARWINLIKE { 312 // Timeout operations before they hit the osxfuse time limit, 313 // so we don't hose the entire mount (Fixed in OSXFUSE 3.2.0). 314 // The timeout is 60 seconds, but it looks like sometimes it 315 // tries multiple attempts within that 60 seconds, so let's go 316 // a little under 60/3 to be safe. 317 // 318 // It should be safe to ignore the CancelFunc here because our 319 // parent context will be canceled by the FUSE serve loop. 320 ctx, _ = context.WithDeadline(ctx, start.Add(19*time.Second)) 321 } 322 323 return ctx 324 325 })) 326 327 if err != nil { 328 panic(err) // this should never happen 329 } 330 331 return ctx 332 } 333 334 // Serve FS. Will block. 335 func (f *FS) Serve(ctx context.Context) error { 336 srv := fs.New(f.conn, &fs.Config{ 337 WithContext: func(ctx context.Context, _ fuse.Request) context.Context { 338 return f.WithContext(ctx) 339 }, 340 }) 341 f.fuse = srv 342 343 f.notifications.LaunchProcessor(ctx) 344 f.remoteStatus.Init(ctx, f.log, f.config, f) 345 // Blocks forever, unless an interrupt signal is received 346 // (handled by libkbfs.Init). 347 return srv.Serve(f) 348 } 349 350 // UserChanged is called from libfs. 351 func (f *FS) UserChanged(ctx context.Context, oldName, newName kbname.NormalizedUsername) { 352 f.log.CDebugf(ctx, "User changed: %q -> %q", oldName, newName) 353 f.root.public.userChanged(ctx, oldName, newName) 354 f.root.private.userChanged(ctx, oldName, newName) 355 } 356 357 var _ libfs.RemoteStatusUpdater = (*FS)(nil) 358 359 var _ fs.FS = (*FS)(nil) 360 361 var _ fs.FSStatfser = (*FS)(nil) 362 363 func (f *FS) processError(ctx context.Context, 364 mode libkbfs.ErrorModeType, err error) error { 365 if err == nil { 366 f.errVlog.CLogf(ctx, libkb.VLog1, "Request complete") 367 return nil 368 } 369 370 f.config.Reporter().ReportErr(ctx, "", tlf.Private, mode, err) 371 // We just log the error as debug, rather than error, because it 372 // might just indicate an expected error such as an ENOENT. 373 // 374 // TODO: Classify errors and escalate the logging level of the 375 // important ones. 376 f.errLog.CDebugf(ctx, err.Error()) 377 return filterError(err) 378 } 379 380 // Root implements the fs.FS interface for FS. 381 func (f *FS) Root() (fs.Node, error) { 382 return f.root, nil 383 } 384 385 // quotaUsageStaleTolerance is the lifespan of stale usage data that libfuse 386 // accepts in the Statfs handler. In other words, this causes libkbfs to issue 387 // a fresh RPC call if cached usage data is older than 10s. 388 const quotaUsageStaleTolerance = 10 * time.Second 389 390 // Statfs implements the fs.FSStatfser interface for FS. 391 func (f *FS) Statfs(ctx context.Context, req *fuse.StatfsRequest, resp *fuse.StatfsResponse) error { 392 ctx, maybeUnmounting, cancel := wrapCtxWithShorterTimeoutForUnmount(ctx, f.log, int(req.Pid)) 393 defer cancel() 394 if maybeUnmounting { 395 f.log.CInfof(ctx, "Statfs: maybeUnmounting=true") 396 } 397 398 *resp = fuse.StatfsResponse{ 399 Bsize: fuseBlockSize, 400 Namelen: ^uint32(0), 401 Frsize: fuseBlockSize, 402 } 403 404 if f.remoteStatus.ExtraFileName() != "" { 405 f.vlog.CLogf( 406 ctx, libkb.VLog1, 407 "Skipping quota usage check while errors are present") 408 return nil 409 } 410 411 session, err := idutil.GetCurrentSessionIfPossible( 412 ctx, f.config.KBPKI(), true) 413 if err != nil { 414 return err 415 } else if session == (idutil.SessionInfo{}) { 416 // If user is not logged in, don't bother getting quota info. Otherwise 417 // reading a public TLF while logged out can fail on macOS. 418 return nil 419 } 420 _, usageBytes, _, limitBytes, err := f.config.GetQuotaUsage( 421 session.UID.AsUserOrTeam()).Get( 422 ctx, quotaUsageStaleTolerance/2, quotaUsageStaleTolerance) 423 if err != nil { 424 f.vlog.CLogf(ctx, libkb.VLog1, "Getting quota usage error: %v", err) 425 return err 426 } 427 428 total := getNumBlocksFromSize(uint64(limitBytes)) 429 used := getNumBlocksFromSize(uint64(usageBytes)) 430 resp.Blocks = total 431 resp.Bavail = total - used 432 resp.Bfree = total - used 433 434 return nil 435 } 436 437 // Root represents the root of the KBFS file system. 438 type Root struct { 439 private *FolderList 440 public *FolderList 441 team *FolderList 442 443 lookupLock sync.RWMutex 444 lookupMap map[tlf.Type]bool 445 } 446 447 // NewRoot creates a new root structure for KBFS FUSE mounts. 448 func NewRoot() *Root { 449 return &Root{ 450 lookupMap: make(map[tlf.Type]bool), 451 } 452 } 453 454 var _ fs.NodeAccesser = (*FolderList)(nil) 455 456 // Access implements fs.NodeAccesser interface for *Root. 457 func (*Root) Access(ctx context.Context, r *fuse.AccessRequest) error { 458 if int(r.Uid) != os.Getuid() && 459 // Finder likes to use UID 0 for some operations. osxfuse already allows 460 // ACCESS and GETXATTR requests from root to go through. This allows root 461 // in ACCESS handler. See KBFS-1733 for more details. 462 int(r.Uid) != 0 { 463 // short path: not accessible by anybody other than root or the user who 464 // executed the kbfsfuse process. 465 return fuse.EPERM 466 } 467 468 if r.Mask&02 != 0 { 469 return fuse.EPERM 470 } 471 472 return nil 473 } 474 475 var _ fs.Node = (*Root)(nil) 476 477 // Attr implements the fs.Node interface for Root. 478 func (*Root) Attr(ctx context.Context, a *fuse.Attr) error { 479 a.Mode = os.ModeDir | 0500 480 a.Inode = 1 481 return nil 482 } 483 484 var _ fs.NodeRequestLookuper = (*Root)(nil) 485 486 // Lookup implements the fs.NodeRequestLookuper interface for Root. 487 func (r *Root) Lookup(ctx context.Context, req *fuse.LookupRequest, resp *fuse.LookupResponse) (_ fs.Node, err error) { 488 r.private.fs.vlog.CLogf(ctx, libkb.VLog1, "FS Lookup %s", req.Name) 489 defer func() { err = r.private.fs.processError(ctx, libkbfs.ReadMode, err) }() 490 491 specialNode := handleNonTLFSpecialFile( 492 req.Name, r.private.fs, &resp.EntryValid) 493 if specialNode != nil { 494 return specialNode, nil 495 } 496 497 platformNode, err := r.platformLookup(ctx, req, resp) 498 if platformNode != nil || err != nil { 499 return platformNode, err 500 } 501 502 r.lookupLock.Lock() 503 defer r.lookupLock.Unlock() 504 switch req.Name { 505 case PrivateName: 506 r.lookupMap[tlf.Private] = true 507 return r.private, nil 508 case PublicName: 509 r.lookupMap[tlf.Public] = true 510 return r.public, nil 511 case TeamName: 512 r.lookupMap[tlf.SingleTeam] = true 513 return r.team, nil 514 } 515 516 // Don't want to pop up errors on special OS files. 517 if strings.HasPrefix(req.Name, ".") { 518 return nil, fuse.ENOENT 519 } 520 521 nameToLog := req.Name 522 // This error is logged, but we don't have a handy obfuscator 523 // here, so just log a special string to avoid exposing the user's 524 // typos or misdirected lookups. 525 if r.private.fs.config.Mode().DoLogObfuscation() { 526 nameToLog = "<obfuscated>" 527 } 528 529 return nil, libkbfs.NoSuchFolderListError{ 530 Name: req.Name, 531 NameToLog: nameToLog, 532 PrivName: PrivateName, 533 PubName: PublicName, 534 } 535 } 536 537 // PathType returns PathType for this folder 538 func (r *Root) PathType() tlfhandle.PathType { 539 return tlfhandle.KeybasePathType 540 } 541 542 var _ fs.NodeCreater = (*Root)(nil) 543 544 // Create implements the fs.NodeCreater interface for Root. 545 func (r *Root) Create(ctx context.Context, req *fuse.CreateRequest, resp *fuse.CreateResponse) (_ fs.Node, _ fs.Handle, err error) { 546 r.log().CDebugf(ctx, "FS Create") 547 defer func() { err = r.private.fs.processError(ctx, libkbfs.WriteMode, err) }() 548 if strings.HasPrefix(req.Name, "._") { 549 // Quietly ignore writes to special macOS files, without 550 // triggering a notification. 551 return nil, nil, syscall.ENOENT 552 } 553 return nil, nil, libkbfs.NewWriteUnsupportedError(tlfhandle.BuildCanonicalPath(r.PathType(), req.Name)) 554 } 555 556 // Mkdir implements the fs.NodeMkdirer interface for Root. 557 func (r *Root) Mkdir(ctx context.Context, req *fuse.MkdirRequest) (_ fs.Node, err error) { 558 r.log().CDebugf(ctx, "FS Mkdir") 559 defer func() { err = r.private.fs.processError(ctx, libkbfs.WriteMode, err) }() 560 return nil, libkbfs.NewWriteUnsupportedError(tlfhandle.BuildCanonicalPath(r.PathType(), req.Name)) 561 } 562 563 var _ fs.Handle = (*Root)(nil) 564 565 var _ fs.HandleReadDirAller = (*Root)(nil) 566 567 // ReadDirAll implements the ReadDirAll interface for Root. 568 func (r *Root) ReadDirAll(ctx context.Context) (res []fuse.Dirent, err error) { 569 r.log().CDebugf(ctx, "FS ReadDirAll") 570 defer func() { err = r.private.fs.processError(ctx, libkbfs.ReadMode, err) }() 571 res = []fuse.Dirent{ 572 { 573 Type: fuse.DT_Dir, 574 Name: PrivateName, 575 }, 576 { 577 Type: fuse.DT_Dir, 578 Name: PublicName, 579 }, 580 { 581 Type: fuse.DT_Dir, 582 Name: TeamName, 583 }, 584 } 585 if r.private.fs.platformParams.shouldAppendPlatformRootDirs() { 586 res = append(res, platformRootDirs...) 587 } 588 589 if name := r.private.fs.remoteStatus.ExtraFileName(); name != "" { 590 res = append(res, fuse.Dirent{Type: fuse.DT_File, Name: name}) 591 } 592 return res, nil 593 } 594 595 var _ fs.NodeSymlinker = (*Root)(nil) 596 597 // Symlink implements the fs.NodeSymlinker interface for Root. 598 func (r *Root) Symlink( 599 _ context.Context, _ *fuse.SymlinkRequest) (fs.Node, error) { 600 return nil, fuse.ENOTSUP 601 } 602 603 var _ fs.NodeLinker = (*Root)(nil) 604 605 // Link implements the fs.NodeLinker interface for Root. 606 func (r *Root) Link( 607 _ context.Context, _ *fuse.LinkRequest, _ fs.Node) (fs.Node, error) { 608 return nil, fuse.ENOTSUP 609 } 610 611 func (r *Root) log() logger.Logger { 612 return r.private.fs.log 613 } 614 func (r *Root) openFileCount() (ret int64) { 615 ret += r.private.openFileCount() 616 ret += r.public.openFileCount() 617 ret += r.team.openFileCount() 618 619 r.lookupLock.RLock() 620 defer r.lookupLock.RUnlock() 621 return ret + int64(len(r.lookupMap)) 622 } 623 624 func (r *Root) forgetFolderList(t tlf.Type) { 625 // If the kernel ever forgets a folder list, reset the lookup 626 // function for that type and decrement the lookup counter. 627 r.lookupLock.Lock() 628 defer r.lookupLock.Unlock() 629 delete(r.lookupMap, t) 630 }