mirror of https://github.com/ceph/go-ceph
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 <jmulligan@redhat.com>
This commit is contained in:
parent
65227dd4d4
commit
a9738e7d45
|
@ -67,4 +67,5 @@ const (
|
|||
errInvalid = cephFSError(-C.EINVAL)
|
||||
errNameTooLong = cephFSError(-C.ENAMETOOLONG)
|
||||
errNoEntry = cephFSError(-C.ENOENT)
|
||||
errRange = cephFSError(-C.ERANGE)
|
||||
)
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
package cephfs
|
||||
|
||||
/*
|
||||
#cgo LDFLAGS: -lcephfs
|
||||
#cgo CPPFLAGS: -D_FILE_OFFSET_BITS=64
|
||||
#define _GNU_SOURCE
|
||||
#include <stdlib.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/xattr.h>
|
||||
#include <cephfs/libcephfs.h>
|
||||
*/
|
||||
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
|
||||
}
|
|
@ -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)
|
||||
})
|
||||
|
||||
}
|
Loading…
Reference in New Issue