github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/kbfs/libfuse/start.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  	"path"
    14  
    15  	"bazil.org/fuse"
    16  	"github.com/keybase/client/go/kbfs/libfs"
    17  	"github.com/keybase/client/go/kbfs/libgit"
    18  	"github.com/keybase/client/go/kbfs/libkbfs"
    19  	"github.com/keybase/client/go/kbfs/simplefs"
    20  	"github.com/keybase/client/go/libkb"
    21  	"github.com/keybase/client/go/logger"
    22  	"github.com/keybase/client/go/protocol/keybase1"
    23  	"github.com/keybase/client/go/systemd"
    24  	"github.com/keybase/go-framed-msgpack-rpc/rpc"
    25  	"golang.org/x/net/context"
    26  )
    27  
    28  // StartOptions are options for starting up
    29  type StartOptions struct {
    30  	KbfsParams        libkbfs.InitParams
    31  	PlatformParams    PlatformParams
    32  	RuntimeDir        string
    33  	Label             string
    34  	ForceMount        bool
    35  	MountErrorIsFatal bool
    36  	SkipMount         bool
    37  	MountPoint        string
    38  }
    39  
    40  func startMounting(ctx context.Context,
    41  	kbCtx libkbfs.Context, config libkbfs.Config, options StartOptions,
    42  	log logger.Logger, mi *libfs.MountInterrupter) error {
    43  	log.CDebugf(ctx, "Mounting: %q", options.MountPoint)
    44  
    45  	var mounter = &mounter{
    46  		options: options,
    47  		log:     log,
    48  		runMode: kbCtx.GetRunMode(),
    49  	}
    50  	err := mi.MountAndSetUnmount(mounter)
    51  	if err != nil {
    52  		return err
    53  	}
    54  
    55  	log.CDebugf(ctx, "Creating filesystem")
    56  	fs := NewFS(config, mounter.c, options.KbfsParams.Debug,
    57  		options.PlatformParams)
    58  	ctx, cancel := context.WithCancel(ctx)
    59  	defer cancel()
    60  	ctx = context.WithValue(ctx, libfs.CtxAppIDKey, fs)
    61  
    62  	go func() {
    63  		select {
    64  		case <-mounter.c.Ready:
    65  			// We wait for the mounter to finish asynchronously with
    66  			// calling fs.Serve() below, for the rare osxfuse case
    67  			// where `mount(2)` makes a blocking STATFS call before
    68  			// completing.  If we aren't listening for the STATFS call
    69  			// when this happens, there will be a deadlock, and the
    70  			// mount will silently fail after two minutes.  See
    71  			// KBFS-2409.
    72  			err = mounter.c.MountError
    73  			if err != nil {
    74  				log.CWarningf(ctx, "Mount error: %+v", err)
    75  				cancel()
    76  				return
    77  			}
    78  			log.CDebugf(ctx, "Mount ready")
    79  		case <-ctx.Done():
    80  		}
    81  	}()
    82  
    83  	log.CDebugf(ctx, "Serving filesystem")
    84  	if err = fs.Serve(ctx); err != nil {
    85  		return err
    86  	}
    87  
    88  	log.CDebugf(ctx, "Ending")
    89  	return nil
    90  }
    91  
    92  // Start the filesystem
    93  func Start(options StartOptions, kbCtx libkbfs.Context) *libfs.Error {
    94  	// Hook simplefs implementation in.
    95  	shutdownSimpleFS := func(_ context.Context) error { return nil }
    96  	createSimpleFS := func(
    97  		libkbfsCtx libkbfs.Context, config libkbfs.Config) (rpc.Protocol, error) {
    98  		var sfs *simplefs.SimpleFS
    99  		sfs, shutdownSimpleFS = simplefs.NewSimpleFS(
   100  			libkbfsCtx, config)
   101  		config.AddResetForLoginTarget(sfs)
   102  		return keybase1.SimpleFSProtocol(sfs), nil
   103  	}
   104  	// Hook git implementation in.
   105  	shutdownGit := func() {}
   106  	createGitHandler := func(
   107  		libkbfsCtx libkbfs.Context, config libkbfs.Config) (rpc.Protocol, error) {
   108  		var handler keybase1.KBFSGitInterface
   109  		handler, shutdownGit = libgit.NewRPCHandlerWithCtx(
   110  			libkbfsCtx, config, &options.KbfsParams)
   111  		return keybase1.KBFSGitProtocol(handler), nil
   112  	}
   113  	defer func() {
   114  		err := shutdownSimpleFS(context.Background())
   115  		if err != nil {
   116  			fmt.Fprintf(os.Stderr, "Couldn't shut down SimpleFS: %+v\n", err)
   117  		}
   118  		shutdownGit()
   119  	}()
   120  
   121  	// Patch the kbfsParams to inject two additional protocols.
   122  	options.KbfsParams.AdditionalProtocolCreators = []libkbfs.AdditionalProtocolCreator{
   123  		createSimpleFS, createGitHandler,
   124  	}
   125  
   126  	log, err := libkbfs.InitLog(options.KbfsParams, kbCtx)
   127  	if err != nil {
   128  		return libfs.InitError(err.Error())
   129  	}
   130  
   131  	if options.RuntimeDir != "" {
   132  		err := os.MkdirAll(options.RuntimeDir, libkb.PermDir)
   133  		if err != nil {
   134  			return libfs.InitError(err.Error())
   135  		}
   136  		info := libkb.NewServiceInfo(libkb.Version, libkbfs.PrereleaseBuild, options.Label, os.Getpid())
   137  		err = info.WriteFile(path.Join(options.RuntimeDir, "kbfs.info"), log)
   138  		if err != nil {
   139  			return libfs.InitError(err.Error())
   140  		}
   141  	}
   142  
   143  	log.Debug("Initializing")
   144  	mi := libfs.NewMountInterrupter(log)
   145  	ctx := context.Background()
   146  	config, err := libkbfs.Init(
   147  		ctx, kbCtx, options.KbfsParams, nil, mi.Done, log)
   148  	if err != nil {
   149  		return libfs.InitError(err.Error())
   150  	}
   151  	defer libkbfs.Shutdown()
   152  
   153  	libfs.AddRootWrapper(config)
   154  
   155  	if options.KbfsParams.Debug {
   156  		fuseLog := config.MakeLogger("FUSE").CloneWithAddedDepth(1)
   157  		fuse.Debug = MakeFuseVDebugFn(
   158  			config.MakeVLogger(fuseLog), false /* superVerbose */)
   159  	}
   160  
   161  	// Report "startup successful" to the supervisor (currently just systemd on
   162  	// Linux). This isn't necessary for correctness, but it allows commands
   163  	// like "systemctl start kbfs.service" to report startup errors to the
   164  	// terminal, by delaying their return until they get this notification
   165  	// (Type=notify, in systemd lingo).
   166  	systemd.NotifyStartupFinished()
   167  
   168  	if options.SkipMount {
   169  		log.Debug("Skipping mounting filesystem")
   170  	} else {
   171  		err = startMounting(ctx, kbCtx, config, options, log, mi)
   172  		if err != nil {
   173  			// Abort on error if we were force mounting, otherwise continue.
   174  			if options.MountErrorIsFatal {
   175  				// If we exit we might want to clean a mount behind us.
   176  				_ = mi.Done()
   177  				return libfs.MountError(err.Error())
   178  			}
   179  		}
   180  	}
   181  	mi.Wait()
   182  	return nil
   183  }