cephfs: implement Preadv and Pwritev File methods

Add Preadv implementing ceph_preadv.
Add Pwritev implementing ceph_pwritev.
These calls act similarly to ReadAt and WriteAt but take multiple
buffers at once.

Signed-off-by: John Mulligan <jmulligan@redhat.com>
This commit is contained in:
John Mulligan 2020-08-19 16:40:44 -04:00 committed by John Mulligan
parent ebea82dda5
commit 9c1ed2dfc2
2 changed files with 177 additions and 0 deletions

View File

@ -13,6 +13,8 @@ import "C"
import ( import (
"io" "io"
"unsafe" "unsafe"
"github.com/ceph/go-ceph/internal/cutil"
) )
const ( const (
@ -128,6 +130,37 @@ func (f *File) ReadAt(buf []byte, offset int64) (int, error) {
return f.read(buf, offset) return f.read(buf, offset)
} }
// Preadv will read data from the file, starting at the given offset,
// into the byte-slice data buffers sequentially.
// The number of bytes read will be returned.
// When nothing is left to read from the file the return values will be:
// 0, io.EOF.
//
// Implements:
// int ceph_preadv(struct ceph_mount_info *cmount, int fd, const struct iovec *iov, int iovcnt,
// int64_t offset);
func (f *File) Preadv(data [][]byte, offset int64) (int, error) {
if err := f.validate(); err != nil {
return 0, err
}
iov := cutil.ByteSlicesToIovec(data)
defer iov.Free()
ret := C.ceph_preadv(
f.mount.mount,
f.fd,
(*C.struct_iovec)(iov.Pointer()),
C.int(iov.Len()),
C.int64_t(offset))
switch {
case ret < 0:
return 0, getError(ret)
case ret == 0:
return 0, io.EOF
}
return int(ret), nil
}
// write directly wraps the ceph_write call. Because write is such a common // write directly wraps the ceph_write call. Because write is such a common
// operation we deviate from the ceph naming and expose Write and WriteAt // operation we deviate from the ceph naming and expose Write and WriteAt
// wrappers for external callers of the library. // wrappers for external callers of the library.
@ -163,6 +196,32 @@ func (f *File) WriteAt(buf []byte, offset int64) (int, error) {
return f.write(buf, offset) return f.write(buf, offset)
} }
// Pwritev writes data from the slice of byte-slice buffers to the file at the
// specified offset.
// The number of bytes written is returned.
//
// Implements:
// int ceph_pwritev(struct ceph_mount_info *cmount, int fd, const struct iovec *iov, int iovcnt,
// int64_t offset);
func (f *File) Pwritev(data [][]byte, offset int64) (int, error) {
if err := f.validate(); err != nil {
return 0, err
}
iov := cutil.ByteSlicesToIovec(data)
defer iov.Free()
ret := C.ceph_pwritev(
f.mount.mount,
f.fd,
(*C.struct_iovec)(iov.Pointer()),
C.int(iov.Len()),
C.int64_t(offset))
if ret < 0 {
return 0, getError(ret)
}
return int(ret), nil
}
// Seek will reposition the file stream based on the given offset. // Seek will reposition the file stream based on the given offset.
// //
// Implements: // Implements:

View File

@ -769,3 +769,121 @@ func TestSync(t *testing.T) {
assert.Error(t, err) assert.Error(t, err)
}) })
} }
func TestFilePreadvPwritev(t *testing.T) {
mount := fsConnect(t)
defer fsDisconnect(t, mount)
fname := "TestFilePreadvPwritev.txt"
defer mount.Unlink(fname)
t.Run("simple", func(t *testing.T) {
f, err := mount.Open(fname, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
assert.NoError(t, err)
defer func() { assert.NoError(t, f.Close()) }()
b1 := []byte("foobarbaz")
b2 := []byte("alphabeta")
b3 := []byte("superawseomefuntime")
n, err := f.Pwritev([][]byte{b1, b2, b3}, 0)
assert.NoError(t, err)
assert.Equal(t, 37, n)
o := [][]byte{
make([]byte, 3),
make([]byte, 3),
make([]byte, 3),
make([]byte, 3),
}
n, err = f.Preadv(o, 0)
assert.NoError(t, err)
assert.Equal(t, 12, n)
assert.Equal(t, "foo", string(o[0]))
assert.Equal(t, "bar", string(o[1]))
assert.Equal(t, "baz", string(o[2]))
assert.Equal(t, "alp", string(o[3]))
})
t.Run("silly", func(t *testing.T) {
f, err := mount.Open(fname, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
assert.NoError(t, err)
defer func() { assert.NoError(t, f.Close()) }()
b := []byte("foo")
x := make([][]byte, 8)
for i := range x {
x[i] = b
}
n, err := f.Pwritev(x, 0)
assert.NoError(t, err)
assert.Equal(t, 24, n)
for i := range x {
x[i] = make([]byte, 6)
}
n, err = f.Preadv(x, 1)
assert.NoError(t, err)
assert.Equal(t, 23, n)
assert.Equal(t, "oofoof", string(x[0]))
assert.Equal(t, "oofoof", string(x[1]))
assert.Equal(t, "oofoof", string(x[2]))
assert.Equal(t, "oofoo\x00", string(x[3]))
})
t.Run("readEOF", func(t *testing.T) {
f, err := mount.Open(fname, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
assert.NoError(t, err)
defer func() { assert.NoError(t, f.Close()) }()
x := make([][]byte, 8)
for i := range x {
x[i] = make([]byte, 6)
}
_, err = f.Preadv(x, 16)
assert.Error(t, err)
assert.Equal(t, io.EOF, err)
})
t.Run("openForWriteOnly", func(t *testing.T) {
f1, err := mount.Open(fname, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
assert.NoError(t, err)
defer func() { assert.NoError(t, f1.Close()) }()
x := make([][]byte, 8)
for i := range x {
x[i] = make([]byte, 6)
}
_, err = f1.Preadv(x, 0)
assert.Error(t, err)
})
t.Run("openForReadOnly", func(t *testing.T) {
// "touch" the file
f1, err := mount.Open(fname, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
assert.NoError(t, err)
assert.NoError(t, f1.Close())
f1, err = mount.Open(fname, os.O_RDONLY, 0644)
assert.NoError(t, err)
defer func() { assert.NoError(t, f1.Close()) }()
x := make([][]byte, 8)
for i := range x {
x[i] = []byte("robble")
}
_, err = f1.Pwritev(x, 0)
assert.Error(t, err)
})
t.Run("writeInvalidFile", func(t *testing.T) {
f := &File{}
_, err := f.Pwritev([][]byte{}, 0)
assert.Error(t, err)
})
t.Run("readInvalidFile", func(t *testing.T) {
f := &File{}
_, err := f.Preadv([][]byte{}, 0)
assert.Error(t, err)
})
}