From da41db4712383027a2d5f7bf6ff3fd08d4d3b571 Mon Sep 17 00:00:00 2001 From: Nick Craig-Wood Date: Wed, 8 Apr 2020 18:29:50 +0100 Subject: [PATCH] vfs,mount,cmount: report 1PB free for unknown disk sizes Factor the logic into the VFS layer so we don't have to duplicate it into mount and cmount. See: https://forum.rclone.org/t/rclone-mount-question/15454/ --- cmd/cmount/fs.go | 28 +++++++++------------------ cmd/mount/fs.go | 28 +++++++++------------------ vfs/vfs.go | 46 ++++++++++++++++++++++++++++++++------------- vfs/vfs_test.go | 49 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 100 insertions(+), 51 deletions(-) diff --git a/cmd/cmount/fs.go b/cmd/cmount/fs.go index 352e45d19..9f49c777a 100644 --- a/cmd/cmount/fs.go +++ b/cmd/cmount/fs.go @@ -268,25 +268,15 @@ func (fsys *FS) Releasedir(path string, fh uint64) (errc int) { func (fsys *FS) Statfs(path string, stat *fuse.Statfs_t) (errc int) { defer log.Trace(path, "")("stat=%+v, errc=%d", stat, &errc) const blockSize = 4096 - const fsBlocks = (1 << 50) / blockSize - stat.Blocks = fsBlocks // Total data blocks in file system. - stat.Bfree = fsBlocks // Free blocks in file system. - stat.Bavail = fsBlocks // Free blocks in file system if you're not root. - stat.Files = 1e9 // Total files in file system. - stat.Ffree = 1e9 // Free files in file system. - stat.Bsize = blockSize // Block size - stat.Namemax = 255 // Maximum file name length? - stat.Frsize = blockSize // Fragment size, smallest addressable data size in the file system. - total, used, free := fsys.VFS.Statfs() - if total >= 0 { - stat.Blocks = uint64(total) / blockSize - } - if used >= 0 { - stat.Bfree = stat.Blocks - uint64(used)/blockSize - } - if free >= 0 { - stat.Bavail = uint64(free) / blockSize - } + total, _, free := fsys.VFS.Statfs() + stat.Blocks = uint64(total) / blockSize // Total data blocks in file system. + stat.Bfree = uint64(free) / blockSize // Free blocks in file system. + stat.Bavail = stat.Bfree // Free blocks in file system if you're not root. + stat.Files = 1e9 // Total files in file system. + stat.Ffree = 1e9 // Free files in file system. + stat.Bsize = blockSize // Block size + stat.Namemax = 255 // Maximum file name length? + stat.Frsize = blockSize // Fragment size, smallest addressable data size in the file system. mountlib.ClipBlocks(&stat.Blocks) mountlib.ClipBlocks(&stat.Bfree) mountlib.ClipBlocks(&stat.Bavail) diff --git a/cmd/mount/fs.go b/cmd/mount/fs.go index baa285fb6..016e97eac 100644 --- a/cmd/mount/fs.go +++ b/cmd/mount/fs.go @@ -54,25 +54,15 @@ var _ fusefs.FSStatfser = (*FS)(nil) func (f *FS) Statfs(ctx context.Context, req *fuse.StatfsRequest, resp *fuse.StatfsResponse) (err error) { defer log.Trace("", "")("stat=%+v, err=%v", resp, &err) const blockSize = 4096 - const fsBlocks = (1 << 50) / blockSize - resp.Blocks = fsBlocks // Total data blocks in file system. - resp.Bfree = fsBlocks // Free blocks in file system. - resp.Bavail = fsBlocks // Free blocks in file system if you're not root. - resp.Files = 1e9 // Total files in file system. - resp.Ffree = 1e9 // Free files in file system. - resp.Bsize = blockSize // Block size - resp.Namelen = 255 // Maximum file name length? - resp.Frsize = blockSize // Fragment size, smallest addressable data size in the file system. - total, used, free := f.VFS.Statfs() - if total >= 0 { - resp.Blocks = uint64(total) / blockSize - } - if used >= 0 { - resp.Bfree = resp.Blocks - uint64(used)/blockSize - } - if free >= 0 { - resp.Bavail = uint64(free) / blockSize - } + total, _, free := f.VFS.Statfs() + resp.Blocks = uint64(total) / blockSize // Total data blocks in file system. + resp.Bfree = uint64(free) / blockSize // Free blocks in file system. + resp.Bavail = resp.Bfree // Free blocks in file system if you're not root. + resp.Files = 1e9 // Total files in file system. + resp.Ffree = 1e9 // Free files in file system. + resp.Bsize = blockSize // Block size + resp.Namelen = 255 // Maximum file name length? + resp.Frsize = blockSize // Fragment size, smallest addressable data size in the file system. mountlib.ClipBlocks(&resp.Blocks) mountlib.ClipBlocks(&resp.Bfree) mountlib.ClipBlocks(&resp.Bavail) diff --git a/vfs/vfs.go b/vfs/vfs.go index ccd10265c..2f35a0a8f 100644 --- a/vfs/vfs.go +++ b/vfs/vfs.go @@ -477,6 +477,37 @@ func (vfs *VFS) Rename(oldName, newName string) error { return nil } +// This works out the missing values from (total, used, free) using +// unknownFree as the intended free space +func fillInMissingSizes(total, used, free, unknownFree int64) (newTotal, newUsed, newFree int64) { + if total < 0 { + if free >= 0 { + total = free + } else { + total = unknownFree + } + if used >= 0 { + total += used + } + } + // total is now defined + if used < 0 { + if free >= 0 { + used = total - free + } else { + used = 0 + } + } + // used is now defined + if free < 0 { + free = total - used + } + return total, used, free +} + +// If the total size isn't known then we will aim for this many bytes free (1PB) +const unknownFreeBytes = 1 << 50 + // Statfs returns into about the filing system if known // // The values will be -1 if they aren't known @@ -488,10 +519,7 @@ func (vfs *VFS) Statfs() (total, used, free int64) { defer vfs.usageMu.Unlock() total, used, free = -1, -1, -1 doAbout := vfs.f.Features().About - if doAbout == nil { - return - } - if vfs.usageTime.IsZero() || time.Since(vfs.usageTime) >= vfs.Opt.DirCacheTime { + if doAbout != nil && (vfs.usageTime.IsZero() || time.Since(vfs.usageTime) >= vfs.Opt.DirCacheTime) { var err error vfs.usage, err = doAbout(context.TODO()) vfs.usageTime = time.Now() @@ -510,15 +538,7 @@ func (vfs *VFS) Statfs() (total, used, free int64) { if u.Used != nil { used = *u.Used } - if total < 0 && free >= 0 && used >= 0 { - total = free + used - } - if free < 0 && total >= 0 && used >= 0 { - free = total - used - } - if used < 0 && total >= 0 && free >= 0 { - used = total - free - } } + total, used, free = fillInMissingSizes(total, used, free, unknownFreeBytes) return } diff --git a/vfs/vfs_test.go b/vfs/vfs_test.go index ed3e2faf0..4216a3a89 100644 --- a/vfs/vfs_test.go +++ b/vfs/vfs_test.go @@ -4,6 +4,7 @@ package vfs import ( "context" + "fmt" "io" "os" "testing" @@ -311,3 +312,51 @@ func TestVFSStatfs(t *testing.T) { assert.Equal(t, free, free2) assert.Equal(t, oldTime, vfs.usageTime) } + +func TestFillInMissingSizes(t *testing.T) { + const unknownFree = 10 + for _, test := range []struct { + total, free, used int64 + wantTotal, wantUsed, wantFree int64 + }{ + { + total: 20, free: 5, used: 15, + wantTotal: 20, wantFree: 5, wantUsed: 15, + }, + { + total: 20, free: 5, used: -1, + wantTotal: 20, wantFree: 5, wantUsed: 15, + }, + { + total: 20, free: -1, used: 15, + wantTotal: 20, wantFree: 5, wantUsed: 15, + }, + { + total: 20, free: -1, used: -1, + wantTotal: 20, wantFree: 20, wantUsed: 0, + }, + { + total: -1, free: 5, used: 15, + wantTotal: 20, wantFree: 5, wantUsed: 15, + }, + { + total: -1, free: 15, used: -1, + wantTotal: 15, wantFree: 15, wantUsed: 0, + }, + { + total: -1, free: -1, used: 15, + wantTotal: 25, wantFree: 10, wantUsed: 15, + }, + { + total: -1, free: -1, used: -1, + wantTotal: 10, wantFree: 10, wantUsed: 0, + }, + } { + t.Run(fmt.Sprintf("total=%d,free=%d,used=%d", test.total, test.free, test.used), func(t *testing.T) { + gotTotal, gotUsed, gotFree := fillInMissingSizes(test.total, test.used, test.free, unknownFree) + assert.Equal(t, test.wantTotal, gotTotal, "total") + assert.Equal(t, test.wantUsed, gotUsed, "used") + assert.Equal(t, test.wantFree, gotFree, "free") + }) + } +}