/* $Cambridge: exim/exim-src/src/exim_lock.c,v 1.3 2005/06/27 14:29:43 ph10 Exp $ */
/* A program to lock a file exactly as Exim would, for investigation of
interlocking problems.
Options: -fcntl use fcntl() lock
-flock use flock() lock
-lockfile use lock file
-mbx use mbx locking rules, with either fcntl() or flock()
Default is -fcntl -lockfile.
Argument: the name of the lock file
*/
#include "os.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <time.h>
#include <netdb.h>
#include <fcntl.h>
#include <unistd.h>
#include <utime.h>
#include <sys/utsname.h>
#include <sys/stat.h>
#include <sys/file.h>
#include <pwd.h>
/* Not all systems have flock() available. Those that do must define LOCK_SH
in sys/file.h. */
#ifndef LOCK_SH
#define NO_FLOCK
#endif
typedef int BOOL;
#define FALSE 0
#define TRUE 1
/* Flag for timeout signal handler */
static int sigalrm_seen = FALSE;
/* We need to pull in strerror() and os_non_restarting_signal() from the
os.c source, if they are required for this OS. However, we don't need any of
the other stuff in os.c, so force the other macros to omit it. */
#ifndef OS_RESTARTING_SIGNAL
#define OS_RESTARTING_SIGNAL
#endif
#ifndef OS_STRSIGNAL
#define OS_STRSIGNAL
#endif
#ifndef OS_STREXIT
#define OS_STREXIT
#endif
#ifndef OS_LOAD_AVERAGE
#define OS_LOAD_AVERAGE
#endif
#ifndef FIND_RUNNING_INTERFACES
#define FIND_RUNNING_INTERFACES
#endif
#include "../src/os.c"
/*************************************************
* Timeout handler *
*************************************************/
static void
sigalrm_handler(int sig)
{
sig = sig; /* Keep picky compilers happy */
sigalrm_seen = TRUE;
}
/*************************************************
* Give usage and die *
*************************************************/
static void
usage(void)
{
printf("usage: exim_lock [-v] [-q] [-lockfile] [-fcntl] [-flock] [-mbx]\n"
" [-retries <n>] [-interval <n>] [-timeout <n>] [-restore-times]\n"
" <file name> [command]\n");
exit(1);
}
/*************************************************
* Apply a lock to a file descriptor *
*************************************************/
static int
apply_lock(int fd, int fcntltype, BOOL dofcntl, int fcntltime, BOOL doflock,
int flocktime)
{
int yield = 0;
int save_errno;
struct flock lock_data;
lock_data.l_type = fcntltype;
lock_data.l_whence = lock_data.l_start = lock_data.l_len = 0;
sigalrm_seen = FALSE;
if (dofcntl)
{
if (fcntltime > 0)
{
os_non_restarting_signal(SIGALRM, sigalrm_handler);
alarm(fcntltime);
yield = fcntl(fd, F_SETLKW, &lock_data);
save_errno = errno;
alarm(0);
errno = save_errno;
}
else yield = fcntl(fd, F_SETLK, &lock_data);
if (yield < 0) printf("exim_lock: fcntl() failed: %s\n", strerror(errno));
}
#ifndef NO_FLOCK
if (doflock && (yield >= 0))
{
int flocktype = (fcntltype == F_WRLCK)? LOCK_EX : LOCK_SH;
if (flocktime > 0)
{
os_non_restarting_signal(SIGALRM, sigalrm_handler);
alarm(flocktime);
yield = flock(fd, flocktype);
save_errno = errno;
alarm(0);
errno = save_errno;
}
else yield = flock(fd, flocktype | LOCK_NB);
if (yield < 0) printf("exim_lock: flock() failed: %s\n", strerror(errno));
}
#endif
return yield;
}
/*************************************************
* The exim_lock program *
*************************************************/
int main(int argc, char **argv)
{
int lock_retries = 10;
int lock_interval = 3;
int lock_fcntl_timeout = 0;
int lock_flock_timeout = 0;
int i, j, len;
int fd = -1;
int hd = -1;
int md = -1;
int yield = 0;
int now = time(NULL);
BOOL use_lockfile = FALSE;
BOOL use_fcntl = FALSE;
BOOL use_flock = FALSE;
BOOL use_mbx = FALSE;
BOOL verbose = FALSE;
BOOL quiet = FALSE;
BOOL restore_times = FALSE;
char *filename;
char *lockname = NULL, *hitchname = NULL;
char *primary_hostname, *command;
struct utsname s;
char buffer[256];
char tempname[256];
/* Decode options */
for (i = 1; i < argc; i++)
{
char *arg = argv[i];
if (*arg != '-') break;
if (strcmp(arg, "-fcntl") == 0) use_fcntl = TRUE;
else if (strcmp(arg, "-flock") == 0) use_flock = TRUE;
else if (strcmp(arg, "-lockfile") == 0) use_lockfile = TRUE;
else if (strcmp(arg, "-mbx") == 0) use_mbx = TRUE;
else if (strcmp(arg, "-v") == 0) verbose = TRUE;
else if (strcmp(arg, "-q") == 0) quiet = TRUE;
else if (strcmp(arg, "-restore-times") == 0) restore_times = TRUE;
else if (++i < argc)
{
int value = atoi(argv[i]);
if (strcmp(arg, "-retries") == 0) lock_retries = value;
else if (strcmp(arg, "-interval") == 0) lock_interval = value;
else if (strcmp(arg, "-timeout") == 0)
lock_fcntl_timeout = lock_flock_timeout = value;
else usage();
}
else usage();
}
if (quiet) verbose = 0;
/* Can't use flock() if the OS doesn't provide it */
#ifdef NO_FLOCK
if (use_flock)
{
printf("exim_lock: can't use flock() because it was not available in the\n"
" operating system when exim_lock was compiled\n");
exit(1);
}
#endif
/* Default is to use lockfiles and fcntl(). */
if (!use_lockfile && !use_fcntl && !use_flock && !use_mbx)
use_lockfile = use_fcntl = TRUE;
/* Default fcntl() for use with mbx */
if (use_mbx && !use_fcntl && !use_flock) use_fcntl = TRUE;
/* Unset unused timeouts */
if (!use_fcntl) lock_fcntl_timeout = 0;
if (!use_flock) lock_flock_timeout = 0;
/* A file name is required */
if (i >= argc) usage();
filename = argv[i++];
/* Expand file names starting with ~ */
if (*filename == '~')
{
struct passwd *pw;
if (*(++filename) == '/')
pw = getpwuid(getuid());
else
{
char *s = buffer;
while (*filename != 0 && *filename != '/')
*s++ = *filename++;
*s = 0;
pw = getpwnam(buffer);
}
if (pw == NULL)
{
printf("exim_lock: unable to expand file name %s\n", argv[i-1]);
exit(1);
}
if ((int)strlen(pw->pw_dir) + (int)strlen(filename) + 1 > sizeof(buffer))
{
printf("exim_lock: expanded file name %s%s is too long", pw->pw_dir,
filename);
exit(1);
}
strcpy(buffer, pw->pw_dir);
strcat(buffer, filename);
filename = buffer;
}
/* If using a lock file, prepare by creating the lock file name and
the hitching post name. */
if (use_lockfile)
{
if (uname(&s) < 0)
{
printf("exim_lock: failed to find host name using uname()\n");
exit(1);
}
primary_hostname = s.nodename;
len = (int)strlen(filename);
lockname = malloc(len + 8);
sprintf(lockname, "%s.lock", filename);
hitchname = malloc(len + 32 + (int)strlen(primary_hostname));
sprintf(hitchname, "%s.%s.%08x.%08x", lockname, primary_hostname,
now, (int)getpid());
if (verbose)
printf("exim_lock: lockname = %s\n hitchname = %s\n", lockname,
hitchname);
}
/* Locking retry loop */
for (j = 0; j < lock_retries; j++)
{
int sleep_before_retry = TRUE;
struct stat statbuf, ostatbuf;
/* Try to build a lock file if so configured */
if (use_lockfile)
{
int rc;
if (verbose) printf("exim_lock: creating lock file\n");
hd = open(hitchname, O_WRONLY | O_CREAT | O_EXCL, 0440);
if (hd < 0)
{
printf("exim_lock: failed to create hitching post %s: %s\n", hitchname,
strerror(errno));
exit(1);
}
/* Apply hitching post algorithm. */
if ((rc = link(hitchname, lockname)) != 0) fstat(hd, &statbuf);
(void)close(hd);
unlink(hitchname);
if (rc != 0 && statbuf.st_nlink != 2)
{
printf("exim_lock: failed to link hitching post to lock file\n");
hd = -1;
goto RETRY;
}
if (!quiet) printf("exim_lock: lock file successfully created\n");
}
/* We are done if no other locking required. */
if (!use_fcntl && !use_flock && !use_mbx) break;
/* Open the file for writing. */
fd = open(filename, O_RDWR + O_APPEND);
if (fd < 0)
{
printf("exim_lock: failed to open %s for writing: %s\n", filename,
strerror(errno));
yield = 1;
goto CLEAN_UP;
}
/* If there is a timeout, implying blocked locking, we don't want to
sleep before any retries after this. */
if (lock_fcntl_timeout > 0 || lock_flock_timeout > 0)
sleep_before_retry = FALSE;
/* Lock using fcntl. There are pros and cons to using a blocking call vs
a non-blocking call and retries. Exim is non-blocking by default, but setting
a timeout changes it to blocking. */
if (!use_mbx && (use_fcntl || use_flock))
{
if (apply_lock(fd, F_WRLCK, use_fcntl, lock_fcntl_timeout, use_flock,
lock_flock_timeout) >= 0)
{
if (!quiet)
{
if (use_fcntl) printf("exim_lock: fcntl() lock successfully applied\n");
if (use_flock) printf("exim_lock: flock() lock successfully applied\n");
}
break;
}
else goto RETRY; /* Message already output */
}
/* Lock using MBX rules. This is complicated and is documented with the
source of the c-client library that goes with Pine and IMAP. What has to
be done to interwork correctly is to take out a shared lock on the mailbox,
and an exclusive lock on a /tmp file. */
else
{
if (apply_lock(fd, F_RDLCK, use_fcntl, lock_fcntl_timeout, use_flock,
lock_flock_timeout) >= 0)
{
if (!quiet)
{
if (use_fcntl)
printf("exim_lock: fcntl() read lock successfully applied\n");
if (use_flock)
printf("exim_lock: fcntl() read lock successfully applied\n");
}
}
else goto RETRY; /* Message already output */
if (fstat(fd, &statbuf) < 0)
{
printf("exim_lock: fstat() of %s failed: %s\n", filename,
strerror(errno));
yield = 1;
goto CLEAN_UP;
}
/* Set up file in /tmp and check its state if already existing. */
sprintf(tempname, "/tmp/.%lx.%lx", (long)statbuf.st_dev,
(long)statbuf.st_ino);
if (lstat(tempname, &statbuf) >= 0)
{
if ((statbuf.st_mode & S_IFMT) == S_IFLNK)
{
printf("exim_lock: symbolic link on lock name %s\n", tempname);
yield = 1;
goto CLEAN_UP;
}
if (statbuf.st_nlink > 1)
{
printf("exim_lock: hard link to lock name %s\n", tempname);
yield = 1;
goto CLEAN_UP;
}
}
md = open(tempname, O_RDWR | O_CREAT, 0600);
if (md < 0)
{
printf("exim_lock: failed to create mbx lock file %s: %s\n",
tempname, strerror(errno));
goto CLEAN_UP;
}
(void)chmod(tempname, 0600);
if (apply_lock(md, F_WRLCK, use_fcntl, lock_fcntl_timeout, use_flock,
lock_flock_timeout) >= 0)
{
if (!quiet)
{
if (use_fcntl)
printf("exim_lock: fcntl() lock successfully applied to mbx "
"lock file %s\n", tempname);
if (use_flock)
printf("exim_lock: flock() lock successfully applied to mbx "
"lock file %s\n", tempname);
}
/* This test checks for a race condition */
if (lstat(tempname, &statbuf) != 0 ||
fstat(md, &ostatbuf) != 0 ||
statbuf.st_dev != ostatbuf.st_dev ||
statbuf.st_ino != ostatbuf.st_ino)
{
if (!quiet) printf("exim_lock: mbx lock file %s changed between "
"creation and locking\n", tempname);
goto RETRY;
}
else break;
}
else goto RETRY; /* Message already output */
}
/* Clean up before retrying */
RETRY:
if (md >= 0)
{
if (close(md) < 0)
printf("exim_lock: close %s failed: %s\n", tempname, strerror(errno));
else
if (!quiet) printf("exim_lock: %s closed\n", tempname);
md = -1;
}
if (fd >= 0)
{
if (close(fd) < 0)
printf("exim_lock: close failed: %s\n", strerror(errno));
else
if (!quiet) printf("exim_lock: file closed\n");
fd = -1;
}
if (hd >= 0)
{
if (unlink(lockname) < 0)
printf("exim_lock: unlink of %s failed: %s\n", lockname, strerror(errno));
else
if (!quiet) printf("exim_lock: lock file removed\n");
hd = -1;
}
/* If a blocking call timed out, break the retry loop if the total time
so far is not less than than retries * interval. */
if (sigalrm_seen &&
(j + 1) * ((lock_fcntl_timeout > lock_flock_timeout)?
lock_fcntl_timeout : lock_flock_timeout) >=
lock_retries * lock_interval)
j = lock_retries;
/* Wait a bit before retrying, except when it was a blocked fcntl() that
caused the problem. */
if (j < lock_retries && sleep_before_retry)
{
printf(" ... waiting\n");
sleep(lock_interval);
}
}
if (j >= lock_retries)
{
printf("exim_lock: locking failed too many times\n");
yield = 1;
goto CLEAN_UP;
}
if (!quiet) printf("exim_lock: locking %s succeeded: ", filename);
/* If there are no further arguments, run the user's shell; otherwise
the next argument is a command to run. */
if (i >= argc)
{
command = getenv("SHELL");
if (command == NULL || *command == 0) command = "/bin/sh";
if (!quiet) printf("running %s ...\n", command);
}
else
{
command = argv[i];
if (!quiet) printf("running the command ...\n");
}
/* Run the command, saving and restoring the times if required. */
if (restore_times)
{
struct stat strestore;
struct utimbuf ut;
stat(filename, &strestore);
(void)system(command);
ut.actime = strestore.st_atime;
ut.modtime = strestore.st_mtime;
utime(filename, &ut);
}
else (void)system(command);
/* Remove the locks and exit. Unlink the /tmp file if we can get an exclusive
lock on the mailbox. This should be a non-blocking lock call, as there is no
point in waiting. */
CLEAN_UP:
if (md >= 0)
{
if (apply_lock(fd, F_WRLCK, use_fcntl, 0, use_flock, 0) >= 0)
{
if (!quiet) printf("exim_lock: %s unlinked - no sharers\n", tempname);
unlink(tempname);
}
else if (!quiet)
printf("exim_lock: %s not unlinked - unable to get exclusive mailbox lock\n",
tempname);
if (close(md) < 0)
printf("exim_lock: close %s failed: %s\n", tempname, strerror(errno));
else
if (!quiet) printf("exim_lock: %s closed\n", tempname);
}
if (fd >= 0)
{
if (close(fd) < 0)
printf("exim_lock: close %s failed: %s\n", filename, strerror(errno));
else
if (!quiet) printf("exim_lock: %s closed\n", filename);
}
if (hd >= 0)
{
if (unlink(lockname) < 0)
printf("exim_lock: unlink %s failed: %s\n", lockname, strerror(errno));
else
if (!quiet) printf("exim_lock: lock file removed\n");
}
return yield;
}
/* End */
syntax highlighted by Code2HTML, v. 0.9.1