Coverage Report

Created: 2026-02-26 07:11

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdbm/src/lock.c
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
}