/* lock.c - Implement basic file locking for GDBM. */
/* This file is part of GDBM, the GNU data base manager.
Copyright (C) 2008-2023 Free Software Foundation, Inc.
GDBM is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
GDBM is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with GDBM. If not, see . */
/* Include system configuration before all else. */
#include "autoconf.h"
#include "gdbmdefs.h"
#include
#if HAVE_FLOCK
# ifndef LOCK_SH
# define LOCK_SH 1
# endif
# ifndef LOCK_EX
# define LOCK_EX 2
# endif
# ifndef LOCK_NB
# define LOCK_NB 4
# endif
# ifndef LOCK_UN
# define LOCK_UN 8
# endif
#endif
#if defined(F_SETLK) && defined(F_RDLCK) && defined(F_WRLCK)
# define HAVE_FCNTL_LOCK 1
#else
# define HAVE_FCNTL_LOCK 0
#endif
/* Return values for try_lock_ functions: */
enum
{
TRY_LOCK_OK, /* Locking succeeded */
TRY_LOCK_FAIL, /* File already locked by another process. */
TRY_LOCK_NEXT /* Another error (including locking mechanism not
available). The caller should try next locking
mechanism. */
};
/*
* Locking using flock().
*/
static int
try_lock_flock (GDBM_FILE dbf)
{
#if HAVE_FLOCK
if (flock (dbf->desc,
((dbf->read_write == GDBM_READER) ? LOCK_SH : LOCK_EX)
| LOCK_NB) == 0)
{
return TRY_LOCK_OK;
}
else if (errno == EWOULDBLOCK)
{
return TRY_LOCK_FAIL;
}
#endif
return TRY_LOCK_NEXT;
}
static void
unlock_flock (GDBM_FILE dbf)
{
#if HAVE_FLOCK
flock (dbf->desc, LOCK_UN);
#endif
}
/*
* Locking via lockf.
*/
static int
try_lock_lockf (GDBM_FILE dbf)
{
#if HAVE_LOCKF
/*
* NOTE: lockf will fail with EINVAL unless the database file was opened
* with write-only permission (O_WRONLY) or with read/write permission
* (O_RDWR). This means that this locking mechanism will always fail for
* databases opened with GDBM_READER,
*/
if (dbf->read_write != GDBM_READER)
{
if (lockf (dbf->desc, F_TLOCK, (off_t)0L) == 0)
return TRY_LOCK_OK;
switch (errno)
{
case EACCES:
case EAGAIN:
case EDEADLK:
return TRY_LOCK_FAIL;
default:
/* try next locking method */
break;
}
}
#endif
return TRY_LOCK_NEXT;
}
static void
unlock_lockf (GDBM_FILE dbf)
{
#if HAVE_LOCKF
lockf (dbf->desc, F_ULOCK, (off_t)0L);
#endif
}
/*
* Locking via fcntl().
*/
static int
try_lock_fcntl (GDBM_FILE dbf)
{
#if HAVE_FCNTL_LOCK
struct flock fl;
/* If we're still here, try fcntl. */
if (dbf->read_write == GDBM_READER)
fl.l_type = F_RDLCK;
else
fl.l_type = F_WRLCK;
fl.l_whence = SEEK_SET;
fl.l_start = fl.l_len = (off_t)0L;
if (fcntl (dbf->desc, F_SETLK, &fl) == 0)
return TRY_LOCK_OK;
switch (errno)
{
case EACCES:
case EAGAIN:
case EDEADLK:
return TRY_LOCK_FAIL;
default:
/* try next locking method */
break;
}
#endif
return TRY_LOCK_NEXT;
}
static void
unlock_fcntl (GDBM_FILE dbf)
{
#if HAVE_FCNTL_LOCK
struct flock fl;
fl.l_type = F_UNLCK;
fl.l_whence = SEEK_SET;
fl.l_start = fl.l_len = (off_t)0L;
fcntl (dbf->desc, F_SETLK, &fl);
#endif
}
/* Try each supported locking mechanism. */
int
_gdbm_lock_file (GDBM_FILE dbf)
{
int res;
dbf->lock_type = LOCKING_NONE;
if ((res = try_lock_flock (dbf)) == TRY_LOCK_OK)
dbf->lock_type = LOCKING_FLOCK;
else if (res == TRY_LOCK_NEXT)
{
if ((res = try_lock_lockf (dbf)) == TRY_LOCK_OK)
dbf->lock_type = LOCKING_LOCKF;
else if (res == TRY_LOCK_NEXT)
{
if (try_lock_fcntl (dbf) == TRY_LOCK_OK)
dbf->lock_type = LOCKING_FCNTL;
}
}
return dbf->lock_type == LOCKING_NONE ? -1 : 0;
}
void
_gdbm_unlock_file (GDBM_FILE dbf)
{
void (*unlock_fn[]) (GDBM_FILE) = {
[LOCKING_FLOCK] = unlock_flock,
[LOCKING_LOCKF] = unlock_lockf,
[LOCKING_FCNTL] = unlock_fcntl
};
if (dbf->lock_type != LOCKING_NONE)
{
unlock_fn[dbf->lock_type] (dbf);
dbf->lock_type = LOCKING_NONE;
}
}