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: