fusefrontend: add fallocate support
Mode=0 (default) and mode=1 (keep size) are supported. The patch includes test cases and the whole thing passed xfstests. Fixes https://github.com/rfjakob/gocryptfs/issues/1 .
This commit is contained in:
parent
04ad063515
commit
54470baa23
|
@ -208,8 +208,6 @@ func (f *file) Read(buf []byte, off int64) (resultData fuse.ReadResult, code fus
|
||||||
return fuse.ReadResultData(out), status
|
return fuse.ReadResultData(out), status
|
||||||
}
|
}
|
||||||
|
|
||||||
const FALLOC_FL_KEEP_SIZE = 0x01
|
|
||||||
|
|
||||||
// doWrite - encrypt "data" and write it to plaintext offset "off"
|
// doWrite - encrypt "data" and write it to plaintext offset "off"
|
||||||
//
|
//
|
||||||
// Arguments do not have to be block-aligned, read-modify-write is
|
// Arguments do not have to be block-aligned, read-modify-write is
|
||||||
|
|
|
@ -4,6 +4,7 @@ package fusefrontend
|
||||||
// i.e. ftruncate and fallocate
|
// i.e. ftruncate and fallocate
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
@ -12,17 +13,78 @@ import (
|
||||||
"github.com/rfjakob/gocryptfs/internal/tlog"
|
"github.com/rfjakob/gocryptfs/internal/tlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const FALLOC_DEFAULT = 0x00
|
||||||
|
const FALLOC_FL_KEEP_SIZE = 0x01
|
||||||
|
|
||||||
// Only warn once
|
// Only warn once
|
||||||
var allocateWarnOnce sync.Once
|
var allocateWarnOnce sync.Once
|
||||||
|
|
||||||
// Allocate - FUSE call, fallocate(2)
|
// Allocate - FUSE call for fallocate(2)
|
||||||
// This is not implemented yet in gocryptfs, but it is neither in EncFS. This
|
//
|
||||||
// suggests that the user demand is low.
|
// mode=FALLOC_FL_KEEP_SIZE is implemented directly.
|
||||||
|
//
|
||||||
|
// mode=FALLOC_DEFAULT is implemented as a two-step process:
|
||||||
|
//
|
||||||
|
// (1) Allocate the space using FALLOC_FL_KEEP_SIZE
|
||||||
|
// (2) Set the file size using ftruncate (via truncateGrowFile)
|
||||||
|
//
|
||||||
|
// This allows us to reuse the file grow mechanics from Truncate as they are
|
||||||
|
// complicated and hard to get right.
|
||||||
|
//
|
||||||
|
// Other modes (hole punching, zeroing) are not supported.
|
||||||
func (f *file) Allocate(off uint64, sz uint64, mode uint32) fuse.Status {
|
func (f *file) Allocate(off uint64, sz uint64, mode uint32) fuse.Status {
|
||||||
allocateWarnOnce.Do(func() {
|
if mode != FALLOC_DEFAULT && mode != FALLOC_FL_KEEP_SIZE {
|
||||||
tlog.Warn.Printf("fallocate(2) is not supported, returning ENOSYS - see https://github.com/rfjakob/gocryptfs/issues/1")
|
f := func() {
|
||||||
})
|
tlog.Warn.Print("fallocate: only mode 0 (default) and 1 (keep size) are supported")
|
||||||
return fuse.ENOSYS
|
}
|
||||||
|
allocateWarnOnce.Do(f)
|
||||||
|
return fuse.Status(syscall.EOPNOTSUPP)
|
||||||
|
}
|
||||||
|
|
||||||
|
f.fdLock.RLock()
|
||||||
|
defer f.fdLock.RUnlock()
|
||||||
|
if f.released {
|
||||||
|
return fuse.EBADF
|
||||||
|
}
|
||||||
|
wlock.lock(f.ino)
|
||||||
|
defer wlock.unlock(f.ino)
|
||||||
|
|
||||||
|
blocks := f.contentEnc.ExplodePlainRange(off, sz)
|
||||||
|
firstBlock := blocks[0]
|
||||||
|
lastBlock := blocks[len(blocks)-1]
|
||||||
|
|
||||||
|
// Step (1): Allocate the space the user wants using FALLOC_FL_KEEP_SIZE.
|
||||||
|
// This will fill file holes and/or allocate additional space past the end of
|
||||||
|
// the file.
|
||||||
|
cipherOff := firstBlock.BlockCipherOff()
|
||||||
|
cipherSz := lastBlock.BlockCipherOff() - cipherOff +
|
||||||
|
f.contentEnc.PlainSizeToCipherSize(lastBlock.Skip+lastBlock.Length)
|
||||||
|
err := syscall.Fallocate(f.intFd(), FALLOC_FL_KEEP_SIZE, int64(cipherOff), int64(cipherSz))
|
||||||
|
tlog.Debug.Printf("Allocate off=%d sz=%d mode=%x cipherOff=%d cipherSz=%d\n",
|
||||||
|
off, sz, mode, cipherOff, cipherSz)
|
||||||
|
if err != nil {
|
||||||
|
return fuse.ToStatus(err)
|
||||||
|
}
|
||||||
|
if mode == FALLOC_FL_KEEP_SIZE {
|
||||||
|
// The user did not want to change the apparent size. We are done.
|
||||||
|
return fuse.OK
|
||||||
|
}
|
||||||
|
// Step (2): Grow the apparent file size
|
||||||
|
// We need the old file size to determine if we are growing the file at all.
|
||||||
|
newPlainSz := off + sz
|
||||||
|
oldPlainSz, err := f.statPlainSize()
|
||||||
|
if err != nil {
|
||||||
|
return fuse.ToStatus(err)
|
||||||
|
}
|
||||||
|
if newPlainSz <= oldPlainSz {
|
||||||
|
// The new size is smaller (or equal). Fallocate with mode = 0 never
|
||||||
|
// truncates a file, so we are done.
|
||||||
|
return fuse.OK
|
||||||
|
}
|
||||||
|
// The file grows. The space has already been allocated in (1), so what is
|
||||||
|
// left to do is to pad the first and last block and call truncate.
|
||||||
|
// truncateGrowFile does just that.
|
||||||
|
return f.truncateGrowFile(oldPlainSz, newPlainSz)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Truncate - FUSE call
|
// Truncate - FUSE call
|
||||||
|
@ -50,13 +112,10 @@ func (f *file) Truncate(newSize uint64) fuse.Status {
|
||||||
}
|
}
|
||||||
// We need the old file size to determine if we are growing or shrinking
|
// We need the old file size to determine if we are growing or shrinking
|
||||||
// the file
|
// the file
|
||||||
fi, err := f.fd.Stat()
|
oldSize, err := f.statPlainSize()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tlog.Warn.Printf("ino%d fh%d: Truncate: Fstat failed: %v", f.ino, f.intFd(), err)
|
|
||||||
return fuse.ToStatus(err)
|
return fuse.ToStatus(err)
|
||||||
}
|
} else {
|
||||||
oldSize := f.contentEnc.CipherSizeToPlainSize(uint64(fi.Size()))
|
|
||||||
{
|
|
||||||
oldB := float32(oldSize) / float32(f.contentEnc.PlainBS())
|
oldB := float32(oldSize) / float32(f.contentEnc.PlainBS())
|
||||||
newB := float32(newSize) / float32(f.contentEnc.PlainBS())
|
newB := float32(newSize) / float32(f.contentEnc.PlainBS())
|
||||||
tlog.Debug.Printf("ino%d: FUSE Truncate from %.2f to %.2f blocks (%d to %d bytes)", f.ino, oldB, newB, oldSize, newSize)
|
tlog.Debug.Printf("ino%d: FUSE Truncate from %.2f to %.2f blocks (%d to %d bytes)", f.ino, oldB, newB, oldSize, newSize)
|
||||||
|
@ -67,31 +126,7 @@ func (f *file) Truncate(newSize uint64) fuse.Status {
|
||||||
}
|
}
|
||||||
// File grows
|
// File grows
|
||||||
if newSize > oldSize {
|
if newSize > oldSize {
|
||||||
// File was empty, create new header
|
return f.truncateGrowFile(oldSize, newSize)
|
||||||
if oldSize == 0 {
|
|
||||||
err = f.createHeader()
|
|
||||||
if err != nil {
|
|
||||||
return fuse.ToStatus(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// New blocks to add
|
|
||||||
addBlocks := f.contentEnc.ExplodePlainRange(oldSize, newSize-oldSize)
|
|
||||||
if len(addBlocks) >= 2 {
|
|
||||||
f.zeroPad(oldSize)
|
|
||||||
}
|
|
||||||
lastBlock := addBlocks[len(addBlocks)-1]
|
|
||||||
if lastBlock.IsPartial() {
|
|
||||||
off := lastBlock.BlockPlainOff()
|
|
||||||
_, status := f.doWrite(make([]byte, lastBlock.Length), int64(off+lastBlock.Skip))
|
|
||||||
return status
|
|
||||||
} else {
|
|
||||||
off := lastBlock.BlockCipherOff()
|
|
||||||
err = syscall.Ftruncate(f.intFd(), int64(off+f.contentEnc.CipherBS()))
|
|
||||||
if err != nil {
|
|
||||||
tlog.Warn.Printf("Truncate: grow Ftruncate returned error: %v", err)
|
|
||||||
}
|
|
||||||
return fuse.ToStatus(err)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// File shrinks
|
// File shrinks
|
||||||
blockNo := f.contentEnc.PlainOffToBlockNo(newSize)
|
blockNo := f.contentEnc.PlainOffToBlockNo(newSize)
|
||||||
|
@ -121,3 +156,53 @@ func (f *file) Truncate(newSize uint64) fuse.Status {
|
||||||
return fuse.OK
|
return fuse.OK
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// statPlainSize stats the file and returns the plaintext size
|
||||||
|
func (f *file) statPlainSize() (uint64, error) {
|
||||||
|
fi, err := f.fd.Stat()
|
||||||
|
if err != nil {
|
||||||
|
tlog.Warn.Printf("ino%d fh%d: statPlainSize: %v", f.ino, f.intFd(), err)
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
cipherSz := uint64(fi.Size())
|
||||||
|
plainSz := uint64(f.contentEnc.CipherSizeToPlainSize(cipherSz))
|
||||||
|
return plainSz, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// truncateGrowFile extends a file using seeking or ftruncate performing RMW on
|
||||||
|
// the first and last block as neccessary. New blocks in the middle become
|
||||||
|
// file holes unless they have been fallocate()'d beforehand.
|
||||||
|
func (f *file) truncateGrowFile(oldPlainSz uint64, newPlainSz uint64) fuse.Status {
|
||||||
|
if newPlainSz <= oldPlainSz {
|
||||||
|
log.Panicf("BUG: newSize=%d <= oldSize=%d", newPlainSz, oldPlainSz)
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
// File was empty, create new header
|
||||||
|
if oldPlainSz == 0 {
|
||||||
|
err = f.createHeader()
|
||||||
|
if err != nil {
|
||||||
|
return fuse.ToStatus(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// New blocks to add
|
||||||
|
addBlocks := f.contentEnc.ExplodePlainRange(oldPlainSz, newPlainSz-oldPlainSz)
|
||||||
|
if oldPlainSz > 0 && len(addBlocks) >= 2 {
|
||||||
|
// Zero-pad the first block (unless the first block is also the last block)
|
||||||
|
f.zeroPad(oldPlainSz)
|
||||||
|
}
|
||||||
|
lastBlock := addBlocks[len(addBlocks)-1]
|
||||||
|
if lastBlock.IsPartial() {
|
||||||
|
// Write at the new end of the file. The seek implicitly grows the file
|
||||||
|
// (creates a file hole) and doWrite() takes care of RMW.
|
||||||
|
off := lastBlock.BlockPlainOff()
|
||||||
|
_, status := f.doWrite(make([]byte, lastBlock.Length), int64(off+lastBlock.Skip))
|
||||||
|
return status
|
||||||
|
} else {
|
||||||
|
off := lastBlock.BlockCipherOff()
|
||||||
|
err = syscall.Ftruncate(f.intFd(), int64(off+f.contentEnc.CipherBS()))
|
||||||
|
if err != nil {
|
||||||
|
tlog.Warn.Printf("Truncate: grow Ftruncate returned error: %v", err)
|
||||||
|
}
|
||||||
|
return fuse.ToStatus(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -124,26 +124,26 @@ func TestTruncate(t *testing.T) {
|
||||||
// Grow to two blocks
|
// Grow to two blocks
|
||||||
file.Truncate(7000)
|
file.Truncate(7000)
|
||||||
test_helpers.VerifySize(t, fn, 7000)
|
test_helpers.VerifySize(t, fn, 7000)
|
||||||
if test_helpers.Md5fn(fn) != "95d4ec7038e3e4fdbd5f15c34c3f0b34" {
|
if md5 := test_helpers.Md5fn(fn); md5 != "95d4ec7038e3e4fdbd5f15c34c3f0b34" {
|
||||||
t.Errorf("wrong content")
|
t.Errorf("Wrong md5 %s", md5)
|
||||||
}
|
}
|
||||||
// Shrink - needs RMW
|
// Shrink - needs RMW
|
||||||
file.Truncate(6999)
|
file.Truncate(6999)
|
||||||
test_helpers.VerifySize(t, fn, 6999)
|
test_helpers.VerifySize(t, fn, 6999)
|
||||||
if test_helpers.Md5fn(fn) != "35fd15873ec6c35380064a41b9b9683b" {
|
if md5 := test_helpers.Md5fn(fn); md5 != "35fd15873ec6c35380064a41b9b9683b" {
|
||||||
t.Errorf("wrong content")
|
t.Errorf("Wrong md5 %s", md5)
|
||||||
}
|
}
|
||||||
// Shrink to one partial block
|
// Shrink to one partial block
|
||||||
file.Truncate(465)
|
file.Truncate(465)
|
||||||
test_helpers.VerifySize(t, fn, 465)
|
test_helpers.VerifySize(t, fn, 465)
|
||||||
if test_helpers.Md5fn(fn) != "a1534d6e98a6b21386456a8f66c55260" {
|
if md5 := test_helpers.Md5fn(fn); md5 != "a1534d6e98a6b21386456a8f66c55260" {
|
||||||
t.Errorf("wrong content")
|
t.Errorf("Wrong md5 %s", md5)
|
||||||
}
|
}
|
||||||
// Grow to exactly one block
|
// Grow to exactly one block
|
||||||
file.Truncate(4096)
|
file.Truncate(4096)
|
||||||
test_helpers.VerifySize(t, fn, 4096)
|
test_helpers.VerifySize(t, fn, 4096)
|
||||||
if test_helpers.Md5fn(fn) != "620f0b67a91f7f74151bc5be745b7110" {
|
if md5 := test_helpers.Md5fn(fn); md5 != "620f0b67a91f7f74151bc5be745b7110" {
|
||||||
t.Errorf("wrong content")
|
t.Errorf("Wrong md5 %s", md5)
|
||||||
}
|
}
|
||||||
// Truncate to zero
|
// Truncate to zero
|
||||||
file.Truncate(0)
|
file.Truncate(0)
|
||||||
|
@ -153,25 +153,133 @@ func TestTruncate(t *testing.T) {
|
||||||
sz = 10 * 1024 * 1024
|
sz = 10 * 1024 * 1024
|
||||||
file.Truncate(int64(sz))
|
file.Truncate(int64(sz))
|
||||||
test_helpers.VerifySize(t, fn, sz)
|
test_helpers.VerifySize(t, fn, sz)
|
||||||
if test_helpers.Md5fn(fn) != "f1c9645dbc14efddc7d8a322685f26eb" {
|
if md5 := test_helpers.Md5fn(fn); md5 != "f1c9645dbc14efddc7d8a322685f26eb" {
|
||||||
t.Errorf("wrong content")
|
t.Errorf("Wrong md5 %s", md5)
|
||||||
}
|
}
|
||||||
// Grow to 10MB + 100B (partial block on the end)
|
// Grow to 10MB + 100B (partial block on the end)
|
||||||
sz = 10*1024*1024 + 100
|
sz = 10*1024*1024 + 100
|
||||||
file.Truncate(int64(sz))
|
file.Truncate(int64(sz))
|
||||||
test_helpers.VerifySize(t, fn, sz)
|
test_helpers.VerifySize(t, fn, sz)
|
||||||
if test_helpers.Md5fn(fn) != "c23ea79b857b91a7ff07c6ecf185f1ca" {
|
if md5 := test_helpers.Md5fn(fn); md5 != "c23ea79b857b91a7ff07c6ecf185f1ca" {
|
||||||
t.Errorf("wrong content")
|
t.Errorf("Wrong md5 %s", md5)
|
||||||
}
|
}
|
||||||
// Grow to 20MB (creates file holes, partial block on the front)
|
// Grow to 20MB (creates file holes, partial block on the front)
|
||||||
sz = 20 * 1024 * 1024
|
sz = 20 * 1024 * 1024
|
||||||
file.Truncate(int64(sz))
|
file.Truncate(int64(sz))
|
||||||
test_helpers.VerifySize(t, fn, sz)
|
test_helpers.VerifySize(t, fn, sz)
|
||||||
if test_helpers.Md5fn(fn) != "8f4e33f3dc3e414ff94e5fb6905cba8c" {
|
if md5 := test_helpers.Md5fn(fn); md5 != "8f4e33f3dc3e414ff94e5fb6905cba8c" {
|
||||||
t.Errorf("wrong content")
|
t.Errorf("Wrong md5 %s", md5)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const FALLOC_DEFAULT = 0x00
|
||||||
|
const FALLOC_FL_KEEP_SIZE = 0x01
|
||||||
|
|
||||||
|
func TestFallocate(t *testing.T) {
|
||||||
|
fn := test_helpers.DefaultPlainDir + "/fallocate"
|
||||||
|
file, err := os.Create(fn)
|
||||||
|
if err != nil {
|
||||||
|
t.FailNow()
|
||||||
|
}
|
||||||
|
var nBlocks int64
|
||||||
|
fd := int(file.Fd())
|
||||||
|
_, nBlocks = test_helpers.Du(t, fd)
|
||||||
|
if nBlocks != 0 {
|
||||||
|
t.Fatalf("Empty file has %d blocks", nBlocks)
|
||||||
|
}
|
||||||
|
// Allocate 30 bytes, keep size
|
||||||
|
// gocryptfs || (0 blocks)
|
||||||
|
// ext4 | d | (1 block)
|
||||||
|
err = syscall.Fallocate(fd, FALLOC_FL_KEEP_SIZE, 0, 30)
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
_, nBlocks = test_helpers.Du(t, fd)
|
||||||
|
if want := 1; nBlocks/8 != int64(want) {
|
||||||
|
t.Errorf("Expected %d 4k block(s), got %d", want, nBlocks/8)
|
||||||
|
}
|
||||||
|
test_helpers.VerifySize(t, fn, 0)
|
||||||
|
// Three ciphertext blocks. The middle one should be a file hole.
|
||||||
|
// gocryptfs | h | h | d| (1 block)
|
||||||
|
// ext4 | d | h | d | (2 blocks)
|
||||||
|
// (Note that gocryptfs blocks are slightly bigger than the ext4 blocks,
|
||||||
|
// but the last one is partial)
|
||||||
|
err = file.Truncate(9000)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
_, nBlocks = test_helpers.Du(t, fd)
|
||||||
|
if want := 2; nBlocks/8 != int64(want) {
|
||||||
|
t.Errorf("Expected %d 4k block(s), got %d", want, nBlocks/8)
|
||||||
|
}
|
||||||
|
if md5 := test_helpers.Md5fn(fn); md5 != "5420afa22f6423a9f59e669540656bb4" {
|
||||||
|
t.Errorf("Wrong md5 %s", md5)
|
||||||
|
}
|
||||||
|
// Allocate the whole file space
|
||||||
|
// gocryptfs | h | h | d| (1 block)
|
||||||
|
// ext4 | d | d | d | (3 blocks
|
||||||
|
err = syscall.Fallocate(fd, FALLOC_DEFAULT, 0, 9000)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
_, nBlocks = test_helpers.Du(t, fd)
|
||||||
|
if want := 3; nBlocks/8 != int64(want) {
|
||||||
|
t.Errorf("Expected %d 4k block(s), got %d", want, nBlocks/8)
|
||||||
|
}
|
||||||
|
// Neither apparent size nor content should have changed
|
||||||
|
test_helpers.VerifySize(t, fn, 9000)
|
||||||
|
if md5 := test_helpers.Md5fn(fn); md5 != "5420afa22f6423a9f59e669540656bb4" {
|
||||||
|
t.Errorf("Wrong md5 %s", md5)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Partial block on the end. The first ext4 block is dirtied by the header.
|
||||||
|
// gocryptfs | h | h | d| (1 block)
|
||||||
|
// ext4 | d | h | d | (2 blocks)
|
||||||
|
file.Truncate(0)
|
||||||
|
file.Truncate(9000)
|
||||||
|
_, nBlocks = test_helpers.Du(t, fd)
|
||||||
|
if want := 2; nBlocks/8 != int64(want) {
|
||||||
|
t.Errorf("Expected %d 4k block(s), got %d", want, nBlocks/8)
|
||||||
|
}
|
||||||
|
// Allocate 10 bytes in the second block
|
||||||
|
// gocryptfs | h | h | d| (1 block)
|
||||||
|
// ext4 | d | d | d | (2 blocks)
|
||||||
|
syscall.Fallocate(fd, FALLOC_DEFAULT, 5000, 10)
|
||||||
|
_, nBlocks = test_helpers.Du(t, fd)
|
||||||
|
if want := 3; nBlocks/8 != int64(want) {
|
||||||
|
t.Errorf("Expected %d 4k block(s), got %d", want, nBlocks/8)
|
||||||
|
}
|
||||||
|
// Neither apparent size nor content should have changed
|
||||||
|
test_helpers.VerifySize(t, fn, 9000)
|
||||||
|
if md5 := test_helpers.Md5fn(fn); md5 != "5420afa22f6423a9f59e669540656bb4" {
|
||||||
|
t.Errorf("Wrong md5 %s", md5)
|
||||||
|
}
|
||||||
|
// Grow the file to 4 blocks
|
||||||
|
// gocryptfs | h | h | d |d| (2 blocks)
|
||||||
|
// ext4 | d | d | d | d | (3 blocks)
|
||||||
|
syscall.Fallocate(fd, FALLOC_DEFAULT, 15000, 10)
|
||||||
|
_, nBlocks = test_helpers.Du(t, fd)
|
||||||
|
if want := 4; nBlocks/8 != int64(want) {
|
||||||
|
t.Errorf("Expected %d 4k block(s), got %d", want, nBlocks/8)
|
||||||
|
}
|
||||||
|
test_helpers.VerifySize(t, fn, 15010)
|
||||||
|
if md5 := test_helpers.Md5fn(fn); md5 != "c4c44c7a41ab7798a79d093eb44f99fc" {
|
||||||
|
t.Errorf("Wrong md5 %s", md5)
|
||||||
|
}
|
||||||
|
// Shrinking a file using fallocate should have no effect
|
||||||
|
for _, off := range []int64{0, 10, 2000, 5000} {
|
||||||
|
for _, sz := range []int64{0, 1, 42, 6000} {
|
||||||
|
syscall.Fallocate(fd, FALLOC_DEFAULT, off, sz)
|
||||||
|
test_helpers.VerifySize(t, fn, 15010)
|
||||||
|
if md5 := test_helpers.Md5fn(fn); md5 != "c4c44c7a41ab7798a79d093eb44f99fc" {
|
||||||
|
t.Errorf("Wrong md5 %s", md5)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Cleanup
|
||||||
|
syscall.Unlink(fn)
|
||||||
|
}
|
||||||
|
|
||||||
func TestAppend(t *testing.T) {
|
func TestAppend(t *testing.T) {
|
||||||
fn := test_helpers.DefaultPlainDir + "/append"
|
fn := test_helpers.DefaultPlainDir + "/append"
|
||||||
file, err := os.Create(fn)
|
file, err := os.Create(fn)
|
||||||
|
|
|
@ -283,3 +283,14 @@ func VerifyExistence(path string) bool {
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Du returns the disk usage of the file "fd" points to, in bytes.
|
||||||
|
// Same as "du --block-size=1".
|
||||||
|
func Du(t *testing.T, fd int) (nBytes int64, nBlocks int64) {
|
||||||
|
var st syscall.Stat_t
|
||||||
|
err := syscall.Fstat(fd, &st)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return st.Blocks * st.Blksize, st.Blocks
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue