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