From 2b855751fca766011d3df817ff3926d94f3b02b7 Mon Sep 17 00:00:00 2001
From: Nick Craig-Wood <nick@craig-wood.com>
Date: Tue, 17 Apr 2018 23:19:34 +0100
Subject: [PATCH] vfs,mount,cmount: use About to return the correct disk
 total/used/free

Disks total, used, free now shows correctly for mount and cmount (eg
`df` for Unix or in the Windows explorer).
---
 cmd/cmount/fs.go | 10 ++++++++++
 cmd/mount/fs.go  | 10 ++++++++++
 vfs/vfs.go       | 51 +++++++++++++++++++++++++++++++++++++++++++-----
 vfs/vfs_test.go  | 40 +++++++++++++++++++++++++++++++++++++
 4 files changed, 106 insertions(+), 5 deletions(-)

diff --git a/cmd/cmount/fs.go b/cmd/cmount/fs.go
index 1b21ca7b5..4a0d071f7 100644
--- a/cmd/cmount/fs.go
+++ b/cmd/cmount/fs.go
@@ -275,6 +275,16 @@ func (fsys *FS) Statfs(path string, stat *fuse.Statfs_t) (errc int) {
 	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
+	}
 	return 0
 }
 
diff --git a/cmd/mount/fs.go b/cmd/mount/fs.go
index 77d84b36f..caab6a060 100644
--- a/cmd/mount/fs.go
+++ b/cmd/mount/fs.go
@@ -62,6 +62,16 @@ func (f *FS) Statfs(ctx context.Context, req *fuse.StatfsRequest, resp *fuse.Sta
 	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
+	}
 	return nil
 }
 
diff --git a/vfs/vfs.go b/vfs/vfs.go
index 923b3e8a6..2ec5c0d73 100644
--- a/vfs/vfs.go
+++ b/vfs/vfs.go
@@ -24,6 +24,7 @@ import (
 	"os"
 	"path"
 	"strings"
+	"sync"
 	"sync/atomic"
 	"time"
 
@@ -165,11 +166,14 @@ var (
 
 // VFS represents the top level filing system
 type VFS struct {
-	f      fs.Fs
-	root   *Dir
-	Opt    Options
-	cache  *cache
-	cancel context.CancelFunc
+	f         fs.Fs
+	root      *Dir
+	Opt       Options
+	cache     *cache
+	cancel    context.CancelFunc
+	usageMu   sync.Mutex
+	usageTime time.Time
+	usage     *fs.Usage
 }
 
 // Options is options for creating the vfs
@@ -452,3 +456,40 @@ func (vfs *VFS) Rename(oldName, newName string) error {
 	}
 	return nil
 }
+
+// Statfs returns into about the filing system if known
+//
+// The values will be -1 if they aren't known
+//
+// This information is cached for the DirCacheTime interval
+func (vfs *VFS) Statfs() (total, used, free int64) {
+	// defer log.Trace("/", "")("total=%d, used=%d, free=%d", &total, &used, &free)
+	vfs.usageMu.Lock()
+	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 {
+		var err error
+		vfs.usage, err = doAbout()
+		vfs.usageTime = time.Now()
+		if err != nil {
+			fs.Errorf(vfs.f, "Statfs failed: %v", err)
+			return
+		}
+	}
+	if u := vfs.usage; u != nil {
+		if u.Total != nil {
+			total = *u.Total
+		}
+		if u.Free != nil {
+			free = *u.Free
+		}
+		if u.Used != nil {
+			used = *u.Used
+		}
+	}
+	return
+}
diff --git a/vfs/vfs_test.go b/vfs/vfs_test.go
index 3bf10a9a0..b028a1227 100644
--- a/vfs/vfs_test.go
+++ b/vfs/vfs_test.go
@@ -251,3 +251,43 @@ func TestVFSRename(t *testing.T) {
 	err = vfs.Rename("file0", "not found/file0")
 	assert.Equal(t, os.ErrNotExist, err)
 }
+
+func TestVFSStatfs(t *testing.T) {
+	r := fstest.NewRun(t)
+	defer r.Finalise()
+	vfs := New(r.Fremote, nil)
+
+	// pre-conditions
+	assert.Nil(t, vfs.usage)
+	assert.True(t, vfs.usageTime.IsZero())
+
+	// read
+	total, used, free := vfs.Statfs()
+	require.NotNil(t, vfs.usage)
+	assert.False(t, vfs.usageTime.IsZero())
+	if vfs.usage.Total != nil {
+		assert.Equal(t, *vfs.usage.Total, total)
+	} else {
+		assert.Equal(t, -1, total)
+	}
+	if vfs.usage.Free != nil {
+		assert.Equal(t, *vfs.usage.Free, free)
+	} else {
+		assert.Equal(t, -1, free)
+	}
+	if vfs.usage.Used != nil {
+		assert.Equal(t, *vfs.usage.Used, used)
+	} else {
+		assert.Equal(t, -1, used)
+	}
+
+	// read cached
+	oldUsage := vfs.usage
+	oldTime := vfs.usageTime
+	total2, used2, free2 := vfs.Statfs()
+	assert.Equal(t, oldUsage, vfs.usage)
+	assert.Equal(t, total, total2)
+	assert.Equal(t, used, used2)
+	assert.Equal(t, free, free2)
+	assert.Equal(t, oldTime, vfs.usageTime)
+}