From: Alex Ainscow Date: Mon, 24 Nov 2025 16:45:44 +0000 (+0000) Subject: librados/cls/neorados: Refactor interface to provide compile time policing of read... X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=7b525308418eeb3a6710dc3480fd711b877980cc;p=ceph-ci.git librados/cls/neorados: Refactor interface to provide compile time policing of read-only execs Signed-off-by: Alex Ainscow --- diff --git a/ceph.spec.in b/ceph.spec.in index 66f2973b584..fc4fe07da15 100644 --- a/ceph.spec.in +++ b/ceph.spec.in @@ -2436,6 +2436,8 @@ fi %dir %{_includedir}/rados %{_includedir}/rados/librados.h %{_includedir}/rados/rados_types.h +%{_includedir}/rados/cls_flags.h +%{_includedir}/rados/cls_traits.h %{_libdir}/librados.so %if %{with lttng} %{_libdir}/librados_tp.so @@ -2453,6 +2455,8 @@ fi %{_includedir}/rados/librados_fwd.hpp %{_includedir}/rados/page.h %{_includedir}/rados/rados_types.hpp +%{_includedir}/rados/cls_flags.h +%{_includedir}/rados/cls_traits.h %files -n python%{python3_pkgversion}-rados %{python3_sitearch}/rados.cpython*.so diff --git a/debian/librados-dev.install b/debian/librados-dev.install index 75d5ea7de73..cce2b5ff1e6 100644 --- a/debian/librados-dev.install +++ b/debian/librados-dev.install @@ -1,6 +1,8 @@ usr/bin/librados-config usr/include/rados/librados.h usr/include/rados/rados_types.h +usr/include/rados/cls_flags.h +usr/include/rados/cls_traits.h usr/lib/librados.so usr/lib/librados_tp.so usr/share/man/man8/librados-config.8 diff --git a/debian/libradospp-dev.install b/debian/libradospp-dev.install index 749cdd772d6..76c286a34ef 100644 --- a/debian/libradospp-dev.install +++ b/debian/libradospp-dev.install @@ -6,3 +6,5 @@ usr/include/rados/librados.hpp usr/include/rados/librados_fwd.hpp usr/include/rados/page.h usr/include/rados/rados_types.hpp +usr/include/rados/cls_flags.h +usr/include/rados/cls_traits.h diff --git a/src/include/CMakeLists.txt b/src/include/CMakeLists.txt index cb9c2fea8f8..b2007538871 100644 --- a/src/include/CMakeLists.txt +++ b/src/include/CMakeLists.txt @@ -14,6 +14,8 @@ install(FILES page.h crc32c.h rados/objclass.h + rados/cls_flags.h + rados/cls_traits.h DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/rados) if(WITH_LIBRADOSSTRIPER) install(FILES diff --git a/src/include/neorados/RADOS.hpp b/src/include/neorados/RADOS.hpp index 5dfc51a33b1..59a5a08860c 100644 --- a/src/include/neorados/RADOS.hpp +++ b/src/include/neorados/RADOS.hpp @@ -64,6 +64,9 @@ #include "common/ceph_time.h" +// For compile-time policing of class calls. +#include "include/rados/cls_traits.h" + namespace neorados { class Object; class IOContext; @@ -352,22 +355,43 @@ public: void assert_exists(); void cmp_omap(const std::vector& assertions); - void exec(std::string_view cls, std::string_view method, - const ceph::buffer::list& inbl, - ceph::buffer::list* out, - boost::system::error_code* ec = nullptr); - void exec(std::string_view cls, std::string_view method, - const ceph::buffer::list& inbl, - fu2::unique_function f); - void exec(std::string_view cls, std::string_view method, - const ceph::buffer::list& inbl, - fu2::unique_function f); - void exec(std::string_view cls, std::string_view method, - const ceph::buffer::list& inbl, - boost::system::error_code* ec = nullptr); - + protected: + // These methods being protected is part of the compile-time protection for + // read/write execs. + void exec_impl(std::string_view cls, std::string_view method, + const ceph::buffer::list& inbl, + ceph::buffer::list* out, + boost::system::error_code* ec); + + void exec_impl(std::string_view cls, std::string_view method, + const ceph::buffer::list& inbl, + fu2::unique_function f); + void exec_impl(std::string_view cls, std::string_view method, + const ceph::buffer::list& inbl, + fu2::unique_function f); + void exec_impl(std::string_view cls, std::string_view method, + const ceph::buffer::list& inbl, + boost::system::error_code* ec); + // templates don't work well with default arguments. + void exec_impl(std::string_view cls, std::string_view method, + const ceph::buffer::list& inbl) { + exec_impl(cls, method, inbl, (boost::system::error_code*)nullptr); + } + void exec_impl(std::string_view cls, std::string_view method, + const ceph::buffer::list& inbl, + ceph::buffer::list* out) { + exec_impl(cls, method, inbl, out, (boost::system::error_code*)nullptr); + } + public: + + template + void exec(const ClsMethod& method, const ceph::buffer::list& inbl, Args&&... args) { + static_assert(FlagTraits::is_readonly, + "Attempt to call a non-readonly class method as part of a non-write op "); + exec_impl(method.cls, method.name, inbl, std::forward(args)...); + } // Flags that apply to all ops in the operation vector void balance_reads(); @@ -649,61 +673,19 @@ public: return std::move(*this); } - ReadOp& exec(std::string_view cls, std::string_view method, - const ceph::buffer::list& inbl, - ceph::buffer::list* out, - boost::system::error_code* ec = nullptr) & { - Op::exec(cls, method, inbl, out, ec); - return *this; - } - ReadOp&& exec(std::string_view cls, std::string_view method, - const ceph::buffer::list& inbl, - ceph::buffer::list* out, - boost::system::error_code* ec = nullptr) && { - Op::exec(cls, method, inbl, out, ec); - return std::move(*this); - } - - ReadOp& exec(std::string_view cls, std::string_view method, - const ceph::buffer::list& inbl, - fu2::unique_function f) & { - Op::exec(cls, method, inbl, std::move(f)); + template + ReadOp& exec(const ClsMethod& method, Args&&... args) & { + static_assert(FlagTraits::is_readonly, + "Attempt to call a non-readonly class method as part of read. "); + Op::exec_impl(method.cls, method.name, std::forward(args)...); return *this; } - ReadOp&& exec(std::string_view cls, std::string_view method, - const ceph::buffer::list& inbl, - fu2::unique_function f) && { - Op::exec(cls, method, inbl, std::move(f)); - return std::move(*this); - } - ReadOp& exec(std::string_view cls, std::string_view method, - const ceph::buffer::list& inbl, - fu2::unique_function f) & { - Op::exec(cls, method, inbl, std::move(f)); - return *this; - } - ReadOp&& exec(std::string_view cls, std::string_view method, - const ceph::buffer::list& inbl, - fu2::unique_function f) && { - Op::exec(cls, method, inbl, std::move(f)); - return std::move(*this); - } - - ReadOp& exec(std::string_view cls, std::string_view method, - const ceph::buffer::list& inbl, - boost::system::error_code* ec = nullptr) & { - Op::exec(cls, method, inbl, ec); - return *this; - } - ReadOp&& exec(std::string_view cls, std::string_view method, - const ceph::buffer::list& inbl, - boost::system::error_code* ec = nullptr) && { - Op::exec(cls, method, inbl, ec); + template + ReadOp&& exec(const ClsMethod& method, Args&&... args) && { + static_assert(FlagTraits::is_readonly, + "Attempt to call a non-readonly class method as part of read. "); + Op::exec_impl(method.cls, method.name, std::forward(args)...); return std::move(*this); } @@ -1029,61 +1011,15 @@ public: return std::move(*this); } - WriteOp& exec(std::string_view cls, std::string_view method, - const ceph::buffer::list& inbl, - ceph::buffer::list* out, - boost::system::error_code* ec = nullptr) & { - Op::exec(cls, method, inbl, out, ec); + template + decltype(auto) exec(const ClsMethod& method, const ceph::buffer::list& inbl, Args&&... args) & { + Op::exec_impl(method.cls, method.name, inbl, std::forward(args)...); return *this; } - WriteOp&& exec(std::string_view cls, std::string_view method, - const ceph::buffer::list& inbl, - ceph::buffer::list* out, - boost::system::error_code* ec = nullptr) && { - Op::exec(cls, method, inbl, out, ec); - return std::move(*this); - } - - WriteOp& exec(std::string_view cls, std::string_view method, - const ceph::buffer::list& inbl, - fu2::unique_function f) & { - Op::exec(cls, method, inbl, std::move(f)); - return *this; - } - WriteOp&& exec(std::string_view cls, std::string_view method, - const ceph::buffer::list& inbl, - fu2::unique_function f) && { - Op::exec(cls, method, inbl, std::move(f)); - return std::move(*this); - } - WriteOp& exec(std::string_view cls, std::string_view method, - const ceph::buffer::list& inbl, - fu2::unique_function f) & { - Op::exec(cls, method, inbl, std::move(f)); - return *this; - } - WriteOp&& exec(std::string_view cls, std::string_view method, - const ceph::buffer::list& inbl, - fu2::unique_function f) && { - Op::exec(cls, method, inbl, std::move(f)); - return std::move(*this); - } - - WriteOp& exec(std::string_view cls, std::string_view method, - const ceph::buffer::list& inbl, - boost::system::error_code* ec = nullptr) & { - Op::exec(cls, method, inbl, ec); - return *this; - } - WriteOp&& exec(std::string_view cls, std::string_view method, - const ceph::buffer::list& inbl, - boost::system::error_code* ec = nullptr) && { - Op::exec(cls, method, inbl, ec); + template + decltype(auto) exec(const ClsMethod& method, const ceph::buffer::list& inbl, Args&&... args) && { + Op::exec_impl(method.cls, method.name, inbl, std::forward(args)...); return std::move(*this); } diff --git a/src/include/rados/cls_flags.h b/src/include/rados/cls_flags.h new file mode 100644 index 00000000000..55347e1a449 --- /dev/null +++ b/src/include/rados/cls_flags.h @@ -0,0 +1,5 @@ +#pragma once + +#define CLS_METHOD_RD 0x1 /// method executes read operations +#define CLS_METHOD_WR 0x2 /// method executes write operations +#define CLS_METHOD_PROMOTE 0x8 /// method cannot be proxied to base tier \ No newline at end of file diff --git a/src/include/rados/cls_traits.h b/src/include/rados/cls_traits.h new file mode 100644 index 00000000000..21ccf6c3f4a --- /dev/null +++ b/src/include/rados/cls_traits.h @@ -0,0 +1,38 @@ +#pragma once +#include +#include "cls_flags.h" + +template +struct MethodTag {}; + +// Tags representing flags. +using RdTag = MethodTag; +using WrTag = MethodTag; +using PromoteTag = MethodTag; + +// Combinations of flags. +using RdWrTag = MethodTag; +using RdPromoteTag = MethodTag; +using WrPromoteTag = MethodTag; +using RdWrPromoteTag = MethodTag; + +template +struct ClsMethod { + const char* cls; + const char* name; + + constexpr ClsMethod(const char* n) : cls(ClassDef::name), name(n) {} +}; + +// Traits to map Tags to properties +template struct FlagTraits; + +template +struct FlagTraits> { + + static constexpr int value = Flags; + static constexpr bool is_readonly = (Flags & CLS_METHOD_WR) == 0; +}; + +template +constexpr bool is_safe_for_ro_v = FlagTraits::is_readonly; \ No newline at end of file diff --git a/src/include/rados/librados.hpp b/src/include/rados/librados.hpp index 96ed711def0..2a7c1e0ade2 100644 --- a/src/include/rados/librados.hpp +++ b/src/include/rados/librados.hpp @@ -14,6 +14,8 @@ #include "librados_fwd.hpp" #include "rados_types.hpp" +#include "include/rados/cls_traits.h" + namespace libradosstriper { class RadosStriper; @@ -340,9 +342,21 @@ inline namespace v14_2_0 { void cmpext(uint64_t off, const bufferlist& cmp_bl, int *prval); void cmpxattr(const char *name, uint8_t op, const bufferlist& val); void cmpxattr(const char *name, uint8_t op, uint64_t v); - void exec(const char *cls, const char *method, bufferlist& inbl); - void exec(const char *cls, const char *method, bufferlist& inbl, bufferlist *obl, int *prval); - void exec(const char *cls, const char *method, bufferlist& inbl, ObjectOperationCompletion *completion); + protected: + void exec_impl(const char *cls, const char *method, bufferlist& inbl); + void exec_impl(const char *cls, const char *method, bufferlist& inbl, bufferlist *obl, int *prval); + void exec_impl(const char *cls, const char *method, bufferlist& inbl, ObjectOperationCompletion *completion); + public: + + // By default only allow READ operations. ObjectWriteOperation overrides this + // to allow writes. + template + void exec(const ClsMethod& method, Args&&... args) { + static_assert(FlagTraits::is_readonly, + "Attempt to call a non-readonly class method as part of read. "); + exec_impl(method.cls, method.name, std::forward(args)...); + } + /** * Guard operation with a check that object version == ver * @@ -530,6 +544,24 @@ inline namespace v14_2_0 { void unset_manifest(); friend class IoCtx; + + /** + * Execute an OSD class method on an object + * See IoCtx::exec() for general description. + * + * Add an exec to write operation. Read or Write exec methods are permitted. + * + * @param method the method as defined in cls//cls__ops.h + * @param inbl where to find input + * @param obl (optional) where to store output + * @param prval (optional) storage for return value. + * @param completion (optional) completion callback. + */ + template + void exec(const ClsMethod& method, Args&&... args) { + // Read or write operations are permitted, so allow this. + exec_impl(method.cls, method.name, std::forward(args)...); + } }; /* @@ -888,8 +920,37 @@ inline namespace v14_2_0 { int rmxattr(const std::string& oid, const char *name); int stat(const std::string& oid, uint64_t *psize, time_t *pmtime); int stat2(const std::string& oid, uint64_t *psize, struct timespec *pts); - int exec(const std::string& oid, const char *cls, const char *method, - bufferlist& inbl, bufferlist& outbl); + protected: + // IoCtx needs a distinction between ro and rw to pick the correct flags + // for the operate call. + int exec_impl(const std::string& oid, const char *cls, const char *method, + bufferlist& inbl, bufferlist& outbl); + public: + /** + * Execute an OSD class method on an object + * + * The OSD has a plugin mechanism for performing complicated + * operations on an object atomically. These plugins are called + * classes. This function allows librados users to call the custom + * methods. The input and output formats are defined by the class. + * Classes in ceph.git can be found in src/cls subdirectories + * + * Synchronous variant of exec. Only reads are permitted through this + * interface. + * + * @param oid the object name + * @param method the method as defined in cls//cls__ops.h + * @param inbl where to find input + * @param outbl where to store output + * @returns return code (>=0 for success, otherwise stanard OSD errors) + */ + template + int exec(const std::string& oid, const ClsMethod& method, bufferlist& inbl, bufferlist& outbl) { + static_assert(FlagTraits::is_readonly, + "Attempt to call a non-readonly class method as part of read. "); + return exec_impl(oid, method.cls, method.name, inbl, outbl); + } + /** * modify object tmap based on encoded update sequence * @@ -1178,8 +1239,31 @@ inline namespace v14_2_0 { */ int aio_cancel(AioCompletion *c); - int aio_exec(const std::string& oid, AioCompletion *c, const char *cls, const char *method, - bufferlist& inbl, bufferlist *outbl); + + private: + int aio_exec_impl(const std::string& oid, AioCompletion *c, const char *cls, const char *method, + bufferlist& inbl, bufferlist *outbl); + public: + /** + * Execute an OSD class method on an object + * See exec() for general description. + * + * Asynchronous variant of exec. Only exec reads are permitted. + * + * @param oid the object name + * @param c aio completion + * @param method the method as defined in cls//cls__ops.h + * @param inbl where to find input + * @param outbl where to store output + */ + template + int aio_exec(const std::string& oid, AioCompletion *c, + const ClsMethod& method, bufferlist& inbl, bufferlist *outbl) { + static_assert(FlagTraits::is_readonly, + "Attempt to call a non-readonly class method as part of read. "); + + return aio_exec_impl(oid, c, method.cls, method.name, inbl, outbl); + } /* * asynchronous version of unlock diff --git a/src/include/rados/objclass.h b/src/include/rados/objclass.h index 1c188cc4bc8..9906d71aca6 100644 --- a/src/include/rados/objclass.h +++ b/src/include/rados/objclass.h @@ -7,6 +7,7 @@ #ifdef __cplusplus #include "buffer.h" +#include "include/rados/cls_traits.h" extern "C" { #endif @@ -25,10 +26,6 @@ const char *__cls_name = #name; #define CLS_INIT(name) \ CEPH_CLS_API void __cls_init() -#define CLS_METHOD_RD 0x1 /// method executes read operations -#define CLS_METHOD_WR 0x2 /// method executes write operations -#define CLS_METHOD_PROMOTE 0x8 /// method cannot be proxied to base tier - #define CLS_LOG(level, fmt, ...) \ cls_log(level, " %s:%d: " fmt, __FILE__, __LINE__, ##__VA_ARGS__) #define CLS_ERR(fmt, ...) CLS_LOG(0, fmt, ##__VA_ARGS__) @@ -85,8 +82,26 @@ typedef int (*cls_method_cxx_call_t)(cls_method_context_t ctx, * @param class_call * @param handle */ -extern int cls_register_cxx_method(cls_handle_t hclass, const char *method, int flags, +namespace detail { +extern int cls_register_cxx_method_impl(cls_handle_t hclass, const char *method, int flags, cls_method_cxx_call_t class_call, cls_method_handle_t *handle); +} + +template +struct ClassRegistrar { + cls_handle_t h_class; + + ClassRegistrar(cls_handle_t h) : h_class(h) {} + + template + void register_cxx_method(const ClsMethod& method_def, + cls_method_cxx_call_t class_call, + cls_method_handle_t* handle) { + + int flags = FlagTraits::value; + detail::cls_register_cxx_method_impl(h_class, method_def.name, flags, class_call, handle); + } +}; /** * Create an object. diff --git a/src/librados/librados_cxx.cc b/src/librados/librados_cxx.cc index 223e8d4606c..cc66f732cf3 100644 --- a/src/librados/librados_cxx.cc +++ b/src/librados/librados_cxx.cc @@ -151,15 +151,15 @@ void librados::ObjectOperation::assert_exists() o->stat(nullptr, nullptr, nullptr); } -void librados::ObjectOperation::exec(const char *cls, const char *method, - bufferlist& inbl) +void librados::ObjectOperation::exec_impl(const char *cls, const char *method, + bufferlist& inbl) { ceph_assert(impl); ::ObjectOperation *o = &impl->o; o->call(cls, method, inbl); } -void librados::ObjectOperation::exec(const char *cls, const char *method, bufferlist& inbl, bufferlist *outbl, int *prval) +void librados::ObjectOperation::exec_impl(const char *cls, const char *method, bufferlist& inbl, bufferlist *outbl, int *prval) { ceph_assert(impl); ::ObjectOperation *o = &impl->o; @@ -181,7 +181,8 @@ public: } }; -void librados::ObjectOperation::exec(const char *cls, const char *method, bufferlist& inbl, librados::ObjectOperationCompletion *completion) +void librados::ObjectOperation::exec_impl(const char *cls, const char *method, + bufferlist& inbl, librados::ObjectOperationCompletion *completion) { ceph_assert(impl); ::ObjectOperation *o = &impl->o; @@ -1363,7 +1364,7 @@ int librados::IoCtx::stat2(const std::string& oid, uint64_t *psize, struct times return io_ctx_impl->stat2(obj, psize, pts); } -int librados::IoCtx::exec(const std::string& oid, const char *cls, const char *method, +int librados::IoCtx::exec_impl(const std::string& oid, const char *cls, const char *method, bufferlist& inbl, bufferlist& outbl) { object_t obj(oid); @@ -1953,7 +1954,7 @@ int librados::IoCtx::aio_read(const std::string& oid, librados::AioCompletion *c return io_ctx_impl->aio_read(oid, c->pc, pbl, len, off, snapid); } -int librados::IoCtx::aio_exec(const std::string& oid, +int librados::IoCtx::aio_exec_impl(const std::string& oid, librados::AioCompletion *c, const char *cls, const char *method, bufferlist& inbl, bufferlist *outbl) diff --git a/src/neorados/RADOS.cc b/src/neorados/RADOS.cc index 3824e3fe6c3..174778b387a 100644 --- a/src/neorados/RADOS.cc +++ b/src/neorados/RADOS.cc @@ -492,29 +492,26 @@ void Op::cmp_omap(const std::vector& assertions) { reinterpret_cast(&impl)->op.omap_cmp(std::move(bl), nullptr); } -void Op::exec(std::string_view cls, std::string_view method, - const bufferlist& inbl, - cb::list* out, - bs::error_code* ec) { +void Op::exec_impl(std::string_view cls, std::string_view method, + const bufferlist& inbl, + cb::list* out, + bs::error_code* ec) { reinterpret_cast(&impl)->op.call(cls, method, inbl, ec, out); } - -void Op::exec(std::string_view cls, std::string_view method, - const bufferlist& inbl, - fu2::unique_function f) { +void Op::exec_impl(std::string_view cls, std::string_view method, + const bufferlist& inbl, + fu2::unique_function f) { reinterpret_cast(&impl)->op.call(cls, method, inbl, std::move(f)); } - -void Op::exec(std::string_view cls, std::string_view method, - const bufferlist& inbl, - fu2::unique_function f) { +void Op::exec_impl(std::string_view cls, std::string_view method, + const bufferlist& inbl, + fu2::unique_function f) { reinterpret_cast(&impl)->op.call(cls, method, inbl, std::move(f)); } - -void Op::exec(std::string_view cls, std::string_view method, - const bufferlist& inbl, bs::error_code* ec) { +void Op::exec_impl(std::string_view cls, std::string_view method, + const bufferlist& inbl, bs::error_code* ec) { reinterpret_cast(&impl)->op.call(cls, method, inbl, ec); } diff --git a/src/objclass/class_api.cc b/src/objclass/class_api.cc index b84b05afc2d..d3766e7dfe6 100644 --- a/src/objclass/class_api.cc +++ b/src/objclass/class_api.cc @@ -55,7 +55,7 @@ int cls_register_method(cls_handle_t hclass, const char *method, return (hmethod != NULL); } -int cls_register_cxx_method(cls_handle_t hclass, const char *method, +int detail::cls_register_cxx_method_impl(cls_handle_t hclass, const char *method, int flags, cls_method_cxx_call_t class_call, cls_method_handle_t *handle) {