cephfs: add ceph_fallocate() function

Added ceph_fallocate() which  preallocates or releases disk space
for the file for the given byte range.

Fixes: https://github.com/ceph/go-ceph/issues/246

Signed-off-by: Mudit Agarwal muagarwa@redhat.com
This commit is contained in:
Mudit Agarwal 2020-05-18 08:53:10 +05:30 committed by John Mulligan
parent 80833a6789
commit 1d30b636ff
2 changed files with 172 additions and 0 deletions

View File

@ -3,7 +3,9 @@ package cephfs
/*
#cgo LDFLAGS: -lcephfs
#cgo CPPFLAGS: -D_FILE_OFFSET_BITS=64
#define _GNU_SOURCE
#include <stdlib.h>
#include <fcntl.h>
#include <cephfs/libcephfs.h>
*/
import "C"
@ -223,3 +225,33 @@ func (f *File) Fstatx(want StatxMask, flags AtFlags) (*CephStatx, error) {
}
return cStructToCephStatx(stx), nil
}
// FallocFlags represent flags which determine the operation to be
// performed on the given range.
// CephFS supports only following two flags.
type FallocFlags int
const (
// FallocNoFlag means default option.
FallocNoFlag = FallocFlags(0)
// FallocFlKeepSize specifies that the file size will not be changed.
FallocFlKeepSize = FallocFlags(C.FALLOC_FL_KEEP_SIZE)
// FallocFlPunchHole specifies that the operation is to deallocate
// space and zero the byte range.
FallocFlPunchHole = FallocFlags(C.FALLOC_FL_PUNCH_HOLE)
)
// Fallocate preallocates or releases disk space for the file for the
// given byte range, the flags determine the operation to be performed
// on the given range.
//
// Implements:
// int ceph_fallocate(struct ceph_mount_info *cmount, int fd, int mode,
// int64_t offset, int64_t length);
func (f *File) Fallocate(mode FallocFlags, offset, length int64) error {
if err := f.validate(); err != nil {
return err
}
ret := C.ceph_fallocate(f.mount.mount, f.fd, C.int(mode), C.int64_t(offset), C.int64_t(length))
return getError(ret)
}

View File

@ -475,3 +475,143 @@ func TestFstatx(t *testing.T) {
assert.Error(t, err)
})
}
func TestFallocate(t *testing.T) {
mount := fsConnect(t)
defer fsDisconnect(t, mount)
fname := "file1.txt"
f, err := mount.Open(fname, os.O_RDWR|os.O_CREATE, 0666)
assert.NoError(t, err)
assert.NotNil(t, f)
defer func() {
assert.NoError(t, f.Close())
assert.NoError(t, mount.Unlink(fname))
}()
// assert that negative values will return error.
t.Run("NegativeOffsetLength", func(t *testing.T) {
err = f.Fallocate(FallocNoFlag, -1, 10)
assert.Error(t, err)
err = f.Fallocate(FallocNoFlag, 10, -1)
assert.Error(t, err)
})
// Allocate space - default case, mode == 0.
t.Run("modeIsZero", func(t *testing.T) {
useMount(t)
// check file size.
s, err := os.Stat(path.Join(CephMountDir, fname))
assert.NoError(t, err)
assert.EqualValues(t, 0, s.Size())
// write 10 bytes at offset 0.
err = f.Fallocate(FallocNoFlag, 0, 10)
assert.NoError(t, err)
// check file size again.
s, err = os.Stat(path.Join(CephMountDir, fname))
assert.NoError(t, err)
assert.EqualValues(t, 10, s.Size())
})
// Allocate space - size increases, data remains intact.
t.Run("increaseSize", func(t *testing.T) {
useMount(t)
fname := "file2.txt"
f1, err := mount.Open(fname, os.O_RDWR|os.O_CREATE, 0666)
assert.NoError(t, err)
assert.NotNil(t, f1)
defer func() {
assert.NoError(t, f1.Close())
assert.NoError(t, mount.Unlink(fname))
}()
// write to file.
n, err := f1.Write([]byte("Ten chars!"))
assert.NoError(t, err)
assert.EqualValues(t, 10, n)
// check the file size.
s, err := os.Stat(path.Join(CephMountDir, fname))
assert.NoError(t, err)
assert.EqualValues(t, 10, s.Size())
// allocate 10 more bytes from the middle.
err = f1.Fallocate(FallocNoFlag, 5, 10)
assert.NoError(t, err)
// check the size, it should increase.
s, err = os.Stat(path.Join(CephMountDir, fname))
assert.NoError(t, err)
assert.EqualValues(t, 15, s.Size())
// Read the contents, first ten chars remain intact.
buf := make([]byte, 10)
n, err = f1.ReadAt(buf, 0)
assert.NoError(t, err)
assert.Equal(t, "Ten chars!", string(buf[:n]))
})
// Allocate space - with FALLOC_FL_KEEP_SIZE.
t.Run("allocateSpaceWithFlag", func(t *testing.T) {
useMount(t)
fname := "file3.txt"
f1, err := mount.Open(fname, os.O_RDWR|os.O_CREATE, 0666)
assert.NoError(t, err)
assert.NotNil(t, f1)
defer func() {
assert.NoError(t, f1.Close())
assert.NoError(t, mount.Unlink(fname))
}()
// Write to file.
n, err := f1.Write([]byte("tenchars!!"))
assert.NoError(t, err)
assert.EqualValues(t, 10, n)
// Allocate 10 more bytes from the middle.
err = f1.Fallocate(FallocFlKeepSize, 5, 10)
assert.NoError(t, err)
// Check the file size, it should not increase.
s, err := os.Stat(path.Join(CephMountDir, fname))
assert.NoError(t, err)
assert.EqualValues(t, 10, s.Size())
})
// Deallocate space - with only FALLOC_FL_PUNCH_HOLE.
t.Run("punchHoleFlagAlone", func(t *testing.T) {
err := f.Fallocate(FallocFlPunchHole, 0, 10)
// Not supported.
assert.Error(t, err)
})
// De-allocate space - punch holes.
t.Run("punchActualHoles", func(t *testing.T) {
fname := "file4.txt"
f1, err := mount.Open(fname, os.O_RDWR|os.O_CREATE, 0666)
assert.NoError(t, err)
assert.NotNil(t, f1)
defer func() {
assert.NoError(t, f1.Close())
assert.NoError(t, mount.Unlink(fname))
}()
// Write some data.
n, err := f1.Write([]byte("Ten chars!"))
assert.NoError(t, err)
assert.EqualValues(t, 10, n)
// Read it back.
buf := make([]byte, 10)
n, err = f1.ReadAt(buf, 0)
assert.NoError(t, err)
assert.Equal(t, "Ten chars!", string(buf[:n]))
// Punch holes.
err = f1.Fallocate(FallocFlPunchHole|FallocFlKeepSize, 0, 5)
assert.NoError(t, err)
// Read again - first five chars.
buf = make([]byte, 5)
n, err = f1.ReadAt(buf, 0)
assert.NoError(t, err)
assert.Equal(t, "\x00\x00\x00\x00\x00", string(buf[:n]))
// Read again - last five chars.
n, err = f1.ReadAt(buf, 5)
assert.Equal(t, "hars!", string(buf[:n]))
})
t.Run("checkValidate", func(t *testing.T) {
f1 := &File{}
err := f1.Fallocate(FallocNoFlag, 0, 10)
assert.Error(t, err)
})
}