cephfs: add path-based xattr functions

This change adds wrappers for:

* ceph_getxattr
* ceph_listxattr
* ceph_removexattr
* ceph_setxattr

As well as:
* ceph_lgetxattr
* ceph_llistxattr
* ceph_lremovexattr
* ceph_lsetxattr

Signed-off-by: John Mulligan <jmulligan@redhat.com>
This commit is contained in:
John Mulligan 2020-07-31 10:08:09 -04:00 committed by John Mulligan
parent 80ce962b68
commit f020a3ffac
2 changed files with 581 additions and 0 deletions

283
cephfs/path_xattr.go Normal file
View File

@ -0,0 +1,283 @@
package cephfs
/*
#cgo LDFLAGS: -lcephfs
#cgo CPPFLAGS: -D_FILE_OFFSET_BITS=64
#define _GNU_SOURCE
#include <stdlib.h>
#include <cephfs/libcephfs.h>
*/
import "C"
import (
"unsafe"
"github.com/ceph/go-ceph/internal/cutil"
"github.com/ceph/go-ceph/internal/retry"
)
// SetXattr sets an extended attribute on the file at the supplied path.
//
// NOTE: Attempting to set an xattr value with an empty value may cause
// the xattr to be unset. Please refer to https://tracker.ceph.com/issues/46084
//
// Implements:
// int ceph_setxattr(struct ceph_mount_info *cmount, const char *path, const char *name,
// const void *value, size_t size, int flags);
func (mount *MountInfo) SetXattr(path, name string, value []byte, flags XattrFlags) error {
if err := mount.validate(); err != nil {
return err
}
if name == "" {
return errInvalid
}
var vptr unsafe.Pointer
if len(value) > 0 {
vptr = unsafe.Pointer(&value[0])
}
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))
cName := C.CString(name)
defer C.free(unsafe.Pointer(cName))
ret := C.ceph_setxattr(
mount.mount,
cPath,
cName,
vptr,
C.size_t(len(value)),
C.int(flags))
return getError(ret)
}
// GetXattr gets an extended attribute from the file at the supplied path.
//
// Implements:
// int ceph_getxattr(struct ceph_mount_info *cmount, const char *path, const char *name,
// void *value, size_t size);
func (mount *MountInfo) GetXattr(path, name string) ([]byte, error) {
if err := mount.validate(); err != nil {
return nil, err
}
if name == "" {
return nil, errInvalid
}
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))
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_getxattr(
mount.mount,
cPath,
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
}
// ListXattr returns a slice containing strings for the name of each xattr set
// on the file at the supplied path.
//
// Implements:
// int ceph_listxattr(struct ceph_mount_info *cmount, const char *path, char *list, size_t size);
func (mount *MountInfo) ListXattr(path string) ([]string, error) {
if err := mount.validate(); err != nil {
return nil, err
}
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))
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_listxattr(
mount.mount,
cPath,
(*C.char)(unsafe.Pointer(&buf[0])),
C.size_t(size))
err = getErrorIfNegative(ret)
return retry.DoubleSize.If(err == errRange)
})
if err != nil {
return nil, err
}
names := cutil.SplitSparseBuffer(buf[:ret])
return names, nil
}
// RemoveXattr removes the named xattr from the open file.
//
// Implements:
// int ceph_removexattr(struct ceph_mount_info *cmount, const char *path, const char *name);
func (mount *MountInfo) RemoveXattr(path, name string) error {
if err := mount.validate(); err != nil {
return err
}
if name == "" {
return errInvalid
}
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))
cName := C.CString(name)
defer C.free(unsafe.Pointer(cName))
ret := C.ceph_removexattr(
mount.mount,
cPath,
cName)
return getError(ret)
}
// LsetXattr sets an extended attribute on the file at the supplied path.
//
// NOTE: Attempting to set an xattr value with an empty value may cause
// the xattr to be unset. Please refer to https://tracker.ceph.com/issues/46084
//
// Implements:
// int ceph_lsetxattr(struct ceph_mount_info *cmount, const char *path, const char *name,
// const void *value, size_t size, int flags);
func (mount *MountInfo) LsetXattr(path, name string, value []byte, flags XattrFlags) error {
if err := mount.validate(); err != nil {
return err
}
if name == "" {
return errInvalid
}
var vptr unsafe.Pointer
if len(value) > 0 {
vptr = unsafe.Pointer(&value[0])
}
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))
cName := C.CString(name)
defer C.free(unsafe.Pointer(cName))
ret := C.ceph_lsetxattr(
mount.mount,
cPath,
cName,
vptr,
C.size_t(len(value)),
C.int(flags))
return getError(ret)
}
// LgetXattr gets an extended attribute from the file at the supplied path.
//
// Implements:
// int ceph_lgetxattr(struct ceph_mount_info *cmount, const char *path, const char *name,
// void *value, size_t size);
func (mount *MountInfo) LgetXattr(path, name string) ([]byte, error) {
if err := mount.validate(); err != nil {
return nil, err
}
if name == "" {
return nil, errInvalid
}
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))
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_lgetxattr(
mount.mount,
cPath,
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
}
// LlistXattr returns a slice containing strings for the name of each xattr set
// on the file at the supplied path.
//
// Implements:
// int ceph_llistxattr(struct ceph_mount_info *cmount, const char *path, char *list, size_t size);
func (mount *MountInfo) LlistXattr(path string) ([]string, error) {
if err := mount.validate(); err != nil {
return nil, err
}
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))
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_llistxattr(
mount.mount,
cPath,
(*C.char)(unsafe.Pointer(&buf[0])),
C.size_t(size))
err = getErrorIfNegative(ret)
return retry.DoubleSize.If(err == errRange)
})
if err != nil {
return nil, err
}
names := cutil.SplitSparseBuffer(buf[:ret])
return names, nil
}
// LremoveXattr removes the named xattr from the open file.
//
// Implements:
// int ceph_lremovexattr(struct ceph_mount_info *cmount, const char *path, const char *name);
func (mount *MountInfo) LremoveXattr(path, name string) error {
if err := mount.validate(); err != nil {
return err
}
if name == "" {
return errInvalid
}
cPath := C.CString(path)
defer C.free(unsafe.Pointer(cPath))
cName := C.CString(name)
defer C.free(unsafe.Pointer(cName))
ret := C.ceph_lremovexattr(
mount.mount,
cPath,
cName)
return getError(ret)
}

