Repository: ceph/go-ceph
Pull request #288 (2020-05-18 03:33:55)

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


- [x] Added tests for features and functional changes
- [x] Public functions and types are documented
- [x] Standard formatting is applied to Go code


phlogistonjohn: Looks like merging some of the older PRs has produced some conflicts. Please resolve, thanks!
agarwal-mudit: > Looks like merging some of the older PRs has produced some conflicts. Please resolve, thanks!

Done, merge please.

Patch:
diff --git a/cephfs/file.go b/cephfs/file.go
index 17dd2de..60f4bfa 100644
--- a/cephfs/file.go
+++ b/cephfs/file.go
@@ -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)
+}


Test patch:
diff --git a/cephfs/file_test.go b/cephfs/file_test.go
index 9bc54ca..836d7df 100644
--- a/cephfs/file_test.go
+++ b/cephfs/file_test.go
@@ -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)
+	})
+}