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