summaryrefslogtreecommitdiffstats
path: root/src/cls/cmpomap
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:45:59 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 18:45:59 +0000
commit19fcec84d8d7d21e796c7624e521b60d28ee21ed (patch)
tree42d26aa27d1e3f7c0b8bd3fd14e7d7082f5008dc /src/cls/cmpomap
parentInitial commit. (diff)
downloadceph-19fcec84d8d7d21e796c7624e521b60d28ee21ed.tar.xz
ceph-19fcec84d8d7d21e796c7624e521b60d28ee21ed.zip
Adding upstream version 16.2.11+ds.upstream/16.2.11+dsupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/cls/cmpomap')
-rw-r--r--src/cls/cmpomap/CMakeLists.txt9
-rw-r--r--src/cls/cmpomap/client.cc76
-rw-r--r--src/cls/cmpomap/client.h68
-rw-r--r--src/cls/cmpomap/ops.h100
-rw-r--r--src/cls/cmpomap/server.cc302
-rw-r--r--src/cls/cmpomap/types.h44
6 files changed, 599 insertions, 0 deletions
diff --git a/src/cls/cmpomap/CMakeLists.txt b/src/cls/cmpomap/CMakeLists.txt
new file mode 100644
index 000000000..de8ca278c
--- /dev/null
+++ b/src/cls/cmpomap/CMakeLists.txt
@@ -0,0 +1,9 @@
+add_library(cls_cmpomap SHARED server.cc)
+set_target_properties(cls_cmpomap PROPERTIES
+ VERSION "1.0.0"
+ SOVERSION "1"
+ INSTALL_RPATH ""
+ CXX_VISIBILITY_PRESET hidden)
+install(TARGETS cls_cmpomap DESTINATION ${cls_dir})
+
+add_library(cls_cmpomap_client STATIC client.cc)
diff --git a/src/cls/cmpomap/client.cc b/src/cls/cmpomap/client.cc
new file mode 100644
index 000000000..e0fbbff18
--- /dev/null
+++ b/src/cls/cmpomap/client.cc
@@ -0,0 +1,76 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab ft=cpp
+
+/*
+ * Ceph - scalable distributed file system
+ *
+ * Copyright (C) 2020 Red Hat, Inc
+ *
+ * This is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2.1, as published by the Free Software
+ * Foundation. See file COPYING.
+ */
+
+#include "include/rados/librados.hpp"
+#include "client.h"
+#include "ops.h"
+
+namespace cls::cmpomap {
+
+int cmp_vals(librados::ObjectReadOperation& op,
+ Mode mode, Op comparison, ComparisonMap values,
+ std::optional<ceph::bufferlist> default_value)
+{
+ if (values.size() > max_keys) {
+ return -E2BIG;
+ }
+ cmp_vals_op call;
+ call.mode = mode;
+ call.comparison = comparison;
+ call.values = std::move(values);
+ call.default_value = std::move(default_value);
+
+ bufferlist in;
+ encode(call, in);
+ op.exec("cmpomap", "cmp_vals", in);
+ return 0;
+}
+
+int cmp_set_vals(librados::ObjectWriteOperation& op,
+ Mode mode, Op comparison, ComparisonMap values,
+ std::optional<ceph::bufferlist> default_value)
+{
+ if (values.size() > max_keys) {
+ return -E2BIG;
+ }
+ cmp_set_vals_op call;
+ call.mode = mode;
+ call.comparison = comparison;
+ call.values = std::move(values);
+ call.default_value = std::move(default_value);
+
+ bufferlist in;
+ encode(call, in);
+ op.exec("cmpomap", "cmp_set_vals", in);
+ return 0;
+}
+
+int cmp_rm_keys(librados::ObjectWriteOperation& op,
+ Mode mode, Op comparison, ComparisonMap values)
+{
+ if (values.size() > max_keys) {
+ return -E2BIG;
+ }
+ cmp_rm_keys_op call;
+ call.mode = mode;
+ call.comparison = comparison;
+ call.values = std::move(values);
+
+ bufferlist in;
+ encode(call, in);
+ op.exec("cmpomap", "cmp_rm_keys", in);
+ return 0;
+}
+
+} // namespace cls::cmpomap
diff --git a/src/cls/cmpomap/client.h b/src/cls/cmpomap/client.h
new file mode 100644
index 000000000..013d85cc7
--- /dev/null
+++ b/src/cls/cmpomap/client.h
@@ -0,0 +1,68 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab ft=cpp
+
+/*
+ * Ceph - scalable distributed file system
+ *
+ * Copyright (C) 2020 Red Hat, Inc
+ *
+ * This is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2.1, as published by the Free Software
+ * Foundation. See file COPYING.
+ */
+
+#pragma once
+
+#include <optional>
+#include "include/rados/librados_fwd.hpp"
+#include "types.h"
+
+namespace cls::cmpomap {
+
+/// requests with too many key comparisons will be rejected with -E2BIG
+static constexpr uint32_t max_keys = 1000;
+
+/// process each of the omap value comparisons according to the same rules as
+/// cmpxattr(), and return -ECANCELED if a comparison is unsuccessful. for
+/// comparisons with Mode::U64, failure to decode an input value is reported
+/// as -EINVAL, an empty stored value is compared as 0, and failure to decode
+/// a stored value is reported as -EIO
+[[nodiscard]] int cmp_vals(librados::ObjectReadOperation& op,
+ Mode mode, Op comparison, ComparisonMap values,
+ std::optional<ceph::bufferlist> default_value);
+
+/// process each of the omap value comparisons according to the same rules as
+/// cmpxattr(). any key/value pairs that compare successfully are overwritten
+/// with the corresponding input value. for comparisons with Mode::U64, failure
+/// to decode an input value is reported as -EINVAL. an empty stored value is
+/// compared as 0, while decode failure of a stored value is treated as an
+/// unsuccessful comparison and is not reported as an error
+[[nodiscard]] int cmp_set_vals(librados::ObjectWriteOperation& writeop,
+ Mode mode, Op comparison, ComparisonMap values,
+ std::optional<ceph::bufferlist> default_value);
+
+/// process each of the omap value comparisons according to the same rules as
+/// cmpxattr(). any key/value pairs that compare successfully are removed. for
+/// comparisons with Mode::U64, failure to decode an input value is reported as
+/// -EINVAL. an empty stored value is compared as 0, while decode failure of a
+/// stored value is treated as an unsuccessful comparison and is not reported
+/// as an error
+[[nodiscard]] int cmp_rm_keys(librados::ObjectWriteOperation& writeop,
+ Mode mode, Op comparison, ComparisonMap values);
+
+
+// bufferlist factories for comparison values
+inline ceph::bufferlist string_buffer(std::string_view value) {
+ ceph::bufferlist bl;
+ bl.append(value);
+ return bl;
+}
+inline ceph::bufferlist u64_buffer(uint64_t value) {
+ ceph::bufferlist bl;
+ using ceph::encode;
+ encode(value, bl);
+ return bl;
+}
+
+} // namespace cls::cmpomap
diff --git a/src/cls/cmpomap/ops.h b/src/cls/cmpomap/ops.h
new file mode 100644
index 000000000..39b1049e8
--- /dev/null
+++ b/src/cls/cmpomap/ops.h
@@ -0,0 +1,100 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab ft=cpp
+
+/*
+ * Ceph - scalable distributed file system
+ *
+ * Copyright (C) 2020 Red Hat, Inc
+ *
+ * This is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2.1, as published by the Free Software
+ * Foundation. See file COPYING.
+ */
+
+#pragma once
+
+#include "types.h"
+#include "include/encoding.h"
+
+namespace cls::cmpomap {
+
+struct cmp_vals_op {
+ Mode mode;
+ Op comparison;
+ ComparisonMap values;
+ std::optional<ceph::bufferlist> default_value;
+};
+
+inline void encode(const cmp_vals_op& o, ceph::bufferlist& bl, uint64_t f=0)
+{
+ ENCODE_START(1, 1, bl);
+ encode(o.mode, bl);
+ encode(o.comparison, bl);
+ encode(o.values, bl);
+ encode(o.default_value, bl);
+ ENCODE_FINISH(bl);
+}
+
+inline void decode(cmp_vals_op& o, ceph::bufferlist::const_iterator& bl)
+{
+ DECODE_START(1, bl);
+ decode(o.mode, bl);
+ decode(o.comparison, bl);
+ decode(o.values, bl);
+ decode(o.default_value, bl);
+ DECODE_FINISH(bl);
+}
+
+struct cmp_set_vals_op {
+ Mode mode;
+ Op comparison;
+ ComparisonMap values;
+ std::optional<ceph::bufferlist> default_value;
+};
+
+inline void encode(const cmp_set_vals_op& o, ceph::bufferlist& bl, uint64_t f=0)
+{
+ ENCODE_START(1, 1, bl);
+ encode(o.mode, bl);
+ encode(o.comparison, bl);
+ encode(o.values, bl);
+ encode(o.default_value, bl);
+ ENCODE_FINISH(bl);
+}
+
+inline void decode(cmp_set_vals_op& o, ceph::bufferlist::const_iterator& bl)
+{
+ DECODE_START(1, bl);
+ decode(o.mode, bl);
+ decode(o.comparison, bl);
+ decode(o.values, bl);
+ decode(o.default_value, bl);
+ DECODE_FINISH(bl);
+}
+
+struct cmp_rm_keys_op {
+ Mode mode;
+ Op comparison;
+ ComparisonMap values;
+};
+
+inline void encode(const cmp_rm_keys_op& o, ceph::bufferlist& bl, uint64_t f=0)
+{
+ ENCODE_START(1, 1, bl);
+ encode(o.mode, bl);
+ encode(o.comparison, bl);
+ encode(o.values, bl);
+ ENCODE_FINISH(bl);
+}
+
+inline void decode(cmp_rm_keys_op& o, ceph::bufferlist::const_iterator& bl)
+{
+ DECODE_START(1, bl);
+ decode(o.mode, bl);
+ decode(o.comparison, bl);
+ decode(o.values, bl);
+ DECODE_FINISH(bl);
+}
+
+} // namespace cls::cmpomap
diff --git a/src/cls/cmpomap/server.cc b/src/cls/cmpomap/server.cc
new file mode 100644
index 000000000..86e16d940
--- /dev/null
+++ b/src/cls/cmpomap/server.cc
@@ -0,0 +1,302 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab ft=cpp
+
+/*
+ * Ceph - scalable distributed file system
+ *
+ * Copyright (C) 2020 Red Hat, Inc
+ *
+ * This is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2.1, as published by the Free Software
+ * Foundation. See file COPYING.
+ */
+
+#include "objclass/objclass.h"
+#include "ops.h"
+
+CLS_VER(1,0)
+CLS_NAME(cmpomap)
+
+using namespace cls::cmpomap;
+
+// returns negative error codes or 0/1 for failed/successful comparisons
+template <typename T>
+static int compare_values(Op op, const T& lhs, const T& rhs)
+{
+ switch (op) {
+ case Op::EQ: return (lhs == rhs);
+ case Op::NE: return (lhs != rhs);
+ case Op::GT: return (lhs > rhs);
+ case Op::GTE: return (lhs >= rhs);
+ case Op::LT: return (lhs < rhs);
+ case Op::LTE: return (lhs <= rhs);
+ default: return -EINVAL;
+ }
+}
+
+static int compare_values_u64(Op op, uint64_t lhs, const bufferlist& value)
+{
+ // empty values compare as 0 for backward compat
+ uint64_t rhs = 0;
+ if (value.length()) {
+ try {
+ // decode existing value as rhs
+ auto p = value.cbegin();
+ using ceph::decode;
+ decode(rhs, p);
+ } catch (const buffer::error&) {
+ // failures to decode existing values are reported as EIO
+ return -EIO;
+ }
+ }
+ return compare_values(op, lhs, rhs);
+}
+
+static int compare_value(Mode mode, Op op, const bufferlist& input,
+ const bufferlist& value)
+{
+ switch (mode) {
+ case Mode::String:
+ return compare_values(op, input, value);
+ case Mode::U64:
+ try {
+ // decode input value as lhs
+ uint64_t lhs;
+ auto p = input.cbegin();
+ using ceph::decode;
+ decode(lhs, p);
+ return compare_values_u64(op, lhs, value);
+ } catch (const buffer::error&) {
+ // failures to decode input values are reported as EINVAL
+ return -EINVAL;
+ }
+ default:
+ return -EINVAL;
+ }
+}
+
+static int cmp_vals(cls_method_context_t hctx, bufferlist *in, bufferlist *out)
+{
+ cmp_vals_op op;
+ try {
+ auto p = in->cbegin();
+ decode(op, p);
+ } catch (const buffer::error&) {
+ CLS_LOG(1, "ERROR: cmp_vals(): failed to decode input");
+ return -EINVAL;
+ }
+
+ // collect the keys we need to read
+ std::set<std::string> keys;
+ for (const auto& kv : op.values) {
+ keys.insert(kv.first);
+ }
+
+ // read the values for each key to compare
+ std::map<std::string, bufferlist> values;
+ int r = cls_cxx_map_get_vals_by_keys(hctx, keys, &values);
+ if (r < 0) {
+ CLS_LOG(4, "ERROR: cmp_vals() failed to read values r=%d", r);
+ return r;
+ }
+
+ auto v = values.cbegin();
+ for (const auto& [key, input] : op.values) {
+ bufferlist value;
+ if (v != values.end() && v->first == key) {
+ value = std::move(v->second);
+ ++v;
+ CLS_LOG(20, "cmp_vals() comparing key=%s mode=%d op=%d",
+ key.c_str(), (int)op.mode, (int)op.comparison);
+ } else if (!op.default_value) {
+ CLS_LOG(20, "cmp_vals() missing key=%s", key.c_str());
+ return -ECANCELED;
+ } else {
+ // use optional default for missing keys
+ value = *op.default_value;
+ CLS_LOG(20, "cmp_vals() comparing missing key=%s mode=%d op=%d",
+ key.c_str(), (int)op.mode, (int)op.comparison);
+ }
+
+ r = compare_value(op.mode, op.comparison, input, value);
+ if (r < 0) {
+ CLS_LOG(10, "cmp_vals() failed to compare key=%s r=%d", key.c_str(), r);
+ return r;
+ }
+ if (r == 0) {
+ CLS_LOG(10, "cmp_vals() comparison at key=%s returned false", key.c_str());
+ return -ECANCELED;
+ }
+ CLS_LOG(20, "cmp_vals() comparison at key=%s returned true", key.c_str());
+ }
+
+ return 0;
+}
+
+static int cmp_set_vals(cls_method_context_t hctx, bufferlist *in, bufferlist *out)
+{
+ cmp_set_vals_op op;
+ try {
+ auto p = in->cbegin();
+ decode(op, p);
+ } catch (const buffer::error&) {
+ CLS_LOG(1, "ERROR: cmp_set_vals(): failed to decode input");
+ return -EINVAL;
+ }
+
+ // collect the keys we need to read
+ std::set<std::string> keys;
+ for (const auto& kv : op.values) {
+ keys.insert(kv.first);
+ }
+
+ // read the values for each key to compare
+ std::map<std::string, bufferlist> values;
+ int r = cls_cxx_map_get_vals_by_keys(hctx, keys, &values);
+ if (r < 0) {
+ CLS_LOG(4, "ERROR: cmp_set_vals() failed to read values r=%d", r);
+ return r;
+ }
+
+ auto v = values.begin();
+ for (const auto& [key, input] : op.values) {
+ auto k = values.end();
+ bufferlist value;
+ if (v != values.end() && v->first == key) {
+ value = std::move(v->second);
+ k = v++;
+ CLS_LOG(20, "cmp_set_vals() comparing key=%s mode=%d op=%d",
+ key.c_str(), (int)op.mode, (int)op.comparison);
+ } else if (!op.default_value) {
+ CLS_LOG(20, "cmp_set_vals() missing key=%s", key.c_str());
+ continue;
+ } else {
+ // use optional default for missing keys
+ value = *op.default_value;
+ CLS_LOG(20, "cmp_set_vals() comparing missing key=%s mode=%d op=%d",
+ key.c_str(), (int)op.mode, (int)op.comparison);
+ }
+
+ r = compare_value(op.mode, op.comparison, input, value);
+ if (r == -EIO) {
+ r = 0; // treat EIO as a failed comparison
+ }
+ if (r < 0) {
+ CLS_LOG(10, "cmp_set_vals() failed to compare key=%s r=%d",
+ key.c_str(), r);
+ return r;
+ }
+ if (r == 0) {
+ // unsuccessful comparison
+ if (k != values.end()) {
+ values.erase(k); // remove this key from the values to overwrite
+ CLS_LOG(20, "cmp_set_vals() not overwriting key=%s", key.c_str());
+ } else {
+ CLS_LOG(20, "cmp_set_vals() not writing missing key=%s", key.c_str());
+ }
+ } else {
+ // successful comparison
+ if (k != values.end()) {
+ // overwrite the value
+ k->second = std::move(input);
+ CLS_LOG(20, "cmp_set_vals() overwriting key=%s", key.c_str());
+ } else {
+ // insert the value
+ values.emplace(key, std::move(input));
+ CLS_LOG(20, "cmp_set_vals() overwriting missing key=%s", key.c_str());
+ }
+ }
+ }
+
+ if (values.empty()) {
+ CLS_LOG(20, "cmp_set_vals() has no values to overwrite");
+ return 0;
+ }
+
+ CLS_LOG(20, "cmp_set_vals() overwriting count=%d", (int)values.size());
+ return cls_cxx_map_set_vals(hctx, &values);
+}
+
+static int cmp_rm_keys(cls_method_context_t hctx, bufferlist *in, bufferlist *out)
+{
+ cmp_rm_keys_op op;
+ try {
+ auto p = in->cbegin();
+ decode(op, p);
+ } catch (const buffer::error&) {
+ CLS_LOG(1, "ERROR: cmp_rm_keys(): failed to decode input");
+ return -EINVAL;
+ }
+
+ // collect the keys we need to read
+ std::set<std::string> keys;
+ for (const auto& kv : op.values) {
+ keys.insert(kv.first);
+ }
+
+ // read the values for each key to compare
+ std::map<std::string, bufferlist> values;
+ int r = cls_cxx_map_get_vals_by_keys(hctx, keys, &values);
+ if (r < 0) {
+ CLS_LOG(4, "ERROR: cmp_rm_keys() failed to read values r=%d", r);
+ return r;
+ }
+
+ auto v = values.cbegin();
+ for (const auto& [key, input] : op.values) {
+ if (v == values.end() || v->first != key) {
+ CLS_LOG(20, "cmp_rm_keys() missing key=%s", key.c_str());
+ continue;
+ }
+ CLS_LOG(20, "cmp_rm_keys() comparing key=%s mode=%d op=%d",
+ key.c_str(), (int)op.mode, (int)op.comparison);
+
+ const bufferlist& value = v->second;
+ ++v;
+
+ r = compare_value(op.mode, op.comparison, input, value);
+ if (r == -EIO) {
+ r = 0; // treat EIO as a failed comparison
+ }
+ if (r < 0) {
+ CLS_LOG(10, "cmp_rm_keys() failed to compare key=%s r=%d",
+ key.c_str(), r);
+ return r;
+ }
+ if (r == 0) {
+ // unsuccessful comparison
+ CLS_LOG(20, "cmp_rm_keys() preserving key=%s", key.c_str());
+ } else {
+ // successful comparison
+ CLS_LOG(20, "cmp_rm_keys() removing key=%s", key.c_str());
+ r = cls_cxx_map_remove_key(hctx, key);
+ if (r < 0) {
+ CLS_LOG(1, "ERROR: cmp_rm_keys() failed to remove key=%s r=%d",
+ key.c_str(), r);
+ return r;
+ }
+ }
+ }
+
+ return 0;
+}
+
+CLS_INIT(cmpomap)
+{
+ CLS_LOG(1, "Loaded cmpomap class!");
+
+ cls_handle_t h_class;
+ cls_method_handle_t h_cmp_vals;
+ cls_method_handle_t h_cmp_set_vals;
+ cls_method_handle_t h_cmp_rm_keys;
+
+ cls_register("cmpomap", &h_class);
+
+ cls_register_cxx_method(h_class, "cmp_vals", CLS_METHOD_RD,
+ cmp_vals, &h_cmp_vals);
+ cls_register_cxx_method(h_class, "cmp_set_vals", CLS_METHOD_RD | CLS_METHOD_WR,
+ cmp_set_vals, &h_cmp_set_vals);
+ cls_register_cxx_method(h_class, "cmp_rm_keys", CLS_METHOD_RD | CLS_METHOD_WR,
+ cmp_rm_keys, &h_cmp_rm_keys);
+}
diff --git a/src/cls/cmpomap/types.h b/src/cls/cmpomap/types.h
new file mode 100644
index 000000000..11e39575f
--- /dev/null
+++ b/src/cls/cmpomap/types.h
@@ -0,0 +1,44 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab ft=cpp
+
+/*
+ * Ceph - scalable distributed file system
+ *
+ * Copyright (C) 2020 Red Hat, Inc
+ *
+ * This is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2.1, as published by the Free Software
+ * Foundation. See file COPYING.
+ */
+
+#pragma once
+
+#include <string>
+#include <boost/container/flat_map.hpp>
+#include "include/rados.h" // CEPH_OSD_CMPXATTR_*
+#include "include/encoding.h"
+
+namespace cls::cmpomap {
+
+/// comparison operand type
+enum class Mode : uint8_t {
+ String = CEPH_OSD_CMPXATTR_MODE_STRING,
+ U64 = CEPH_OSD_CMPXATTR_MODE_U64,
+};
+
+/// comparison operation, where the left-hand operand is the input value and
+/// the right-hand operand is the stored value (or the optional default)
+enum class Op : uint8_t {
+ EQ = CEPH_OSD_CMPXATTR_OP_EQ,
+ NE = CEPH_OSD_CMPXATTR_OP_NE,
+ GT = CEPH_OSD_CMPXATTR_OP_GT,
+ GTE = CEPH_OSD_CMPXATTR_OP_GTE,
+ LT = CEPH_OSD_CMPXATTR_OP_LT,
+ LTE = CEPH_OSD_CMPXATTR_OP_LTE,
+};
+
+/// mapping of omap keys to value comparisons
+using ComparisonMap = boost::container::flat_map<std::string, ceph::bufferlist>;
+
+} // namespace cls::cmpomap