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:
John Mulligan 2020-06-18 13:36:38 -04:00 committed by John Mulligan
parent 65227dd4d4
commit a9738e7d45
3 changed files with 178 additions and 0 deletions

View File

@ -67,4 +67,5 @@ const (
errInvalid = cephFSError(-C.EINVAL)
errNameTooLong = cephFSError(-C.ENAMETOOLONG)
errNoEntry = cephFSError(-C.ENOENT)
errRange = cephFSError(-C.ERANGE)
)

99
cephfs/file_xattr.go Normal file
View File

@ -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
}

78
cephfs/file_xattr_test.go Normal file
View File

@ -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)
})
}