Line | Count | Source |
1 | | /* lock.c - Implement basic file locking for GDBM. */ |
2 | | |
3 | | /* This file is part of GDBM, the GNU data base manager. |
4 | | Copyright (C) 2008-2025 Free Software Foundation, Inc. |
5 | | |
6 | | GDBM is free software; you can redistribute it and/or modify |
7 | | it under the terms of the GNU General Public License as published by |
8 | | the Free Software Foundation; either version 3, or (at your option) |
9 | | any later version. |
10 | | |
11 | | GDBM is distributed in the hope that it will be useful, |
12 | | but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
14 | | GNU General Public License for more details. |
15 | | |
16 | | You should have received a copy of the GNU General Public License |
17 | | along with GDBM. If not, see <http://www.gnu.org/licenses/>. */ |
18 | | |
19 | | /* Include system configuration before all else. */ |
20 | | #include "autoconf.h" |
21 | | |
22 | | #include "gdbmdefs.h" |
23 | | |
24 | | #include <sys/time.h> |
25 | | #include <signal.h> |
26 | | #include <errno.h> |
27 | | |
28 | | #if HAVE_FLOCK |
29 | | # ifndef LOCK_SH |
30 | | # define LOCK_SH 1 |
31 | | # endif |
32 | | |
33 | | # ifndef LOCK_EX |
34 | | # define LOCK_EX 2 |
35 | | # endif |
36 | | |
37 | | # ifndef LOCK_NB |
38 | | # define LOCK_NB 4 |
39 | | # endif |
40 | | |
41 | | # ifndef LOCK_UN |
42 | | # define LOCK_UN 8 |
43 | | # endif |
44 | | #endif |
45 | | |
46 | | #if defined(F_SETLK) && defined(F_RDLCK) && defined(F_WRLCK) |
47 | | # define HAVE_FCNTL_LOCK 1 |
48 | | #else |
49 | | # define HAVE_FCNTL_LOCK 0 |
50 | | #endif |
51 | | |
52 | | /* Return values for try_lock_ functions: */ |
53 | | enum |
54 | | { |
55 | | TRY_LOCK_OK, /* Locking succeeded */ |
56 | | TRY_LOCK_FAIL, /* File already locked by another process. */ |
57 | | TRY_LOCK_NEXT /* Another error (including locking mechanism not |
58 | | available). The caller should try next locking |
59 | | mechanism. */ |
60 | | |
61 | | }; |
62 | | |
63 | | /* |
64 | | * Locking using flock(). |
65 | | */ |
66 | | static int |
67 | | try_lock_flock (GDBM_FILE dbf, int nb) |
68 | 6.34k | { |
69 | 6.34k | #if HAVE_FLOCK |
70 | 6.34k | if (flock (dbf->desc, |
71 | 6.34k | ((dbf->read_write == GDBM_READER) ? LOCK_SH : LOCK_EX) |
72 | 6.34k | | (nb ? LOCK_NB : 0)) == 0) |
73 | 6.34k | { |
74 | 6.34k | return TRY_LOCK_OK; |
75 | 6.34k | } |
76 | 0 | else if (errno == EWOULDBLOCK || errno == EINTR) |
77 | 0 | { |
78 | 0 | return TRY_LOCK_FAIL; |
79 | 0 | } |
80 | 0 | #endif |
81 | 0 | return TRY_LOCK_NEXT; |
82 | 6.34k | } |
83 | | |
84 | | static void |
85 | | unlock_flock (GDBM_FILE dbf) |
86 | 6.34k | { |
87 | 6.34k | #if HAVE_FLOCK |
88 | 6.34k | flock (dbf->desc, LOCK_UN); |
89 | 6.34k | #endif |
90 | 6.34k | } |
91 | | |
92 | | /* |
93 | | * Locking via lockf. |
94 | | */ |
95 | | |
96 | | static int |
97 | | try_lock_lockf (GDBM_FILE dbf, int nb) |
98 | 0 | { |
99 | 0 | #if HAVE_LOCKF |
100 | | /* |
101 | | * NOTE: lockf will fail with EINVAL unless the database file was opened |
102 | | * with write-only permission (O_WRONLY) or with read/write permission |
103 | | * (O_RDWR). This means that this locking mechanism will always fail for |
104 | | * databases opened with GDBM_READER, |
105 | | */ |
106 | 0 | if (dbf->read_write != GDBM_READER) |
107 | 0 | { |
108 | 0 | if (lockf (dbf->desc, nb ? F_TLOCK : F_LOCK, (off_t)0L) == 0) |
109 | 0 | return TRY_LOCK_OK; |
110 | | |
111 | 0 | switch (errno) |
112 | 0 | { |
113 | 0 | case EINTR: |
114 | 0 | case EACCES: |
115 | 0 | case EAGAIN: |
116 | 0 | case EDEADLK: |
117 | 0 | return TRY_LOCK_FAIL; |
118 | | |
119 | 0 | default: |
120 | | /* try next locking method */ |
121 | 0 | break; |
122 | 0 | } |
123 | 0 | } |
124 | 0 | #endif |
125 | 0 | return TRY_LOCK_NEXT; |
126 | 0 | } |
127 | | |
128 | | static void |
129 | | unlock_lockf (GDBM_FILE dbf) |
130 | 0 | { |
131 | 0 | #if HAVE_LOCKF |
132 | 0 | lockf (dbf->desc, F_ULOCK, (off_t)0L); |
133 | 0 | #endif |
134 | 0 | } |
135 | | |
136 | | /* |
137 | | * Locking via fcntl(). |
138 | | */ |
139 | | |
140 | | static int |
141 | | try_lock_fcntl (GDBM_FILE dbf, int nb) |
142 | 0 | { |
143 | 0 | #if HAVE_FCNTL_LOCK |
144 | 0 | struct flock fl; |
145 | | |
146 | | /* If we're still here, try fcntl. */ |
147 | 0 | if (dbf->read_write == GDBM_READER) |
148 | 0 | fl.l_type = F_RDLCK; |
149 | 0 | else |
150 | 0 | fl.l_type = F_WRLCK; |
151 | 0 | fl.l_whence = SEEK_SET; |
152 | 0 | fl.l_start = fl.l_len = (off_t)0L; |
153 | 0 | if (fcntl (dbf->desc, nb ? F_SETLK : F_SETLKW, &fl) == 0) |
154 | 0 | return TRY_LOCK_OK; |
155 | | |
156 | 0 | switch (errno) |
157 | 0 | { |
158 | 0 | case EINTR: |
159 | 0 | case EACCES: |
160 | 0 | case EAGAIN: |
161 | 0 | case EDEADLK: |
162 | 0 | return TRY_LOCK_FAIL; |
163 | | |
164 | 0 | default: |
165 | | /* try next locking method */ |
166 | 0 | break; |
167 | 0 | } |
168 | | |
169 | 0 | #endif |
170 | 0 | return TRY_LOCK_NEXT; |
171 | 0 | } |
172 | | |
173 | | static void |
174 | | unlock_fcntl (GDBM_FILE dbf) |
175 | 0 | { |
176 | 0 | #if HAVE_FCNTL_LOCK |
177 | 0 | struct flock fl; |
178 | |
|
179 | 0 | fl.l_type = F_UNLCK; |
180 | 0 | fl.l_whence = SEEK_SET; |
181 | 0 | fl.l_start = fl.l_len = (off_t)0L; |
182 | 0 | fcntl (dbf->desc, F_SETLK, &fl); |
183 | 0 | #endif |
184 | 0 | } |
185 | | |
186 | | /* Try each supported locking mechanism. */ |
187 | | int |
188 | | _gdbm_lock_file (GDBM_FILE dbf, int nb) |
189 | 6.34k | { |
190 | 6.34k | int res; |
191 | 6.34k | static int (*try_lock_fn[]) (GDBM_FILE, int) = { |
192 | 6.34k | [LOCKING_FLOCK] = try_lock_flock, |
193 | 6.34k | [LOCKING_LOCKF] = try_lock_lockf, |
194 | 6.34k | [LOCKING_FCNTL] = try_lock_fcntl |
195 | 6.34k | }; |
196 | 6.34k | int i; |
197 | | |
198 | 6.34k | dbf->lock_type = LOCKING_NONE; |
199 | 6.34k | for (i = 1; i < sizeof (try_lock_fn) / sizeof (try_lock_fn[0]); i++) |
200 | 6.34k | { |
201 | 6.34k | if ((res = try_lock_fn[i] (dbf, nb)) == TRY_LOCK_OK) |
202 | 6.34k | { |
203 | 6.34k | dbf->lock_type = LOCKING_FLOCK; |
204 | 6.34k | return 0; |
205 | 6.34k | } |
206 | 0 | else if (res != TRY_LOCK_NEXT) |
207 | 0 | break; |
208 | 6.34k | } |
209 | 0 | return -1; |
210 | 6.34k | } |
211 | | |
212 | | void |
213 | | _gdbm_unlock_file (GDBM_FILE dbf) |
214 | 6.34k | { |
215 | 6.34k | static void (*unlock_fn[]) (GDBM_FILE) = { |
216 | 6.34k | [LOCKING_FLOCK] = unlock_flock, |
217 | 6.34k | [LOCKING_LOCKF] = unlock_lockf, |
218 | 6.34k | [LOCKING_FCNTL] = unlock_fcntl |
219 | 6.34k | }; |
220 | | |
221 | 6.34k | if (dbf->lock_type != LOCKING_NONE) |
222 | 6.34k | { |
223 | 6.34k | unlock_fn[dbf->lock_type] (dbf); |
224 | 6.34k | dbf->lock_type = LOCKING_NONE; |
225 | 6.34k | } |
226 | 6.34k | } |
227 | | |
228 | | enum { NANO = 1000000000L }; |
229 | | |
230 | | static inline void |
231 | | timespec_sub (struct timespec *a, struct timespec const *b) |
232 | 0 | { |
233 | 0 | a->tv_sec -= b->tv_sec; |
234 | 0 | a->tv_nsec -= b->tv_nsec; |
235 | 0 | if (a->tv_nsec < 0) |
236 | 0 | { |
237 | 0 | --a->tv_sec; |
238 | 0 | a->tv_nsec += NANO; |
239 | 0 | } |
240 | 0 | } |
241 | | |
242 | | static inline void |
243 | | timespec_add (struct timespec *a, struct timespec const *b) |
244 | 0 | { |
245 | 0 | a->tv_sec += b->tv_sec; |
246 | 0 | a->tv_nsec += b->tv_nsec; |
247 | 0 | if (a->tv_nsec >= NANO) |
248 | 0 | { |
249 | 0 | a->tv_sec += a->tv_nsec / NANO; |
250 | 0 | a->tv_nsec %= NANO; |
251 | 0 | } |
252 | 0 | } |
253 | | |
254 | | static inline int |
255 | | timespec_cmp (struct timespec const *a, struct timespec const *b) |
256 | 0 | { |
257 | 0 | if (a->tv_sec < b->tv_sec) |
258 | 0 | return -1; |
259 | 0 | if (a->tv_sec > b->tv_sec) |
260 | 0 | return 1; |
261 | 0 | if (a->tv_nsec < b->tv_nsec) |
262 | 0 | return -1; |
263 | 0 | if (a->tv_nsec > b->tv_nsec) |
264 | 0 | return 1; |
265 | 0 | return 0; |
266 | 0 | } |
267 | | |
268 | | static int |
269 | | _gdbm_lockwait_retry (GDBM_FILE dbf, struct timespec const *ts, |
270 | | struct timespec const *iv) |
271 | 0 | { |
272 | 0 | int ret; |
273 | 0 | struct timespec ttw = *ts; |
274 | 0 | struct timespec r; |
275 | |
|
276 | 0 | if (ts == NULL || (ts->tv_sec == 0 && ts->tv_nsec == 0)) |
277 | 0 | return _gdbm_lock_file (dbf, 1); |
278 | | |
279 | 0 | for (;;) |
280 | 0 | { |
281 | 0 | ret = _gdbm_lock_file (dbf, 1); |
282 | 0 | if (ret == 0) |
283 | 0 | break; |
284 | 0 | if (timespec_cmp (&ttw, iv) < 0) |
285 | 0 | break; |
286 | 0 | timespec_sub (&ttw, iv); |
287 | 0 | if (nanosleep (iv, &r)) |
288 | 0 | { |
289 | 0 | if (errno == EINTR) |
290 | 0 | timespec_add (&ttw, &r); |
291 | 0 | else |
292 | 0 | break; |
293 | 0 | } |
294 | 0 | } |
295 | 0 | return ret; |
296 | 0 | } |
297 | | |
298 | | static void |
299 | | signull (int sig) |
300 | 0 | { |
301 | | /* nothing */ |
302 | 0 | } |
303 | | |
304 | | static int |
305 | | _gdbm_lockwait_signal (GDBM_FILE dbf, struct timespec const *ts) |
306 | 0 | { |
307 | 0 | int ret = -1; |
308 | 0 | int ec = 0; |
309 | |
|
310 | 0 | if (ts == NULL || (ts->tv_sec == 0 && ts->tv_nsec == 0)) |
311 | 0 | ret = _gdbm_lock_file (dbf, 1); |
312 | 0 | else |
313 | 0 | { |
314 | 0 | struct sigaction act, oldact; |
315 | 0 | #if HAVE_TIMER_SETTIME |
316 | 0 | struct itimerspec itv; |
317 | 0 | timer_t timer; |
318 | | #else |
319 | | struct itimerval itv, olditv; |
320 | | #endif |
321 | |
|
322 | 0 | act.sa_handler = signull; |
323 | 0 | sigemptyset (&act.sa_mask); |
324 | 0 | act.sa_flags = 0; |
325 | 0 | if (sigaction (SIGALRM, &act, &oldact)) |
326 | 0 | return -1; |
327 | | |
328 | 0 | #if HAVE_TIMER_SETTIME |
329 | 0 | if (timer_create (CLOCK_REALTIME, NULL, &timer) == 0) |
330 | 0 | { |
331 | 0 | itv.it_interval.tv_sec = 0; |
332 | 0 | itv.it_interval.tv_nsec = 0; |
333 | 0 | itv.it_value.tv_sec = ts->tv_sec; |
334 | 0 | itv.it_value.tv_nsec = ts->tv_nsec; |
335 | |
|
336 | 0 | if (timer_settime (timer, 0, &itv, NULL) == 0) |
337 | 0 | ret = _gdbm_lock_file (dbf, 0); |
338 | 0 | ec = errno; |
339 | |
|
340 | 0 | timer_delete (timer); |
341 | 0 | } |
342 | 0 | else |
343 | 0 | ec = errno; |
344 | | #else |
345 | | itv.it_interval.tv_sec = 0; |
346 | | itv.it_interval.tv_usec = 0; |
347 | | itv.it_value.tv_sec = ts->tv_sec; |
348 | | itv.it_value.tv_usec = ts->tv_nsec / 1000000; |
349 | | |
350 | | if (setitimer (ITIMER_REAL, &itv, &olditv) == 0) |
351 | | ret = _gdbm_lock_file (dbf, 0); |
352 | | ec = errno; |
353 | | |
354 | | /* Reset the timer */ |
355 | | setitimer (ITIMER_REAL, &olditv, NULL); |
356 | | #endif |
357 | 0 | sigaction (SIGALRM, &oldact, NULL); //FIXME: error checking |
358 | 0 | } |
359 | 0 | if (ret != 0) |
360 | 0 | errno = ec; |
361 | 0 | return ret; |
362 | 0 | } |
363 | | |
364 | | int |
365 | | _gdbm_lock_file_wait (GDBM_FILE dbf, struct gdbm_open_spec const *op) |
366 | 6.34k | { |
367 | 6.34k | switch (op->lock_wait) |
368 | 6.34k | { |
369 | 6.34k | case GDBM_LOCKWAIT_NONE: |
370 | 6.34k | return _gdbm_lock_file (dbf, 1); |
371 | | |
372 | 0 | case GDBM_LOCKWAIT_RETRY: |
373 | 0 | return _gdbm_lockwait_retry (dbf, &op->lock_timeout, &op->lock_interval); |
374 | | |
375 | 0 | case GDBM_LOCKWAIT_SIGNAL: |
376 | 0 | return _gdbm_lockwait_signal (dbf, &op->lock_timeout); |
377 | 6.34k | } |
378 | 6.34k | errno = EINVAL; |
379 | 0 | return -1; |
380 | 6.34k | } |