diff options
Diffstat (limited to 'camel')
-rw-r--r-- | camel/camel-lock.c | 404 | ||||
-rw-r--r-- | camel/camel-lock.h | 64 |
2 files changed, 468 insertions, 0 deletions
diff --git a/camel/camel-lock.c b/camel/camel-lock.c new file mode 100644 index 0000000000..d6c3ad9312 --- /dev/null +++ b/camel/camel-lock.c @@ -0,0 +1,404 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Author: Michael Zucchi <notzed@helixcode.com> + * + * Copyright (C) 1999 Helix Code (http://www.helixcode.com/). + * + * This program 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 2 of the + * License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +#include <config.h> + +/* need configure checks here */ +#define USE_DOT +#define USE_FCNTL +#define USE_FLOCK + +#include <sys/stat.h> +#include <unistd.h> +#include <errno.h> +#include <alloca.h> +#include <string.h> + +#ifdef USE_DOT +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#endif + +#ifdef USE_FCNTL +#include <unistd.h> +#include <fcntl.h> +#endif + +#ifdef USE_FLOCK +#include <sys/file.h> +#endif + +#include "camel-lock.h" + +/* dunno where this fucking thing is got from */ +/*#define _(x) (x)*/ + +#define d(x) (printf("%s(%d): ", __FILE__, __LINE__),(x)) + +/** + * camel_lock_dot: + * @path: + * @ex: + * + * Create an exclusive lock using .lock semantics. + * All locks are equivalent to write locks (exclusive). + * + * Return value: -1 on error, sets @ex appropriately. + **/ +int +camel_lock_dot(const char *path, CamelException *ex) +{ +#ifdef USE_DOT + char *locktmp, *lock; + int retry = 0; + int fdtmp, fd; + struct stat st; + + /* TODO: Is there a reliable way to refresh the lock, if we're still busy with it? + Does it matter? We will normally also use fcntl too ... */ + + /* use alloca, save cleaning up afterwards */ + lock = alloca(strlen(path) + strlen(".lock") + 1); + sprintf(lock, "%s.lock", path); + locktmp = alloca(strlen(path) + strlen("XXXXXX") + 1); + +#ifndef HAVE_MKSTEMP + sprintf(locktmp, "%sXXXXXX", path); + if (mktemp(locktmp) == NULL) { + /* well, this is really only a programatic error */ + camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, _("Could not create lock file for %s: %s"), path, strerror(errno)); + return -1; + } +#endif + + while (retry < CAMEL_LOCK_DOT_RETRY) { + + d(printf("trying to lock '%s', attempt %d\n", lock, retry)); + + if (retry > 0) + sleep(CAMEL_LOCK_DOT_DELAY); + +#ifdef HAVE_MKSTEMP + sprintf(locktmp, "%sXXXXXX", path); + fdtmp = mkstemp(locktmp); +#else + fdtmp = open(locktmp, O_RDWR|O_CREAT|O_EXCL, 0600); +#endif + if (fdtmp == -1) { + camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, _("Could not create lock file for %s: %s"), path, strerror(errno)); + return -1; + } + close(fdtmp); + + /* apparently return code from link can be unreliable for nfs (see link(2)), so we ignore it */ + link(locktmp, lock); + + /* but we check stat instead (again, see link(2)) */ + if (stat(locktmp, &st) == -1) { + d(printf("Out lock file %s vanished!?\n", locktmp)); + + /* well that was unexpected, try cleanup/retry */ + unlink(locktmp); + unlink(lock); + } else { + d(printf("tmp lock created, link count is %d\n", st.st_nlink)); + + unlink(locktmp); + + /* if we had 2 links, we have created the .lock, return ok, otherwise we need to keep trying */ + if (st.st_nlink == 2) + return 0; + } + + /* check for stale lock, kill it */ + if (stat(lock, &st) == 0) { + time_t now = time(0); + d(printf("There is an existing lock %d seconds old\n", now-st.st_ctime)); + if (st.st_ctime < now - CAMEL_LOCK_DOT_STALE) { + d(printf("Removing it now\n")); + unlink(lock); + } + } + + retry++; + } + + d(printf("failed to get lock after %d retries\n", retry)); + + camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, _("Timed out trying to get lock file on %s. Try again later."), path); + return -1; +#else /* ! USE_DOT */ + return 0; +#endif +} + +/** + * camel_unlock_dot: + * @path: + * + * Attempt to unlock a .lock lock. + **/ +void +camel_unlock_dot(const char *path) +{ +#ifdef USE_DOT + char *lock; + + lock = alloca(strlen(path) + strlen(".lock") + 1); + sprintf(lock, "%s.lock", path); + d(printf("unlocking %s\n", lock)); + (void)unlink(lock); +#endif +} + +/** + * camel_lock_fcntl: + * @fd: + * @type: + * @ex: + * + * Create a lock using fcntl(2). + * + * @type is CAMEL_LOCK_WRITE or CAMEL_LOCK_READ, + * to create exclusive or shared read locks + * + * Return value: -1 on error. + **/ +int +camel_lock_fcntl(int fd, CamelLockType type, CamelException *ex) +{ +#ifdef USE_FCNTL + struct flock lock; + + d(printf("fcntl locking %d\n", fd)); + + memset(&lock, 0, sizeof(lock)); + lock.l_type = type==CAMEL_LOCK_READ?F_RDLCK:F_WRLCK; + if (fcntl(fd, F_SETLK, &lock) == -1) { + camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, _("Failed to get lock using fcntl(2): %s"), strerror(errno)); + return -1; + } +#endif + return 0; +} + +/** + * camel_unlock_fcntl: + * @fd: + * + * Unlock an fcntl lock. + **/ +void +camel_unlock_fcntl(int fd) +{ +#ifdef USE_FCNTL + struct flock lock; + + d(printf("fcntl unlocking %d\n", fd)); + + memset(&lock, 0, sizeof(lock)); + lock.l_type = F_UNLCK; + fcntl(fd, F_SETLK, &lock); +#endif +} + +/** + * camel_lock_flock: + * @fd: + * @type: + * @ex: + * + * Create a lock using flock(2). + * + * @type is CAMEL_LOCK_WRITE or CAMEL_LOCK_READ, + * to create exclusive or shared read locks + * + * Return value: -1 on error. + **/ +int +camel_lock_flock(int fd, CamelLockType type, CamelException *ex) +{ +#ifdef USE_FLOCK + int op; + + d(printf("flock locking %d\n", fd)); + + if (type == CAMEL_LOCK_READ) + op = LOCK_SH|LOCK_NB; + else + op = LOCK_EX|LOCK_NB; + + if (flock(fd, op) == -1) { + camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, _("Failed to get lock using flock(2): %s"), strerror(errno)); + return -1; + } +#endif + return 0; +} + +/** + * camel_unlock_flock: + * @fd: + * + * Unlock an flock lock. + **/ +void +camel_unlock_flock(int fd) +{ +#ifdef USE_FLOCK + d(printf("flock unlocking %d\n", fd)); + + (void)flock(fd, LOCK_UN); +#endif +} + +/** + * camel_lock_folder: + * @path: Path to the file to lock (used for .locking only). + * @fd: Open file descriptor of the right type to lock. + * @type: Type of lock, CAMEL_LOCK_READ or CAMEL_LOCK_WRITE. + * @ex: + * + * Attempt to lock a folder, multiple attempts will be made using all + * locking strategies available. + * + * Return value: -1 on error, @ex will describe the locking system that failed. + **/ +int +camel_lock_folder(const char *path, int fd, CamelLockType type, CamelException *ex) +{ + int retry = 0; + + while (retry < CAMEL_LOCK_RETRY) { + if (retry > 0) + sleep(CAMEL_LOCK_DELAY); + + camel_exception_clear(ex); + + if (camel_lock_fcntl(fd, type, ex) == 0) { + if (camel_lock_flock(fd, type, ex) == 0) { + if (camel_lock_dot(path, ex) == 0) + return 0; + camel_unlock_flock(fd); + } + camel_unlock_fcntl(fd); + } + retry++; + } + + return -1; +} + +/** + * camel_unlock_folder: + * @path: Filename of folder. + * @fd: Open descrptor on which locks were placed. + * + * Free a lock on a folder. + **/ +void +camel_unlock_folder(const char *path, int fd) +{ + camel_unlock_dot(path); + camel_unlock_flock(fd); + camel_unlock_fcntl(fd); +} + +#if 0 +int main(int argc, char **argv) +{ + CamelException *ex; + int fd1, fd2; + + ex = camel_exception_new(); + +#if 0 + if (camel_lock_dot("mylock", ex) == 0) { + if (camel_lock_dot("mylock", ex) == 0) { + printf("Got lock twice?\n"); + } else { + printf("failed to get lock 2: %s\n", camel_exception_get_description(ex)); + } + camel_unlock_dot("mylock"); + } else { + printf("failed to get lock 1: %s\n", camel_exception_get_description(ex)); + } + + camel_exception_clear(ex); +#endif + + fd1 = open("mylock", O_RDWR); + fd2 = open("mylock", O_RDWR); + + if (camel_lock_fcntl(fd1, CAMEL_LOCK_WRITE, ex) == 0) { + printf("got fcntl write lock once\n"); + sleep(5); + if (camel_lock_fcntl(fd2, CAMEL_LOCK_WRITE, ex) == 0) { + printf("got fcntl write lock twice!\n"); + } else { + printf("failed to get write lock: %s\n", camel_exception_get_description(ex)); + } + + camel_exception_clear(ex); + + if (camel_lock_fcntl(fd2, CAMEL_LOCK_READ, ex) == 0) { + printf("got fcntl read lock as well?\n"); + camel_unlock_fcntl(fd2); + } else { + printf("failed to get read lock: %s\n", camel_exception_get_description(ex)); + } + + camel_exception_clear(ex); + camel_unlock_fcntl(fd1); + } else { + printf("failed to get write lock at all: %s\n", camel_exception_get_description(ex)); + } + + if (camel_lock_fcntl(fd1, CAMEL_LOCK_READ, ex) == 0) { + printf("got fcntl read lock once\n"); + sleep(5); + if (camel_lock_fcntl(fd2, CAMEL_LOCK_WRITE, ex) == 0) { + printf("got fcntl write lock too?!\n"); + } else { + printf("failed to get write lock: %s\n", camel_exception_get_description(ex)); + } + + camel_exception_clear(ex); + + if (camel_lock_fcntl(fd2, CAMEL_LOCK_READ, ex) == 0) { + printf("got fcntl read lock twice\n"); + camel_unlock_fcntl(fd2); + } else { + printf("failed to get read lock: %s\n", camel_exception_get_description(ex)); + } + + camel_exception_clear(ex); + camel_unlock_fcntl(fd1); + } + + close(fd1); + close(fd2); + + return 0; +} +#endif diff --git a/camel/camel-lock.h b/camel/camel-lock.h new file mode 100644 index 0000000000..37da6ea135 --- /dev/null +++ b/camel/camel-lock.h @@ -0,0 +1,64 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Author: Michael Zucchi <notzed@helixcode.com> + * + * Copyright (C) 1999 Helix Code (http://www.helixcode.com/). + * + * This program 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 2 of the + * License, or (at your option) any later version. + * + * This program 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 this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +#ifndef _CAMEL_LOCK_H +#define _CAMEL_LOCK_H + +#ifdef __cplusplus +extern "C" { +#pragma } +#endif /* __cplusplus }*/ + +#include <camel/camel-exception.h> + +/* for .lock locking, retry, delay and stale counts */ +#define CAMEL_LOCK_DOT_RETRY (5) /* number of times to retry lock */ +#define CAMEL_LOCK_DOT_DELAY (2) /* delay between locking retries */ +#define CAMEL_LOCK_DOT_STALE (60) /* seconds before a lock becomes stale */ + +/* for locking folders, retry/interretry delay */ +#define CAMEL_LOCK_RETRY (5) /* number of times to retry lock */ +#define CAMEL_LOCK_DELAY (2) /* delay between locking retries */ + +typedef enum { + CAMEL_LOCK_READ, + CAMEL_LOCK_WRITE, +} CamelLockType; + +/* specific locking strategies */ +int camel_lock_dot(const char *path, CamelException *ex); +int camel_lock_fcntl(int fd, CamelLockType type, CamelException *ex); +int camel_lock_flock(int fd, CamelLockType type, CamelException *ex); + +void camel_unlock_dot(const char *path); +void camel_unlock_fcntl(int fd); +void camel_unlock_flock(int fd); + +/* lock a folder in a standard way */ +int camel_lock_folder(const char *path, int fd, CamelLockType type, CamelException *ex); +void camel_unlock_folder(const char *path, int fd); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* !_CAMEL_LOCK_H */ |