]> git.proxmox.com Git - ceph.git/blame - ceph/src/cls/lock/cls_lock.cc
update sources to v12.1.1
[ceph.git] / ceph / src / cls / lock / cls_lock.cc
CommitLineData
7c673cae
FG
1// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2// vim: ts=8 sw=2 smarttab
3
4/** \file
5 *
6 * This is an OSD class that implements methods for object
7 * advisory locking.
8 *
9 */
10
7c673cae 11#include <errno.h>
7c673cae 12#include <map>
7c673cae
FG
13
14#include "include/types.h"
15#include "include/utime.h"
16#include "objclass/objclass.h"
17
18#include "common/errno.h"
19#include "common/Clock.h"
20
21#include "cls/lock/cls_lock_types.h"
22#include "cls/lock/cls_lock_ops.h"
23
24#include "global/global_context.h"
25
26#include "include/compat.h"
27
28
29using namespace rados::cls::lock;
30
31
32CLS_VER(1,0)
33CLS_NAME(lock)
34
35#define LOCK_PREFIX "lock."
36
7c673cae
FG
37static int read_lock(cls_method_context_t hctx, const string& name, lock_info_t *lock)
38{
39 bufferlist bl;
40 string key = LOCK_PREFIX;
41 key.append(name);
42
43 int r = cls_cxx_getxattr(hctx, key.c_str(), &bl);
44 if (r < 0) {
45 if (r == -ENODATA) {
46 *lock = lock_info_t();
47 return 0;
48 }
49 if (r != -ENOENT) {
50 CLS_ERR("error reading xattr %s: %d", key.c_str(), r);
51 }
52 return r;
53 }
54
55 try {
56 bufferlist::iterator it = bl.begin();
57 ::decode(*lock, it);
58 } catch (const buffer::error &err) {
59 CLS_ERR("error decoding %s", key.c_str());
60 return -EIO;
61 }
62
63 /* now trim expired locks */
64
65 utime_t now = ceph_clock_now();
66
67 map<locker_id_t, locker_info_t>::iterator iter = lock->lockers.begin();
68
69 while (iter != lock->lockers.end()) {
70 map<locker_id_t, locker_info_t>::iterator next = iter;
71 ++next;
72
73 struct locker_info_t& info = iter->second;
74 if (!info.expiration.is_zero() && info.expiration < now) {
75 CLS_LOG(20, "expiring locker");
76 lock->lockers.erase(iter);
77 }
78
79 iter = next;
80 }
81
82 return 0;
83}
84
85static int write_lock(cls_method_context_t hctx, const string& name, const lock_info_t& lock)
86{
87 string key = LOCK_PREFIX;
88 key.append(name);
89
90 bufferlist lock_bl;
91 ::encode(lock, lock_bl, cls_get_client_features(hctx));
92
93 int r = cls_cxx_setxattr(hctx, key.c_str(), &lock_bl);
94 if (r < 0)
95 return r;
96
97 return 0;
98}
99
100/**
101 * helper function to add a lock and update disk state.
102 *
103 * Input:
104 * @param name Lock name
105 * @param lock_type Type of lock (exclusive / shared)
106 * @param duration Duration of lock (in seconds). Zero means it doesn't expire.
107 * @param flags lock flags
108 * @param cookie The cookie to set in the lock
109 * @param tag The tag to match with the lock (can only lock with matching tags)
110 * @param lock_description The lock description to set (if not empty)
111 * @param locker_description The locker description
112 *
113 * @return 0 on success, or -errno on failure
114 */
115static int lock_obj(cls_method_context_t hctx,
116 const string& name,
117 ClsLockType lock_type,
118 utime_t duration,
119 const string& description,
120 uint8_t flags,
121 const string& cookie,
122 const string& tag)
123{
124 bool exclusive = lock_type == LOCK_EXCLUSIVE;
125 lock_info_t linfo;
126 bool fail_if_exists = (flags & LOCK_FLAG_RENEW) == 0;
127
128 CLS_LOG(20, "requested lock_type=%s fail_if_exists=%d", cls_lock_type_str(lock_type), fail_if_exists);
129 if (lock_type != LOCK_EXCLUSIVE &&
130 lock_type != LOCK_SHARED)
131 return -EINVAL;
132
133 if (name.empty())
134 return -EINVAL;
135
136 // see if there's already a locker
137 int r = read_lock(hctx, name, &linfo);
138 if (r < 0 && r != -ENOENT) {
139 CLS_ERR("Could not read lock info: %s", cpp_strerror(r).c_str());
140 return r;
141 }
142 map<locker_id_t, locker_info_t>& lockers = linfo.lockers;
143 map<locker_id_t, locker_info_t>::iterator iter;
144
145 locker_id_t id;
146 id.cookie = cookie;
147 entity_inst_t inst;
148 r = cls_get_request_origin(hctx, &inst);
149 id.locker = inst.name;
150 assert(r == 0);
151
152 /* check this early, before we check fail_if_exists, otherwise we might
153 * remove the locker entry and not check it later */
154 if (lockers.size() && tag != linfo.tag) {
155 CLS_LOG(20, "cannot take lock on object, conflicting tag");
156 return -EBUSY;
157 }
158
159 ClsLockType existing_lock_type = linfo.lock_type;
160 CLS_LOG(20, "existing_lock_type=%s", cls_lock_type_str(existing_lock_type));
161 iter = lockers.find(id);
162 if (iter != lockers.end()) {
163 if (fail_if_exists) {
164 return -EEXIST;
165 } else {
166 lockers.erase(iter); // remove old entry
167 }
168 }
169
170 if (!lockers.empty()) {
171 if (exclusive) {
172 CLS_LOG(20, "could not exclusive-lock object, already locked");
173 return -EBUSY;
174 }
175
176 if (existing_lock_type != lock_type) {
177 CLS_LOG(20, "cannot take lock on object, conflicting lock type");
178 return -EBUSY;
179 }
180 }
181
182 linfo.lock_type = lock_type;
183 linfo.tag = tag;
184 utime_t expiration;
185 if (!duration.is_zero()) {
186 expiration = ceph_clock_now();
187 expiration += duration;
188
189 }
190 struct locker_info_t info(expiration, inst.addr, description);
191
192 linfo.lockers[id] = info;
193
194 r = write_lock(hctx, name, linfo);
195 if (r < 0)
196 return r;
197
198 return 0;
199}
200
201/**
202 * Set an exclusive lock on an object for the activating client, if possible.
203 *
204 * Input:
205 * @param cls_lock_lock_op request input
206 *
207 * @returns 0 on success, -EINVAL if it can't decode the lock_cookie,
208 * -EBUSY if the object is already locked, or -errno on (unexpected) failure.
209 */
210static int lock_op(cls_method_context_t hctx,
211 bufferlist *in, bufferlist *out)
212{
213 CLS_LOG(20, "lock_op");
214 cls_lock_lock_op op;
215 try {
216 bufferlist::iterator iter = in->begin();
217 ::decode(op, iter);
218 } catch (const buffer::error &err) {
219 return -EINVAL;
220 }
221
222 return lock_obj(hctx,
223 op.name, op.type, op.duration, op.description,
224 op.flags, op.cookie, op.tag);
225}
226
227/**
228 * helper function to remove a lock from on disk and clean up state.
229 *
230 * @param name The lock name
231 * @param locker The locker entity name
232 * @param cookie The user-defined cookie associated with the lock.
233 *
234 * @return 0 on success, -ENOENT if there is no such lock (either
235 * entity or cookie is wrong), or -errno on other error.
236 */
237static int remove_lock(cls_method_context_t hctx,
238 const string& name,
239 entity_name_t& locker,
240 const string& cookie)
241{
242 // get current lockers
243 lock_info_t linfo;
244 int r = read_lock(hctx, name, &linfo);
245 if (r < 0) {
246 CLS_ERR("Could not read list of current lockers off disk: %s", cpp_strerror(r).c_str());
247 return r;
248 }
249
250 map<locker_id_t, locker_info_t>& lockers = linfo.lockers;
251 struct locker_id_t id(locker, cookie);
252
253 // remove named locker from set
254 map<locker_id_t, locker_info_t>::iterator iter = lockers.find(id);
255 if (iter == lockers.end()) { // no such key
256 return -ENOENT;
257 }
258 lockers.erase(iter);
259
260 r = write_lock(hctx, name, linfo);
261
262 return r;
263}
264
265/**
266 * Unlock an object which the activating client currently has locked.
267 *
268 * Input:
269 * @param cls_lock_unlock_op request input
270 *
271 * @return 0 on success, -EINVAL if it can't decode the cookie, -ENOENT
272 * if there is no such lock (either entity or cookie is wrong), or
273 * -errno on other (unexpected) error.
274 */
275static int unlock_op(cls_method_context_t hctx,
276 bufferlist *in, bufferlist *out)
277{
278 CLS_LOG(20, "unlock_op");
279 cls_lock_unlock_op op;
280 try {
281 bufferlist::iterator iter = in->begin();
282 ::decode(op, iter);
283 } catch (const buffer::error& err) {
284 return -EINVAL;
285 }
286
287 entity_inst_t inst;
288 int r = cls_get_request_origin(hctx, &inst);
289 assert(r == 0);
290 return remove_lock(hctx, op.name, inst.name, op.cookie);
291}
292
293/**
294 * Break the lock on an object held by any client.
295 *
296 * Input:
297 * @param cls_lock_break_op request input
298 *
299 * @return 0 on success, -EINVAL if it can't decode the locker and
300 * cookie, -ENOENT if there is no such lock (either entity or cookie
301 * is wrong), or -errno on other (unexpected) error.
302 */
303static int break_lock(cls_method_context_t hctx,
304 bufferlist *in, bufferlist *out)
305{
306 CLS_LOG(20, "break_lock");
307 cls_lock_break_op op;
308 try {
309 bufferlist::iterator iter = in->begin();
310 ::decode(op, iter);
311 } catch (const buffer::error& err) {
312 return -EINVAL;
313 }
314
315 return remove_lock(hctx, op.name, op.locker, op.cookie);
316}
317
318
319/**
320 * Retrieve lock info: lockers, tag, exclusive
321 *
322 * Input:
323 * @param cls_lock_list_lockers_op request input
324 *
325 * Output:
326 * @param cls_lock_list_lockers_reply result
327 *
328 * @return 0 on success, -errno on failure.
329 */
330static int get_info(cls_method_context_t hctx, bufferlist *in, bufferlist *out)
331{
332 CLS_LOG(20, "get_info");
333 cls_lock_get_info_op op;
334 try {
335 bufferlist::iterator iter = in->begin();
336 ::decode(op, iter);
337 } catch (const buffer::error& err) {
338 return -EINVAL;
339 }
340
341 // get current lockers
342 lock_info_t linfo;
343 int r = read_lock(hctx, op.name, &linfo);
344 if (r < 0) {
345 CLS_ERR("Could not read lock info: %s", cpp_strerror(r).c_str());
346 return r;
347 }
348
349 struct cls_lock_get_info_reply ret;
350
351 map<locker_id_t, locker_info_t>::iterator iter;
352 for (iter = linfo.lockers.begin(); iter != linfo.lockers.end(); ++iter) {
353 ret.lockers[iter->first] = iter->second;
354 }
355 ret.lock_type = linfo.lock_type;
356 ret.tag = linfo.tag;
357
358 ::encode(ret, *out, cls_get_client_features(hctx));
359
360 return 0;
361}
362
363
364/**
365 * Retrieve a list of locks for this object
366 *
367 * Input:
368 * @param in is ignored.
369 *
370 * Output:
371 * @param out contains encoded cls_list_locks_reply
372 *
373 * @return 0 on success, -errno on failure.
374 */
375static int list_locks(cls_method_context_t hctx, bufferlist *in, bufferlist *out)
376{
377 CLS_LOG(20, "list_locks");
378
379 map<string, bufferlist> attrs;
380
381 int r = cls_cxx_getxattrs(hctx, &attrs);
382 if (r < 0)
383 return r;
384
385 cls_lock_list_locks_reply ret;
386
387 map<string, bufferlist>::iterator iter;
388 size_t pos = sizeof(LOCK_PREFIX) - 1;
389 for (iter = attrs.begin(); iter != attrs.end(); ++iter) {
390 const string& attr = iter->first;
391 if (attr.substr(0, pos).compare(LOCK_PREFIX) == 0) {
392 ret.locks.push_back(attr.substr(pos));
393 }
394 }
395
396 ::encode(ret, *out);
397
398 return 0;
399}
400
401/**
402 * Assert that the object is currently locked
403 *
404 * Input:
405 * @param cls_lock_assert_op request input
406 *
407 * Output:
408 * @param none
409 *
410 * @return 0 on success, -errno on failure.
411 */
412int assert_locked(cls_method_context_t hctx, bufferlist *in, bufferlist *out)
413{
414 CLS_LOG(20, "assert_locked");
415
416 cls_lock_assert_op op;
417 try {
418 bufferlist::iterator iter = in->begin();
419 ::decode(op, iter);
420 } catch (const buffer::error& err) {
421 return -EINVAL;
422 }
423
424 if (op.type != LOCK_EXCLUSIVE && op.type != LOCK_SHARED) {
425 return -EINVAL;
426 }
427
428 if (op.name.empty()) {
429 return -EINVAL;
430 }
431
432 // see if there's already a locker
433 lock_info_t linfo;
434 int r = read_lock(hctx, op.name, &linfo);
435 if (r < 0) {
436 CLS_ERR("Could not read lock info: %s", cpp_strerror(r).c_str());
437 return r;
438 }
439
440 if (linfo.lockers.empty()) {
441 CLS_LOG(20, "object not locked");
442 return -EBUSY;
443 }
444
445 if (linfo.lock_type != op.type) {
446 CLS_LOG(20, "lock type mismatch: current=%s, assert=%s",
447 cls_lock_type_str(linfo.lock_type), cls_lock_type_str(op.type));
448 return -EBUSY;
449 }
450
451 if (linfo.tag != op.tag) {
452 CLS_LOG(20, "lock tag mismatch: current=%s, assert=%s", linfo.tag.c_str(),
453 op.tag.c_str());
454 return -EBUSY;
455 }
456
457 entity_inst_t inst;
458 r = cls_get_request_origin(hctx, &inst);
459 assert(r == 0);
460
461 locker_id_t id;
462 id.cookie = op.cookie;
463 id.locker = inst.name;
464
465 map<locker_id_t, locker_info_t>::iterator iter = linfo.lockers.find(id);
466 if (iter == linfo.lockers.end()) {
467 CLS_LOG(20, "not locked by assert client");
468 return -EBUSY;
469 }
470 return 0;
471}
472
473/**
474 * Update the cookie associated with an object lock
475 *
476 * Input:
477 * @param cls_lock_set_cookie_op request input
478 *
479 * Output:
480 * @param none
481 *
482 * @return 0 on success, -errno on failure.
483 */
484int set_cookie(cls_method_context_t hctx, bufferlist *in, bufferlist *out)
485{
486 CLS_LOG(20, "set_cookie");
487
488 cls_lock_set_cookie_op op;
489 try {
490 bufferlist::iterator iter = in->begin();
491 ::decode(op, iter);
492 } catch (const buffer::error& err) {
493 return -EINVAL;
494 }
495
496 if (op.type != LOCK_EXCLUSIVE && op.type != LOCK_SHARED) {
497 return -EINVAL;
498 }
499
500 if (op.name.empty()) {
501 return -EINVAL;
502 }
503
504 // see if there's already a locker
505 lock_info_t linfo;
506 int r = read_lock(hctx, op.name, &linfo);
507 if (r < 0) {
508 CLS_ERR("Could not read lock info: %s", cpp_strerror(r).c_str());
509 return r;
510 }
511
512 if (linfo.lockers.empty()) {
513 CLS_LOG(20, "object not locked");
514 return -EBUSY;
515 }
516
517 if (linfo.lock_type != op.type) {
518 CLS_LOG(20, "lock type mismatch: current=%s, assert=%s",
519 cls_lock_type_str(linfo.lock_type), cls_lock_type_str(op.type));
520 return -EBUSY;
521 }
522
523 if (linfo.tag != op.tag) {
524 CLS_LOG(20, "lock tag mismatch: current=%s, assert=%s", linfo.tag.c_str(),
525 op.tag.c_str());
526 return -EBUSY;
527 }
528
529 entity_inst_t inst;
530 r = cls_get_request_origin(hctx, &inst);
531 assert(r == 0);
532
533 locker_id_t id;
534 id.cookie = op.cookie;
535 id.locker = inst.name;
536
537 map<locker_id_t, locker_info_t>::iterator iter = linfo.lockers.find(id);
538 if (iter == linfo.lockers.end()) {
539 CLS_LOG(20, "not locked by client");
540 return -EBUSY;
541 }
542
543 id.cookie = op.new_cookie;
544 if (linfo.lockers.count(id) != 0) {
545 CLS_LOG(20, "lock cookie in-use");
546 return -EBUSY;
547 }
548
549 locker_info_t locker_info(iter->second);
550 linfo.lockers.erase(iter);
551
552 linfo.lockers[id] = locker_info;
553 r = write_lock(hctx, op.name, linfo);
554 if (r < 0) {
555 CLS_ERR("Could not update lock info: %s", cpp_strerror(r).c_str());
556 return r;
557 }
558 return 0;
559}
560
561CLS_INIT(lock)
562{
563 CLS_LOG(20, "Loaded lock class!");
564
565 cls_handle_t h_class;
566 cls_method_handle_t h_lock_op;
567 cls_method_handle_t h_unlock_op;
568 cls_method_handle_t h_break_lock;
569 cls_method_handle_t h_get_info;
570 cls_method_handle_t h_list_locks;
571 cls_method_handle_t h_assert_locked;
572 cls_method_handle_t h_set_cookie;
573
574 cls_register("lock", &h_class);
575 cls_register_cxx_method(h_class, "lock",
576 CLS_METHOD_RD | CLS_METHOD_WR | CLS_METHOD_PROMOTE,
577 lock_op, &h_lock_op);
578 cls_register_cxx_method(h_class, "unlock",
579 CLS_METHOD_RD | CLS_METHOD_WR | CLS_METHOD_PROMOTE,
580 unlock_op, &h_unlock_op);
581 cls_register_cxx_method(h_class, "break_lock",
582 CLS_METHOD_RD | CLS_METHOD_WR,
583 break_lock, &h_break_lock);
584 cls_register_cxx_method(h_class, "get_info",
585 CLS_METHOD_RD,
586 get_info, &h_get_info);
587 cls_register_cxx_method(h_class, "list_locks",
588 CLS_METHOD_RD,
589 list_locks, &h_list_locks);
590 cls_register_cxx_method(h_class, "assert_locked",
591 CLS_METHOD_RD | CLS_METHOD_PROMOTE,
592 assert_locked, &h_assert_locked);
593 cls_register_cxx_method(h_class, "set_cookie",
594 CLS_METHOD_RD | CLS_METHOD_WR | CLS_METHOD_PROMOTE,
595 set_cookie, &h_set_cookie);
596
597 return;
598}