298
cephfs/path_xattr_test.go Normal file
View File

@ -0,0 +1,298 @@
package cephfs
import (
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestGetSetXattrPath(t *testing.T) {
mount := fsConnect(t)
defer fsDisconnect(t, mount)
fname := "TestGetSetXattrPath.txt"
f1, err := mount.Open(fname, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
require.NoError(t, err)
assert.NoError(t, f1.Close())
defer func() {
assert.NoError(t, mount.Unlink(fname))
}()
for _, s := range xattrSamples {
t.Run("roundTrip-"+s.name, func(t *testing.T) {
err := mount.SetXattr(fname, s.name, s.value, XattrDefault)
assert.NoError(t, err)
b, err := mount.GetXattr(fname, s.name)
assert.NoError(t, err)
assert.EqualValues(t, s.value, b)
})
}
t.Run("missingXattrOnGet", func(t *testing.T) {
_, err := mount.GetXattr(fname, "user.never-set")
assert.Error(t, err)
})
t.Run("emptyNameGet", func(t *testing.T) {
_, err := mount.GetXattr(fname, "")
assert.Error(t, err)
})
t.Run("emptyNameSet", func(t *testing.T) {
err := mount.SetXattr(fname, "", []byte("foo"), XattrDefault)
assert.Error(t, err)
})
t.Run("invalidMount", func(t *testing.T) {
m := &MountInfo{}
err := m.SetXattr(fname, xattrSamples[0].name, xattrSamples[0].value, XattrDefault)
assert.Error(t, err)
_, err = m.GetXattr(fname, xattrSamples[0].name)
assert.Error(t, err)
})
}
func TestListXattrPath(t *testing.T) {
mount := fsConnect(t)
defer fsDisconnect(t, mount)
fname := "TestListXattrPath.txt"
f1, err := mount.Open(fname, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
require.NoError(t, err)
assert.NoError(t, f1.Close())
defer func() {
assert.NoError(t, mount.Unlink(fname))
}()
t.Run("listXattrs1", func(t *testing.T) {
for _, s := range xattrSamples[:1] {
err := mount.SetXattr(fname, s.name, s.value, XattrDefault)
assert.NoError(t, err)
}
xl, err := mount.ListXattr(fname)
assert.NoError(t, err)
assert.Len(t, xl, 1)
assert.Contains(t, xl, xattrSamples[0].name)
})
t.Run("listXattrs2", func(t *testing.T) {
for _, s := range xattrSamples {
err := mount.SetXattr(fname, s.name, s.value, XattrDefault)
assert.NoError(t, err)
}
xl, err := mount.ListXattr(fname)
assert.NoError(t, err)
assert.Len(t, xl, 3)
assert.Contains(t, xl, xattrSamples[0].name)
assert.Contains(t, xl, xattrSamples[1].name)
assert.Contains(t, xl, xattrSamples[2].name)
})
t.Run("invalidMount", func(t *testing.T) {
m := &MountInfo{}
_, err := m.ListXattr(fname)
assert.Error(t, err)
})
}
func TestRemoveXattrPath(t *testing.T) {
mount := fsConnect(t)
defer fsDisconnect(t, mount)
fname := "TestRemoveXattrPath.txt"
f1, err := mount.Open(fname, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
require.NoError(t, err)
assert.NoError(t, f1.Close())
defer func() {
assert.NoError(t, mount.Unlink(fname))
}()
t.Run("removeXattr", func(t *testing.T) {
s := xattrSamples[0]
err := mount.SetXattr(fname, s.name, s.value, XattrDefault)
err = mount.RemoveXattr(fname, s.name)
assert.NoError(t, err)
})
t.Run("removeMissingXattr", func(t *testing.T) {
s := xattrSamples[1]
err := mount.RemoveXattr(fname, s.name)
assert.Error(t, err)
})
t.Run("emptyName", func(t *testing.T) {
err := mount.RemoveXattr(fname, "")
assert.Error(t, err)
})
t.Run("invalidMount", func(t *testing.T) {
m := &MountInfo{}
err := m.RemoveXattr(fname, xattrSamples[0].name)
assert.Error(t, err)
})
}
func TestGetSetXattrLinkPath(t *testing.T) {
mount := fsConnect(t)
defer fsDisconnect(t, mount)
fname := "TestGetSetXattrLinkPath.txt"
lname := "TestGetSetXattrLinkPath.lnk"
f1, err := mount.Open(fname, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
require.NoError(t, err)
assert.NoError(t, f1.Close())
err = mount.Symlink(fname, lname)
require.NoError(t, err)
defer func() {
assert.NoError(t, mount.Unlink(fname))
assert.NoError(t, mount.Unlink(lname))
}()
for _, s := range xattrSamples {
t.Run("roundTrip-"+s.name, func(t *testing.T) {
err := mount.LsetXattr(lname, s.name, s.value, XattrDefault)
assert.NoError(t, err)
b, err := mount.LgetXattr(lname, s.name)
assert.NoError(t, err)
assert.EqualValues(t, s.value, b)
})
}
t.Run("linkVsFile", func(t *testing.T) {
s := xattrSamples[0]
err := mount.LsetXattr(lname, s.name, s.value, XattrDefault)
assert.NoError(t, err)
// not on the file
err = mount.LremoveXattr(fname, s.name)
assert.Error(t, err)
// on the link
err = mount.LremoveXattr(lname, s.name)
assert.NoError(t, err)
})
t.Run("missingXattrOnGet", func(t *testing.T) {
_, err := mount.LgetXattr(lname, "user.never-set")
assert.Error(t, err)
})
t.Run("emptyNameGet", func(t *testing.T) {
_, err := mount.LgetXattr(lname, "")
assert.Error(t, err)
})
t.Run("emptyNameSet", func(t *testing.T) {
err := mount.LsetXattr(lname, "", []byte("foo"), XattrDefault)
assert.Error(t, err)
})
t.Run("invalidMount", func(t *testing.T) {
m := &MountInfo{}
err := m.LsetXattr(lname, xattrSamples[0].name, xattrSamples[0].value, XattrDefault)
assert.Error(t, err)
_, err = m.LgetXattr(lname, xattrSamples[0].name)
assert.Error(t, err)
})
}
func TestListXattrLinkPath(t *testing.T) {
mount := fsConnect(t)
defer fsDisconnect(t, mount)
fname := "TestListXattrLinkPath.txt"
lname := "TestListXattrLinkPath.lnk"
f1, err := mount.Open(fname, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
require.NoError(t, err)
assert.NoError(t, f1.Close())
err = mount.Symlink(fname, lname)
require.NoError(t, err)
defer func() {
assert.NoError(t, mount.Unlink(fname))
assert.NoError(t, mount.Unlink(lname))
}()
t.Run("listXattrs1", func(t *testing.T) {
for _, s := range xattrSamples[:1] {
err := mount.LsetXattr(lname, s.name, s.value, XattrDefault)
assert.NoError(t, err)
}
// not on the file
xl, err := mount.LlistXattr(fname)
assert.NoError(t, err)
assert.Len(t, xl, 0)
// on the link
xl, err = mount.LlistXattr(lname)
assert.NoError(t, err)
assert.Len(t, xl, 1)
assert.Contains(t, xl, xattrSamples[0].name)
})
t.Run("listXattrs2", func(t *testing.T) {
for _, s := range xattrSamples {
err := mount.LsetXattr(lname, s.name, s.value, XattrDefault)
assert.NoError(t, err)
}
xl, err := mount.LlistXattr(lname)
assert.NoError(t, err)
assert.Len(t, xl, 3)
assert.Contains(t, xl, xattrSamples[0].name)
assert.Contains(t, xl, xattrSamples[1].name)
assert.Contains(t, xl, xattrSamples[2].name)
})
t.Run("invalidMount", func(t *testing.T) {
m := &MountInfo{}
_, err := m.LlistXattr(lname)
assert.Error(t, err)
})
}
func TestRemoveXattrLinkPath(t *testing.T) {
mount := fsConnect(t)
defer fsDisconnect(t, mount)
fname := "TestRemoveXattrLinkPath.txt"
lname := "TestRemoveXattrLinkPath.lnk"
f1, err := mount.Open(fname, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
require.NoError(t, err)
assert.NoError(t, f1.Close())
err = mount.Symlink(fname, lname)
require.NoError(t, err)
defer func() {
assert.NoError(t, mount.Unlink(fname))
assert.NoError(t, mount.Unlink(lname))
}()
t.Run("removeXattr", func(t *testing.T) {
s := xattrSamples[0]
err := mount.LsetXattr(lname, s.name, s.value, XattrDefault)
assert.NoError(t, err)
// not on the file
err = mount.LremoveXattr(fname, s.name)
assert.Error(t, err)
// on the link
err = mount.LremoveXattr(lname, s.name)
assert.NoError(t, err)
})
t.Run("removeMissingXattr", func(t *testing.T) {
s := xattrSamples[1]
err := mount.LremoveXattr(lname, s.name)
assert.Error(t, err)
})
t.Run("emptyName", func(t *testing.T) {
err := mount.LremoveXattr(lname, "")
assert.Error(t, err)
})
t.Run("invalidMount", func(t *testing.T) {
m := &MountInfo{}
err := m.LremoveXattr(lname, xattrSamples[0].name)
assert.Error(t, err)
})
}