forked from keybase/client
-
Notifications
You must be signed in to change notification settings - Fork 0
/
util.go
429 lines (379 loc) · 12.8 KB
/
util.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
// Copyright 2016 Keybase Inc. All rights reserved.
// Use of this source code is governed by a BSD
// license that can be found in the LICENSE file.
package libkbfs
import (
"context"
"encoding/base64"
"fmt"
"os"
"path/filepath"
"strings"
"time"
"github.com/keybase/client/go/kbfs/data"
"github.com/keybase/client/go/kbfs/idutil"
"github.com/keybase/client/go/kbfs/kbfscrypto"
"github.com/keybase/client/go/kbfs/kbfsmd"
"github.com/keybase/client/go/kbfs/libcontext"
"github.com/keybase/client/go/kbfs/tlf"
"github.com/keybase/client/go/kbfs/tlfhandle"
"github.com/keybase/client/go/libkb"
"github.com/keybase/client/go/logger"
"github.com/keybase/client/go/protocol/keybase1"
"github.com/pkg/errors"
)
// Runs fn (which may block) in a separate goroutine and waits for it
// to finish, unless ctx is cancelled. Returns nil only when fn was
// run to completion and succeeded. Any closed-over variables updated
// in fn should be considered visible only if nil is returned.
func runUnlessCanceled(ctx context.Context, fn func() error) error {
c := make(chan error, 1) // buffered, in case the request is canceled
go func() {
c <- fn()
}()
select {
case <-ctx.Done():
return ctx.Err()
case err := <-c:
return err
}
}
// MakeRandomRequestID generates a random ID suitable for tagging a
// request in KBFS, and very likely to be universally unique.
func MakeRandomRequestID() (string, error) {
// Use a random ID to tag each request. We want this to be really
// universally unique, as these request IDs might need to be
// propagated all the way to the server. Use a base64-encoded
// random 128-bit number.
buf := make([]byte, 128/8)
err := kbfscrypto.RandRead(buf)
if err != nil {
return "", err
}
return base64.RawURLEncoding.EncodeToString(buf), nil
}
// BoolForString returns false if trimmed string is "" (empty), "0", "false", or "no"
func BoolForString(s string) bool {
s = strings.TrimSpace(s)
if s == "" || s == "0" || s == "false" || s == "no" {
return false
}
return true
}
// PrereleaseBuild is set at compile time for prerelease builds
var PrereleaseBuild string
// VersionString returns semantic version string
func VersionString() string {
if PrereleaseBuild != "" {
return fmt.Sprintf("%s-%s", libkb.Version, PrereleaseBuild)
}
return libkb.Version
}
// CtxBackgroundSyncKeyType is the type for a context background sync key.
type CtxBackgroundSyncKeyType int
const (
// CtxBackgroundSyncKey is set in the context for any change
// notifications that are triggered from a background sync.
// Observers can ignore these if they want, since they will have
// already gotten the relevant notifications via LocalChanges.
CtxBackgroundSyncKey CtxBackgroundSyncKeyType = iota
)
// Warninger is an interface that only waprs the Warning method.
type Warninger interface {
Warning(format string, args ...interface{})
}
// CtxWithRandomIDReplayable returns a replayable context with a
// random id associated with the given log key.
func CtxWithRandomIDReplayable(ctx context.Context, tagKey interface{},
tagName string, log Warninger) context.Context {
ctx = logger.ConvertRPCTagsToLogTags(ctx)
id, err := MakeRandomRequestID()
if err != nil && log != nil {
log.Warning("Couldn't generate a random request ID: %v", err)
}
return libcontext.NewContextReplayable(ctx, func(ctx context.Context) context.Context {
logTags := make(logger.CtxLogTags)
logTags[tagKey] = tagName
newCtx := logger.NewContextWithLogTags(ctx, logTags)
if err == nil {
newCtx = context.WithValue(newCtx, tagKey, id)
}
return newCtx
})
}
// checkDataVersion validates that the data version for a
// block pointer is valid for the given version validator
func checkDataVersion(
versioner data.Versioner, p data.Path, ptr data.BlockPointer) error {
if ptr.DataVer < data.FirstValidVer {
return errors.WithStack(InvalidDataVersionError{ptr.DataVer})
}
if versioner != nil && ptr.DataVer > versioner.DataVersion() {
return errors.WithStack(NewDataVersionError{p, ptr.DataVer})
}
return nil
}
func checkContext(ctx context.Context) error {
select {
case <-ctx.Done():
return errors.WithStack(ctx.Err())
default:
return nil
}
}
func chargedToForTLF(
ctx context.Context, sessionGetter idutil.CurrentSessionGetter,
rootIDGetter teamRootIDGetter, osg idutil.OfflineStatusGetter,
handle *tlfhandle.Handle) (keybase1.UserOrTeamID, error) {
if handle.Type() == tlf.SingleTeam {
chargedTo := handle.FirstResolvedWriter()
if tid := chargedTo.AsTeamOrBust(); tid.IsSubTeam() {
offline := keybase1.OfflineAvailability_NONE
if osg != nil {
offline = osg.OfflineAvailabilityForID(handle.TlfID())
}
// Subteam blocks should be charged to the root team ID.
rootID, err := rootIDGetter.GetTeamRootID(ctx, tid, offline)
if err != nil {
return keybase1.UserOrTeamID(""), err
}
return rootID.AsUserOrTeam(), nil
}
return chargedTo, nil
}
// For private and public folders, use the session user.
session, err := sessionGetter.GetCurrentSession(ctx)
if err != nil {
return keybase1.UserOrTeamID(""), err
}
return session.UID.AsUserOrTeam(), nil
}
// GetHandleFromFolderNameAndType returns a TLFHandle given a folder
// name (e.g., "u1,u2#u3") and a TLF type.
func GetHandleFromFolderNameAndType(
ctx context.Context, kbpki KBPKI, idGetter tlfhandle.IDGetter,
syncGetter syncedTlfGetterSetter, tlfName string,
t tlf.Type) (*tlfhandle.Handle, error) {
for {
tlfHandle, err := tlfhandle.ParseHandle(
ctx, kbpki, idGetter, syncGetter, tlfName, t)
switch e := errors.Cause(err).(type) {
case idutil.TlfNameNotCanonical:
tlfName = e.NameToTry
case nil:
return tlfHandle, nil
default:
return nil, err
}
}
}
// getHandleFromFolderName returns a TLFHandle given a folder
// name (e.g., "u1,u2#u3") and a public/private bool. DEPRECATED.
func getHandleFromFolderName(
ctx context.Context, kbpki KBPKI, idGetter tlfhandle.IDGetter,
syncGetter syncedTlfGetterSetter, tlfName string,
public bool) (*tlfhandle.Handle, error) {
// TODO(KBFS-2185): update the protocol to support requests
// for single-team TLFs.
t := tlf.Private
if public {
t = tlf.Public
}
return GetHandleFromFolderNameAndType(
ctx, kbpki, idGetter, syncGetter, tlfName, t)
}
// IsWriterFromHandle checks whether the given UID is a writer for the
// given handle. It understands team-keyed handles as well as
// classically-keyed handles.
func IsWriterFromHandle(
ctx context.Context, h *tlfhandle.Handle, checker kbfsmd.TeamMembershipChecker,
osg idutil.OfflineStatusGetter, uid keybase1.UID,
verifyingKey kbfscrypto.VerifyingKey) (bool, error) {
if h.TypeForKeying() != tlf.TeamKeying {
return h.IsWriter(uid), nil
}
// Team membership needs to be checked with the service. For a
// SingleTeam TLF, there is always only a single writer in the
// handle.
tid, err := h.FirstResolvedWriter().AsTeam()
if err != nil {
return false, err
}
offline := keybase1.OfflineAvailability_NONE
if osg != nil {
offline = osg.OfflineAvailabilityForID(h.TlfID())
}
return checker.IsTeamWriter(ctx, tid, uid, verifyingKey, offline)
}
func isReaderFromHandle(
ctx context.Context, h *tlfhandle.Handle, checker kbfsmd.TeamMembershipChecker,
osg idutil.OfflineStatusGetter, uid keybase1.UID) (bool, error) {
if h.TypeForKeying() != tlf.TeamKeying {
return h.IsReader(uid), nil
}
// Team membership needs to be checked with the service. For a
// SingleTeam TLF, there is always only a single writer in the
// handle.
tid, err := h.FirstResolvedWriter().AsTeam()
if err != nil {
return false, err
}
offline := keybase1.OfflineAvailability_NONE
if osg != nil {
offline = osg.OfflineAvailabilityForID(h.TlfID())
}
return checker.IsTeamReader(ctx, tid, uid, offline)
}
func tlfToMerkleTreeID(id tlf.ID) keybase1.MerkleTreeID {
switch id.Type() {
case tlf.Private:
return keybase1.MerkleTreeID_KBFS_PRIVATE
case tlf.Public:
return keybase1.MerkleTreeID_KBFS_PUBLIC
case tlf.SingleTeam:
return keybase1.MerkleTreeID_KBFS_PRIVATETEAM
default:
panic(fmt.Sprintf("Unexpected TLF type: %d", id.Type()))
}
}
// IsOnlyWriterInNonTeamTlf returns true if and only if the TLF described by h
// is a non-team TLF, and the currently logged-in user is the only writer for
// the TLF. In case of any error false is returned.
func IsOnlyWriterInNonTeamTlf(ctx context.Context, kbpki KBPKI,
h *tlfhandle.Handle) bool {
session, err := idutil.GetCurrentSessionIfPossible(
ctx, kbpki, h.Type() == tlf.Public)
if err != nil {
return false
}
if h.TypeForKeying() == tlf.TeamKeying {
return false
}
return tlf.UserIsOnlyWriter(session.Name, h.GetCanonicalName())
}
const (
// GitStorageRootPrefix is the prefix of the temp storage root
// directory made for single-op git operations.
GitStorageRootPrefix = "kbfsgit"
// ConflictStorageRootPrefix is the prefix of the temp directory
// made for the conflict resolution disk cache.
ConflictStorageRootPrefix = "kbfs_conflict_disk_cache"
minAgeForStorageCleanup = 24 * time.Hour
)
func cleanOldTempStorageRoots(config Config) {
log := config.MakeLogger("")
ctx := CtxWithRandomIDReplayable(
context.Background(), CtxInitKey, CtxInitID, log)
storageRoot := config.StorageRoot()
d, err := os.Open(storageRoot)
if err != nil {
log.CDebugf(ctx, "Error opening storage root %s: %+v", storageRoot, err)
return
}
defer d.Close()
fis, err := d.Readdir(0)
if err != nil {
log.CDebugf(ctx, "Error reading storage root %s: %+v", storageRoot, err)
return
}
modTimeCutoff := config.Clock().Now().Add(-minAgeForStorageCleanup)
cleanedOne := false
for _, fi := range fis {
if fi.ModTime().After(modTimeCutoff) {
continue
}
if !strings.HasPrefix(fi.Name(), GitStorageRootPrefix) &&
!strings.HasPrefix(fi.Name(), ConflictStorageRootPrefix) {
continue
}
cleanedOne = true
dir := filepath.Join(storageRoot, fi.Name())
log.CDebugf(ctx, "Cleaning up old storage root %s, "+
"last modified at %s", dir, fi.ModTime())
err = os.RemoveAll(dir)
if err != nil {
log.CDebugf(ctx, "Error deleting %s: %+v", dir, err)
continue
}
}
if cleanedOne {
log.CDebugf(ctx, "Done cleaning old storage roots")
}
}
// GetLocalDiskStats returns the local disk stats, according to the
// disk block cache.
func GetLocalDiskStats(ctx context.Context, dbc DiskBlockCache) (
bytesAvail, bytesTotal int64) {
if dbc == nil {
return 0, 0
}
dbcStatus := dbc.Status(ctx)
if status, ok := dbcStatus["SyncBlockCache"]; ok {
return int64(status.LocalDiskBytesAvailable),
int64(status.LocalDiskBytesTotal)
}
return 0, 0
}
// FillInDiskSpaceStatus fills in the `OutOfSyncSpace`,
// prefetchStatus, and local disk space fields of the given status.
func FillInDiskSpaceStatus(
ctx context.Context, status *keybase1.FolderSyncStatus,
prefetchStatus keybase1.PrefetchStatus, dbc DiskBlockCache) {
status.PrefetchStatus = prefetchStatus
if dbc == nil {
return
}
status.LocalDiskBytesAvailable, status.LocalDiskBytesTotal =
GetLocalDiskStats(ctx, dbc)
if prefetchStatus == keybase1.PrefetchStatus_COMPLETE {
return
}
hasRoom, _, err := dbc.DoesCacheHaveSpace(
context.Background(), DiskBlockSyncCache)
if err != nil {
return
}
status.OutOfSyncSpace = !hasRoom
}
// KeybaseServicePassthrough is an implementation of
// `KeybaseServiceCn` that just uses the existing services in a given,
// existing Config object.
type KeybaseServicePassthrough struct {
config Config
}
// NewKeybaseServicePassthrough returns a new service passthrough
// using the given config.
func NewKeybaseServicePassthrough(config Config) KeybaseServicePassthrough {
return KeybaseServicePassthrough{config: config}
}
var _ KeybaseServiceCn = KeybaseServicePassthrough{}
// NewKeybaseService implements the KeybaseServiceCn for
// KeybaseServicePassthrough.
func (ksp KeybaseServicePassthrough) NewKeybaseService(
_ Config, _ InitParams, _ Context, _ logger.Logger) (
KeybaseService, error) {
return ksp.config.KeybaseService(), nil
}
// NewCrypto implements the KeybaseServiceCn for
// KeybaseServicePassthrough.
func (ksp KeybaseServicePassthrough) NewCrypto(
_ Config, _ InitParams, _ Context, _ logger.Logger) (Crypto, error) {
return ksp.config.Crypto(), nil
}
// NewChat implements the KeybaseServiceCn for
// KeybaseServicePassthrough.
func (ksp KeybaseServicePassthrough) NewChat(
_ Config, _ InitParams, _ Context, _ logger.Logger) (Chat, error) {
return ksp.config.Chat(), nil
}
// MakeDiskMDServer creates a disk-based local MD server.
func MakeDiskMDServer(config Config, serverRootDir string) (MDServer, error) {
mdPath := filepath.Join(serverRootDir, "kbfs_md")
return NewMDServerDir(mdServerLocalConfigAdapter{config}, mdPath)
}
// MakeDiskBlockServer creates a disk-based local block server.
func MakeDiskBlockServer(config Config, serverRootDir string) BlockServer {
blockPath := filepath.Join(serverRootDir, "kbfs_block")
bserverLog := config.MakeLogger("BSD")
return NewBlockServerDir(config.Codec(), bserverLog, blockPath)
}