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 }