]>
Commit | Line | Data |
---|---|---|
f67539c2 TL |
1 | // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- |
2 | // vim: ts=8 sw=2 smarttab ft=cpp | |
3 | ||
4 | /* | |
5 | * Ceph - scalable distributed file system | |
6 | * | |
7 | * Copyright (C) 2020 Red Hat, Inc | |
8 | * | |
9 | * This is free software; you can redistribute it and/or | |
10 | * modify it under the terms of the GNU Lesser General Public | |
11 | * License version 2.1, as published by the Free Software | |
12 | * Foundation. See file COPYING. | |
13 | */ | |
14 | ||
15 | #include "objclass/objclass.h" | |
16 | #include "ops.h" | |
17 | ||
18 | CLS_VER(1,0) | |
19 | CLS_NAME(cmpomap) | |
20 | ||
21 | using namespace cls::cmpomap; | |
22 | ||
23 | // returns negative error codes or 0/1 for failed/successful comparisons | |
24 | template <typename T> | |
25 | static int compare_values(Op op, const T& lhs, const T& rhs) | |
26 | { | |
27 | switch (op) { | |
28 | case Op::EQ: return (lhs == rhs); | |
29 | case Op::NE: return (lhs != rhs); | |
30 | case Op::GT: return (lhs > rhs); | |
31 | case Op::GTE: return (lhs >= rhs); | |
32 | case Op::LT: return (lhs < rhs); | |
33 | case Op::LTE: return (lhs <= rhs); | |
34 | default: return -EINVAL; | |
35 | } | |
36 | } | |
37 | ||
38 | static int compare_values_u64(Op op, uint64_t lhs, const bufferlist& value) | |
39 | { | |
522d829b TL |
40 | // empty values compare as 0 for backward compat |
41 | uint64_t rhs = 0; | |
42 | if (value.length()) { | |
43 | try { | |
44 | // decode existing value as rhs | |
45 | auto p = value.cbegin(); | |
46 | using ceph::decode; | |
47 | decode(rhs, p); | |
48 | } catch (const buffer::error&) { | |
49 | // failures to decode existing values are reported as EIO | |
50 | return -EIO; | |
51 | } | |
f67539c2 | 52 | } |
522d829b | 53 | return compare_values(op, lhs, rhs); |
f67539c2 TL |
54 | } |
55 | ||
56 | static int compare_value(Mode mode, Op op, const bufferlist& input, | |
57 | const bufferlist& value) | |
58 | { | |
59 | switch (mode) { | |
60 | case Mode::String: | |
61 | return compare_values(op, input, value); | |
62 | case Mode::U64: | |
63 | try { | |
64 | // decode input value as lhs | |
65 | uint64_t lhs; | |
66 | auto p = input.cbegin(); | |
67 | using ceph::decode; | |
68 | decode(lhs, p); | |
69 | return compare_values_u64(op, lhs, value); | |
70 | } catch (const buffer::error&) { | |
71 | // failures to decode input values are reported as EINVAL | |
72 | return -EINVAL; | |
73 | } | |
74 | default: | |
75 | return -EINVAL; | |
76 | } | |
77 | } | |
78 | ||
79 | static int cmp_vals(cls_method_context_t hctx, bufferlist *in, bufferlist *out) | |
80 | { | |
81 | cmp_vals_op op; | |
82 | try { | |
83 | auto p = in->cbegin(); | |
84 | decode(op, p); | |
85 | } catch (const buffer::error&) { | |
86 | CLS_LOG(1, "ERROR: cmp_vals(): failed to decode input"); | |
87 | return -EINVAL; | |
88 | } | |
89 | ||
90 | // collect the keys we need to read | |
91 | std::set<std::string> keys; | |
92 | for (const auto& kv : op.values) { | |
93 | keys.insert(kv.first); | |
94 | } | |
95 | ||
96 | // read the values for each key to compare | |
97 | std::map<std::string, bufferlist> values; | |
98 | int r = cls_cxx_map_get_vals_by_keys(hctx, keys, &values); | |
99 | if (r < 0) { | |
100 | CLS_LOG(4, "ERROR: cmp_vals() failed to read values r=%d", r); | |
101 | return r; | |
102 | } | |
103 | ||
104 | auto v = values.cbegin(); | |
105 | for (const auto& [key, input] : op.values) { | |
106 | bufferlist value; | |
107 | if (v != values.end() && v->first == key) { | |
108 | value = std::move(v->second); | |
109 | ++v; | |
110 | CLS_LOG(20, "cmp_vals() comparing key=%s mode=%d op=%d", | |
111 | key.c_str(), (int)op.mode, (int)op.comparison); | |
112 | } else if (!op.default_value) { | |
113 | CLS_LOG(20, "cmp_vals() missing key=%s", key.c_str()); | |
114 | return -ECANCELED; | |
115 | } else { | |
116 | // use optional default for missing keys | |
117 | value = *op.default_value; | |
118 | CLS_LOG(20, "cmp_vals() comparing missing key=%s mode=%d op=%d", | |
119 | key.c_str(), (int)op.mode, (int)op.comparison); | |
120 | } | |
121 | ||
122 | r = compare_value(op.mode, op.comparison, input, value); | |
123 | if (r < 0) { | |
124 | CLS_LOG(10, "cmp_vals() failed to compare key=%s r=%d", key.c_str(), r); | |
125 | return r; | |
126 | } | |
127 | if (r == 0) { | |
128 | CLS_LOG(10, "cmp_vals() comparison at key=%s returned false", key.c_str()); | |
129 | return -ECANCELED; | |
130 | } | |
131 | CLS_LOG(20, "cmp_vals() comparison at key=%s returned true", key.c_str()); | |
132 | } | |
133 | ||
134 | return 0; | |
135 | } | |
136 | ||
137 | static int cmp_set_vals(cls_method_context_t hctx, bufferlist *in, bufferlist *out) | |
138 | { | |
139 | cmp_set_vals_op op; | |
140 | try { | |
141 | auto p = in->cbegin(); | |
142 | decode(op, p); | |
143 | } catch (const buffer::error&) { | |
144 | CLS_LOG(1, "ERROR: cmp_set_vals(): failed to decode input"); | |
145 | return -EINVAL; | |
146 | } | |
147 | ||
148 | // collect the keys we need to read | |
149 | std::set<std::string> keys; | |
150 | for (const auto& kv : op.values) { | |
151 | keys.insert(kv.first); | |
152 | } | |
153 | ||
154 | // read the values for each key to compare | |
155 | std::map<std::string, bufferlist> values; | |
156 | int r = cls_cxx_map_get_vals_by_keys(hctx, keys, &values); | |
157 | if (r < 0) { | |
158 | CLS_LOG(4, "ERROR: cmp_set_vals() failed to read values r=%d", r); | |
159 | return r; | |
160 | } | |
161 | ||
162 | auto v = values.begin(); | |
163 | for (const auto& [key, input] : op.values) { | |
164 | auto k = values.end(); | |
165 | bufferlist value; | |
166 | if (v != values.end() && v->first == key) { | |
167 | value = std::move(v->second); | |
168 | k = v++; | |
169 | CLS_LOG(20, "cmp_set_vals() comparing key=%s mode=%d op=%d", | |
170 | key.c_str(), (int)op.mode, (int)op.comparison); | |
171 | } else if (!op.default_value) { | |
172 | CLS_LOG(20, "cmp_set_vals() missing key=%s", key.c_str()); | |
173 | continue; | |
174 | } else { | |
175 | // use optional default for missing keys | |
176 | value = *op.default_value; | |
177 | CLS_LOG(20, "cmp_set_vals() comparing missing key=%s mode=%d op=%d", | |
178 | key.c_str(), (int)op.mode, (int)op.comparison); | |
179 | } | |
180 | ||
181 | r = compare_value(op.mode, op.comparison, input, value); | |
182 | if (r == -EIO) { | |
183 | r = 0; // treat EIO as a failed comparison | |
184 | } | |
185 | if (r < 0) { | |
186 | CLS_LOG(10, "cmp_set_vals() failed to compare key=%s r=%d", | |
187 | key.c_str(), r); | |
188 | return r; | |
189 | } | |
190 | if (r == 0) { | |
191 | // unsuccessful comparison | |
192 | if (k != values.end()) { | |
193 | values.erase(k); // remove this key from the values to overwrite | |
194 | CLS_LOG(20, "cmp_set_vals() not overwriting key=%s", key.c_str()); | |
195 | } else { | |
196 | CLS_LOG(20, "cmp_set_vals() not writing missing key=%s", key.c_str()); | |
197 | } | |
198 | } else { | |
199 | // successful comparison | |
200 | if (k != values.end()) { | |
201 | // overwrite the value | |
202 | k->second = std::move(input); | |
203 | CLS_LOG(20, "cmp_set_vals() overwriting key=%s", key.c_str()); | |
204 | } else { | |
205 | // insert the value | |
206 | values.emplace(key, std::move(input)); | |
207 | CLS_LOG(20, "cmp_set_vals() overwriting missing key=%s", key.c_str()); | |
208 | } | |
209 | } | |
210 | } | |
211 | ||
212 | if (values.empty()) { | |
213 | CLS_LOG(20, "cmp_set_vals() has no values to overwrite"); | |
214 | return 0; | |
215 | } | |
216 | ||
217 | CLS_LOG(20, "cmp_set_vals() overwriting count=%d", (int)values.size()); | |
218 | return cls_cxx_map_set_vals(hctx, &values); | |
219 | } | |
220 | ||
221 | static int cmp_rm_keys(cls_method_context_t hctx, bufferlist *in, bufferlist *out) | |
222 | { | |
223 | cmp_rm_keys_op op; | |
224 | try { | |
225 | auto p = in->cbegin(); | |
226 | decode(op, p); | |
227 | } catch (const buffer::error&) { | |
228 | CLS_LOG(1, "ERROR: cmp_rm_keys(): failed to decode input"); | |
229 | return -EINVAL; | |
230 | } | |
231 | ||
232 | // collect the keys we need to read | |
233 | std::set<std::string> keys; | |
234 | for (const auto& kv : op.values) { | |
235 | keys.insert(kv.first); | |
236 | } | |
237 | ||
238 | // read the values for each key to compare | |
239 | std::map<std::string, bufferlist> values; | |
240 | int r = cls_cxx_map_get_vals_by_keys(hctx, keys, &values); | |
241 | if (r < 0) { | |
242 | CLS_LOG(4, "ERROR: cmp_rm_keys() failed to read values r=%d", r); | |
243 | return r; | |
244 | } | |
245 | ||
246 | auto v = values.cbegin(); | |
247 | for (const auto& [key, input] : op.values) { | |
248 | if (v == values.end() || v->first != key) { | |
249 | CLS_LOG(20, "cmp_rm_keys() missing key=%s", key.c_str()); | |
250 | continue; | |
251 | } | |
252 | CLS_LOG(20, "cmp_rm_keys() comparing key=%s mode=%d op=%d", | |
253 | key.c_str(), (int)op.mode, (int)op.comparison); | |
254 | ||
255 | const bufferlist& value = v->second; | |
256 | ++v; | |
257 | ||
258 | r = compare_value(op.mode, op.comparison, input, value); | |
259 | if (r == -EIO) { | |
260 | r = 0; // treat EIO as a failed comparison | |
261 | } | |
262 | if (r < 0) { | |
263 | CLS_LOG(10, "cmp_rm_keys() failed to compare key=%s r=%d", | |
264 | key.c_str(), r); | |
265 | return r; | |
266 | } | |
267 | if (r == 0) { | |
268 | // unsuccessful comparison | |
269 | CLS_LOG(20, "cmp_rm_keys() preserving key=%s", key.c_str()); | |
270 | } else { | |
271 | // successful comparison | |
272 | CLS_LOG(20, "cmp_rm_keys() removing key=%s", key.c_str()); | |
273 | r = cls_cxx_map_remove_key(hctx, key); | |
274 | if (r < 0) { | |
275 | CLS_LOG(1, "ERROR: cmp_rm_keys() failed to remove key=%s r=%d", | |
276 | key.c_str(), r); | |
277 | return r; | |
278 | } | |
279 | } | |
280 | } | |
281 | ||
282 | return 0; | |
283 | } | |
284 | ||
285 | CLS_INIT(cmpomap) | |
286 | { | |
287 | CLS_LOG(1, "Loaded cmpomap class!"); | |
288 | ||
289 | cls_handle_t h_class; | |
290 | cls_method_handle_t h_cmp_vals; | |
291 | cls_method_handle_t h_cmp_set_vals; | |
292 | cls_method_handle_t h_cmp_rm_keys; | |
293 | ||
294 | cls_register("cmpomap", &h_class); | |
295 | ||
296 | cls_register_cxx_method(h_class, "cmp_vals", CLS_METHOD_RD, | |
297 | cmp_vals, &h_cmp_vals); | |
298 | cls_register_cxx_method(h_class, "cmp_set_vals", CLS_METHOD_RD | CLS_METHOD_WR, | |
299 | cmp_set_vals, &h_cmp_set_vals); | |
300 | cls_register_cxx_method(h_class, "cmp_rm_keys", CLS_METHOD_RD | CLS_METHOD_WR, | |
301 | cmp_rm_keys, &h_cmp_rm_keys); | |
302 | } |