]>
Commit | Line | Data |
---|---|---|
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 | ||
29 | using namespace rados::cls::lock; | |
30 | ||
31 | ||
32 | CLS_VER(1,0) | |
33 | CLS_NAME(lock) | |
34 | ||
35 | #define LOCK_PREFIX "lock." | |
36 | ||
7c673cae FG |
37 | static 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 | ||
85 | static 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 | */ | |
115 | static 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 | */ | |
210 | static 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 | */ | |
237 | static 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 | */ | |
275 | static 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 | */ | |
303 | static 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 | */ | |
330 | static 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 | */ | |
375 | static 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 | */ | |
412 | int 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 | */ | |
484 | int 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 | ||
561 | CLS_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 | } |