/
fs.go
703 lines (627 loc) · 21.2 KB
/
fs.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
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
// 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 libdokan
import (
"errors"
"os"
"strconv"
"strings"
"sync"
"time"
"github.com/adamwalz/keybase-client/go/kbfs/dokan"
"github.com/adamwalz/keybase-client/go/kbfs/dokan/winacl"
"github.com/adamwalz/keybase-client/go/kbfs/idutil"
"github.com/adamwalz/keybase-client/go/kbfs/libcontext"
"github.com/adamwalz/keybase-client/go/kbfs/libfs"
"github.com/adamwalz/keybase-client/go/kbfs/libkbfs"
"github.com/adamwalz/keybase-client/go/kbfs/tlf"
"github.com/adamwalz/keybase-client/go/kbfs/tlfhandle"
kbname "github.com/adamwalz/keybase-client/go/kbun"
"github.com/adamwalz/keybase-client/go/libkb"
"github.com/adamwalz/keybase-client/go/logger"
"github.com/adamwalz/keybase-client/go/protocol/keybase1"
"golang.org/x/net/context"
)
// FS implements the newfuse FS interface for KBFS.
type FS struct {
config libkbfs.Config
log logger.Logger
vlog *libkb.VDebugLog
// renameAndDeletionLock should be held when doing renames or deletions.
renameAndDeletionLock sync.Mutex
notifications *libfs.FSNotifications
root *Root
// remoteStatus is the current status of remote connections.
remoteStatus libfs.RemoteStatus
}
// DefaultMountFlags are the default mount flags for libdokan.
const DefaultMountFlags = dokan.CurrentSession
// currentUserSID stores the Windows identity of the user running
// this process. This is the same process-wide.
var currentUserSID, currentUserSIDErr = winacl.CurrentProcessUserSid()
var currentGroupSID, _ = winacl.CurrentProcessPrimaryGroupSid()
// NewFS creates an FS
func NewFS(ctx context.Context, config libkbfs.Config, log logger.Logger) (*FS, error) {
if currentUserSIDErr != nil {
return nil, currentUserSIDErr
}
f := &FS{
config: config,
log: log,
vlog: config.MakeVLogger(log),
notifications: libfs.NewFSNotifications(log),
}
f.root = &Root{
private: &FolderList{
fs: f,
tlfType: tlf.Private,
folders: make(map[string]fileOpener),
aliasCache: map[string]string{},
},
public: &FolderList{
fs: f,
tlfType: tlf.Public,
folders: make(map[string]fileOpener),
aliasCache: map[string]string{},
},
team: &FolderList{
fs: f,
tlfType: tlf.SingleTeam,
folders: make(map[string]fileOpener),
aliasCache: map[string]string{},
}}
ctx = wrapContext(ctx, f)
f.remoteStatus.Init(ctx, f.log, f.config, f)
f.notifications.LaunchProcessor(ctx)
go clearFolderListCacheLoop(ctx, f.root)
return f, nil
}
// Adds log tags etc
func wrapContext(ctx context.Context, f *FS) context.Context {
ctx = context.WithValue(ctx, libfs.CtxAppIDKey, f)
logTags := make(logger.CtxLogTags)
logTags[CtxIDKey] = CtxOpID
ctx = logger.NewContextWithLogTags(ctx, logTags)
return ctx
}
// WithContext creates context for filesystem operations.
func (f *FS) WithContext(ctx context.Context) (context.Context, context.CancelFunc) {
id, err := libkbfs.MakeRandomRequestID()
if err != nil {
f.log.CErrorf(ctx, "Couldn't make request ID: %v", err)
return ctx, func() {}
}
ctx, cancel := context.WithCancel(ctx)
// context.WithDeadline uses clock from `time` package, so we are not using
// f.config.Clock() here
start := time.Now()
ctx, err = libcontext.NewContextWithCancellationDelayer(
libcontext.NewContextReplayable(ctx, func(ctx context.Context) context.Context {
ctx = wrapContext(context.WithValue(ctx, CtxIDKey, id), f)
ctx, _ = context.WithDeadline(ctx, start.Add(29*time.Second))
return ctx
}))
if err != nil {
panic(err)
}
return ctx, cancel
}
var vinfo = dokan.VolumeInformation{
VolumeName: "KBFS",
MaximumComponentLength: 0xFF, // This can be changed.
FileSystemFlags: dokan.FileCasePreservedNames | dokan.FileCaseSensitiveSearch |
dokan.FileUnicodeOnDisk | dokan.FileSupportsReparsePoints |
dokan.FileSupportsRemoteStorage,
FileSystemName: "KBFS",
}
// GetVolumeInformation returns information about the whole filesystem for dokan.
func (f *FS) GetVolumeInformation(ctx context.Context) (dokan.VolumeInformation, error) {
// TODO should this be explicitely refused to other users?
// As the mount is limited to current session there is little need.
return vinfo, nil
}
const dummyFreeSpace = 10 * 1024 * 1024 * 1024
// quotaUsageStaleTolerance is the lifespan of stale usage data that libdokan
// accepts in the Statfs handler. In other words, this causes libkbfs to issue
// a fresh RPC call if cached usage data is older than 10s.
const quotaUsageStaleTolerance = 10 * time.Second
// GetDiskFreeSpace returns information about free space on the volume for dokan.
func (f *FS) GetDiskFreeSpace(ctx context.Context) (freeSpace dokan.FreeSpace, err error) {
// TODO should this be refused to other users?
// As the mount is limited to current session there is little need.
f.logEnter(ctx, "FS GetDiskFreeSpace")
// Refuse private directories while we are in a error state.
if f.remoteStatus.ExtraFileName() != "" {
f.log.Warning("Dummy disk free space while errors are present!")
return dokan.FreeSpace{
TotalNumberOfBytes: dummyFreeSpace,
TotalNumberOfFreeBytes: dummyFreeSpace,
FreeBytesAvailable: dummyFreeSpace,
}, nil
}
defer func() {
if err == nil {
f.vlog.CLogf(ctx, libkb.VLog1, "Request complete")
} else {
// Don't report the error (perhaps resulting in a user
// notification) since this method is mostly called by the
// OS and not because of a user action.
f.log.CDebugf(ctx, err.Error())
}
}()
session, err := idutil.GetCurrentSessionIfPossible(
ctx, f.config.KBPKI(), true)
if err != nil {
return dokan.FreeSpace{}, err
} else if session == (idutil.SessionInfo{}) {
// If user is not logged in, don't bother getting quota info. Otherwise
// reading a public TLF while logged out can fail on macOS.
return dokan.FreeSpace{
TotalNumberOfBytes: dummyFreeSpace,
TotalNumberOfFreeBytes: dummyFreeSpace,
FreeBytesAvailable: dummyFreeSpace,
}, nil
}
_, usageBytes, _, limitBytes, err := f.config.GetQuotaUsage(
session.UID.AsUserOrTeam()).Get(
ctx, quotaUsageStaleTolerance/2, quotaUsageStaleTolerance)
if err != nil {
return dokan.FreeSpace{}, errToDokan(err)
}
free := uint64(limitBytes - usageBytes)
return dokan.FreeSpace{
TotalNumberOfBytes: uint64(limitBytes),
TotalNumberOfFreeBytes: free,
FreeBytesAvailable: free,
}, nil
}
// openContext is for opening files.
type openContext struct {
fi *dokan.FileInfo
*dokan.CreateData
redirectionsLeft int
// isUppercasePath marks a path containing only upper case letters,
// associated with e.g. resolving some reparse points. This has
// special case insensitive path resolving functionality.
isUppercasePath bool
}
// reduceRedictionsLeft reduces redirections and returns whether there are
// redirections left (true), or whether processing should be stopped (false).
func (oc *openContext) reduceRedirectionsLeft() bool {
oc.redirectionsLeft--
return oc.redirectionsLeft > 0
}
// isCreation checks the flags whether a file creation is wanted.
func (oc *openContext) isCreateDirectory() bool {
return oc.isCreation() && oc.CreateOptions&fileDirectoryFile != 0
}
const fileDirectoryFile = 1
// isCreation checks the flags whether a file creation is wanted.
func (oc *openContext) isCreation() bool {
switch oc.CreateDisposition {
case dokan.FileSupersede, dokan.FileCreate, dokan.FileOpenIf, dokan.FileOverwriteIf:
return true
}
return false
}
func (oc *openContext) isExistingError() bool {
return oc.CreateDisposition == dokan.FileCreate
}
// isTruncate checks the flags whether a file truncation is wanted.
func (oc *openContext) isTruncate() bool {
switch oc.CreateDisposition {
case dokan.FileSupersede, dokan.FileOverwrite, dokan.FileOverwriteIf:
return true
}
return false
}
// isOpenReparsePoint checks the flags whether a reparse point open is wanted.
func (oc *openContext) isOpenReparsePoint() bool {
return oc.CreateOptions&dokan.FileOpenReparsePoint != 0
}
// returnDirNoCleanup returns a dir or nothing depending on the open
// flags and does not call .Cleanup on error.
func (oc *openContext) returnDirNoCleanup(f dokan.File) (
dokan.File, dokan.CreateStatus, error) {
if err := oc.ReturningDirAllowed(); err != nil {
return nil, 0, err
}
return f, dokan.ExistingDir, nil
}
// returnFileNoCleanup returns a file or nothing depending on the open
// flags and does not call .Cleanup on error.
func (oc *openContext) returnFileNoCleanup(f dokan.File) (
dokan.File, dokan.CreateStatus, error) {
if err := oc.ReturningFileAllowed(); err != nil {
return nil, 0, err
}
return f, dokan.ExistingFile, nil
}
func newSyntheticOpenContext() *openContext {
var oc openContext
oc.CreateData = &dokan.CreateData{}
oc.CreateDisposition = dokan.FileOpen
oc.redirectionsLeft = 30
return &oc
}
// CreateFile called from dokan, may be a file or directory.
func (f *FS) CreateFile(ctx context.Context, fi *dokan.FileInfo, cd *dokan.CreateData) (dokan.File, dokan.CreateStatus, error) {
// Only allow the current user access
if !fi.IsRequestorUserSidEqualTo(currentUserSID) {
f.log.CErrorf(ctx, "FS CreateFile - Refusing real access: SID match error")
return openFakeRoot(ctx, f, fi)
}
return f.openRaw(ctx, fi, cd)
}
// openRaw is a wrapper between CreateFile/CreateDirectory/OpenDirectory and open
func (f *FS) openRaw(ctx context.Context, fi *dokan.FileInfo, caf *dokan.CreateData) (dokan.File, dokan.CreateStatus, error) {
ps, err := windowsPathSplit(fi.Path())
if err != nil {
f.log.CErrorf(ctx, "FS openRaw - path split error: %v", err)
return nil, 0, err
}
oc := openContext{fi: fi, CreateData: caf, redirectionsLeft: 30}
file, cst, err := f.open(ctx, &oc, ps)
if err != nil {
f.log.CDebugf(ctx, "FS Open failed %#v with: %v", *caf, err)
err = errToDokan(err)
}
return file, cst, err
}
// open tries to open a file deferring to more specific implementations.
func (f *FS) open(ctx context.Context, oc *openContext, ps []string) (dokan.File, dokan.CreateStatus, error) {
f.vlog.CLogf(ctx, libkb.VLog1, "FS Open: %q", ps)
psl := len(ps)
switch {
case psl < 1:
return nil, 0, dokan.ErrObjectNameNotFound
case psl == 1 && ps[0] == ``:
return oc.returnDirNoCleanup(f.root)
// This section is equivalent to
// handleCommonSpecialFile in libfuse.
case libfs.ErrorFileName == ps[psl-1]:
return oc.returnFileNoCleanup(NewErrorFile(f))
case libfs.MetricsFileName == ps[psl-1]:
return oc.returnFileNoCleanup(NewMetricsFile(f))
// TODO: Make the two cases below available from any
// directory.
case libfs.ProfileListDirName == ps[0]:
return (ProfileList{fs: f}).open(ctx, oc, ps[1:])
case libfs.ResetCachesFileName == ps[0]:
return oc.returnFileNoCleanup(&ResetCachesFile{fs: f.root.private.fs})
// This section is equivalent to
// handleNonTLFSpecialFile in libfuse.
//
// TODO: Make the two cases below available from any
// non-TLF directory.
case libfs.StatusFileName == ps[0]:
return oc.returnFileNoCleanup(NewNonTLFStatusFile(f.root.private.fs))
case libfs.HumanErrorFileName == ps[0], libfs.HumanNoLoginFileName == ps[0]:
return oc.returnFileNoCleanup(&SpecialReadFile{
read: f.remoteStatus.NewSpecialReadFunc,
fs: f})
case libfs.EnableAutoJournalsFileName == ps[0]:
return oc.returnFileNoCleanup(&JournalControlFile{
folder: &Folder{fs: f}, // fake Folder for logging, etc.
action: libfs.JournalEnableAuto,
})
case libfs.DisableAutoJournalsFileName == ps[0]:
return oc.returnFileNoCleanup(&JournalControlFile{
folder: &Folder{fs: f}, // fake Folder for logging, etc.
action: libfs.JournalDisableAuto,
})
case libfs.EnableBlockPrefetchingFileName == ps[0]:
return oc.returnFileNoCleanup(&PrefetchFile{
fs: f,
enable: true,
})
case libfs.DisableBlockPrefetchingFileName == ps[0]:
return oc.returnFileNoCleanup(&PrefetchFile{
fs: f,
enable: false,
})
case libfs.EditHistoryName == ps[0]:
return oc.returnFileNoCleanup(NewUserEditHistoryFile(&Folder{fs: f}))
case ".kbfs_unmount" == ps[0]:
f.log.CInfof(ctx, "Exiting due to .kbfs_unmount")
logger.Shutdown()
os.Exit(0)
case ".kbfs_restart" == ps[0]:
f.log.CInfof(ctx, "Exiting due to .kbfs_restart, should get restarted by watchdog process")
logger.Shutdown()
os.Exit(int(keybase1.ExitCode_RESTART))
case ".kbfs_number_of_handles" == ps[0]:
x := stringReadFile(strconv.Itoa(int(oc.fi.NumberOfFileHandles())))
return oc.returnFileNoCleanup(x)
// TODO
// Unfortunately sometimes we end up in this case while using
// reparse points.
case strings.ToUpper(PublicName) == ps[0]:
oc.isUppercasePath = true
fallthrough
case PublicName == ps[0]:
return f.root.public.open(ctx, oc, ps[1:])
case strings.ToUpper(PrivateName) == ps[0]:
oc.isUppercasePath = true
fallthrough
case PrivateName == ps[0]:
return f.root.private.open(ctx, oc, ps[1:])
case strings.ToUpper(TeamName) == ps[0]:
oc.isUppercasePath = true
fallthrough
case TeamName == ps[0]:
return f.root.team.open(ctx, oc, ps[1:])
}
return nil, 0, dokan.ErrObjectNameNotFound
}
// windowsPathSplit handles paths we get from Dokan.
// As a special case “ means `\`, it gets generated
// on special occasions.
func windowsPathSplit(raw string) ([]string, error) {
if raw == `` {
raw = `\`
}
if raw[0] != '\\' || raw[len(raw)-1] == '*' {
return nil, dokan.ErrObjectNameNotFound
}
return strings.Split(raw[1:], `\`), nil
}
// ErrorPrint prints errors from the Dokan library.
func (f *FS) ErrorPrint(err error) {
f.log.Errorf("Dokan error: %v", err)
}
// Printf prints information from the Dokan library.
func (f *FS) Printf(fmt string, args ...interface{}) {
f.log.Info("Dokan info: "+fmt, args...)
}
// MoveFile tries to move a file.
func (f *FS) MoveFile(ctx context.Context, src dokan.File, sourceFI *dokan.FileInfo, targetPath string, replaceExisting bool) (err error) {
// User checking was handled by original file open, this is no longer true.
// However we only allow fake files with names that are not potential rename
// paths. Filter those out here.
f.vlog.CLogf(
ctx, libkb.VLog1, "MoveFile %T %q -> %q", src,
sourceFI.Path(), targetPath)
// isPotentialRenamePath filters out some special paths
// for rename. Especially those provided by fakeroot.go.
if !isPotentialRenamePath(sourceFI.Path()) {
f.log.CErrorf(ctx, "Refusing MoveFile access: not potential rename path")
return dokan.ErrAccessDenied
}
switch src.(type) {
case *FolderList, *File, *Dir, *TLF, *EmptyFolder:
default:
f.log.CErrorf(ctx, "Refusing MoveFile access: wrong type source argument")
return dokan.ErrAccessDenied
}
f.logEnter(ctx, "FS MoveFile")
// No racing deletions or renames.
// Note that this calls Cleanup multiple times, however with nil
// FileInfo which means that Cleanup will not try to lock renameAndDeletionLock.
// renameAndDeletionLock should be the first lock to be grabbed in libdokan.
f.renameAndDeletionLock.Lock()
defer func() {
f.renameAndDeletionLock.Unlock()
f.reportErr(ctx, libkbfs.WriteMode, err)
}()
oc := newSyntheticOpenContext()
// Source directory
srcDirPath, err := windowsPathSplit(sourceFI.Path())
if err != nil {
return err
}
if len(srcDirPath) < 1 {
return errors.New("Invalid source for move")
}
srcName := srcDirPath[len(srcDirPath)-1]
srcDirPath = srcDirPath[0 : len(srcDirPath)-1]
srcDir, _, err := f.open(ctx, oc, srcDirPath)
if err != nil {
return err
}
defer srcDir.Cleanup(ctx, nil)
// Destination directory, not the destination file
dstPath, err := windowsPathSplit(targetPath)
if err != nil {
return err
}
if len(dstPath) < 1 {
return errors.New("Invalid destination for move")
}
dstDirPath := dstPath[0 : len(dstPath)-1]
dstDir, dstCst, err := f.open(ctx, oc, dstDirPath)
f.vlog.CLogf(
ctx, libkb.VLog1, "FS MoveFile dstDir open %v -> %v,%v,%v dstType %T",
dstDirPath, dstDir, dstCst, err, dstDir)
if err != nil {
return err
}
defer dstDir.Cleanup(ctx, nil)
if !dstCst.IsDir() {
return errors.New("Tried to move to a non-directory path")
}
fl1, ok := srcDir.(*FolderList)
fl2, ok2 := dstDir.(*FolderList)
if ok && ok2 && fl1 == fl2 {
return f.folderListRename(ctx, fl1, oc, src, srcName, dstPath, replaceExisting)
}
srcDirD := asDir(ctx, srcDir)
if srcDirD == nil {
return errors.New("Parent of src not a Dir")
}
srcFolder := srcDirD.folder
srcParent := srcDirD.node
ddst := asDir(ctx, dstDir)
if ddst == nil {
return errors.New("Destination directory is not of type Dir")
}
switch src.(type) {
case *Dir:
case *File:
case *TLF:
default:
return dokan.ErrAccessDenied
}
// here we race...
if !replaceExisting {
x, _, err := f.open(ctx, oc, dstPath)
if err == nil {
defer x.Cleanup(ctx, nil)
}
if !isNoSuchNameError(err) {
f.vlog.CLogf(
ctx, libkb.VLog1,
"FS MoveFile required non-existent destination, got: %T %v",
err, err)
return dokan.ErrObjectNameCollision
}
}
if srcFolder != ddst.folder {
return dokan.ErrNotSameDevice
}
// overwritten node, if any, will be removed from Folder.nodes, if
// it is there in the first place, by its Forget
dstName := dstPath[len(dstPath)-1]
f.vlog.CLogf(
ctx, libkb.VLog1, "FS MoveFile KBFSOps().Rename(ctx,%v,%v,%v,%v)",
srcParent, srcName, ddst.node, dstName)
if err := srcFolder.fs.config.KBFSOps().Rename(
ctx, srcParent, srcParent.ChildName(srcName), ddst.node,
ddst.node.ChildName(dstName)); err != nil {
f.log.CDebugf(ctx, "FS MoveFile KBFSOps().Rename FAILED %v", err)
return err
}
switch x := src.(type) {
case *Dir:
x.parent = ddst.node
x.name = dstName
case *File:
x.parent = ddst.node
x.name = dstName
}
f.vlog.CLogf(ctx, libkb.VLog1, "FS MoveFile SUCCESS")
return nil
}
func isPotentialRenamePath(s string) bool {
if len(s) < 3 || s[0] != '\\' {
return false
}
s = s[1:]
return strings.HasPrefix(s, PrivateName) ||
strings.HasPrefix(s, PublicName) ||
strings.HasPrefix(s, TeamName)
}
func (f *FS) folderListRename(ctx context.Context, fl *FolderList, oc *openContext, src dokan.File, srcName string, dstPath []string, replaceExisting bool) error {
ef, ok := src.(*EmptyFolder)
f.vlog.CLogf(ctx, libkb.VLog1, "FS MoveFile folderlist %v", ef)
if !ok || !isNewFolderName(srcName) {
return dokan.ErrAccessDenied
}
dstName := dstPath[len(dstPath)-1]
// Yes, this is slow, but that is ok here.
if _, err := tlfhandle.ParseHandlePreferred(
ctx, f.config.KBPKI(), f.config.MDOps(), f.config, dstName,
fl.tlfType); err != nil {
return dokan.ErrObjectNameNotFound
}
fl.mu.Lock()
_, ok = fl.folders[dstName]
fl.mu.Unlock()
if !replaceExisting && ok {
f.vlog.CLogf(
ctx, libkb.VLog1,
"FS MoveFile folderlist refusing to replace target")
return dokan.ErrAccessDenied
}
// Perhaps create destination by opening it.
x, _, err := f.open(ctx, oc, dstPath)
if err == nil {
x.Cleanup(ctx, nil)
}
fl.mu.Lock()
defer fl.mu.Unlock()
_, ok = fl.folders[dstName]
delete(fl.folders, srcName)
if !ok {
f.vlog.CLogf(ctx, libkb.VLog1, "FS MoveFile folderlist adding target")
fl.folders[dstName] = ef
}
f.vlog.CLogf(ctx, libkb.VLog1, "FS MoveFile folderlist success")
return nil
}
func (f *FS) queueNotification(fn func()) {
f.notifications.QueueNotification(fn)
}
func (f *FS) reportErr(ctx context.Context, mode libkbfs.ErrorModeType, err error) {
if err == nil {
f.vlog.CLogf(ctx, libkb.VLog1, "Request complete")
return
}
f.config.Reporter().ReportErr(ctx, "", tlf.Private, mode, err)
// We just log the error as debug, rather than error, because it
// might just indicate an expected error such as an ENOENT.
//
// TODO: Classify errors and escalate the logging level of the
// important ones.
f.log.CDebugf(ctx, err.Error())
}
// NotificationGroupWait waits till the local notification group is done.
func (f *FS) NotificationGroupWait() {
f.notifications.Wait()
}
func (f *FS) logEnter(ctx context.Context, s string) {
f.vlog.CLogf(ctx, libkb.VLog1, "=> %s", s)
}
func (f *FS) logEnterf(ctx context.Context, fmt string, args ...interface{}) {
f.vlog.CLogf(ctx, libkb.VLog1, "=> "+fmt, args...)
}
// UserChanged is called from libfs.
func (f *FS) UserChanged(ctx context.Context, oldName, newName kbname.NormalizedUsername) {
f.log.CDebugf(ctx, "User changed: %q -> %q", oldName, newName)
f.root.public.userChanged(ctx, oldName, newName)
f.root.private.userChanged(ctx, oldName, newName)
}
var _ libfs.RemoteStatusUpdater = (*FS)(nil)
// Root represents the root of the KBFS file system.
type Root struct {
emptyFile
private *FolderList
public *FolderList
team *FolderList
}
// GetFileInformation for dokan stats.
func (r *Root) GetFileInformation(ctx context.Context, fi *dokan.FileInfo) (*dokan.Stat, error) {
return defaultDirectoryInformation()
}
// FindFiles for dokan readdir.
func (r *Root) FindFiles(ctx context.Context, fi *dokan.FileInfo, ignored string, callback func(*dokan.NamedStat) error) error {
var ns dokan.NamedStat
var err error
ns.FileAttributes = dokan.FileAttributeDirectory
ns.Name = PrivateName
err = callback(&ns)
if err != nil {
return err
}
ns.Name = TeamName
err = callback(&ns)
if err != nil {
return err
}
ns.Name = PublicName
err = callback(&ns)
if err != nil {
return err
}
if ename, esize := r.private.fs.remoteStatus.ExtraFileNameAndSize(); ename != "" {
ns.Name = ename
ns.FileAttributes = dokan.FileAttributeNormal
ns.FileSize = esize
err = callback(&ns)
if err != nil {
return err
}
}
return nil
}