diff --git a/internal/dlsym/dlsym.go b/internal/dlsym/dlsym.go new file mode 100644 index 000000000..9e5782c4d --- /dev/null +++ b/internal/dlsym/dlsym.go @@ -0,0 +1,41 @@ +package dlsym + +// #cgo LDFLAGS: -ldl +// +// #include +// #include +// +// #ifndef RTLD_DEFAULT /* from dlfcn.h */ +// #define RTLD_DEFAULT ((void *) 0) +// #endif +import "C" + +import ( + "errors" + "fmt" + "unsafe" +) + +// ErrUndefinedSymbol is returned by LookupSymbol when the requested symbol +// could not be found. +var ErrUndefinedSymbol = errors.New("symbol not found") + +// LookupSymbol resolves the named symbol from the already dynamically loaded +// libraries. If the symbol is found, a pointer to it is returned, in case of a +// failure, the message provided by dlerror() is included in the error message. +func LookupSymbol(symbol string) (unsafe.Pointer, error) { + cSymName := C.CString(symbol) + defer C.free(unsafe.Pointer(cSymName)) + + // clear dlerror before looking up the symbol + C.dlerror() + // resolve the address of the symbol + sym := C.dlsym(C.RTLD_DEFAULT, cSymName) + e := C.dlerror() + dlerr := C.GoString(e) + if dlerr != "" { + return nil, fmt.Errorf("%w: %s", ErrUndefinedSymbol, dlerr) + } + + return sym, nil +} diff --git a/internal/dlsym/dlsym_test.go b/internal/dlsym/dlsym_test.go new file mode 100644 index 000000000..c33ecccc9 --- /dev/null +++ b/internal/dlsym/dlsym_test.go @@ -0,0 +1,22 @@ +package dlsym + +import ( + "errors" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestLookupSymbol(t *testing.T) { + t.Run("ValidSymbol", func(t *testing.T) { + sym, err := LookupSymbol("dlsym") + assert.NotNil(t, sym) + assert.NoError(t, err) + }) + + t.Run("InvalidSymbol", func(t *testing.T) { + sym, err := LookupSymbol("go_ceph_dlsym") + assert.Nil(t, sym) + assert.True(t, errors.Is(err, ErrUndefinedSymbol)) + }) +} diff --git a/rbd/clone_image_by_id.go b/rbd/clone_image_by_id.go index a73c99b18..63cf552d9 100644 --- a/rbd/clone_image_by_id.go +++ b/rbd/clone_image_by_id.go @@ -1,20 +1,46 @@ -//go:build !(nautilus || octopus || pacific || quincy || reef) && ceph_preview +//go:build ceph_preview package rbd -// #cgo LDFLAGS: -lrbd -// #include -// #include -// #include -// #include +/* +#cgo LDFLAGS: -lrbd +#include +#include +#include +#include + +// rbd_clone4_fn matches the rbd_clone4 function signature. +typedef int(*rbd_clone4_fn)(rados_ioctx_t p_ioctx, const char *p_name, + uint64_t p_snap_id, rados_ioctx_t c_ioctx, + const char *c_name, rbd_image_options_t c_opts); + +// rbd_clone4_dlsym take *fn as rbd_clone4_fn and calls the dynamically loaded +// rbd_clone4 function passed as 1st argument. +static inline int rbd_clone4_dlsym(void *fn, rados_ioctx_t p_ioctx, + const char *p_name, uint64_t p_snap_id, + rados_ioctx_t c_ioctx, const char *c_name, + rbd_image_options_t c_opts) { + // cast function pointer fn to rbd_clone4 and call the function + return ((rbd_clone4_fn) fn)(p_ioctx, p_name, p_snap_id, c_ioctx, c_name, c_opts); +} +*/ import "C" import ( + "fmt" + "sync" "unsafe" + "github.com/ceph/go-ceph/internal/dlsym" "github.com/ceph/go-ceph/rados" ) +var ( + rbdClone4Once sync.Once + rbdClone4 unsafe.Pointer + rbdClone4Err error +) + // CloneImageByID creates a clone of the image from a snapshot with the given // ID in the provided io-context with the given name and image options. // @@ -25,22 +51,33 @@ import ( // const char *c_name, rbd_image_options_t c_opts); func CloneImageByID(ioctx *rados.IOContext, parentName string, snapID uint64, destctx *rados.IOContext, name string, rio *ImageOptions) error { - if rio == nil { return rbdError(C.EINVAL) } + rbdClone4Once.Do(func() { + rbdClone4, rbdClone4Err = dlsym.LookupSymbol("rbd_clone4") + }) + + if rbdClone4Err != nil { + return fmt.Errorf("%w: %w", ErrNotImplemented, rbdClone4Err) + } + cParentName := C.CString(parentName) defer C.free(unsafe.Pointer(cParentName)) cCloneName := C.CString(name) defer C.free(unsafe.Pointer(cCloneName)) - ret := C.rbd_clone4( + // call rbd_clone4_dlsym with the function pointer to rbd_clone4 as 1st + // argument + ret := C.rbd_clone4_dlsym( + rbdClone4, cephIoctx(ioctx), cParentName, C.uint64_t(snapID), cephIoctx(destctx), cCloneName, C.rbd_image_options_t(rio.options)) + return getError(ret) } diff --git a/rbd/clone_image_by_id_test.go b/rbd/clone_image_by_id_test.go index db944eca9..9a50b71c7 100644 --- a/rbd/clone_image_by_id_test.go +++ b/rbd/clone_image_by_id_test.go @@ -1,8 +1,9 @@ -//go:build !(nautilus || octopus || pacific || quincy || reef) && ceph_preview +//go:build ceph_preview package rbd import ( + "errors" "testing" "github.com/stretchr/testify/assert" @@ -91,6 +92,9 @@ func TestCloneImageByID(t *testing.T) { // Create a clone of the image using the snapshot. err = CloneImageByID(ioctx, name1, snapID, ioctx, cloneName, optionsClone) + if errors.Is(err, ErrNotImplemented) { + t.Skipf("CloneImageByID is not supported: %v", err) + } assert.NoError(t, err) defer func() { assert.NoError(t, RemoveImage(ioctx, cloneName)) }() @@ -112,6 +116,9 @@ func TestCloneImageByID(t *testing.T) { t.Run("CloneFromGroupSnap", func(t *testing.T) { err := GroupSnapCreate(ioctx, gname, "groupsnap") assert.NoError(t, err) + defer func() { + assert.NoError(t, GroupSnapRemove(ioctx, gname, "groupsnap")) + }() cloneName := "img-clone" optionsClone := NewRbdImageOptions() @@ -132,6 +139,9 @@ func TestCloneImageByID(t *testing.T) { // Create a clone of the image using the snapshot. err = CloneImageByID(ioctx, name1, snapID, ioctx, cloneName, optionsClone) + if errors.Is(err, ErrNotImplemented) { + t.Skipf("CloneImageByID is not supported: %v", err) + } assert.NoError(t, err) defer func() { assert.NoError(t, RemoveImage(ioctx, cloneName)) }() @@ -147,8 +157,5 @@ func TestCloneImageByID(t *testing.T) { assert.Equal(t, parentInfo.Snap.ID, snapID) assert.Equal(t, parentInfo.Image.PoolName, poolname) assert.False(t, parentInfo.Image.Trash) - - err = GroupSnapRemove(ioctx, gname, "groupsnap") - assert.NoError(t, err) }) } diff --git a/rbd/errors.go b/rbd/errors.go index 34693333f..4d98cce7c 100644 --- a/rbd/errors.go +++ b/rbd/errors.go @@ -75,6 +75,8 @@ var ( const ( // ErrNotExist indicates a non-specific missing resource. ErrNotExist = rbdError(-C.ENOENT) + // ErrNotImplemented indicates a function is not implemented in by librbd. + ErrNotImplemented = rbdError(-C.ENOSYS) ) // Private errors: