1/*	$NetBSD$	*/
2
3/* alock.c - access lock library */
4/* OpenLDAP: pkg/ldap/servers/slapd/alock.c,v 1.5.2.13 2010/04/13 20:23:10 kurt Exp */
5/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
6 *
7 * Copyright 2005-2010 The OpenLDAP Foundation.
8 * Portions Copyright 2004-2005 Symas Corporation.
9 * All rights reserved.
10 *
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted only as authorized by the OpenLDAP
13 * Public License.
14 *
15 * A copy of this license is available in the file LICENSE in the
16 * top-level directory of the distribution or, alternatively, at
17 * <http://www.OpenLDAP.org/license.html>.
18 */
19/* ACKNOWLEDGEMENTS:
20 * This work was initially developed by Matthew Backes at Symas
21 * Corporation for inclusion in OpenLDAP Software.
22 */
23
24#include "portable.h"
25
26#if SLAPD_BDB || SLAPD_HDB
27
28#include <lber.h>
29#include "alock.h"
30#include "lutil.h"
31
32#include <ac/stdlib.h>
33#include <ac/string.h>
34#include <ac/unistd.h>
35#include <ac/errno.h>
36#include <ac/assert.h>
37#include <sys/types.h>
38#include <sys/stat.h>
39#ifdef HAVE_SYS_FILE_H
40#include <sys/file.h>
41#endif
42#include <fcntl.h>
43
44#ifdef _WIN32
45#include <stdio.h>
46#include <io.h>
47#include <sys/locking.h>
48#endif
49
50
51static int
52alock_grab_lock ( int fd, int slot )
53{
54	int res;
55
56#if defined( HAVE_LOCKF )
57	res = lseek (fd, (off_t) (ALOCK_SLOT_SIZE * slot), SEEK_SET);
58	if (res == -1) return -1;
59	res = lockf (fd, F_LOCK, (off_t) ALOCK_SLOT_SIZE);
60#elif defined( HAVE_FCNTL )
61	struct flock lock_info;
62	(void) memset ((void *) &lock_info, 0, sizeof (struct flock));
63
64	lock_info.l_type = F_WRLCK;
65	lock_info.l_whence = SEEK_SET;
66	lock_info.l_start = (off_t) (ALOCK_SLOT_SIZE * slot);
67	lock_info.l_len = (off_t) ALOCK_SLOT_SIZE;
68
69	res = fcntl (fd, F_SETLKW, &lock_info);
70#elif defined( _WIN32 )
71	if( _lseek( fd, (ALOCK_SLOT_SIZE * slot), SEEK_SET ) < 0 )
72		return -1;
73	/*
74	 * _lock will try for the lock once per second, returning EDEADLOCK
75	 * after ten tries. We just loop until we either get the lock
76	 * or some other error is returned.
77	 */
78	while((res = _locking( fd, _LK_LOCK, ALOCK_SLOT_SIZE )) < 0 ) {
79		if( errno != EDEADLOCK )
80			break;
81	}
82#else
83#   error alock needs lockf, fcntl, or _locking
84#endif
85	if (res == -1) {
86		assert (errno != EDEADLK);
87		return -1;
88	}
89	return 0;
90}
91
92static int
93alock_release_lock ( int fd, int slot )
94{
95	int res;
96
97#if defined( HAVE_LOCKF )
98	res = lseek (fd, (off_t) (ALOCK_SLOT_SIZE * slot), SEEK_SET);
99	if (res == -1) return -1;
100	res = lockf (fd, F_ULOCK, (off_t) ALOCK_SLOT_SIZE);
101	if (res == -1) return -1;
102#elif defined ( HAVE_FCNTL )
103	struct flock lock_info;
104	(void) memset ((void *) &lock_info, 0, sizeof (struct flock));
105
106	lock_info.l_type = F_UNLCK;
107	lock_info.l_whence = SEEK_SET;
108	lock_info.l_start = (off_t) (ALOCK_SLOT_SIZE * slot);
109	lock_info.l_len = (off_t) ALOCK_SLOT_SIZE;
110
111	res = fcntl (fd, F_SETLKW, &lock_info);
112	if (res == -1) return -1;
113#elif defined( _WIN32 )
114	res = _lseek (fd, (ALOCK_SLOT_SIZE * slot), SEEK_SET);
115	if (res == -1) return -1;
116	res = _locking( fd, _LK_UNLCK, ALOCK_SLOT_SIZE );
117	if (res == -1) return -1;
118#else
119#   error alock needs lockf, fcntl, or _locking
120#endif
121
122	return 0;
123}
124
125static int
126alock_test_lock ( int fd, int slot )
127{
128	int res;
129
130#if defined( HAVE_LOCKF )
131	res = lseek (fd, (off_t) (ALOCK_SLOT_SIZE * slot), SEEK_SET);
132	if (res == -1) return -1;
133
134	res = lockf (fd, F_TEST, (off_t) ALOCK_SLOT_SIZE);
135	if (res == -1) {
136		if (errno == EACCES || errno == EAGAIN) {
137			return ALOCK_LOCKED;
138		} else {
139			return -1;
140		}
141	}
142#elif defined( HAVE_FCNTL )
143	struct flock lock_info;
144	(void) memset ((void *) &lock_info, 0, sizeof (struct flock));
145
146	lock_info.l_type = F_WRLCK;
147	lock_info.l_whence = SEEK_SET;
148	lock_info.l_start = (off_t) (ALOCK_SLOT_SIZE * slot);
149	lock_info.l_len = (off_t) ALOCK_SLOT_SIZE;
150
151	res = fcntl (fd, F_GETLK, &lock_info);
152	if (res == -1) return -1;
153
154	if (lock_info.l_type != F_UNLCK) return ALOCK_LOCKED;
155#elif defined( _WIN32 )
156	res = _lseek (fd, (ALOCK_SLOT_SIZE * slot), SEEK_SET);
157	if (res == -1) return -1;
158	res = _locking( fd, _LK_NBLCK, ALOCK_SLOT_SIZE );
159	_locking( fd, _LK_UNLCK, ALOCK_SLOT_SIZE );
160	if (res == -1) {
161	   if( errno == EACCES ) {
162		   return ALOCK_LOCKED;
163	   } else {
164		   return -1;
165	   }
166	}
167#else
168#   error alock needs lockf, fcntl, or _locking
169#endif
170
171	return 0;
172}
173
174/* Read a 64bit LE value */
175static unsigned long int
176alock_read_iattr ( unsigned char * bufptr )
177{
178	unsigned long int val = 0;
179	int count;
180
181	assert (bufptr != NULL);
182
183	bufptr += sizeof (unsigned long int);
184	for (count=0; count <= (int) sizeof (unsigned long int); ++count) {
185		val <<= 8;
186		val += (unsigned long int) *bufptr--;
187	}
188
189	return val;
190}
191
192/* Write a 64bit LE value */
193static void
194alock_write_iattr ( unsigned char * bufptr,
195		    unsigned long int val )
196{
197	int count;
198
199	assert (bufptr != NULL);
200
201	for (count=0; count < 8; ++count) {
202		*bufptr++ = (unsigned char) (val & 0xff);
203		val >>= 8;
204	}
205}
206
207static int
208alock_read_slot ( alock_info_t * info,
209		  alock_slot_t * slot_data )
210{
211	unsigned char slotbuf [ALOCK_SLOT_SIZE];
212	int res, size, size_total, err;
213
214	assert (info != NULL);
215	assert (slot_data != NULL);
216	assert (info->al_slot > 0);
217
218	res = lseek (info->al_fd,
219		     (off_t) (ALOCK_SLOT_SIZE * info->al_slot),
220		     SEEK_SET);
221	if (res == -1) return -1;
222
223	size_total = 0;
224	while (size_total < ALOCK_SLOT_SIZE) {
225		size = read (info->al_fd,
226			     slotbuf + size_total,
227			     ALOCK_SLOT_SIZE - size_total);
228		if (size == 0) return -1;
229		if (size < 0) {
230			err = errno;
231			if (err != EINTR && err != EAGAIN) return -1;
232		} else {
233			size_total += size;
234		}
235	}
236
237	if (alock_read_iattr (slotbuf) != ALOCK_MAGIC) {
238		return -1;
239	}
240	slot_data->al_lock  = alock_read_iattr (slotbuf+8);
241	slot_data->al_stamp = alock_read_iattr (slotbuf+16);
242	slot_data->al_pid   = alock_read_iattr (slotbuf+24);
243
244	if (slot_data->al_appname) ber_memfree (slot_data->al_appname);
245	slot_data->al_appname = ber_memcalloc (1, ALOCK_MAX_APPNAME);
246	if (slot_data->al_appname == NULL) {
247		return -1;
248	}
249	strncpy (slot_data->al_appname, (char *)slotbuf+32, ALOCK_MAX_APPNAME-1);
250	(slot_data->al_appname) [ALOCK_MAX_APPNAME-1] = '\0';
251
252	return 0;
253}
254
255static int
256alock_write_slot ( alock_info_t * info,
257		   alock_slot_t * slot_data )
258{
259	unsigned char slotbuf [ALOCK_SLOT_SIZE];
260	int res, size, size_total, err;
261
262	assert (info != NULL);
263	assert (slot_data != NULL);
264	assert (info->al_slot > 0);
265
266	(void) memset ((void *) slotbuf, 0, ALOCK_SLOT_SIZE);
267
268	alock_write_iattr (slotbuf,    ALOCK_MAGIC);
269	assert (alock_read_iattr (slotbuf) == ALOCK_MAGIC);
270	alock_write_iattr (slotbuf+8,  slot_data->al_lock);
271	alock_write_iattr (slotbuf+16, slot_data->al_stamp);
272	alock_write_iattr (slotbuf+24, slot_data->al_pid);
273
274	if (slot_data->al_appname)
275		strncpy ((char *)slotbuf+32, slot_data->al_appname, ALOCK_MAX_APPNAME-1);
276	slotbuf[ALOCK_SLOT_SIZE-1] = '\0';
277
278	res = lseek (info->al_fd,
279		     (off_t) (ALOCK_SLOT_SIZE * info->al_slot),
280		     SEEK_SET);
281	if (res == -1) return -1;
282
283	size_total = 0;
284	while (size_total < ALOCK_SLOT_SIZE) {
285		size = write (info->al_fd,
286			      slotbuf + size_total,
287			      ALOCK_SLOT_SIZE - size_total);
288		if (size == 0) return -1;
289		if (size < 0) {
290			err = errno;
291			if (err != EINTR && err != EAGAIN) return -1;
292		} else {
293			size_total += size;
294		}
295	}
296
297	return 0;
298}
299
300static int
301alock_query_slot ( alock_info_t * info )
302{
303	int res, nosave;
304	alock_slot_t slot_data;
305
306	assert (info != NULL);
307	assert (info->al_slot > 0);
308
309	(void) memset ((void *) &slot_data, 0, sizeof (alock_slot_t));
310	alock_read_slot (info, &slot_data);
311
312	if (slot_data.al_appname != NULL) ber_memfree (slot_data.al_appname);
313	slot_data.al_appname = NULL;
314
315	nosave = slot_data.al_lock & ALOCK_NOSAVE;
316
317	if ((slot_data.al_lock & ALOCK_SMASK) == ALOCK_UNLOCKED)
318		return slot_data.al_lock;
319
320	res = alock_test_lock (info->al_fd, info->al_slot);
321	if (res < 0) return -1;
322	if (res > 0) {
323		if ((slot_data.al_lock & ALOCK_SMASK) == ALOCK_UNIQUE) {
324			return slot_data.al_lock;
325		} else {
326			return ALOCK_LOCKED | nosave;
327		}
328	}
329
330	return ALOCK_DIRTY | nosave;
331}
332
333int
334alock_open ( alock_info_t * info,
335	     const char * appname,
336	     const char * envdir,
337	     int locktype )
338{
339	struct stat statbuf;
340	alock_info_t scan_info;
341	alock_slot_t slot_data;
342	char * filename;
343	int res, max_slot;
344	int dirty_count, live_count, nosave;
345	char *ptr;
346
347	assert (info != NULL);
348	assert (appname != NULL);
349	assert (envdir != NULL);
350	assert ((locktype & ALOCK_SMASK) >= 1 && (locktype & ALOCK_SMASK) <= 2);
351
352	slot_data.al_lock = locktype;
353	slot_data.al_stamp = time(NULL);
354	slot_data.al_pid = getpid();
355	slot_data.al_appname = ber_memcalloc (1, ALOCK_MAX_APPNAME);
356	if (slot_data.al_appname == NULL) {
357		return ALOCK_UNSTABLE;
358	}
359	strncpy (slot_data.al_appname, appname, ALOCK_MAX_APPNAME-1);
360	slot_data.al_appname [ALOCK_MAX_APPNAME-1] = '\0';
361
362	filename = ber_memcalloc (1, strlen (envdir) + strlen ("/alock") + 1);
363	if (filename == NULL ) {
364		ber_memfree (slot_data.al_appname);
365		return ALOCK_UNSTABLE;
366	}
367	ptr = lutil_strcopy(filename, envdir);
368	lutil_strcopy(ptr, "/alock");
369	info->al_fd = open (filename, O_CREAT|O_RDWR, 0666);
370	ber_memfree (filename);
371	if (info->al_fd < 0) {
372		ber_memfree (slot_data.al_appname);
373		return ALOCK_UNSTABLE;
374	}
375	info->al_slot = 0;
376
377	res = alock_grab_lock (info->al_fd, 0);
378	if (res == -1) {
379		close (info->al_fd);
380		ber_memfree (slot_data.al_appname);
381		return ALOCK_UNSTABLE;
382	}
383
384	res = fstat (info->al_fd, &statbuf);
385	if (res == -1) {
386		close (info->al_fd);
387		ber_memfree (slot_data.al_appname);
388		return ALOCK_UNSTABLE;
389	}
390
391	max_slot = (statbuf.st_size + ALOCK_SLOT_SIZE - 1) / ALOCK_SLOT_SIZE;
392	dirty_count = 0;
393	live_count = 0;
394	nosave = 0;
395	scan_info.al_fd = info->al_fd;
396	for (scan_info.al_slot = 1;
397	     scan_info.al_slot < max_slot;
398	     ++ scan_info.al_slot) {
399		if (scan_info.al_slot != info->al_slot) {
400			res = alock_query_slot (&scan_info);
401
402			if (res & ALOCK_NOSAVE) {
403				nosave = ALOCK_NOSAVE;
404				res ^= ALOCK_NOSAVE;
405			}
406			if (res == ALOCK_UNLOCKED
407			    && info->al_slot == 0) {
408				info->al_slot = scan_info.al_slot;
409
410			} else if (res == ALOCK_LOCKED) {
411				++live_count;
412
413			} else if (res == ALOCK_UNIQUE
414				&& (( locktype & ALOCK_SMASK ) == ALOCK_UNIQUE
415				|| nosave )) {
416				close (info->al_fd);
417				ber_memfree (slot_data.al_appname);
418				return ALOCK_BUSY;
419
420			} else if (res == ALOCK_DIRTY) {
421				++dirty_count;
422
423			} else if (res == -1) {
424				close (info->al_fd);
425				ber_memfree (slot_data.al_appname);
426				return ALOCK_UNSTABLE;
427
428			}
429		}
430	}
431
432	if (dirty_count && live_count) {
433		close (info->al_fd);
434		ber_memfree (slot_data.al_appname);
435		return ALOCK_UNSTABLE;
436	}
437
438	if (info->al_slot == 0) info->al_slot = max_slot + 1;
439	res = alock_grab_lock (info->al_fd,
440			       info->al_slot);
441	if (res == -1) {
442		close (info->al_fd);
443		ber_memfree (slot_data.al_appname);
444		return ALOCK_UNSTABLE;
445	}
446	res = alock_write_slot (info, &slot_data);
447	ber_memfree (slot_data.al_appname);
448	if (res == -1) {
449		close (info->al_fd);
450		return ALOCK_UNSTABLE;
451	}
452
453	res = alock_release_lock (info->al_fd, 0);
454	if (res == -1) {
455		close (info->al_fd);
456		return ALOCK_UNSTABLE;
457	}
458
459	if (dirty_count) return ALOCK_RECOVER | nosave;
460	return ALOCK_CLEAN | nosave;
461}
462
463int
464alock_scan ( alock_info_t * info )
465{
466	struct stat statbuf;
467	alock_info_t scan_info;
468	int res, max_slot;
469	int dirty_count, live_count, nosave;
470
471	assert (info != NULL);
472
473	scan_info.al_fd = info->al_fd;
474
475	res = alock_grab_lock (info->al_fd, 0);
476	if (res == -1) {
477		close (info->al_fd);
478		return ALOCK_UNSTABLE;
479	}
480
481	res = fstat (info->al_fd, &statbuf);
482	if (res == -1) {
483		close (info->al_fd);
484		return ALOCK_UNSTABLE;
485	}
486
487	max_slot = (statbuf.st_size + ALOCK_SLOT_SIZE - 1) / ALOCK_SLOT_SIZE;
488	dirty_count = 0;
489	live_count = 0;
490	nosave = 0;
491	for (scan_info.al_slot = 1;
492	     scan_info.al_slot < max_slot;
493	     ++ scan_info.al_slot) {
494		if (scan_info.al_slot != info->al_slot) {
495			res = alock_query_slot (&scan_info);
496
497			if (res & ALOCK_NOSAVE) {
498				nosave = ALOCK_NOSAVE;
499				res ^= ALOCK_NOSAVE;
500			}
501
502			if (res == ALOCK_LOCKED) {
503				++live_count;
504
505			} else if (res == ALOCK_DIRTY) {
506				++dirty_count;
507
508			} else if (res == -1) {
509				close (info->al_fd);
510				return ALOCK_UNSTABLE;
511
512			}
513		}
514	}
515
516	res = alock_release_lock (info->al_fd, 0);
517	if (res == -1) {
518		close (info->al_fd);
519		return ALOCK_UNSTABLE;
520	}
521
522	if (dirty_count) {
523		if (live_count) {
524			close (info->al_fd);
525			return ALOCK_UNSTABLE;
526		} else {
527			return ALOCK_RECOVER | nosave;
528		}
529	}
530
531	return ALOCK_CLEAN | nosave;
532}
533
534int
535alock_close ( alock_info_t * info, int nosave )
536{
537	alock_slot_t slot_data;
538	int res;
539
540	if ( !info->al_slot )
541		return ALOCK_CLEAN;
542
543	(void) memset ((void *) &slot_data, 0, sizeof(alock_slot_t));
544
545	res = alock_grab_lock (info->al_fd, 0);
546	if (res == -1) {
547		close (info->al_fd);
548		return ALOCK_UNSTABLE;
549	}
550
551	/* mark our slot as clean */
552	res = alock_read_slot (info, &slot_data);
553	if (res == -1) {
554		close (info->al_fd);
555		if (slot_data.al_appname != NULL)
556			ber_memfree (slot_data.al_appname);
557		return ALOCK_UNSTABLE;
558	}
559	slot_data.al_lock = ALOCK_UNLOCKED;
560	if ( nosave )
561		slot_data.al_lock |= ALOCK_NOSAVE;
562	res = alock_write_slot (info, &slot_data);
563	if (res == -1) {
564		close (info->al_fd);
565		if (slot_data.al_appname != NULL)
566			ber_memfree (slot_data.al_appname);
567		return ALOCK_UNSTABLE;
568	}
569	if (slot_data.al_appname != NULL) {
570		ber_memfree (slot_data.al_appname);
571		slot_data.al_appname = NULL;
572	}
573
574	res = alock_release_lock (info->al_fd, info->al_slot);
575	if (res == -1) {
576		close (info->al_fd);
577		return ALOCK_UNSTABLE;
578	}
579	res = alock_release_lock (info->al_fd, 0);
580	if (res == -1) {
581		close (info->al_fd);
582		return ALOCK_UNSTABLE;
583	}
584
585	res = close (info->al_fd);
586	if (res == -1) return ALOCK_UNSTABLE;
587
588	return ALOCK_CLEAN;
589}
590
591int
592alock_recover ( alock_info_t * info )
593{
594	struct stat statbuf;
595	alock_slot_t slot_data;
596	alock_info_t scan_info;
597	int res, max_slot;
598
599	assert (info != NULL);
600
601	scan_info.al_fd = info->al_fd;
602
603	(void) memset ((void *) &slot_data, 0, sizeof(alock_slot_t));
604
605	res = alock_grab_lock (info->al_fd, 0);
606	if (res == -1) {
607		close (info->al_fd);
608		return ALOCK_UNSTABLE;
609	}
610
611	res = fstat (info->al_fd, &statbuf);
612	if (res == -1) {
613		close (info->al_fd);
614		return ALOCK_UNSTABLE;
615	}
616
617	max_slot = (statbuf.st_size + ALOCK_SLOT_SIZE - 1) / ALOCK_SLOT_SIZE;
618	for (scan_info.al_slot = 1;
619	     scan_info.al_slot < max_slot;
620	     ++ scan_info.al_slot) {
621		if (scan_info.al_slot != info->al_slot) {
622			res = alock_query_slot (&scan_info) & ~ALOCK_NOSAVE;
623
624			if (res == ALOCK_LOCKED
625			    || res == ALOCK_UNIQUE) {
626				/* recovery attempt on an active db? */
627				close (info->al_fd);
628				return ALOCK_UNSTABLE;
629
630			} else if (res == ALOCK_DIRTY) {
631				/* mark it clean */
632				res = alock_read_slot (&scan_info, &slot_data);
633				if (res == -1) {
634					close (info->al_fd);
635					return ALOCK_UNSTABLE;
636				}
637				slot_data.al_lock = ALOCK_UNLOCKED;
638				res = alock_write_slot (&scan_info, &slot_data);
639				if (res == -1) {
640					close (info->al_fd);
641					if (slot_data.al_appname != NULL)
642						ber_memfree (slot_data.al_appname);
643					return ALOCK_UNSTABLE;
644				}
645				if (slot_data.al_appname != NULL) {
646					ber_memfree (slot_data.al_appname);
647					slot_data.al_appname = NULL;
648				}
649
650			} else if (res == -1) {
651				close (info->al_fd);
652				return ALOCK_UNSTABLE;
653
654			}
655		}
656	}
657
658	res = alock_release_lock (info->al_fd, 0);
659	if (res == -1) {
660		close (info->al_fd);
661		return ALOCK_UNSTABLE;
662	}
663
664	return ALOCK_CLEAN;
665}
666
667#endif /* SLAPD_BDB || SLAPD_HDB */
668