source: branches/samba-3.5.x/lib/tdb/common/lock.c

Last change on this file was 847, checked in by Silvan Scherrer, 12 years ago

samba server 3.5: bring 3.5 code base in line with 3.6

File size: 16.8 KB
Line 
1 /*
2 Unix SMB/CIFS implementation.
3
4 trivial database library
5
6 Copyright (C) Andrew Tridgell 1999-2005
7 Copyright (C) Paul `Rusty' Russell 2000
8 Copyright (C) Jeremy Allison 2000-2003
9
10 ** NOTE! The following LGPL license applies to the tdb
11 ** library. This does NOT imply that all of Samba is released
12 ** under the LGPL
13
14 This library is free software; you can redistribute it and/or
15 modify it under the terms of the GNU Lesser General Public
16 License as published by the Free Software Foundation; either
17 version 3 of the License, or (at your option) any later version.
18
19 This library is distributed in the hope that it will be useful,
20 but WITHOUT ANY WARRANTY; without even the implied warranty of
21 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
22 Lesser General Public License for more details.
23
24 You should have received a copy of the GNU Lesser General Public
25 License along with this library; if not, see <http://www.gnu.org/licenses/>.
26*/
27
28#include "tdb_private.h"
29
30#define TDB_MARK_LOCK 0x80000000
31
32
33void tdb_setalarm_sigptr(struct tdb_context *tdb, volatile sig_atomic_t *ptr)
34{
35 tdb->interrupt_sig_ptr = ptr;
36}
37
38#ifdef __OS2__
39static int handle_mutex_lock(struct tdb_context *tdb, HMTX mutex, int rw, int waitflag)
40{
41 ULONG ulTimeout;
42 APIRET rc;
43
44 if (mutex == 0) {
45 printf( "fcntl_lock: sem not initialised\n");
46 exit(1);
47 }
48
49 if (waitflag == F_SETLKW)
50 ulTimeout = SEM_INDEFINITE_WAIT;
51 else
52 ulTimeout = SEM_IMMEDIATE_RETURN;
53
54 switch (rw) {
55 case F_UNLCK:
56 rc = DosReleaseMutexSem(mutex);
57 break;
58 case F_RDLCK: // we never wait for a read lock, as several want to hold one
59 rc = DosRequestMutexSem(mutex, SEM_IMMEDIATE_RETURN);
60 if (rc == ERROR_NOT_OWNER || ERROR_TIMEOUT)
61 rc = NO_ERROR;
62 break;
63 case F_WRLCK:
64 rc = DosRequestMutexSem(mutex, ulTimeout);
65 break;
66 }
67
68 if (rc == NO_ERROR || rc == ERROR_SEM_OWNER_DIED)
69 return 0;
70
71 TDB_LOG((tdb, TDB_DEBUG_TRACE,"handle_mutex_lock: (fd=%d) rw_type=%d waitflag=%d (rc=%d)\n",
72 tdb->fd, rw, waitflag, rc));
73
74 errno = EINVAL;
75 return -1;
76}
77#endif
78
79/* a byte range locking function - return 0 on success
80 this functions locks/unlocks 1 byte at the specified offset.
81
82 On error, errno is also set so that errors are passed back properly
83 through tdb_open().
84
85 note that a len of zero means lock to end of file
86*/
87int tdb_brlock(struct tdb_context *tdb, tdb_off_t offset,
88 int rw_type, int lck_type, int probe, size_t len)
89{
90
91 struct flock fl;
92 int ret;
93
94 if (tdb->flags & TDB_NOLOCK) {
95 return 0;
96 }
97
98 if ((rw_type == F_WRLCK) && (tdb->read_only || tdb->traverse_read)) {
99 tdb->ecode = TDB_ERR_RDONLY;
100 return -1;
101 }
102
103 fl.l_type = rw_type;
104 fl.l_whence = SEEK_SET;
105 fl.l_start = offset;
106#ifdef __OS2__
107 fl.l_len = len ? len:LONG_MAX;
108#else
109 fl.l_len = len;
110#endif
111 fl.l_pid = 0;
112
113#ifdef __OS2__
114 TDB_LOG((tdb, TDB_DEBUG_TRACE,"tdb_brlock: (fd=%d) offset=%d rw_type=%d len=%d lock_type=%d\n",
115 tdb->fd, offset, rw_type, len, lck_type));
116
117 if (offset == ACTIVE_LOCK) {
118 ret = handle_mutex_lock(tdb, tdb->hActiveLock, rw_type, lck_type);
119 } else {
120
121#endif
122 do {
123 ret = fcntl(tdb->fd,lck_type,&fl);
124
125 /* Check for a sigalarm break. */
126 if (ret == -1 && errno == EINTR &&
127 tdb->interrupt_sig_ptr &&
128 *tdb->interrupt_sig_ptr) {
129 break;
130 }
131 } while (ret == -1 && errno == EINTR);
132
133#ifdef __OS2__
134 }
135#endif
136 if (ret == -1) {
137 tdb->ecode = TDB_ERR_LOCK;
138 /* Generic lock error. errno set by fcntl.
139 * EAGAIN is an expected return from non-blocking
140 * locks. */
141 if (!probe && lck_type != F_SETLK) {
142 TDB_LOG((tdb, TDB_DEBUG_TRACE,"tdb_brlock failed (fd=%d) at offset %d rw_type=%d lck_type=%d len=%d\n",
143 tdb->fd, offset, rw_type, lck_type, (int)len));
144 }
145 return -1;
146 }
147 return 0;
148
149}
150
151
152/*
153 upgrade a read lock to a write lock. This needs to be handled in a
154 special way as some OSes (such as solaris) have too conservative
155 deadlock detection and claim a deadlock when progress can be
156 made. For those OSes we may loop for a while.
157*/
158int tdb_brlock_upgrade(struct tdb_context *tdb, tdb_off_t offset, size_t len)
159{
160 int count = 1000;
161 while (count--) {
162 struct timeval tv;
163
164#ifdef __OS2__
165 // we need to remove locks, as upgrade doesn't work
166 tdb_brlock(tdb, offset, F_UNLCK, F_SETLK, 1, len);
167#endif
168 if (tdb_brlock(tdb, offset, F_WRLCK, F_SETLKW, 1, len) == 0) {
169 return 0;
170 }
171 if (errno != EDEADLK) {
172 break;
173 }
174 /* sleep for as short a time as we can - more portable than usleep() */
175 tv.tv_sec = 0;
176 tv.tv_usec = 1;
177 select(0, NULL, NULL, NULL, &tv);
178 }
179 TDB_LOG((tdb, TDB_DEBUG_TRACE,"tdb_brlock_upgrade failed at offset %d\n", offset));
180 return -1;
181}
182
183
184/* lock a list in the database. list -1 is the alloc list */
185static int _tdb_lock(struct tdb_context *tdb, int list, int ltype, int op)
186{
187 struct tdb_lock_type *new_lck;
188 int i;
189 bool mark_lock = ((ltype & TDB_MARK_LOCK) == TDB_MARK_LOCK);
190
191 ltype &= ~TDB_MARK_LOCK;
192
193 /* a global lock allows us to avoid per chain locks */
194 if (tdb->global_lock.count &&
195 (ltype == tdb->global_lock.ltype || ltype == F_RDLCK)) {
196 return 0;
197 }
198
199 if (tdb->global_lock.count) {
200 tdb->ecode = TDB_ERR_LOCK;
201 return -1;
202 }
203
204 if (list < -1 || list >= (int)tdb->header.hash_size) {
205 tdb->ecode = TDB_ERR_LOCK;
206 TDB_LOG((tdb, TDB_DEBUG_ERROR,"tdb_lock: invalid list %d for ltype=%d\n",
207 list, ltype));
208 return -1;
209 }
210 if (tdb->flags & TDB_NOLOCK)
211 return 0;
212
213 for (i=0; i<tdb->num_lockrecs; i++) {
214 if (tdb->lockrecs[i].list == list) {
215 if (tdb->lockrecs[i].count == 0) {
216 /*
217 * Can't happen, see tdb_unlock(). It should
218 * be an assert.
219 */
220 TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_lock: "
221 "lck->count == 0 for list %d", list));
222 }
223 /*
224 * Just increment the in-memory struct, posix locks
225 * don't stack.
226 */
227 tdb->lockrecs[i].count++;
228 return 0;
229 }
230 }
231
232 new_lck = (struct tdb_lock_type *)realloc(
233 tdb->lockrecs,
234 sizeof(*tdb->lockrecs) * (tdb->num_lockrecs+1));
235 if (new_lck == NULL) {
236 errno = ENOMEM;
237 return -1;
238 }
239 tdb->lockrecs = new_lck;
240
241 /* Since fcntl locks don't nest, we do a lock for the first one,
242 and simply bump the count for future ones */
243 if (!mark_lock &&
244 tdb->methods->tdb_brlock(tdb,FREELIST_TOP+4*list, ltype, op,
245 0, 1)) {
246 return -1;
247 }
248
249 tdb->num_locks++;
250
251 tdb->lockrecs[tdb->num_lockrecs].list = list;
252 tdb->lockrecs[tdb->num_lockrecs].count = 1;
253 tdb->lockrecs[tdb->num_lockrecs].ltype = ltype;
254 tdb->num_lockrecs += 1;
255
256 return 0;
257}
258
259/* lock a list in the database. list -1 is the alloc list */
260int tdb_lock(struct tdb_context *tdb, int list, int ltype)
261{
262 int ret;
263 ret = _tdb_lock(tdb, list, ltype, F_SETLKW);
264 if (ret) {
265 TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_lock failed on list %d "
266 "ltype=%d (%s)\n", list, ltype, strerror(errno)));
267 }
268 return ret;
269}
270
271/* lock a list in the database. list -1 is the alloc list. non-blocking lock */
272int tdb_lock_nonblock(struct tdb_context *tdb, int list, int ltype)
273{
274 return _tdb_lock(tdb, list, ltype, F_SETLK);
275}
276
277
278/* unlock the database: returns void because it's too late for errors. */
279 /* changed to return int it may be interesting to know there
280 has been an error --simo */
281int tdb_unlock(struct tdb_context *tdb, int list, int ltype)
282{
283 int ret = -1;
284 int i;
285 struct tdb_lock_type *lck = NULL;
286 bool mark_lock = ((ltype & TDB_MARK_LOCK) == TDB_MARK_LOCK);
287
288 ltype &= ~TDB_MARK_LOCK;
289
290 /* a global lock allows us to avoid per chain locks */
291 if (tdb->global_lock.count &&
292 (ltype == tdb->global_lock.ltype || ltype == F_RDLCK)) {
293 return 0;
294 }
295
296 if (tdb->global_lock.count) {
297 tdb->ecode = TDB_ERR_LOCK;
298 return -1;
299 }
300
301 if (tdb->flags & TDB_NOLOCK)
302 return 0;
303
304 /* Sanity checks */
305 if (list < -1 || list >= (int)tdb->header.hash_size) {
306 TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_unlock: list %d invalid (%d)\n", list, tdb->header.hash_size));
307 return ret;
308 }
309
310 for (i=0; i<tdb->num_lockrecs; i++) {
311 if (tdb->lockrecs[i].list == list) {
312 lck = &tdb->lockrecs[i];
313 break;
314 }
315 }
316
317 if ((lck == NULL) || (lck->count == 0)) {
318 TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_unlock: count is 0\n"));
319 return -1;
320 }
321
322 if (lck->count > 1) {
323 lck->count--;
324 return 0;
325 }
326
327 /*
328 * This lock has count==1 left, so we need to unlock it in the
329 * kernel. We don't bother with decrementing the in-memory array
330 * element, we're about to overwrite it with the last array element
331 * anyway.
332 */
333
334 if (mark_lock) {
335 ret = 0;
336 } else {
337 ret = tdb->methods->tdb_brlock(tdb, FREELIST_TOP+4*list, F_UNLCK,
338 F_SETLKW, 0, 1);
339 }
340 tdb->num_locks--;
341
342 /*
343 * Shrink the array by overwriting the element just unlocked with the
344 * last array element.
345 */
346
347 if (tdb->num_lockrecs > 1) {
348 *lck = tdb->lockrecs[tdb->num_lockrecs-1];
349 }
350 tdb->num_lockrecs -= 1;
351
352 /*
353 * We don't bother with realloc when the array shrinks, but if we have
354 * a completely idle tdb we should get rid of the locked array.
355 */
356
357 if (tdb->num_lockrecs == 0) {
358 SAFE_FREE(tdb->lockrecs);
359 }
360
361 if (ret)
362 TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_unlock: An error occurred unlocking!\n"));
363 return ret;
364}
365
366/*
367 get the transaction lock
368 */
369int tdb_transaction_lock(struct tdb_context *tdb, int ltype)
370{
371 if (tdb->global_lock.count) {
372 return 0;
373 }
374 if (tdb->transaction_lock_count > 0) {
375 tdb->transaction_lock_count++;
376 return 0;
377 }
378
379 if (tdb->methods->tdb_brlock(tdb, TRANSACTION_LOCK, ltype,
380 F_SETLKW, 0, 1) == -1) {
381 TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_transaction_lock: failed to get transaction lock\n"));
382 tdb->ecode = TDB_ERR_LOCK;
383 return -1;
384 }
385 tdb->transaction_lock_count++;
386 return 0;
387}
388
389/*
390 release the transaction lock
391 */
392int tdb_transaction_unlock(struct tdb_context *tdb)
393{
394 int ret;
395 if (tdb->global_lock.count) {
396 return 0;
397 }
398 if (tdb->transaction_lock_count > 1) {
399 tdb->transaction_lock_count--;
400 return 0;
401 }
402 ret = tdb->methods->tdb_brlock(tdb, TRANSACTION_LOCK, F_UNLCK, F_SETLKW, 0, 1);
403 if (ret == 0) {
404 tdb->transaction_lock_count = 0;
405 }
406 return ret;
407}
408
409
410
411
412/* lock/unlock entire database */
413static int _tdb_lockall(struct tdb_context *tdb, int ltype, int op)
414{
415 bool mark_lock = ((ltype & TDB_MARK_LOCK) == TDB_MARK_LOCK);
416
417 ltype &= ~TDB_MARK_LOCK;
418
419 /* There are no locks on read-only dbs */
420 if (tdb->read_only || tdb->traverse_read) {
421 tdb->ecode = TDB_ERR_LOCK;
422 return -1;
423 }
424
425 if (tdb->global_lock.count && tdb->global_lock.ltype == ltype) {
426 tdb->global_lock.count++;
427 return 0;
428 }
429
430 if (tdb->global_lock.count) {
431 /* a global lock of a different type exists */
432 tdb->ecode = TDB_ERR_LOCK;
433 return -1;
434 }
435
436 if (tdb->num_locks != 0) {
437 /* can't combine global and chain locks */
438 tdb->ecode = TDB_ERR_LOCK;
439 return -1;
440 }
441
442 if (!mark_lock &&
443 tdb->methods->tdb_brlock(tdb, FREELIST_TOP, ltype, op,
444 0, 4*tdb->header.hash_size)) {
445 if (op == F_SETLKW) {
446 TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_lockall failed (%s)\n", strerror(errno)));
447 }
448 return -1;
449 }
450
451 tdb->global_lock.count = 1;
452 tdb->global_lock.ltype = ltype;
453
454 return 0;
455}
456
457
458
459/* unlock entire db */
460static int _tdb_unlockall(struct tdb_context *tdb, int ltype)
461{
462 bool mark_lock = ((ltype & TDB_MARK_LOCK) == TDB_MARK_LOCK);
463
464 ltype &= ~TDB_MARK_LOCK;
465
466 /* There are no locks on read-only dbs */
467 if (tdb->read_only || tdb->traverse_read) {
468 tdb->ecode = TDB_ERR_LOCK;
469 return -1;
470 }
471
472 if (tdb->global_lock.ltype != ltype || tdb->global_lock.count == 0) {
473 tdb->ecode = TDB_ERR_LOCK;
474 return -1;
475 }
476
477 if (tdb->global_lock.count > 1) {
478 tdb->global_lock.count--;
479 return 0;
480 }
481
482 if (!mark_lock &&
483 tdb->methods->tdb_brlock(tdb, FREELIST_TOP, F_UNLCK, F_SETLKW,
484 0, 4*tdb->header.hash_size)) {
485 TDB_LOG((tdb, TDB_DEBUG_ERROR, "tdb_unlockall failed (%s)\n", strerror(errno)));
486 return -1;
487 }
488
489 tdb->global_lock.count = 0;
490 tdb->global_lock.ltype = 0;
491
492 return 0;
493}
494
495/* lock entire database with write lock */
496int tdb_lockall(struct tdb_context *tdb)
497{
498 tdb_trace(tdb, "tdb_lockall");
499 return _tdb_lockall(tdb, F_WRLCK, F_SETLKW);
500}
501
502/* lock entire database with write lock - mark only */
503int tdb_lockall_mark(struct tdb_context *tdb)
504{
505 tdb_trace(tdb, "tdb_lockall_mark");
506 return _tdb_lockall(tdb, F_WRLCK | TDB_MARK_LOCK, F_SETLKW);
507}
508
509/* unlock entire database with write lock - unmark only */
510int tdb_lockall_unmark(struct tdb_context *tdb)
511{
512 tdb_trace(tdb, "tdb_lockall_unmark");
513 return _tdb_unlockall(tdb, F_WRLCK | TDB_MARK_LOCK);
514}
515
516/* lock entire database with write lock - nonblocking varient */
517int tdb_lockall_nonblock(struct tdb_context *tdb)
518{
519 int ret = _tdb_lockall(tdb, F_WRLCK, F_SETLK);
520 tdb_trace_ret(tdb, "tdb_lockall_nonblock", ret);
521 return ret;
522}
523
524/* unlock entire database with write lock */
525int tdb_unlockall(struct tdb_context *tdb)
526{
527 tdb_trace(tdb, "tdb_unlockall");
528 return _tdb_unlockall(tdb, F_WRLCK);
529}
530
531/* lock entire database with read lock */
532int tdb_lockall_read(struct tdb_context *tdb)
533{
534 tdb_trace(tdb, "tdb_lockall_read");
535 return _tdb_lockall(tdb, F_RDLCK, F_SETLKW);
536}
537
538/* lock entire database with read lock - nonblock varient */
539int tdb_lockall_read_nonblock(struct tdb_context *tdb)
540{
541 int ret = _tdb_lockall(tdb, F_RDLCK, F_SETLK);
542 tdb_trace_ret(tdb, "tdb_lockall_read_nonblock", ret);
543 return ret;
544}
545
546/* unlock entire database with read lock */
547int tdb_unlockall_read(struct tdb_context *tdb)
548{
549 tdb_trace(tdb, "tdb_unlockall_read");
550 return _tdb_unlockall(tdb, F_RDLCK);
551}
552
553/* lock/unlock one hash chain. This is meant to be used to reduce
554 contention - it cannot guarantee how many records will be locked */
555int tdb_chainlock(struct tdb_context *tdb, TDB_DATA key)
556{
557 int ret = tdb_lock(tdb, BUCKET(tdb->hash_fn(&key)), F_WRLCK);
558 tdb_trace_1rec(tdb, "tdb_chainlock", key);
559 return ret;
560}
561
562/* lock/unlock one hash chain, non-blocking. This is meant to be used
563 to reduce contention - it cannot guarantee how many records will be
564 locked */
565int tdb_chainlock_nonblock(struct tdb_context *tdb, TDB_DATA key)
566{
567 int ret = tdb_lock_nonblock(tdb, BUCKET(tdb->hash_fn(&key)), F_WRLCK);
568 tdb_trace_1rec_ret(tdb, "tdb_chainlock_nonblock", key, ret);
569 return ret;
570}
571
572/* mark a chain as locked without actually locking it. Warning! use with great caution! */
573int tdb_chainlock_mark(struct tdb_context *tdb, TDB_DATA key)
574{
575 int ret = tdb_lock(tdb, BUCKET(tdb->hash_fn(&key)), F_WRLCK | TDB_MARK_LOCK);
576 tdb_trace_1rec(tdb, "tdb_chainlock_mark", key);
577 return ret;
578}
579
580/* unmark a chain as locked without actually locking it. Warning! use with great caution! */
581int tdb_chainlock_unmark(struct tdb_context *tdb, TDB_DATA key)
582{
583 tdb_trace_1rec(tdb, "tdb_chainlock_unmark", key);
584 return tdb_unlock(tdb, BUCKET(tdb->hash_fn(&key)), F_WRLCK | TDB_MARK_LOCK);
585}
586
587int tdb_chainunlock(struct tdb_context *tdb, TDB_DATA key)
588{
589 tdb_trace_1rec(tdb, "tdb_chainunlock", key);
590 return tdb_unlock(tdb, BUCKET(tdb->hash_fn(&key)), F_WRLCK);
591}
592
593int tdb_chainlock_read(struct tdb_context *tdb, TDB_DATA key)
594{
595 int ret;
596 ret = tdb_lock(tdb, BUCKET(tdb->hash_fn(&key)), F_RDLCK);
597 tdb_trace_1rec(tdb, "tdb_chainlock_read", key);
598 return ret;
599}
600
601int tdb_chainunlock_read(struct tdb_context *tdb, TDB_DATA key)
602{
603 tdb_trace_1rec(tdb, "tdb_chainunlock_read", key);
604 return tdb_unlock(tdb, BUCKET(tdb->hash_fn(&key)), F_RDLCK);
605}
606
607
608
609/* record lock stops delete underneath */
610int tdb_lock_record(struct tdb_context *tdb, tdb_off_t off)
611{
612 if (tdb->global_lock.count) {
613 return 0;
614 }
615 return off ? tdb->methods->tdb_brlock(tdb, off, F_RDLCK, F_SETLKW, 0, 1) : 0;
616}
617
618/*
619 Write locks override our own fcntl readlocks, so check it here.
620 Note this is meant to be F_SETLK, *not* F_SETLKW, as it's not
621 an error to fail to get the lock here.
622*/
623int tdb_write_lock_record(struct tdb_context *tdb, tdb_off_t off)
624{
625 struct tdb_traverse_lock *i;
626 for (i = &tdb->travlocks; i; i = i->next)
627 if (i->off == off)
628 return -1;
629 return tdb->methods->tdb_brlock(tdb, off, F_WRLCK, F_SETLK, 1, 1);
630}
631
632/*
633 Note this is meant to be F_SETLK, *not* F_SETLKW, as it's not
634 an error to fail to get the lock here.
635*/
636int tdb_write_unlock_record(struct tdb_context *tdb, tdb_off_t off)
637{
638 return tdb->methods->tdb_brlock(tdb, off, F_UNLCK, F_SETLK, 0, 1);
639}
640
641/* fcntl locks don't stack: avoid unlocking someone else's */
642int tdb_unlock_record(struct tdb_context *tdb, tdb_off_t off)
643{
644 struct tdb_traverse_lock *i;
645 uint32_t count = 0;
646
647 if (tdb->global_lock.count) {
648 return 0;
649 }
650
651 if (off == 0)
652 return 0;
653 for (i = &tdb->travlocks; i; i = i->next)
654 if (i->off == off)
655 count++;
656 return (count == 1 ? tdb->methods->tdb_brlock(tdb, off, F_UNLCK, F_SETLKW, 0, 1) : 0);
657}
Note: See TracBrowser for help on using the repository browser.