Commit aa1fe8b2 authored by Aaron Jacobs's avatar Aaron Jacobs

Added support for handling statfs(2) and friends.

File systems must now handle StatFSOp in order to mount correctly on
OS X.

For GoogleCloudPlatform/gcsfuse#125.
parents ee4f4770 57b4e879
......@@ -405,14 +405,6 @@ func (c *Connection) ReadOp() (ctx context.Context, op interface{}, err error) {
ctx = c.beginOp(inMsg.Header().Opcode, inMsg.Header().Unique)
ctx = context.WithValue(ctx, contextKey, opState{inMsg, outMsg, op})
// Special case: responding to statfs is required to make mounting work on
// OS X. We don't currently expose the capability for the file system to
// intercept this.
if _, ok := op.(*statFSOp); ok {
c.Reply(ctx, nil)
continue
}
// Return the op to the user.
return
}
......
......@@ -371,7 +371,7 @@ func convertInMessage(
}
case fusekernel.OpStatfs:
o = &statFSOp{}
o = &fuseops.StatFSOp{}
case fusekernel.OpInterrupt:
type input fusekernel.InterruptIn
......@@ -557,8 +557,42 @@ func (c *Connection) kernelResponseForOp(
case *fuseops.ReadSymlinkOp:
m.AppendString(o.Target)
case *statFSOp:
m.Grow(unsafe.Sizeof(fusekernel.StatfsOut{}))
case *fuseops.StatFSOp:
out := (*fusekernel.StatfsOut)(m.Grow(unsafe.Sizeof(fusekernel.StatfsOut{})))
out.St.Blocks = o.Blocks
out.St.Bfree = o.BlocksFree
out.St.Bavail = o.BlocksAvailable
out.St.Files = o.Inodes
out.St.Ffree = o.InodesFree
// The posix spec for sys/statvfs.h defines the following fields, among
// others:
//
// f_bsize File system block size.
// f_frsize Fundamental file system block size.
// f_blocks Total number of blocks on file system in units of f_frsize.
//
// It appears as though f_bsize was the only thing supported by most unixes
// originally, but then f_frsize was added when new sorts of file systems
// came about. Quoth The Linux Programming Interface by Michael Kerrisk
// (https://goo.gl/5LZMxQ):
//
// For most Linux file systems, the values of f_bsize and f_frsize are
// the same. However, some file systems support the notion of block
// fragments, which can be used to allocate a smaller unit of storage
// at the end of the file if if a full block is not required. This
// avoids the waste of space that would otherwise occur if a full block
// was allocated. On such file systems, f_frsize is the size of a
// fragment, and f_bsize is the size of a whole block. (The notion of
// fragments in UNIX file systems first appeared in the early 1980s
// with the 4.2BSD Fast File System.)
//
// Confusingly, it appears as though osxfuse surfaces f_bsize as f_iosize
// (of advisory use only), and f_frsize as f_bsize (which affects free
// space display in the Finder). In any case, we don't care to let the user
// distinguish, so set both to the same value.
out.St.Bsize = o.BlockSize
out.St.Frsize = o.BlockSize
case *initOp:
out := (*fusekernel.InitOut)(m.Grow(unsafe.Sizeof(fusekernel.InitOut{})))
......
......@@ -19,6 +19,59 @@ import (
"time"
)
////////////////////////////////////////////////////////////////////////
// File system
////////////////////////////////////////////////////////////////////////
// Return statistics about the file system's capacity and available resources.
//
// Called by statfs(2) and friends:
//
// * (https://goo.gl/Xi1lDr) sys_statfs called user_statfs, which calls
// vfs_statfs, which calls statfs_by_dentry.
//
// * (https://goo.gl/VAIOwU) statfs_by_dentry calls the superblock
// operation statfs, which in our case points at
// fuse_statfs (cf. https://goo.gl/L7BTM3)
//
// * (https://goo.gl/Zn7Sgl) fuse_statfs sends a statfs op, then uses
// convert_fuse_statfs to convert the response in a straightforward
// manner.
//
// This op is particularly important on OS X: if you don't implement it, the
// file system will not successfully mount. If you don't model a sane amount of
// free space, the Finder will refuse to copy files into the file system.
type StatFSOp struct {
// The size of the file system's blocks. This may be used, in combination
// with the block counts below, by callers of statfs(2) to infer the file
// system's capacity and space availability.
//
// On Linux this can be any value, which will be faitfully returned to the
// caller of statfs(2) (see the code walk above). On OS X it appears it must
// be a power of 2 in [2^9, 2^17].
//
// On OS X this also affects statfs::f_iosize, which is documented as the
// "optimal transfer block size". It does not appear to cause osxfuse to
// change the size of data in WriteFile ops, though.
//
// This interface does not distinguish between blocks and block fragments.
BlockSize uint32
// The total number of blocks in the file system, the number of unused
// blocks, and the count of the latter that are available for use by non-root
// users.
//
// For each category, the corresponding number of bytes is derived by
// multiplying by BlockSize.
Blocks uint64
BlocksFree uint64
BlocksAvailable uint64
// The total number of inodes in the file system, and how many remain free.
Inodes uint64
InodesFree uint64
}
////////////////////////////////////////////////////////////////////////
// Inodes
////////////////////////////////////////////////////////////////////////
......
......@@ -35,6 +35,7 @@ import (
// See NotImplementedFileSystem for a convenient way to embed default
// implementations for methods you don't care about.
type FileSystem interface {
StatFS(context.Context, *fuseops.StatFSOp) error
LookUpInode(context.Context, *fuseops.LookUpInodeOp) error
GetInodeAttributes(context.Context, *fuseops.GetInodeAttributesOp) error
SetInodeAttributes(context.Context, *fuseops.SetInodeAttributesOp) error
......@@ -119,6 +120,9 @@ func (s *fileSystemServer) handleOp(
default:
err = fuse.ENOSYS
case *fuseops.StatFSOp:
err = s.fs.StatFS(ctx, typed)
case *fuseops.LookUpInodeOp:
err = s.fs.LookUpInode(ctx, typed)
......
......@@ -29,6 +29,13 @@ type NotImplementedFileSystem struct {
var _ FileSystem = &NotImplementedFileSystem{}
func (fs *NotImplementedFileSystem) StatFS(
ctx context.Context,
op *fuseops.StatFSOp) (err error) {
err = fuse.ENOSYS
return
}
func (fs *NotImplementedFileSystem) LookUpInode(
ctx context.Context,
op *fuseops.LookUpInodeOp) (err error) {
......
......@@ -26,10 +26,6 @@ type unknownOp struct {
Inode fuseops.InodeID
}
// Required in order to mount on OS X.
type statFSOp struct {
}
// Causes us to cancel the associated context.
type interruptOp struct {
FuseID uint64
......
......@@ -261,6 +261,12 @@ func (fs *cachingFS) SetKeepCache(keep bool) {
// FileSystem methods
////////////////////////////////////////////////////////////////////////
func (fs *cachingFS) StatFS(
ctx context.Context,
op *fuseops.StatFSOp) (err error) {
return
}
// LOCKS_EXCLUDED(fs.mu)
func (fs *cachingFS) LookUpInode(
ctx context.Context,
......
......@@ -119,6 +119,12 @@ func (fs *errorFS) GetInodeAttributes(
return
}
func (fs *errorFS) StatFS(
ctx context.Context,
op *fuseops.StatFSOp) (err error) {
return
}
// LOCKS_EXCLUDED(fs.mu)
func (fs *errorFS) LookUpInode(
ctx context.Context,
......
......@@ -117,6 +117,12 @@ func (fs *flushFS) getAttributes(id fuseops.InodeID) (
// FileSystem methods
////////////////////////////////////////////////////////////////////////
func (fs *flushFS) StatFS(
ctx context.Context,
op *fuseops.StatFSOp) (err error) {
return
}
func (fs *flushFS) LookUpInode(
ctx context.Context,
op *fuseops.LookUpInodeOp) (err error) {
......
......@@ -224,6 +224,12 @@ func (fs *fsImpl) findInodeByID(id fuseops.InodeID) (in *inode) {
// FileSystem methods
////////////////////////////////////////////////////////////////////////
func (fs *fsImpl) StatFS(
ctx context.Context,
op *fuseops.StatFSOp) (err error) {
return
}
func (fs *fsImpl) LookUpInode(
ctx context.Context,
op *fuseops.LookUpInodeOp) (err error) {
......
......@@ -149,6 +149,12 @@ func (fs *helloFS) patchAttributes(
attr.Crtime = now
}
func (fs *helloFS) StatFS(
ctx context.Context,
op *fuseops.StatFSOp) (err error) {
return
}
func (fs *helloFS) LookUpInode(
ctx context.Context,
op *fuseops.LookUpInodeOp) (err error) {
......
......@@ -100,6 +100,12 @@ func (fs *InterruptFS) EnableFlushBlocking() {
// FileSystem methods
////////////////////////////////////////////////////////////////////////
func (fs *InterruptFS) StatFS(
ctx context.Context,
op *fuseops.StatFSOp) (err error) {
return
}
func (fs *InterruptFS) LookUpInode(
ctx context.Context,
op *fuseops.LookUpInodeOp) (err error) {
......
......@@ -184,6 +184,12 @@ func (fs *memFS) deallocateInode(id fuseops.InodeID) {
// FileSystem methods
////////////////////////////////////////////////////////////////////////
func (fs *memFS) StatFS(
ctx context.Context,
op *fuseops.StatFSOp) (err error) {
return
}
func (fs *memFS) LookUpInode(
ctx context.Context,
op *fuseops.LookUpInodeOp) (err error) {
......
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package statfs
import (
"os"
"sync"
"golang.org/x/net/context"
"github.com/jacobsa/fuse"
"github.com/jacobsa/fuse/fuseops"
"github.com/jacobsa/fuse/fuseutil"
)
// A file system that allows orchestrating canned responses to statfs ops, for
// testng out OS-specific statfs behavior.
//
// The file system allows opening and writing to any name that is a child of
// the root inode, and keeps track of the most recent write size delivered by
// the kernel (in order to test statfs response block size effects on write
// size, if any).
//
// Safe for concurrent access.
type FS interface {
fuseutil.FileSystem
// Set the canned response to be used for future statfs ops.
SetStatFSResponse(r fuseops.StatFSOp)
// Return the size of the most recent write delivered by the kernel, or -1 if
// none.
MostRecentWriteSize() int
}
func New() (fs FS) {
fs = &statFS{
mostRecentWriteSize: -1,
}
return
}
const childInodeID = fuseops.RootInodeID + 1
type statFS struct {
fuseutil.NotImplementedFileSystem
mu sync.Mutex
cannedResponse fuseops.StatFSOp // GUARDED_BY(mu)
mostRecentWriteSize int // GUARDED_BY(mu)
}
////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////
func dirAttrs() fuseops.InodeAttributes {
return fuseops.InodeAttributes{
Mode: os.ModeDir | 0777,
}
}
func fileAttrs() fuseops.InodeAttributes {
return fuseops.InodeAttributes{
Mode: 0666,
}
}
////////////////////////////////////////////////////////////////////////
// Public interface
////////////////////////////////////////////////////////////////////////
// LOCKS_EXCLUDED(fs.mu)
func (fs *statFS) SetStatFSResponse(r fuseops.StatFSOp) {
fs.mu.Lock()
defer fs.mu.Unlock()
fs.cannedResponse = r
}
// LOCKS_EXCLUDED(fs.mu)
func (fs *statFS) MostRecentWriteSize() int {
fs.mu.Lock()
defer fs.mu.Unlock()
return fs.mostRecentWriteSize
}
////////////////////////////////////////////////////////////////////////
// FileSystem methods
////////////////////////////////////////////////////////////////////////
// LOCKS_EXCLUDED(fs.mu)
func (fs *statFS) StatFS(
ctx context.Context,
op *fuseops.StatFSOp) (err error) {
fs.mu.Lock()
defer fs.mu.Unlock()
*op = fs.cannedResponse
return
}
func (fs *statFS) LookUpInode(
ctx context.Context,
op *fuseops.LookUpInodeOp) (err error) {
// Only the root has children.
if op.Parent != fuseops.RootInodeID {
err = fuse.ENOENT
return
}
op.Entry.Child = childInodeID
op.Entry.Attributes = fileAttrs()
return
}
func (fs *statFS) GetInodeAttributes(
ctx context.Context,
op *fuseops.GetInodeAttributesOp) (err error) {
switch op.Inode {
case fuseops.RootInodeID:
op.Attributes = dirAttrs()
case childInodeID:
op.Attributes = fileAttrs()
default:
err = fuse.ENOENT
}
return
}
func (fs *statFS) SetInodeAttributes(
ctx context.Context,
op *fuseops.SetInodeAttributesOp) (err error) {
// Ignore calls to truncate existing files when opening.
return
}
func (fs *statFS) OpenFile(
ctx context.Context,
op *fuseops.OpenFileOp) (err error) {
return
}
// LOCKS_EXCLUDED(fs.mu)
func (fs *statFS) WriteFile(
ctx context.Context,
op *fuseops.WriteFileOp) (err error) {
fs.mu.Lock()
defer fs.mu.Unlock()
fs.mostRecentWriteSize = len(op.Data)
return
}
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package statfs_test
import (
"fmt"
"regexp"
"syscall"
"github.com/jacobsa/fuse/fuseops"
. "github.com/jacobsa/oglematchers"
. "github.com/jacobsa/ogletest"
)
// Sample output:
//
// Filesystem 1024-blocks Used Available Capacity iused ifree %iused Mounted on
// fake@bucket 32 16 16 50% 0 0 100% /Users/jacobsa/tmp/mp
//
var gDfOutputRegexp = regexp.MustCompile(`^\S+\s+(\d+)\s+(\d+)\s+(\d+)\s+\d+%\s+\d+\s+\d+\s+\d+%.*$`)
////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////
func convertName(in []int8) (s string) {
var tmp []byte
for _, v := range in {
if v == 0 {
break
}
tmp = append(tmp, byte(v))
}
s = string(tmp)
return
}
////////////////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////////////////
func (t *StatFSTest) Syscall_ZeroValues() {
var err error
var stat syscall.Statfs_t
// Call without configuring a canned response, meaning the OS will see the
// zero value for each field. The assertions below act as documentation for
// the OS's behavior in this case.
err = syscall.Statfs(t.Dir, &stat)
AssertEq(nil, err)
ExpectEq(4096, stat.Bsize)
ExpectEq(65536, stat.Iosize)
ExpectEq(0, stat.Blocks)
ExpectEq(0, stat.Bfree)
ExpectEq(0, stat.Bavail)
ExpectEq(0, stat.Files)
ExpectEq(0, stat.Ffree)
ExpectEq("osxfusefs", convertName(stat.Fstypename[:]))
ExpectEq(t.canonicalDir, convertName(stat.Mntonname[:]))
ExpectThat(
convertName(stat.Mntfromname[:]),
MatchesRegexp(`mount_osxfusefs@osxfuse\d+`))
}
func (t *StatFSTest) Syscall_NonZeroValues() {
var err error
var stat syscall.Statfs_t
// Set up the canned response.
canned := fuseops.StatFSOp{
BlockSize: 1 << 15,
Blocks: 1<<51 + 3,
BlocksFree: 1<<43 + 5,
BlocksAvailable: 1<<41 + 7,
Inodes: 1<<59 + 11,
InodesFree: 1<<58 + 13,
}
t.fs.SetStatFSResponse(canned)
// Stat.
err = syscall.Statfs(t.Dir, &stat)
AssertEq(nil, err)
ExpectEq(canned.BlockSize, stat.Bsize)
ExpectEq(canned.BlockSize, stat.Iosize)
ExpectEq(canned.Blocks, stat.Blocks)
ExpectEq(canned.BlocksFree, stat.Bfree)
ExpectEq(canned.BlocksAvailable, stat.Bavail)
ExpectEq(canned.Inodes, stat.Files)
ExpectEq(canned.InodesFree, stat.Ffree)
ExpectEq("osxfusefs", convertName(stat.Fstypename[:]))
ExpectEq(t.canonicalDir, convertName(stat.Mntonname[:]))
ExpectThat(
convertName(stat.Mntfromname[:]),
MatchesRegexp(`mount_osxfusefs@osxfuse\d+`))
}
func (t *StatFSTest) UnsupportedBlockSizes() {
var err error
// Test a bunch of block sizes that the OS doesn't support faithfully,
// checking what it transforms them too.
testCases := []struct {
fsBlockSize uint32
expectedBsize uint32
expectedIosize uint32
}{
0: {0, 4096, 65536},
1: {1, 512, 512},
2: {3, 512, 512},
3: {511, 512, 512},
4: {513, 1024, 1024},
5: {1023, 1024, 1024},
6: {4095, 4096, 4096},
7: {1<<17 - 1, 1 << 17, 131072},
8: {1<<17 + 1, 1 << 17, 1 << 18},
9: {1<<18 + 1, 1 << 17, 1 << 19},
10: {1<<19 + 1, 1 << 17, 1 << 20},
11: {1<<20 + 1, 1 << 17, 1 << 20},
12: {1 << 21, 1 << 17, 1 << 20},
13: {1 << 30, 1 << 17, 1 << 20},
}
for i, tc := range testCases {
desc := fmt.Sprintf("Case %d: block size %d", i, tc.fsBlockSize)
// Set up.
canned := fuseops.StatFSOp{
BlockSize: tc.fsBlockSize,
Blocks: 10,
}
t.fs.SetStatFSResponse(canned)
// Check.
var stat syscall.Statfs_t
err = syscall.Statfs(t.Dir, &stat)
AssertEq(nil, err)
ExpectEq(tc.expectedBsize, stat.Bsize, "%s", desc)
ExpectEq(tc.expectedIosize, stat.Iosize, "%s", desc)
}
}
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package statfs_test
import (
"fmt"
"math"
"regexp"
"syscall"
"github.com/jacobsa/fuse/fuseops"
. "github.com/jacobsa/ogletest"
)
// Sample output:
//
// Filesystem 1K-blocks Used Available Use% Mounted on
// some_fuse_file_system 512 64 384 15% /tmp/sample_test001288095
//
var gDfOutputRegexp = regexp.MustCompile(`^\S+\s+(\d+)\s+(\d+)\s+(\d+)\s+\d+%.*$`)
////////////////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////////////////
func (t *StatFSTest) Syscall_ZeroValues() {
var err error
var stat syscall.Statfs_t
// Call without configuring a canned response, meaning the OS will see the
// zero value for each field. The assertions below act as documentation for
// the OS's behavior in this case.
err = syscall.Statfs(t.Dir, &stat)
AssertEq(nil, err)
ExpectEq(0, stat.Bsize)
ExpectEq(0, stat.Frsize)
ExpectEq(0, stat.Blocks)
ExpectEq(0, stat.Bfree)
ExpectEq(0, stat.Bavail)
ExpectEq(0, stat.Files)
ExpectEq(0, stat.Ffree)
}
func (t *StatFSTest) Syscall_NonZeroValues() {
var err error
var stat syscall.Statfs_t
// Set up the canned response.
canned := fuseops.StatFSOp{
BlockSize: 1 << 15,
Blocks: 1<<51 + 3,
BlocksFree: 1<<43 + 5,
BlocksAvailable: 1<<41 + 7,
Inodes: 1<<59 + 11,
InodesFree: 1<<58 + 13,
}
t.fs.SetStatFSResponse(canned)
// Stat.
err = syscall.Statfs(t.Dir, &stat)
AssertEq(nil, err)
ExpectEq(canned.BlockSize, stat.Bsize)
ExpectEq(canned.BlockSize, stat.Frsize)
ExpectEq(canned.Blocks, stat.Blocks)
ExpectEq(canned.BlocksFree, stat.Bfree)
ExpectEq(canned.BlocksAvailable, stat.Bavail)
ExpectEq(canned.Inodes, stat.Files)
ExpectEq(canned.InodesFree, stat.Ffree)
}
func (t *StatFSTest) WackyBlockSizes() {
var err error
// Test a bunch of weird block sizes that OS X would be cranky about.
blockSizes := []uint32{
0,
1,
3,
17,
1<<20 - 1,
1<<20 + 0,
1<<20 + 1,
math.MaxInt32,
math.MaxUint32,
}
for _, bs := range blockSizes {
desc := fmt.Sprintf("block size %d", bs)
// Set up.
canned := fuseops.StatFSOp{
BlockSize: bs,
Blocks: 10,
}
t.fs.SetStatFSResponse(canned)
// Check.
var stat syscall.Statfs_t
err = syscall.Statfs(t.Dir, &stat)
AssertEq(nil, err)
ExpectEq(bs, stat.Bsize, "%s", desc)
ExpectEq(bs, stat.Frsize, "%s", desc)
}
}
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package statfs_test
import (
"bytes"
"fmt"
"io/ioutil"
"os/exec"
"path"
"path/filepath"
"runtime"
"strconv"
"testing"
"github.com/jacobsa/fuse/fuseops"
"github.com/jacobsa/fuse/fuseutil"
"github.com/jacobsa/fuse/samples"
"github.com/jacobsa/fuse/samples/statfs"
. "github.com/jacobsa/ogletest"
)
func TestStatFS(t *testing.T) { RunTests(t) }
////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////
// Ask `df` for statistics about the file system's capacity and free space,
// useful for checking that our reading of statfs(2) output matches the
// system's. The output is not guaranteed to have resolution greater than 2^10
// (1 KiB).
func df(dir string) (capacity, used, available uint64, err error) {
// Call df with a block size of 1024 and capture its output.
cmd := exec.Command("df", dir)
cmd.Env = []string{"BLOCKSIZE=1024"}
output, err := cmd.CombinedOutput()
if err != nil {
return
}
// Scrape it.
for _, line := range bytes.Split(output, []byte{'\n'}) {
// Is this the line we're interested in?
if !bytes.Contains(line, []byte(dir)) {
continue
}
submatches := gDfOutputRegexp.FindSubmatch(line)
if submatches == nil {
err = fmt.Errorf("Unable to parse line: %q", line)
return
}
capacity, err = strconv.ParseUint(string(submatches[1]), 10, 64)
if err != nil {
return
}
used, err = strconv.ParseUint(string(submatches[2]), 10, 64)
if err != nil {
return
}
available, err = strconv.ParseUint(string(submatches[3]), 10, 64)
if err != nil {
return
}
// Scale appropriately based on the BLOCKSIZE set above.
capacity *= 1024
used *= 1024
available *= 1024
return
}
err = fmt.Errorf("Unable to parse df output:\n%s", output)
return
}
////////////////////////////////////////////////////////////////////////
// Boilerplate
////////////////////////////////////////////////////////////////////////
type StatFSTest struct {
samples.SampleTest
fs statfs.FS
// t.Dir, with symlinks resolved and redundant path components removed.
canonicalDir string
}
var _ SetUpInterface = &StatFSTest{}
var _ TearDownInterface = &StatFSTest{}
func init() { RegisterTestSuite(&StatFSTest{}) }
func (t *StatFSTest) SetUp(ti *TestInfo) {
var err error
// Writeback caching can ruin our measurement of the write sizes the kernel
// decides to give us, since it causes write acking to race against writes
// being issued from the client.
t.MountConfig.DisableWritebackCaching = true
// Create the file system.
t.fs = statfs.New()
t.Server = fuseutil.NewFileSystemServer(t.fs)
// Mount it.
t.SampleTest.SetUp(ti)
// Canonicalize the mount point.
t.canonicalDir, err = filepath.EvalSymlinks(t.Dir)
AssertEq(nil, err)
t.canonicalDir = path.Clean(t.canonicalDir)
}
////////////////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////////////////
func (t *StatFSTest) CapacityAndFreeSpace() {
canned := fuseops.StatFSOp{
Blocks: 1024,
BlocksFree: 896,
BlocksAvailable: 768,
}
// Check that df agrees with us about a range of block sizes.
for log2BlockSize := uint(9); log2BlockSize <= 17; log2BlockSize++ {
bs := uint64(1) << log2BlockSize
desc := fmt.Sprintf("block size: %d (2^%d)", bs, log2BlockSize)
// Set up the canned response.
canned.BlockSize = uint32(bs)
t.fs.SetStatFSResponse(canned)
// Call df.
capacity, used, available, err := df(t.canonicalDir)
AssertEq(nil, err)
ExpectEq(bs*canned.Blocks, capacity, "%s", desc)
ExpectEq(bs*(canned.Blocks-canned.BlocksFree), used, "%s", desc)
ExpectEq(bs*canned.BlocksAvailable, available, "%s", desc)
}
}
func (t *StatFSTest) WriteSize() {
var err error
// Set up a smallish block size.
canned := fuseops.StatFSOp{
BlockSize: 8192,
Blocks: 1234,
BlocksFree: 1234,
BlocksAvailable: 1234,
}
t.fs.SetStatFSResponse(canned)
// Cause a large amount of date to be written.
err = ioutil.WriteFile(
path.Join(t.Dir, "foo"),
bytes.Repeat([]byte{'x'}, 1<<22),
0400)
AssertEq(nil, err)
// Despite the small block size, the OS shouldn't have given us pitifully
// small chunks of data.
switch runtime.GOOS {
case "linux":
ExpectEq(1<<17, t.fs.MostRecentWriteSize())
case "darwin":
ExpectEq(1<<20, t.fs.MostRecentWriteSize())
default:
AddFailure("Unhandled OS: %s", runtime.GOOS)
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment