Commit bc664bf3 authored by Aaron Jacobs's avatar Aaron Jacobs

Allow the user to independently set block size and IO size.

On OS X we can't set block size above 2^17, but we want to encourage
users to send large reads and writes.

For GoogleCloudPlatform/gcsfuse#125.
parents e59a45f1 246d37a0
......@@ -565,8 +565,8 @@ func (c *Connection) kernelResponseForOp(
out.St.Files = o.Inodes
out.St.Ffree = o.InodesFree
// The posix spec for sys/statvfs.h defines the following fields, among
// others:
// The posix spec for sys/statvfs.h (http://goo.gl/LktgrF) defines the
// following fields of statvfs, among others:
//
// f_bsize File system block size.
// f_frsize Fundamental file system block size.
......@@ -587,11 +587,10 @@ func (c *Connection) kernelResponseForOp(
// 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
// Confusingly, it appears as though osxfuse surfaces fuse_kstatfs::bsize
// as statfs::f_iosize (of advisory use only), and fuse_kstatfs::frsize as
// statfs::f_bsize (which affects free space display in the Finder).
out.St.Bsize = o.IoSize
out.St.Frsize = o.BlockSize
case *initOp:
......
......@@ -46,13 +46,21 @@ type StatFSOp struct {
// 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.
// On Linux this is surfaced as statfs::f_frsize, matching the posix standard
// (http://goo.gl/LktgrF), which says that f_blocks and friends are in units
// of f_frsize. On OS X this is surfaced as statfs::f_bsize, which plays the
// same roll.
//
// It appears as though the original intent of statvfs::f_frsize in the posix
// standard was to support a smaller addressable unit than statvfs::f_bsize
// (cf. The Linux Programming Interface by Michael Kerrisk,
// https://goo.gl/5LZMxQ). Therefore users should probably arrange for this
// to be no larger than IoSize.
//
// On Linux this can be any value, and will be faithfully returned to the
// caller of statfs(2) (see the code walk above). On OS X it appears that
// only powers of 2 in the range [2^9, 2^17] are preserved, and a value of
// zero is treated as 4096.
//
// This interface does not distinguish between blocks and block fragments.
BlockSize uint32
......@@ -67,6 +75,20 @@ type StatFSOp struct {
BlocksFree uint64
BlocksAvailable uint64
// The preferred size of writes to and reads from the file system, in bytes.
// This may affect clients that use statfs(2) to size buffers correctly. It
// does not appear to influence the size of writes sent from the kernel to
// the file system daemon.
//
// On Linux this is surfaced as statfs::f_bsize, and on OS X as
// statfs::f_iosize. Both are documented in `man 2 statfs` as "optimal
// transfer block size".
//
// On Linux this can be any value. On OS X it appears that only powers of 2
// in the range [2^12, 2^20] are faithfully preserved, and a value of zero is
// treated as 65536.
IoSize uint32
// The total number of inodes in the file system, and how many remain free.
Inodes uint64
InodesFree uint64
......
......@@ -16,6 +16,7 @@ package statfs_test
import (
"fmt"
"math"
"regexp"
"syscall"
......@@ -81,6 +82,7 @@ func (t *StatFSTest) Syscall_NonZeroValues() {
// Set up the canned response.
canned := fuseops.StatFSOp{
BlockSize: 1 << 15,
IoSize: 1 << 16,
Blocks: 1<<51 + 3,
BlocksFree: 1<<43 + 5,
......@@ -97,7 +99,7 @@ func (t *StatFSTest) Syscall_NonZeroValues() {
AssertEq(nil, err)
ExpectEq(canned.BlockSize, stat.Bsize)
ExpectEq(canned.BlockSize, stat.Iosize)
ExpectEq(canned.IoSize, stat.Iosize)
ExpectEq(canned.Blocks, stat.Blocks)
ExpectEq(canned.BlocksFree, stat.Bfree)
ExpectEq(canned.BlocksAvailable, stat.Bavail)
......@@ -108,30 +110,35 @@ func (t *StatFSTest) Syscall_NonZeroValues() {
ExpectEq(fsName, convertName(stat.Mntfromname[:]))
}
func (t *StatFSTest) UnsupportedBlockSizes() {
func (t *StatFSTest) BlockSizes() {
var err error
// Test a bunch of block sizes that the OS doesn't support faithfully,
// checking what it transforms them too.
// Test a bunch of block sizes that the OS does or 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},
0: {0, 4096},
1: {1, 512},
2: {3, 512},
3: {511, 512},
4: {512, 512},
5: {513, 1024},
6: {1023, 1024},
7: {1024, 1024},
8: {4095, 4096},
9: {1 << 16, 1 << 16},
10: {1<<17 - 1, 1 << 17},
11: {1 << 17, 1 << 17},
12: {1<<17 + 1, 1 << 17},
13: {1 << 18, 1 << 17},
14: {1 << 20, 1 << 17},
15: {math.MaxInt32 - 1, 1 << 17},
16: {math.MaxInt32, 1 << 17},
17: {math.MaxInt32 + 1, 512},
18: {math.MaxInt32 + 1<<15, 1 << 15},
19: {math.MaxUint32, 1 << 17},
}
for i, tc := range testCases {
......@@ -151,6 +158,53 @@ func (t *StatFSTest) UnsupportedBlockSizes() {
AssertEq(nil, err)
ExpectEq(tc.expectedBsize, stat.Bsize, "%s", desc)
}
}
func (t *StatFSTest) IoSizes() {
var err error
// Test a bunch of io sizes that the OS does or doesn't support faithfully,
// checking what it transforms them too.
testCases := []struct {
fsIoSize uint32
expectedIosize uint32
}{
0: {0, 65536},
1: {1, 4096},
2: {3, 4096},
3: {4095, 4096},
4: {4096, 4096},
5: {4097, 8192},
6: {8191, 8192},
7: {8192, 8192},
8: {8193, 16384},
9: {1<<20 - 1, 1 << 20},
10: {1 << 20, 1 << 20},
11: {1<<20 + 1, 1 << 20},
12: {math.MaxInt32 - 1, 1 << 20},
13: {math.MaxInt32, 1 << 20},
14: {math.MaxInt32 + 1, 4096},
15: {math.MaxInt32 + 1<<15, 1 << 15},
16: {math.MaxUint32, 1 << 20},
}
for i, tc := range testCases {
desc := fmt.Sprintf("Case %d: IO size %d", i, tc.fsIoSize)
// Set up.
canned := fuseops.StatFSOp{
IoSize: tc.fsIoSize,
Blocks: 10,
}
t.fs.SetStatFSResponse(canned)
// Check.
var stat syscall.Statfs_t
err = syscall.Statfs(t.Dir, &stat)
AssertEq(nil, err)
ExpectEq(tc.expectedIosize, stat.Iosize, "%s", desc)
}
}
......@@ -61,6 +61,7 @@ func (t *StatFSTest) Syscall_NonZeroValues() {
// Set up the canned response.
canned := fuseops.StatFSOp{
BlockSize: 1 << 15,
IoSize: 1 << 16,
Blocks: 1<<51 + 3,
BlocksFree: 1<<43 + 5,
......@@ -76,8 +77,8 @@ func (t *StatFSTest) Syscall_NonZeroValues() {
err = syscall.Statfs(t.Dir, &stat)
AssertEq(nil, err)
ExpectEq(canned.BlockSize, stat.Bsize)
ExpectEq(canned.BlockSize, stat.Frsize)
ExpectEq(canned.IoSize, stat.Bsize)
ExpectEq(canned.Blocks, stat.Blocks)
ExpectEq(canned.BlocksFree, stat.Bfree)
ExpectEq(canned.BlocksAvailable, stat.Bavail)
......@@ -85,7 +86,7 @@ func (t *StatFSTest) Syscall_NonZeroValues() {
ExpectEq(canned.InodesFree, stat.Ffree)
}
func (t *StatFSTest) WackyBlockSizes() {
func (t *StatFSTest) BlockSizes() {
var err error
// Test a bunch of weird block sizes that OS X would be cranky about.
......@@ -98,6 +99,7 @@ func (t *StatFSTest) WackyBlockSizes() {
1<<20 + 0,
1<<20 + 1,
math.MaxInt32,
math.MaxInt32 + 1,
math.MaxUint32,
}
......@@ -117,7 +119,43 @@ func (t *StatFSTest) WackyBlockSizes() {
err = syscall.Statfs(t.Dir, &stat)
AssertEq(nil, err)
ExpectEq(bs, stat.Bsize, "%s", desc)
ExpectEq(bs, stat.Frsize, "%s", desc)
}
}
func (t *StatFSTest) IoSizes() {
var err error
// Test a bunch of weird IO sizes that OS X would be cranky about.
ioSizes := []uint32{
0,
1,
3,
17,
1<<20 - 1,
1<<20 + 0,
1<<20 + 1,
math.MaxInt32,
math.MaxInt32 + 1,
math.MaxUint32,
}
for _, bs := range ioSizes {
desc := fmt.Sprintf("IO size %d", bs)
// Set up.
canned := fuseops.StatFSOp{
IoSize: 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)
}
}
......@@ -146,6 +146,8 @@ func (t *StatFSTest) CapacityAndFreeSpace() {
Blocks: 1024,
BlocksFree: 896,
BlocksAvailable: 768,
IoSize: 1024, // Shouldn't matter.
}
// Check that df agrees with us about a range of block sizes.
......@@ -173,6 +175,7 @@ func (t *StatFSTest) WriteSize() {
// Set up a smallish block size.
canned := fuseops.StatFSOp{
BlockSize: 8192,
IoSize: 16384,
Blocks: 1234,
BlocksFree: 1234,
BlocksAvailable: 1234,
......
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