github.com/keybase/client/go@v0.0.0-20240309051027-028f7c731f8b/kbfs/libgit/rpc.go (about)

     1  // Copyright 2017 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 libgit
     6  
     7  import (
     8  	"os"
     9  	"time"
    10  
    11  	"github.com/keybase/client/go/kbfs/data"
    12  	"github.com/keybase/client/go/kbfs/kbfsmd"
    13  	"github.com/keybase/client/go/kbfs/libkbfs"
    14  	"github.com/keybase/client/go/kbfs/tlf"
    15  	"github.com/keybase/client/go/kbfs/tlfhandle"
    16  	"github.com/keybase/client/go/logger"
    17  	"github.com/keybase/client/go/protocol/keybase1"
    18  	"github.com/pkg/errors"
    19  	"golang.org/x/net/context"
    20  )
    21  
    22  // RPCHandler handles service->KBFS git RPC calls.
    23  type RPCHandler struct {
    24  	kbCtx          libkbfs.Context
    25  	config         libkbfs.Config
    26  	kbfsInitParams *libkbfs.InitParams
    27  	log            logger.Logger
    28  }
    29  
    30  // NewRPCHandlerWithCtx returns a new instance of a Git RPC handler.
    31  func NewRPCHandlerWithCtx(kbCtx libkbfs.Context, config libkbfs.Config,
    32  	kbfsInitParams *libkbfs.InitParams) (*RPCHandler, func()) {
    33  	shutdown := StartAutogit(config, 100)
    34  	return &RPCHandler{
    35  		kbCtx:          kbCtx,
    36  		config:         config,
    37  		kbfsInitParams: kbfsInitParams,
    38  		log:            config.MakeLogger(""),
    39  	}, shutdown
    40  }
    41  
    42  var _ keybase1.KBFSGitInterface = (*RPCHandler)(nil)
    43  
    44  func (rh *RPCHandler) waitForJournal(
    45  	ctx context.Context, gitConfig libkbfs.Config,
    46  	h *tlfhandle.Handle) error {
    47  	err := CleanOldDeletedReposTimeLimited(ctx, gitConfig, h)
    48  	if err != nil {
    49  		return err
    50  	}
    51  
    52  	rootNode, _, err := gitConfig.KBFSOps().GetOrCreateRootNode(
    53  		ctx, h, data.MasterBranch)
    54  	if err != nil {
    55  		return err
    56  	}
    57  
    58  	err = gitConfig.KBFSOps().SyncAll(ctx, rootNode.GetFolderBranch())
    59  	if err != nil {
    60  		return err
    61  	}
    62  
    63  	jManager, err := libkbfs.GetJournalManager(gitConfig)
    64  	if err != nil {
    65  		rh.log.CDebugf(ctx, "No journal server: %+v", err)
    66  		return nil
    67  	}
    68  
    69  	_, err = jManager.JournalStatus(rootNode.GetFolderBranch().Tlf)
    70  	if err != nil {
    71  		rh.log.CDebugf(ctx, "No journal: %+v", err)
    72  		return nil
    73  	}
    74  
    75  	// This squashes everything written to the journal into a single
    76  	// revision, to make sure that no partial states of the bare repo
    77  	// are seen by other readers of the TLF.  It also waits for any
    78  	// necessary conflict resolution to complete.
    79  	err = jManager.FinishSingleOp(ctx,
    80  		rootNode.GetFolderBranch().Tlf, nil, keybase1.MDPriorityGit)
    81  	if err != nil {
    82  		return err
    83  	}
    84  
    85  	// Make sure that everything is truly flushed.
    86  	status, err := jManager.JournalStatus(rootNode.GetFolderBranch().Tlf)
    87  	if err != nil {
    88  		return err
    89  	}
    90  
    91  	if status.RevisionStart != kbfsmd.RevisionUninitialized {
    92  		rh.log.CDebugf(ctx, "Journal status: %+v", status)
    93  		return errors.New("Journal is non-empty after a wait")
    94  	}
    95  	return nil
    96  }
    97  
    98  func (rh *RPCHandler) doShutdown(
    99  	ctx context.Context, gitConfig libkbfs.Config, tempDir string) {
   100  	shutdownErr := gitConfig.Shutdown(ctx)
   101  	if shutdownErr != nil {
   102  		rh.log.CDebugf(
   103  			ctx, "Error shutting down git: %+v\n", shutdownErr)
   104  	}
   105  	rmErr := os.RemoveAll(tempDir)
   106  	if rmErr != nil {
   107  		rh.log.CDebugf(
   108  			ctx, "Error cleaning storage dir %s: %+v\n", tempDir, rmErr)
   109  	}
   110  }
   111  
   112  func (rh *RPCHandler) getHandleAndConfig(
   113  	ctx context.Context, folder keybase1.FolderHandle) (
   114  	newCtx context.Context, gitConfigRet libkbfs.Config,
   115  	tlfHandle *tlfhandle.Handle, tempDirRet string, err error) {
   116  	newCtx, gitConfig, tempDir, err := getNewConfig(
   117  		ctx, rh.config, rh.kbCtx, rh.kbfsInitParams, rh.log)
   118  	if err != nil {
   119  		return nil, nil, nil, "", err
   120  	}
   121  	defer func() {
   122  		if err != nil {
   123  			rh.doShutdown(ctx, gitConfig, tempDir)
   124  		}
   125  	}()
   126  
   127  	// Make sure we have a legit folder name, and get the TLF handle.
   128  	// Use `gitConfig`, rather than `rh.config`, to make sure the
   129  	// journal is created under the right journal server.
   130  	tlfHandle, err = libkbfs.GetHandleFromFolderNameAndType(
   131  		ctx, gitConfig.KBPKI(), gitConfig.MDOps(), gitConfig, folder.Name,
   132  		tlf.TypeFromFolderType(folder.FolderType))
   133  	if err != nil {
   134  		return nil, nil, nil, "", err
   135  	}
   136  
   137  	return newCtx, gitConfig, tlfHandle, tempDir, nil
   138  }
   139  
   140  // CreateRepo implements keybase1.KBFSGitInterface for KeybaseServiceBase.
   141  func (rh *RPCHandler) CreateRepo(
   142  	ctx context.Context, arg keybase1.CreateRepoArg) (
   143  	id keybase1.RepoID, err error) {
   144  	rh.log.CDebugf(ctx, "Creating repo %s in folder %s/%s",
   145  		arg.Name, arg.Folder.FolderType, arg.Folder.Name)
   146  	defer func() {
   147  		rh.log.CDebugf(ctx, "Done creating repo: %+v", err)
   148  	}()
   149  
   150  	ctx, cancel := context.WithCancel(ctx)
   151  	defer cancel()
   152  	ctx, gitConfig, tlfHandle, tempDir, err := rh.getHandleAndConfig(
   153  		ctx, arg.Folder)
   154  	if err != nil {
   155  		return "", err
   156  	}
   157  	defer rh.doShutdown(ctx, gitConfig, tempDir)
   158  
   159  	ctx = context.WithValue(ctx, libkbfs.CtxAllowNameKey, kbfsRepoDir)
   160  	gitID, err := CreateRepoAndID(ctx, gitConfig, tlfHandle, string(arg.Name))
   161  	if err != nil {
   162  		return "", err
   163  	}
   164  
   165  	err = rh.waitForJournal(ctx, gitConfig, tlfHandle)
   166  	if err != nil {
   167  		return "", err
   168  	}
   169  
   170  	return keybase1.RepoID(gitID.String()), nil
   171  }
   172  
   173  func (rh *RPCHandler) scheduleCleaning(folder keybase1.FolderHandle) {
   174  	// TODO: cancel outstanding timers on shutdown, if we ever utilize
   175  	// the DeleteRepo RPC handler in a test.
   176  	time.AfterFunc(minDeletedAgeForCleaning+1*time.Second, func() {
   177  		log := rh.config.MakeLogger("")
   178  
   179  		ctx, cancel := context.WithCancel(context.Background())
   180  		defer cancel()
   181  		ctx, gitConfig, tlfHandle, tempDir, err := rh.getHandleAndConfig(
   182  			ctx, folder)
   183  		if err != nil {
   184  			log.CDebugf(
   185  				context.TODO(),
   186  				"Couldn't init for scheduled cleaning of %s: %+v",
   187  				folder.Name, err)
   188  			return
   189  		}
   190  		defer rh.doShutdown(ctx, gitConfig, tempDir)
   191  
   192  		log.CDebugf(ctx, "Starting a scheduled repo clean for folder %s",
   193  			tlfHandle.GetCanonicalPath())
   194  		err = CleanOldDeletedRepos(ctx, gitConfig, tlfHandle)
   195  		if err != nil {
   196  			log.CDebugf(ctx, "Couldn't clean folder %s: %+v",
   197  				tlfHandle.GetCanonicalPath(), err)
   198  			return
   199  		}
   200  
   201  		err = rh.waitForJournal(ctx, gitConfig, tlfHandle)
   202  		if err != nil {
   203  			log.CDebugf(ctx, "Error waiting for journal after cleaning "+
   204  				"folder %s: %+v", tlfHandle.GetCanonicalPath(), err)
   205  			return
   206  		}
   207  	})
   208  }
   209  
   210  // DeleteRepo implements keybase1.KBFSGitInterface for KeybaseServiceBase.
   211  func (rh *RPCHandler) DeleteRepo(
   212  	ctx context.Context, arg keybase1.DeleteRepoArg) (err error) {
   213  	rh.log.CDebugf(ctx, "Deleting repo %s from folder %s/%s",
   214  		arg.Name, arg.Folder.FolderType, arg.Folder.Name)
   215  	defer func() {
   216  		rh.log.CDebugf(ctx, "Done deleting repo: %+v", err)
   217  	}()
   218  
   219  	ctx, cancel := context.WithCancel(ctx)
   220  	defer cancel()
   221  	ctx, gitConfig, tlfHandle, tempDir, err := rh.getHandleAndConfig(
   222  		ctx, arg.Folder)
   223  	if err != nil {
   224  		return err
   225  	}
   226  	defer rh.doShutdown(ctx, gitConfig, tempDir)
   227  
   228  	err = DeleteRepo(ctx, gitConfig, tlfHandle, string(arg.Name))
   229  	if err != nil {
   230  		return err
   231  	}
   232  
   233  	err = rh.waitForJournal(ctx, gitConfig, tlfHandle)
   234  	if err != nil {
   235  		return err
   236  	}
   237  
   238  	rh.scheduleCleaning(arg.Folder)
   239  	return nil
   240  }
   241  
   242  // Gc implements keybase1.KBFSGitInterface for KeybaseServiceBase.
   243  func (rh *RPCHandler) Gc(
   244  	ctx context.Context, arg keybase1.GcArg) (err error) {
   245  	rh.log.CDebugf(ctx, "Garbage-collecting repo %s from folder %s/%s",
   246  		arg.Name, arg.Folder.FolderType, arg.Folder.Name)
   247  	defer func() {
   248  		rh.log.CDebugf(ctx, "Done garbage-collecting repo: %+v", err)
   249  	}()
   250  
   251  	ctx, cancel := context.WithCancel(ctx)
   252  	defer cancel()
   253  	ctx, gitConfig, tlfHandle, tempDir, err := rh.getHandleAndConfig(
   254  		ctx, arg.Folder)
   255  	if err != nil {
   256  		return err
   257  	}
   258  	defer rh.doShutdown(ctx, gitConfig, tempDir)
   259  
   260  	gco := GCOptions{
   261  		MaxLooseRefs:         arg.Options.MaxLooseRefs,
   262  		PruneMinLooseObjects: arg.Options.PruneMinLooseObjects,
   263  		PruneExpireTime:      keybase1.FromTime(arg.Options.PruneExpireTime),
   264  		MaxObjectPacks:       arg.Options.MaxObjectPacks,
   265  	}
   266  	err = GCRepo(ctx, gitConfig, tlfHandle, string(arg.Name), gco)
   267  	if err != nil {
   268  		return err
   269  	}
   270  
   271  	return rh.waitForJournal(ctx, gitConfig, tlfHandle)
   272  }
   273  
   274  // RenameRepo renames an existing git repository.
   275  //
   276  // TODO: Hook this up to an RPC.
   277  func (rh *RPCHandler) RenameRepo(ctx context.Context,
   278  	folder keybase1.FolderHandle, oldName, newName string) (err error) {
   279  	rh.log.CDebugf(ctx, "Renaming repo %s to %s", oldName, newName)
   280  	defer func() {
   281  		rh.log.CDebugf(ctx, "Done renaming repo: %+v", err)
   282  	}()
   283  
   284  	ctx, cancel := context.WithCancel(ctx)
   285  	defer cancel()
   286  	ctx, gitConfig, tlfHandle, tempDir, err := rh.getHandleAndConfig(
   287  		ctx, folder)
   288  	if err != nil {
   289  		return err
   290  	}
   291  	defer rh.doShutdown(ctx, gitConfig, tempDir)
   292  
   293  	err = RenameRepo(ctx, gitConfig, tlfHandle, oldName, newName)
   294  	if err != nil {
   295  		return err
   296  	}
   297  
   298  	err = rh.waitForJournal(ctx, gitConfig, tlfHandle)
   299  	if err != nil {
   300  		return err
   301  	}
   302  
   303  	return nil
   304  }