github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/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 simplefsIface keybase1.SimpleFSInterface
    99  		simplefsIface, shutdownSimpleFS = simplefs.NewSimpleFS(
   100  			libkbfsCtx, config)
   101  		return keybase1.SimpleFSProtocol(simplefsIface), nil
   102  	}
   103  	// Hook git implementation in.
   104  	shutdownGit := func() {}
   105  	createGitHandler := func(
   106  		libkbfsCtx libkbfs.Context, config libkbfs.Config) (rpc.Protocol, error) {
   107  		var handler keybase1.KBFSGitInterface
   108  		handler, shutdownGit = libgit.NewRPCHandlerWithCtx(
   109  			libkbfsCtx, config, &options.KbfsParams)
   110  		return keybase1.KBFSGitProtocol(handler), nil
   111  	}
   112  	defer func() {
   113  		err := shutdownSimpleFS(context.Background())
   114  		if err != nil {
   115  			fmt.Fprintf(os.Stderr, "Couldn't shut down SimpleFS: %+v\n", err)
   116  		}
   117  		shutdownGit()
   118  	}()
   119  
   120  	// Patch the kbfsParams to inject two additional protocols.
   121  	options.KbfsParams.AdditionalProtocolCreators = []libkbfs.AdditionalProtocolCreator{
   122  		createSimpleFS, createGitHandler,
   123  	}
   124  
   125  	log, err := libkbfs.InitLog(options.KbfsParams, kbCtx)
   126  	if err != nil {
   127  		return libfs.InitError(err.Error())
   128  	}
   129  
   130  	if options.RuntimeDir != "" {
   131  		err := os.MkdirAll(options.RuntimeDir, libkb.PermDir)
   132  		if err != nil {
   133  			return libfs.InitError(err.Error())
   134  		}
   135  		info := libkb.NewServiceInfo(libkb.Version, libkbfs.PrereleaseBuild, options.Label, os.Getpid())
   136  		err = info.WriteFile(path.Join(options.RuntimeDir, "kbfs.info"), log)
   137  		if err != nil {
   138  			return libfs.InitError(err.Error())
   139  		}
   140  	}
   141  
   142  	log.Debug("Initializing")
   143  	mi := libfs.NewMountInterrupter(log)
   144  	ctx := context.Background()
   145  	config, err := libkbfs.Init(
   146  		ctx, kbCtx, options.KbfsParams, nil, mi.Done, log)
   147  	if err != nil {
   148  		return libfs.InitError(err.Error())
   149  	}
   150  	defer libkbfs.Shutdown()
   151  
   152  	libfs.AddRootWrapper(config)
   153  
   154  	if options.KbfsParams.Debug {
   155  		fuseLog := config.MakeLogger("FUSE").CloneWithAddedDepth(1)
   156  		fuse.Debug = MakeFuseVDebugFn(
   157  			config.MakeVLogger(fuseLog), false /* superVerbose */)
   158  	}
   159  
   160  	// Report "startup successful" to the supervisor (currently just systemd on
   161  	// Linux). This isn't necessary for correctness, but it allows commands
   162  	// like "systemctl start kbfs.service" to report startup errors to the
   163  	// terminal, by delaying their return until they get this notification
   164  	// (Type=notify, in systemd lingo).
   165  	systemd.NotifyStartupFinished()
   166  
   167  	if options.SkipMount {
   168  		log.Debug("Skipping mounting filesystem")
   169  	} else {
   170  		err = startMounting(ctx, kbCtx, config, options, log, mi)
   171  		if err != nil {
   172  			// Abort on error if we were force mounting, otherwise continue.
   173  			if options.MountErrorIsFatal {
   174  				// If we exit we might want to clean a mount behind us.
   175  				_ = mi.Done()
   176  				return libfs.MountError(err.Error())
   177  			}
   178  		}
   179  	}
   180  	mi.Wait()
   181  	return nil
   182  }