mirror of https://github.com/ceph/go-ceph
cephfs: implement file IO functions for open/close/read/write/seek
Implement core file I/O functions based on a file handle wrapper. Signed-off-by: John Mulligan <jmulligan@redhat.com>
This commit is contained in:
parent
fb2736dd0a
commit
854a834b1c
|
@ -38,4 +38,6 @@ func getError(e C.int) error {
|
|||
|
||||
const (
|
||||
errNameTooLong = CephFSError(-C.ENAMETOOLONG)
|
||||
|
||||
errInvalid = CephFSError(-C.EINVAL)
|
||||
)
|
||||
|
|
|
@ -0,0 +1,133 @@
|
|||
package cephfs
|
||||
|
||||
/*
|
||||
#cgo LDFLAGS: -lcephfs
|
||||
#cgo CPPFLAGS: -D_FILE_OFFSET_BITS=64
|
||||
#include <stdlib.h>
|
||||
#include <cephfs/libcephfs.h>
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
const (
|
||||
// SeekSet is used with Seek to set the absolute position in the file.
|
||||
SeekSet = int(C.SEEK_SET)
|
||||
// SeekCur is used with Seek to position the file relative to the current
|
||||
// position.
|
||||
SeekCur = int(C.SEEK_CUR)
|
||||
// SeekEnd is used with Seek to position the file relative to the end.
|
||||
SeekEnd = int(C.SEEK_END)
|
||||
)
|
||||
|
||||
// File represents an open file descriptor in cephfs.
|
||||
type File struct {
|
||||
mount *MountInfo
|
||||
fd C.int
|
||||
}
|
||||
|
||||
// Open a file at the given path. The flags are the same os flags as
|
||||
// a local open call. Mode is the same mode bits as a local open call.
|
||||
//
|
||||
// Implements:
|
||||
// int ceph_open(struct ceph_mount_info *cmount, const char *path, int flags, mode_t mode);
|
||||
func (mount *MountInfo) Open(path string, flags int, mode uint32) (*File, error) {
|
||||
cPath := C.CString(path)
|
||||
defer C.free(unsafe.Pointer(cPath))
|
||||
ret := C.ceph_open(mount.mount, cPath, C.int(flags), C.mode_t(mode))
|
||||
if ret < 0 {
|
||||
return nil, getError(ret)
|
||||
}
|
||||
return &File{mount: mount, fd: ret}, nil
|
||||
}
|
||||
|
||||
// Close the file.
|
||||
//
|
||||
// Implements:
|
||||
// int ceph_close(struct ceph_mount_info *cmount, int fd);
|
||||
func (f *File) Close() error {
|
||||
return getError(C.ceph_close(f.mount.mount, f.fd))
|
||||
}
|
||||
|
||||
// read directly wraps the ceph_read call. Because read is such a common
|
||||
// operation we deviate from the ceph naming and expose Read and ReadAt
|
||||
// wrappers for external callers of the library.
|
||||
//
|
||||
// Implements:
|
||||
// int ceph_read(struct ceph_mount_info *cmount, int fd, char *buf, int64_t size, int64_t offset);
|
||||
func (f *File) read(buf []byte, offset int64) (int, error) {
|
||||
bufptr := (*C.char)(unsafe.Pointer(&buf[0]))
|
||||
ret := C.ceph_read(
|
||||
f.mount.mount, f.fd, bufptr, C.int64_t(len(buf)), C.int64_t(offset))
|
||||
if ret < 0 {
|
||||
return int(ret), getError(ret)
|
||||
}
|
||||
return int(ret), nil
|
||||
}
|
||||
|
||||
// Read data from file. Up to len(buf) bytes will be read from the file.
|
||||
// The number of bytes read will be returned.
|
||||
func (f *File) Read(buf []byte) (int, error) {
|
||||
// to-consider: should we mimic Go's behavior of returning an
|
||||
// io.ErrShortWrite error if write length < buf size?
|
||||
return f.read(buf, -1)
|
||||
}
|
||||
|
||||
// ReadAt will read data from the file starting at the given offset.
|
||||
// Up to len(buf) bytes will be read from the file.
|
||||
// The number of bytes read will be returned.
|
||||
func (f *File) ReadAt(buf []byte, offset int64) (int, error) {
|
||||
return f.read(buf, offset)
|
||||
}
|
||||
|
||||
// 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
|
||||
// wrappers for external callers of the library.
|
||||
//
|
||||
// Implements:
|
||||
// int ceph_write(struct ceph_mount_info *cmount, int fd, const char *buf,
|
||||
// int64_t size, int64_t offset);
|
||||
func (f *File) write(buf []byte, offset int64) (int, error) {
|
||||
bufptr := (*C.char)(unsafe.Pointer(&buf[0]))
|
||||
ret := C.ceph_write(
|
||||
f.mount.mount, f.fd, bufptr, C.int64_t(len(buf)), C.int64_t(offset))
|
||||
if ret < 0 {
|
||||
return 0, getError(ret)
|
||||
}
|
||||
return int(ret), nil
|
||||
}
|
||||
|
||||
// Write data from buf to the file.
|
||||
// The number of bytes written is returned.
|
||||
func (f *File) Write(buf []byte) (int, error) {
|
||||
return f.write(buf, -1)
|
||||
}
|
||||
|
||||
// WriteAt writes data from buf to the file at the specified offset.
|
||||
// The number of bytes written is returned.
|
||||
func (f *File) WriteAt(buf []byte, offset int64) (int, error) {
|
||||
return f.write(buf, offset)
|
||||
}
|
||||
|
||||
// Seek will reposition the file stream based on the given offset.
|
||||
//
|
||||
// Implements:
|
||||
// int64_t ceph_lseek(struct ceph_mount_info *cmount, int fd, int64_t offset, int whence);
|
||||
func (f *File) Seek(offset int64, whence int) (int64, error) {
|
||||
// validate the seek whence value in case the caller skews
|
||||
// from the seek values we technically support from C as documented.
|
||||
// TODO: need to support seek-(hole|data) in mimic and later.
|
||||
switch whence {
|
||||
case SeekSet, SeekCur, SeekEnd:
|
||||
default:
|
||||
return 0, errInvalid
|
||||
}
|
||||
|
||||
ret := C.ceph_lseek(f.mount.mount, f.fd, C.int64_t(offset), C.int(whence))
|
||||
if ret < 0 {
|
||||
return 0, getError(C.int(ret))
|
||||
}
|
||||
return int64(ret), nil
|
||||
}
|
|
@ -0,0 +1,216 @@
|
|||
package cephfs
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestFileOpen(t *testing.T) {
|
||||
mount := fsConnect(t)
|
||||
defer fsDisconnect(t, mount)
|
||||
fname := "TestFileOpen.txt"
|
||||
|
||||
// idempotent open for read and write
|
||||
t.Run("create", func(t *testing.T) {
|
||||
f1, err := mount.Open(fname, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, f1)
|
||||
err = f1.Close()
|
||||
assert.NoError(t, err)
|
||||
// TODO: clean up file
|
||||
})
|
||||
|
||||
t.Run("errorMissing", func(t *testing.T) {
|
||||
// try to open a file we know should not exist
|
||||
f2, err := mount.Open(".nope", os.O_RDONLY, 0666)
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, f2)
|
||||
})
|
||||
|
||||
t.Run("existsInMount", func(t *testing.T) {
|
||||
useMount(t)
|
||||
|
||||
f1, err := mount.Open(fname, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, f1)
|
||||
err = f1.Close()
|
||||
assert.NoError(t, err)
|
||||
|
||||
s, err := os.Stat(path.Join(CephMountDir, fname))
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 0, s.Size())
|
||||
})
|
||||
}
|
||||
|
||||
func TestFileReadWrite(t *testing.T) {
|
||||
mount := fsConnect(t)
|
||||
defer fsDisconnect(t, mount)
|
||||
fname := "TestFileReadWrite.txt"
|
||||
|
||||
t.Run("writeAndRead", func(t *testing.T) {
|
||||
// idempotent open for read and write
|
||||
f1, err := mount.Open(fname, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
n, err := f1.Write([]byte("yello world!"))
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 12, n)
|
||||
err = f1.Close()
|
||||
assert.NoError(t, err)
|
||||
|
||||
buf := make([]byte, 1024)
|
||||
f2, err := mount.Open(fname, os.O_RDONLY, 0)
|
||||
n, err = f2.Read(buf)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 12, n)
|
||||
assert.Equal(t, "yello world!", string(buf[:n]))
|
||||
})
|
||||
|
||||
t.Run("openForWriteOnly", func(t *testing.T) {
|
||||
buf := make([]byte, 32)
|
||||
f1, err := mount.Open(fname, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
assert.NoError(t, err)
|
||||
defer func() { assert.NoError(t, f1.Close()) }()
|
||||
_, err = f1.Read(buf)
|
||||
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, 0666)
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, f1.Close())
|
||||
|
||||
f1, err = mount.Open(fname, os.O_RDONLY, 0666)
|
||||
assert.NoError(t, err)
|
||||
defer func() { assert.NoError(t, f1.Close()) }()
|
||||
_, err = f1.Write([]byte("yo"))
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestFileReadWriteAt(t *testing.T) {
|
||||
mount := fsConnect(t)
|
||||
defer fsDisconnect(t, mount)
|
||||
fname := "TestFileReadWriteAt.txt"
|
||||
|
||||
t.Run("writeAtAndReadAt", func(t *testing.T) {
|
||||
// idempotent open for read and write
|
||||
f1, err := mount.Open(fname, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
n, err := f1.WriteAt([]byte("foo"), 0)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 3, n)
|
||||
n, err = f1.WriteAt([]byte("bar"), 6)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 3, n)
|
||||
err = f1.Close()
|
||||
assert.NoError(t, err)
|
||||
|
||||
buf := make([]byte, 4)
|
||||
f2, err := mount.Open(fname, os.O_RDONLY, 0)
|
||||
n, err = f2.ReadAt(buf, 0)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 4, n)
|
||||
assert.Equal(t, "foo", string(buf[:3]))
|
||||
assert.EqualValues(t, 0, string(buf[3]))
|
||||
n, err = f2.ReadAt(buf, 6)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 3, n)
|
||||
assert.Equal(t, "bar", string(buf[:3]))
|
||||
})
|
||||
|
||||
t.Run("openForWriteOnly", func(t *testing.T) {
|
||||
buf := make([]byte, 32)
|
||||
f1, err := mount.Open(fname, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
assert.NoError(t, err)
|
||||
defer func() { assert.NoError(t, f1.Close()) }()
|
||||
_, err = f1.ReadAt(buf, 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, 0666)
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, f1.Close())
|
||||
|
||||
f1, err = mount.Open(fname, os.O_RDONLY, 0666)
|
||||
assert.NoError(t, err)
|
||||
defer func() { assert.NoError(t, f1.Close()) }()
|
||||
_, err = f1.WriteAt([]byte("yo"), 0)
|
||||
assert.Error(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestFileInterfaces(t *testing.T) {
|
||||
mount := fsConnect(t)
|
||||
defer fsDisconnect(t, mount)
|
||||
fname := "TestFileInterfaces.txt"
|
||||
|
||||
t.Run("ioWriter", func(t *testing.T) {
|
||||
f1, err := mount.Open(fname, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
assert.NoError(t, err)
|
||||
defer func() { assert.NoError(t, f1.Close()) }()
|
||||
|
||||
var w io.Writer = f1
|
||||
_, err = w.Write([]byte("foo"))
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("ioReader", func(t *testing.T) {
|
||||
f1, err := mount.Open(fname, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
assert.NoError(t, err)
|
||||
assert.NoError(t, f1.Close())
|
||||
|
||||
f1, err = mount.Open(fname, os.O_RDONLY, 0666)
|
||||
assert.NoError(t, err)
|
||||
defer func() { assert.NoError(t, f1.Close()) }()
|
||||
|
||||
var r io.Reader = f1
|
||||
buf := make([]byte, 32)
|
||||
_, err = r.Read(buf)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestFileSeek(t *testing.T) {
|
||||
mount := fsConnect(t)
|
||||
defer fsDisconnect(t, mount)
|
||||
fname := "TestFileSeek.txt"
|
||||
|
||||
t.Run("validSeek", func(t *testing.T) {
|
||||
f1, err := mount.Open(fname, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
assert.NoError(t, err)
|
||||
defer func() { assert.NoError(t, f1.Close()) }()
|
||||
|
||||
o, err := f1.Seek(8, SeekSet)
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 8, o)
|
||||
|
||||
n, err := f1.Write([]byte("flimflam"))
|
||||
assert.NoError(t, err)
|
||||
assert.EqualValues(t, 8, n)
|
||||
})
|
||||
|
||||
t.Run("invalidWhence", func(t *testing.T) {
|
||||
f1, err := mount.Open(fname, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
assert.NoError(t, err)
|
||||
defer func() { assert.NoError(t, f1.Close()) }()
|
||||
|
||||
o, err := f1.Seek(8, 1776)
|
||||
assert.Error(t, err)
|
||||
assert.EqualValues(t, 0, o)
|
||||
})
|
||||
|
||||
t.Run("invalidSeek", func(t *testing.T) {
|
||||
f1, err := mount.Open(fname, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0666)
|
||||
assert.NoError(t, err)
|
||||
defer func() { assert.NoError(t, f1.Close()) }()
|
||||
|
||||
o, err := f1.Seek(-22, SeekSet)
|
||||
assert.Error(t, err)
|
||||
assert.EqualValues(t, 0, o)
|
||||
})
|
||||
}
|
Loading…
Reference in New Issue