diff -r 71503088f51b -r f880f219c566 openpkg/mutex.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/openpkg/mutex.c Tue Jul 31 12:23:42 2012 +0200 @@ -0,0 +1,260 @@ +/* +** mutex.c -- OpenPKG Mutex Utility +** Copyright (c) 2008-2012 OpenPKG GmbH +** +** This software is property of the OpenPKG GmbH, DE MUC HRB 160208. +** All rights reserved. Licenses which grant limited permission to use, +** copy, modify and distribute this software are available from the +** OpenPKG GmbH. +** +** THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED +** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +** IN NO EVENT SHALL THE AUTHORS AND COPYRIGHT HOLDERS AND THEIR +** CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF +** USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +** OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +** OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +** SUCH DAMAGE. +*/ + +/* standard system headers */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* non-standard add-on headers */ +#include "popt.h" + +/* program information */ +#define PROGRAM_NAME "mutex" + +/* error handling constants */ +#define EXITCODE_NONE 00 +#define EXITCODE_USAGE 64 +#define EXITCODE_OSERR 71 +#define EXITCODE_CANTCREAT 73 +#define EXITCODE_TEMPFAIL 75 +#define ERRORCODE_NONE 0 +#define ERRORCODE_SYSTEM 1 + +/* global variables */ +static const char *mutex_filename = NULL; +static int mutex_fd = -1; +static int mutex_keep = 0; +static int mutex_timeout = -1; +static volatile int mutex_timed_out = 0; + +/* display warning and continue or display error and exit */ +static void +error( + int exitcode, + int errorcode, + const char *fmt, ...) +{ + va_list ap; + int errno_safed = errno; + + va_start(ap, fmt); + if (exitcode != 0) + fprintf(stderr, PROGRAM_NAME ":ERROR: "); + else + fprintf(stderr, PROGRAM_NAME ":WARNING: "); + vfprintf(stderr, fmt, ap); + if (errorcode) + fprintf(stderr, ": %s", strerror(errno_safed)); + fprintf(stderr, "\n"); + va_end(ap); + if (exitcode != 0) + exit(exitcode); + return; +} + +/* acquire mutex */ +static int +mutex_acquire(int nonblock) +{ + static struct flock lock; + int flags; + + /* open mutex file (and optionally create it) */ + flags = O_RDWR|O_CREAT; + if (nonblock) + flags |= O_NONBLOCK; + if ((mutex_fd = open(mutex_filename, flags, 0600)) == -1) { + if (errno == EAGAIN || errno == EINTR) + return 0; + error(EXITCODE_CANTCREAT, ERRORCODE_SYSTEM, "cannot open mutex file \"%s\"", + mutex_filename); + } + + /* acquire exclusive lock on the mutex file */ + lock.l_whence = SEEK_SET; /* from current point */ + lock.l_start = 0; /* -"- */ + lock.l_len = 0; /* until end of file */ + lock.l_type = F_WRLCK; /* set exclusive/read-write lock */ + lock.l_pid = 0; /* pid not actually interesting */ + if (fcntl(mutex_fd, nonblock ? F_SETLK : F_SETLKW, &lock) == -1) { + close(mutex_fd); + mutex_fd = -1; + return 0; + } + + return 1; +} + +/* release mutex */ +static void +mutex_release(void) +{ + static struct flock unlock; + + if (mutex_fd != -1) { + /* release lock on the mutex file */ + unlock.l_whence = SEEK_SET; /* from current point */ + unlock.l_start = 0; /* -"- */ + unlock.l_len = 0; /* until end of file */ + unlock.l_type = F_UNLCK; /* unlock */ + unlock.l_pid = 0; /* pid not actually interesting */ + fcntl(mutex_fd, F_SETLK, &unlock); + + /* close mutex file */ + close(mutex_fd); + mutex_fd = -1; + } + return; +} + +/* destroy the mutex */ +static void +mutex_destroy( + void) +{ + /* release lock on mutex file */ + mutex_release(); + + /* optionally remove mutex file */ + if (!mutex_keep) + unlink(mutex_filename); + + return; +} + +/* signal handler for SIGTERM */ +static void +handler_killed( + int sig) +{ + /* destroy mutex */ + mutex_destroy(); + + /* re-raise the signal */ + signal(sig, SIG_DFL); + if (kill(getpid(), sig) == -1) + error(EXITCODE_OSERR, ERRORCODE_SYSTEM, "kill(2) failed"); + + return; +} + +/* signal handler for SIGALRM */ +static void +handler_timeout( + int sig) +{ + mutex_timed_out = 1; + return; +} + +/* command line options */ +static struct poptOption options_tab[] = { + { "keep", 'k', POPT_ARG_NONE, &mutex_keep, 0, NULL, NULL }, + { "timeout", 't', POPT_ARG_INT, &mutex_timeout, 0, NULL, NULL }, + POPT_TABLEEND +}; + +/* main program procedure */ +int +main( + int argc, + char *argv[]) +{ + int status; + pid_t child; + int locked; + char c; + const char **args; + poptContext options_ctx; + + /* process command line arguments */ + options_ctx = poptGetContext(argv[0], argc, (const char **)argv, + options_tab, POPT_CONTEXT_NO_EXEC|POPT_CONTEXT_POSIXMEHARDER); + while ((c = poptGetNextOpt(options_ctx)) > 0) + ; + if (c < -1) + error(EXITCODE_USAGE, ERRORCODE_NONE, "command line option \"%s\": %s", + poptBadOption(options_ctx, POPT_BADOPTION_NOALIAS), poptStrerror(c)); + mutex_filename = poptGetArg(options_ctx); + if (mutex_filename == NULL) { + fprintf(stderr, + "usage: %s [--keep|-k] [--timeout|-t ] []\n", + PROGRAM_NAME); + exit(EXITCODE_USAGE); + } + args = poptGetArgs(options_ctx); + + /* optionally set up a timeout */ + if (mutex_timeout > 0) { + struct sigaction act; + act.sa_handler = handler_timeout; + act.sa_flags = 0; + sigemptyset(&act.sa_mask); + sigaction(SIGALRM, &act, NULL); + alarm(mutex_timeout); + } + + /* try to acquire the mutex lock */ + locked = mutex_acquire(1 /* non-blocking */); + while (!locked && !mutex_timed_out && mutex_timeout != 0) + locked = mutex_acquire(0 /* blocking */); + + /* optionally destroy timeout */ + if (mutex_timeout > 0) + alarm(0); + + /* exit in case we failed to acquire the mutex lock */ + if (!locked) + error(EXITCODE_TEMPFAIL, ERRORCODE_NONE, "%s: already locked", mutex_filename); + + /* execute the command */ + if (atexit(mutex_destroy) == -1) + error(EXITCODE_OSERR, ERRORCODE_SYSTEM, "atexit(3) failed"); + if ((child = fork()) == -1) + error(EXITCODE_OSERR, ERRORCODE_SYSTEM, "fork(2) failed"); + if (child == 0) { + /* the child process */ + mutex_release(); + execvp((char *)args[0], (char **)args); + error(EXITCODE_NONE, ERRORCODE_SYSTEM, "execvp(2) failed: %s", args[0]); + exit(1); + } + poptFreeContext(options_ctx); + + /* wait for child process and return its exit status */ + signal(SIGINT, SIG_IGN); + signal(SIGQUIT, SIG_IGN); + signal(SIGTERM, handler_killed); + if (waitpid(child, &status, 0) == -1) + error(EXITCODE_OSERR, ERRORCODE_SYSTEM, "waitpid(2) failed"); + return (WIFEXITED(status) ? WEXITSTATUS(status) : 1); +} +