/*
* Amanda, The Advanced Maryland Automatic Network Disk Archiver
* Copyright (c) 1991-1998 University of Maryland at College Park
* Copyright (c) 2007-2012 Zmanda, Inc. All Rights Reserved.
* Copyright (c) 2013-2016 Carbonite, Inc. All Rights Reserved.
* All Rights Reserved.
*
* Permission to use, copy, modify, distribute, and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
* the above copyright notice appear in all copies and that both that
* copyright notice and this permission notice appear in supporting
* documentation, and that the name of U.M. not be used in advertising or
* publicity pertaining to distribution of the software without specific,
* written prior permission. U.M. makes no representations about the
* suitability of this software for any purpose. It is provided "as is"
* without express or implied warranty.
*
* U.M. DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING ALL
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL U.M.
* BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* Authors: the Amanda Development Team. Its members are listed in a
* file named AUTHORS, in the root directory of this distribution.
*/
/*
* $Id: amflock.c 7161 2007-07-03 16:27:26Z dustin $
*
* file locking routines, put here to hide the system dependant stuff
* from the rest of the code
*/
#include "amanda.h"
/*
* New Implementation
*/
#if (GLIB_MAJOR_VERSION > 2 || (GLIB_MAJOR_VERSION == 2 && GLIB_MINOR_VERSION >= 31))
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wmissing-field-initializers"
static GStaticMutex lock_lock = G_STATIC_MUTEX_INIT;
# pragma GCC diagnostic pop
#else
static GStaticMutex lock_lock = G_STATIC_MUTEX_INIT;
#endif
static GHashTable *locally_locked_files = NULL;
static int lock_rw_rd(file_lock *lock, short l_type);
file_lock *
file_lock_new(
const char *filename)
{
file_lock *lock = g_new0(file_lock, 1);
lock->filename = g_strdup(filename);
lock->fd = -1;
return lock;
}
void
file_lock_free(
file_lock *lock)
{
g_static_mutex_lock(&lock_lock);
if (locally_locked_files) {
g_hash_table_remove(locally_locked_files,
lock->filename);
}
if (lock->data)
g_free(lock->data);
if (lock->filename)
g_free(lock->filename);
if (lock->fd != -1)
close(lock->fd);
g_static_mutex_unlock(&lock_lock);
g_free(lock);
}
int
file_lock_lock(
file_lock *lock)
{
int rv = -2;
int fd = -1;
int saved_errno = 0;
struct flock lock_buf;
struct stat stat_buf;
g_assert(!lock->locked);
/* protect from overlapping lock operations within a process */
g_static_mutex_lock(&lock_lock);
if (!locally_locked_files) {
locally_locked_files = g_hash_table_new(g_str_hash, g_str_equal);
}
/* if this filename is in the hash table, then some other thread in this
* process has locked it */
if (g_hash_table_lookup(locally_locked_files, lock->filename)) {
rv = 1;
errno = EBUSY;
saved_errno = errno;
goto done;
}
/* The locks are advisory, so an error here never means the lock is already
* taken. */
lock->fd = fd = open(lock->filename, O_CREAT|O_RDWR, 0666);
if (fd < 0 && errno == ENOENT) { /* create the parent directory */
char *dirname = g_strdup(lock->filename);
char *p = strrchr(dirname, '/');
if (p) {
*p = '\0';
if (*dirname == '/') {
if (mkdir(dirname, 0700) == -1 && errno != EEXIST) {
g_debug("Can't mkdir (%s): %s", dirname, strerror(errno));
}
}
}
lock->fd = fd = open(lock->filename, O_CREAT|O_RDWR, 0666);
}
if (fd < 0) {
saved_errno = errno;
g_debug("file_lock_lock open failed (%s): %s", lock->filename, strerror(saved_errno));
if (saved_errno == EACCES || saved_errno == EAGAIN) {
rv = 1;
} else {
rv = -1;
}
goto done;
}
/* now try locking it */
lock_buf.l_type = F_WRLCK;
lock_buf.l_start = 0;
lock_buf.l_whence = SEEK_SET;
lock_buf.l_len = 0; /* to EOF */
if (fcntl(fd, F_SETLK, &lock_buf) < 0) {
saved_errno = errno;
g_debug("file_lock_lock fcntl failed (%s): %s", lock->filename, strerror(saved_errno));
if (saved_errno == EACCES || saved_errno == EAGAIN) {
rv = 1;
} else {
rv = -1;
}
goto done;
}
/* and read the file in its entirety */
if (fstat(fd, &stat_buf) < 0) {
saved_errno = errno;
g_debug("file_lock_lock fstat failed (%s): %s", lock->filename, strerror(saved_errno));
rv = -1;
goto done;
}
if (!(stat_buf.st_mode & S_IFREG)) {
rv = -1;
errno = EINVAL;
saved_errno = errno;
g_debug("file_lock_lock (%s) !S_IFREG", lock->filename);
goto done;
}
if (stat_buf.st_size) {
lock->data = g_malloc(stat_buf.st_size+1);
lock->len = stat_buf.st_size;
if (read_fully(fd, lock->data, lock->len, NULL) < lock->len) {
saved_errno = errno;
g_debug("file_lock_lock read_fully failed (%s): %s", lock->filename, strerror(saved_errno));
rv = -1;
goto done;
}
lock->data[lock->len] = '\0';
}
fd = -1; /* we'll keep the file now */
lock->locked = TRUE;
/* the lock is acquired; record this in the hash table */
g_hash_table_insert(locally_locked_files, lock->filename, lock->filename);
rv = 0;
done:
g_static_mutex_unlock(&lock_lock);
if (fd >= 0) /* close and unlock if an error occurred */
close(fd);
errno = saved_errno;
return rv;
}
static int
lock_rw_rd(
file_lock *lock,
short l_type)
{
int rv = -2;
int fd = -1;
int saved_errno;
struct flock lock_buf;
struct stat stat_buf;
g_assert(!lock->locked);
/* protect from overlapping lock operations within a process */
g_static_mutex_lock(&lock_lock);
/* The locks are advisory, so an error here never means the lock is already
* taken. */
lock->fd = fd = open(lock->filename, O_CREAT|O_RDWR, 0666);
if (fd < 0) {
rv = -1;
goto done;
}
/* now try locking it */
lock_buf.l_type = l_type;
lock_buf.l_start = 0;
lock_buf.l_whence = SEEK_SET;
lock_buf.l_len = 0; /* to EOF */
if (fcntl(fd, F_SETLK, &lock_buf) < 0) {
if (errno == EACCES || errno == EAGAIN)
rv = 1;
else
rv = -1;
goto done;
}
/* and read the file in its entirety */
if (fstat(fd, &stat_buf) < 0) {
rv = -1;
goto done;
}
if (!(stat_buf.st_mode & S_IFREG)) {
rv = -1;
errno = EINVAL;
goto done;
}
fd = -1; /* we'll keep the file now */
lock->locked = TRUE;
rv = 0;
done:
saved_errno = errno;
g_static_mutex_unlock(&lock_lock);
if (fd >= 0) /* close and unlock if an error occurred */
close(fd);
errno = saved_errno;
return rv;
}
int
file_lock_lock_wr(
file_lock *lock)
{
return lock_rw_rd(lock, F_WRLCK);
}
int
file_lock_lock_rd(
file_lock *lock)
{
return lock_rw_rd(lock, F_RDLCK);
}
int
file_lock_locked(
file_lock *lock)
{
return lock->locked;
}
int
file_lock_write(
file_lock *lock,
const char *data,
size_t len)
{
int fd = lock->fd;
g_assert(lock->locked);
/* seek to position 0, rewrite, and truncate */
if (lseek(fd, 0, SEEK_SET) < 0) {
g_debug("file_lock_write: failed to lseek (%s): %s", lock->filename, strerror(errno));
if (ftruncate(fd, 0) < 0) {};
return -1;
}
/* from here on out, any errors have corrupted the datafile.. */
if (full_write(fd, data, len) < len) {
g_debug("file_lock_write: failed to write (%s): %s", lock->filename, strerror(errno));
if (ftruncate(fd, 0) < 0) {};
return -1;
}
if (lock->len > len) {
if (ftruncate(fd, len) < 0) {
g_debug("file_lock_write: failed to ftruncate (%s): %s", lock->filename, strerror(errno));
if (ftruncate(fd, 0) < 0) {};
return -1;
}
}
if (lock->data)
g_free(lock->data);
lock->data = g_strdup(data);
lock->len = len;
return 0;
}
int
file_lock_unlock(
file_lock *lock)
{
g_assert(lock->locked);
g_static_mutex_lock(&lock_lock);
/* relase the filesystem-level lock */
close(lock->fd);
/* and the hash table entry */
if (locally_locked_files) {
g_hash_table_remove(locally_locked_files, lock->filename);
}
g_static_mutex_unlock(&lock_lock);
if (lock->data)
g_free(lock->data);
lock->data = NULL;
lock->len = 0;
lock->fd = -1;
lock->locked = FALSE;
return 0;
}
/*
* Old Implementation
*/
/*
**
** Notes:
** - These are "best effort" routines.
** - "configure" has four variables that are used to determine which type of
** locking to use:
** USE_POSIX_FCNTL - use fcntl(). The full job.
** USE_FLOCK - use flock(). Does just as well.
** USE_LOCKF - use lockf(). Only handles advisory, exclusive,
** blocking file locks as used by Amanda.
** USE_LNLOCK - Home brew exclusive, blocking file lock.
** <none> - No locking available. User beware!
*/
/* Interface to the implementations in common-src/amflock-*.c */
#ifdef WANT_AMFLOCK_POSIX
extern amflock_impl_t amflock_posix_impl;
#endif
#ifdef WANT_AMFLOCK_FLOCK
extern amflock_impl_t amflock_flock_impl;
#endif
#ifdef WANT_AMFLOCK_LOCKF
extern amflock_impl_t amflock_lockf_impl;
#endif
#ifdef WANT_AMFLOCK_LNLOCK
extern amflock_impl_t amflock_lnlock_impl;
#endif
amflock_impl_t *amflock_impls[] = {
#ifdef WANT_AMFLOCK_POSIX
&amflock_posix_impl,
#endif
#ifdef WANT_AMFLOCK_FLOCK
&amflock_flock_impl,
#endif
#ifdef WANT_AMFLOCK_LOCKF
&amflock_lockf_impl,
#endif
#ifdef WANT_AMFLOCK_LNLOCK
&amflock_lnlock_impl,
#endif
NULL
};
/* Interface functions */
/* FIXME: for now, these just use the first non-NULL implementation
*/
/* Get a file lock (for read/write files).
*/
int
amflock(
int fd,
char * resource)
{
if (!amflock_impls[0]) return 0; /* no locking */
return amflock_impls[0]->amflock_impl(fd, resource);
}
/*
* Get a file lock (for read-only files).
*/
int
amroflock(
int fd,
char * resource)
{
if (!amflock_impls[0]) return 0; /* no locking */
return amflock_impls[0]->amroflock_impl(fd, resource);
}
/*
* Release a file lock.
*/
int
amfunlock(
int fd,
char * resource)
{
if (!amflock_impls[0]) return 0; /* no locking */
return amflock_impls[0]->amfunlock_impl(fd, resource);
}