1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab
6 * This is an OSD class that implements methods for object
14 #include "include/types.h"
15 #include "include/utime.h"
16 #include "objclass/objclass.h"
18 #include "common/errno.h"
19 #include "common/Clock.h"
21 #include "cls/lock/cls_lock_types.h"
22 #include "cls/lock/cls_lock_ops.h"
24 #include "global/global_context.h"
26 #include "include/compat.h"
29 using namespace rados::cls::lock
;
35 #define LOCK_PREFIX "lock."
37 static int read_lock(cls_method_context_t hctx
, const string
& name
, lock_info_t
*lock
)
40 string key
= LOCK_PREFIX
;
43 int r
= cls_cxx_getxattr(hctx
, key
.c_str(), &bl
);
46 *lock
= lock_info_t();
50 CLS_ERR("error reading xattr %s: %d", key
.c_str(), r
);
56 bufferlist::iterator it
= bl
.begin();
58 } catch (const buffer::error
&err
) {
59 CLS_ERR("error decoding %s", key
.c_str());
63 /* now trim expired locks */
65 utime_t now
= ceph_clock_now();
67 map
<locker_id_t
, locker_info_t
>::iterator iter
= lock
->lockers
.begin();
69 while (iter
!= lock
->lockers
.end()) {
70 map
<locker_id_t
, locker_info_t
>::iterator next
= iter
;
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
);
85 static int write_lock(cls_method_context_t hctx
, const string
& name
, const lock_info_t
& lock
)
87 string key
= LOCK_PREFIX
;
91 ::encode(lock
, lock_bl
, cls_get_client_features(hctx
));
93 int r
= cls_cxx_setxattr(hctx
, key
.c_str(), &lock_bl
);
101 * helper function to add a lock and update disk state.
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
113 * @return 0 on success, or -errno on failure
115 static int lock_obj(cls_method_context_t hctx
,
117 ClsLockType lock_type
,
119 const string
& description
,
121 const string
& cookie
,
124 bool exclusive
= lock_type
== LOCK_EXCLUSIVE
;
126 bool fail_if_exists
= (flags
& LOCK_FLAG_RENEW
) == 0;
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
)
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());
142 map
<locker_id_t
, locker_info_t
>& lockers
= linfo
.lockers
;
143 map
<locker_id_t
, locker_info_t
>::iterator iter
;
148 r
= cls_get_request_origin(hctx
, &inst
);
149 id
.locker
= inst
.name
;
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");
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
) {
166 lockers
.erase(iter
); // remove old entry
170 if (!lockers
.empty()) {
172 CLS_LOG(20, "could not exclusive-lock object, already locked");
176 if (existing_lock_type
!= lock_type
) {
177 CLS_LOG(20, "cannot take lock on object, conflicting lock type");
182 linfo
.lock_type
= lock_type
;
185 if (!duration
.is_zero()) {
186 expiration
= ceph_clock_now();
187 expiration
+= duration
;
190 struct locker_info_t
info(expiration
, inst
.addr
, description
);
192 linfo
.lockers
[id
] = info
;
194 r
= write_lock(hctx
, name
, linfo
);
202 * Set an exclusive lock on an object for the activating client, if possible.
205 * @param cls_lock_lock_op request input
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.
210 static int lock_op(cls_method_context_t hctx
,
211 bufferlist
*in
, bufferlist
*out
)
213 CLS_LOG(20, "lock_op");
216 bufferlist::iterator iter
= in
->begin();
218 } catch (const buffer::error
&err
) {
222 return lock_obj(hctx
,
223 op
.name
, op
.type
, op
.duration
, op
.description
,
224 op
.flags
, op
.cookie
, op
.tag
);
228 * helper function to remove a lock from on disk and clean up state.
230 * @param name The lock name
231 * @param locker The locker entity name
232 * @param cookie The user-defined cookie associated with the lock.
234 * @return 0 on success, -ENOENT if there is no such lock (either
235 * entity or cookie is wrong), or -errno on other error.
237 static int remove_lock(cls_method_context_t hctx
,
239 entity_name_t
& locker
,
240 const string
& cookie
)
242 // get current lockers
244 int r
= read_lock(hctx
, name
, &linfo
);
246 CLS_ERR("Could not read list of current lockers off disk: %s", cpp_strerror(r
).c_str());
250 map
<locker_id_t
, locker_info_t
>& lockers
= linfo
.lockers
;
251 struct locker_id_t
id(locker
, cookie
);
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
260 r
= write_lock(hctx
, name
, linfo
);
266 * Unlock an object which the activating client currently has locked.
269 * @param cls_lock_unlock_op request input
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.
275 static int unlock_op(cls_method_context_t hctx
,
276 bufferlist
*in
, bufferlist
*out
)
278 CLS_LOG(20, "unlock_op");
279 cls_lock_unlock_op op
;
281 bufferlist::iterator iter
= in
->begin();
283 } catch (const buffer::error
& err
) {
288 int r
= cls_get_request_origin(hctx
, &inst
);
290 return remove_lock(hctx
, op
.name
, inst
.name
, op
.cookie
);
294 * Break the lock on an object held by any client.
297 * @param cls_lock_break_op request input
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.
303 static int break_lock(cls_method_context_t hctx
,
304 bufferlist
*in
, bufferlist
*out
)
306 CLS_LOG(20, "break_lock");
307 cls_lock_break_op op
;
309 bufferlist::iterator iter
= in
->begin();
311 } catch (const buffer::error
& err
) {
315 return remove_lock(hctx
, op
.name
, op
.locker
, op
.cookie
);
320 * Retrieve lock info: lockers, tag, exclusive
323 * @param cls_lock_list_lockers_op request input
326 * @param cls_lock_list_lockers_reply result
328 * @return 0 on success, -errno on failure.
330 static int get_info(cls_method_context_t hctx
, bufferlist
*in
, bufferlist
*out
)
332 CLS_LOG(20, "get_info");
333 cls_lock_get_info_op op
;
335 bufferlist::iterator iter
= in
->begin();
337 } catch (const buffer::error
& err
) {
341 // get current lockers
343 int r
= read_lock(hctx
, op
.name
, &linfo
);
345 CLS_ERR("Could not read lock info: %s", cpp_strerror(r
).c_str());
349 struct cls_lock_get_info_reply ret
;
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
;
355 ret
.lock_type
= linfo
.lock_type
;
358 ::encode(ret
, *out
, cls_get_client_features(hctx
));
365 * Retrieve a list of locks for this object
368 * @param in is ignored.
371 * @param out contains encoded cls_list_locks_reply
373 * @return 0 on success, -errno on failure.
375 static int list_locks(cls_method_context_t hctx
, bufferlist
*in
, bufferlist
*out
)
377 CLS_LOG(20, "list_locks");
379 map
<string
, bufferlist
> attrs
;
381 int r
= cls_cxx_getxattrs(hctx
, &attrs
);
385 cls_lock_list_locks_reply ret
;
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
));
402 * Assert that the object is currently locked
405 * @param cls_lock_assert_op request input
410 * @return 0 on success, -errno on failure.
412 int assert_locked(cls_method_context_t hctx
, bufferlist
*in
, bufferlist
*out
)
414 CLS_LOG(20, "assert_locked");
416 cls_lock_assert_op op
;
418 bufferlist::iterator iter
= in
->begin();
420 } catch (const buffer::error
& err
) {
424 if (op
.type
!= LOCK_EXCLUSIVE
&& op
.type
!= LOCK_SHARED
) {
428 if (op
.name
.empty()) {
432 // see if there's already a locker
434 int r
= read_lock(hctx
, op
.name
, &linfo
);
436 CLS_ERR("Could not read lock info: %s", cpp_strerror(r
).c_str());
440 if (linfo
.lockers
.empty()) {
441 CLS_LOG(20, "object not locked");
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
));
451 if (linfo
.tag
!= op
.tag
) {
452 CLS_LOG(20, "lock tag mismatch: current=%s, assert=%s", linfo
.tag
.c_str(),
458 r
= cls_get_request_origin(hctx
, &inst
);
462 id
.cookie
= op
.cookie
;
463 id
.locker
= inst
.name
;
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");
474 * Update the cookie associated with an object lock
477 * @param cls_lock_set_cookie_op request input
482 * @return 0 on success, -errno on failure.
484 int set_cookie(cls_method_context_t hctx
, bufferlist
*in
, bufferlist
*out
)
486 CLS_LOG(20, "set_cookie");
488 cls_lock_set_cookie_op op
;
490 bufferlist::iterator iter
= in
->begin();
492 } catch (const buffer::error
& err
) {
496 if (op
.type
!= LOCK_EXCLUSIVE
&& op
.type
!= LOCK_SHARED
) {
500 if (op
.name
.empty()) {
504 // see if there's already a locker
506 int r
= read_lock(hctx
, op
.name
, &linfo
);
508 CLS_ERR("Could not read lock info: %s", cpp_strerror(r
).c_str());
512 if (linfo
.lockers
.empty()) {
513 CLS_LOG(20, "object not locked");
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
));
523 if (linfo
.tag
!= op
.tag
) {
524 CLS_LOG(20, "lock tag mismatch: current=%s, assert=%s", linfo
.tag
.c_str(),
530 r
= cls_get_request_origin(hctx
, &inst
);
534 id
.cookie
= op
.cookie
;
535 id
.locker
= inst
.name
;
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");
543 id
.cookie
= op
.new_cookie
;
544 if (linfo
.lockers
.count(id
) != 0) {
545 CLS_LOG(20, "lock cookie in-use");
549 locker_info_t
locker_info(iter
->second
);
550 linfo
.lockers
.erase(iter
);
552 linfo
.lockers
[id
] = locker_info
;
553 r
= write_lock(hctx
, op
.name
, linfo
);
555 CLS_ERR("Could not update lock info: %s", cpp_strerror(r
).c_str());
563 CLS_LOG(20, "Loaded lock class!");
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
;
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",
586 get_info
, &h_get_info
);
587 cls_register_cxx_method(h_class
, "list_locks",
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
);