From a9738e7d452609290d7aa0181894bc278b76d7d7 Mon Sep 17 00:00:00 2001 From: John Mulligan Date: Thu, 18 Jun 2020 13:36:38 -0400 Subject: [PATCH] cephfs: add file SetXattr and GetXattr functions Add SetXattr implementing ceph_fsetxattr. Add GetXattr implementing ceph_fgetxattr. Add test function to exercise both. Signed-off-by: John Mulligan --- cephfs/errors.go | 1 + cephfs/file_xattr.go | 99 +++++++++++++++++++++++++++++++++++++++ cephfs/file_xattr_test.go | 78 ++++++++++++++++++++++++++++++ 3 files changed, 178 insertions(+) create mode 100644 cephfs/file_xattr.go create mode 100644 cephfs/file_xattr_test.go diff --git a/cephfs/errors.go b/cephfs/errors.go index 9edd55a..1462fc4 100644 --- a/cephfs/errors.go +++ b/cephfs/errors.go @@ -67,4 +67,5 @@ const ( errInvalid = cephFSError(-C.EINVAL) errNameTooLong = cephFSError(-C.ENAMETOOLONG) errNoEntry = cephFSError(-C.ENOENT) + errRange = cephFSError(-C.ERANGE) ) diff --git a/cephfs/file_xattr.go b/cephfs/file_xattr.go new file mode 100644 index 0000000..0ef5e08 --- /dev/null +++ b/cephfs/file_xattr.go @@ -0,0 +1,99 @@ +package cephfs + +/* +#cgo LDFLAGS: -lcephfs +#cgo CPPFLAGS: -D_FILE_OFFSET_BITS=64 +#define _GNU_SOURCE +#include +#include +#include +#include +*/ +import "C" + +import ( + "unsafe" + + "github.com/ceph/go-ceph/internal/retry" +) + +// XattrFlags are used to control the behavior of set-xattr calls. +type XattrFlags int + +const ( + // XattrDefault specifies that set-xattr calls use the default behavior of + // creating or updating an xattr. + XattrDefault = XattrFlags(0) + // XattrCreate specifies that set-xattr calls only set new xattrs. + XattrCreate = XattrFlags(C.XATTR_CREATE) + // XattrReplace specifies that set-xattr calls only replace existing xattr + // values. + XattrReplace = XattrFlags(C.XATTR_REPLACE) +) + +// SetXattr sets an extended attribute on the open file. +// +// Implements: +// int ceph_fsetxattr(struct ceph_mount_info *cmount, int fd, const char *name, +// const void *value, size_t size, int flags); +func (f *File) SetXattr(name string, value []byte, flags XattrFlags) error { + if err := f.validate(); err != nil { + return err + } + if name == "" { + return errInvalid + } + var vptr unsafe.Pointer + if len(value) > 0 { + vptr = unsafe.Pointer(&value[0]) + } + cName := C.CString(name) + defer C.free(unsafe.Pointer(cName)) + + ret := C.ceph_fsetxattr( + f.mount.mount, + f.fd, + cName, + vptr, + C.size_t(len(value)), + C.int(flags)) + return getError(ret) +} + +// GetXattr gets an extended attribute from the open file. +// +// Implements: +// int ceph_fgetxattr(struct ceph_mount_info *cmount, int fd, const char *name, +// void *value, size_t size); +func (f *File) GetXattr(name string) ([]byte, error) { + if err := f.validate(); err != nil { + return nil, err + } + if name == "" { + return nil, errInvalid + } + cName := C.CString(name) + defer C.free(unsafe.Pointer(cName)) + + var ( + ret C.int + err error + buf []byte + ) + // range from 1k to 64KiB + retry.WithSizes(1024, 1<<16, func(size int) retry.Hint { + buf = make([]byte, size) + ret = C.ceph_fgetxattr( + f.mount.mount, + f.fd, + cName, + unsafe.Pointer(&buf[0]), + C.size_t(size)) + err = getErrorIfNegative(ret) + return retry.DoubleSize.If(err == errRange) + }) + if err != nil { + return nil, err + } + return buf[:ret], nil +} diff --git a/cephfs/file_xattr_test.go b/cephfs/file_xattr_test.go new file mode 100644 index 0000000..b7f5c8e --- /dev/null +++ b/cephfs/file_xattr_test.go @@ -0,0 +1,78 @@ +package cephfs + +import ( + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +var samples = []struct { + name string + value []byte +}{ + { + name: "user.xPhrase", + value: []byte("june and july"), + }, + { + name: "user.xHasNulls", + value: []byte("\x00got\x00null?\x00"), + }, + { + name: "user.x2kZeros", + value: make([]byte, 2048), + }, + { + name: "user.xEmpty", + value: []byte(""), + }, +} + +func TestGetSetXattr(t *testing.T) { + mount := fsConnect(t) + defer fsDisconnect(t, mount) + fname := "TestGetSetXattr.txt" + + f, err := mount.Open(fname, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666) + require.NoError(t, err) + defer func() { + assert.NoError(t, f.Close()) + assert.NoError(t, mount.Unlink(fname)) + }() + + for _, s := range samples[:3] { + t.Run("roundTrip-"+s.name, func(t *testing.T) { + err := f.SetXattr(s.name, s.value, XattrDefault) + assert.NoError(t, err) + b, err := f.GetXattr(s.name) + assert.NoError(t, err) + assert.EqualValues(t, s.value, b) + }) + } + + t.Run("missingXattrOnGet", func(t *testing.T) { + _, err := f.GetXattr(samples[3].name) + assert.Error(t, err) + }) + + t.Run("emptyNameGet", func(t *testing.T) { + _, err := f.GetXattr("") + assert.Error(t, err) + }) + + t.Run("emptyNameSet", func(t *testing.T) { + err := f.SetXattr("", []byte("foo"), XattrDefault) + assert.Error(t, err) + }) + + t.Run("invalidFile", func(t *testing.T) { + f1 := &File{} + err := f1.SetXattr(samples[0].name, samples[0].value, XattrDefault) + assert.Error(t, err) + _, err = f1.GetXattr(samples[0].name) + assert.Error(t, err) + }) + +}