/* * Postpone a command if a lockfile exists, also avoid running multiple times. * * Copyright (C) 2007, 2011 Christoph Berg * * Based on code from: * Debian menu system -- update-menus * update-menus/update-menus.cc * * Copyright (C) 1996-2003 Joost Witteveen * Copyright (C) 2002-2004 Bill Allombert and Morten Brix Pedersen * * 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. */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define DPKG_LOCKFILE "/var/lib/dpkg/lock" #define DEBIAN_EXTRA_LOCKFILE "/var/lib/dpkg/postpone.lock" #define MAXLOCKS 32 #define PACKAGE "postpone" #define VERSION "0.3" #define LOCALEDIR "/usr/share/locale" #define _(x) (gettext (x)) #define perror(x) print (stderr, "%s: %s", (x), strerror(errno)) static int foreground = 0; static int verbose = 0; static char *postpone_lock = NULL; static char *waitlist[MAXLOCKS]; static int n_waits = 0; static char *extra_lock = NULL; static char *output = NULL; static int redirect_always = 0; static int backgrounded = 0; static void print (FILE *f, const char *format, ...) { static int opened = 0; va_list ap; va_start (ap, format); if (backgrounded) { if (!opened) { openlog("postpone", LOG_PID, LOG_DAEMON); opened = 1; } vsyslog (LOG_INFO, format, ap); } else { vfprintf (f, format, ap); } va_end (ap); } /* Try to create a lock file for this postpone task */ static int get_lock (char *fname) { int fd; if ((fd = open (fname, O_WRONLY|O_CREAT, 00644)) == -1) { print (stderr, _("open %s: %s\n"), fname, strerror (errno)); exit (1); } if (flock (fd, LOCK_EX|LOCK_NB) == -1) { if (errno != EWOULDBLOCK && errno != EAGAIN) { print (stderr, _("flock %s: %s\n"), fname, strerror (errno)); exit (1); } close (fd); return -1; } char buf[10]; snprintf (buf, 10, "%d\n", getpid()); if (write(fd, buf, strlen(buf)) < 1) { print (stderr, _("write %s: %s\n"), fname, strerror (errno)); close (fd); return -1; } if (verbose > 1) print (stdout, _("Got lock on %s.\n"), fname); return fd; /* beware: might be 0 */ } /* Try to remove postpone lock */ static void remove_lock (char *fname) { if (unlink (fname)) perror (fname); } /* Check whether dpkg is locked * return 1 if DPKG_LOCKFILE is locked and we should wait * return 0 if we don't need to wait * when in doubt return 0 to avoid deadlocks. */ static int wait_for_file (char *fname) { int fd; struct flock fl; fl.l_type = F_WRLCK; fl.l_whence = SEEK_SET; fl.l_start = 0; fl.l_len = 0; fd = open(fname, O_RDWR|O_TRUNC, 0660); if (fd == -1) { /* Probably /var/lib/dpkg does not exist. * Most probably dpkg is not running. */ if (verbose > 1) print (stdout, _("%s does not exist. Good.\n"), fname); return 0; } if (fcntl(fd, F_GETLK, &fl) == -1) { /* Probably /var/lib/dpkg filesystem does not support * locks. */ close (fd); if (verbose > 1) print (stdout, _("Error while locking %s. Assuming this is a good sign.\n"), fname); return 0; } close (fd); if (verbose > 1) { if (fl.l_type == F_UNLCK) print (stdout, _("%s is not locked. Good.\n"), fname); else print (stdout, _("%s is locked. Waiting.\n"), fname); } return fl.l_type != F_UNLCK; } static int wait_for_locks () { int i; for (i = 0; i < n_waits; i++) { int ret = wait_for_file (waitlist[i]); if (ret != 0) { return 1; } } if (extra_lock) { int ret = get_lock (extra_lock); if (ret == -1) { if (verbose > 1) print (stdout, _("Extra lock %s is not available. Waiting.\n"), extra_lock); return 1; } } return 0; /* all locks available */ } static int run_command (char **argv, int foreground) { int pid, status; int redirect = output && (!foreground || redirect_always); if ((pid = fork ()) == 0) { /* child */ backgrounded = 1; if (redirect) { int fd; if (strlen (output) < 6 || strncmp (output + strlen (output) - 6, "XXXXXX", 6)) fd = open (output, O_RDWR|O_CREAT|O_TRUNC, 0600); else fd = mkstemp (output); if (fd == -1) { perror (output); } dup2 (fd, 1); dup2 (fd, 2); } if (verbose > 1) print (stdout, _("Running %s\n"), argv[0]); execvp (argv[0], argv); perror (argv[0]); exit (1); } /* parent */ if (pid == -1 ) { if (foreground) perror ("fork"); exit (1); } waitpid (pid, &status, 0); if (postpone_lock) remove_lock (postpone_lock); if (extra_lock) remove_lock (extra_lock); if (verbose > 1) print (stdout, _("Child %d exited\n"), pid); if (WIFEXITED (status)) if (redirect && WEXITSTATUS (status) == 0) unlink (output); return WEXITSTATUS (status); return 1; } static void usage (FILE *f, int i) { fprintf(f, _("Postpone schedules a command to be executed later when a lockfile disappears\n" "Usage: postpone [-dfv] [-wlLoO FILE] command args...\n" "-w --wait FILE [...] wait for fcntl lock on FILE before running command\n" "-l --lock FILE create lockfile FILE, exit if it exists\n" " use to avoid scheduling a command several times\n" "-L --extra-lock FILE extra lock to serialize different postponed commands\n" "-d --debian equivalent to --wait " DPKG_LOCKFILE "\n" " --extra-lock " DEBIAN_EXTRA_LOCKFILE "\n" "-o --output FILE redirect stdout/stderr to FILE when in background\n" "-O --all-output FILE unconditionally redirect stdout/stderr to FILE\n" "-f --foreground do not detach while waiting for locks\n" "-v --verbose verbose operation, repeat for debugging output\n" " --help print this help and exit\n" " --version print version information and exit\n" " --version-string print version string and exit\n" )); exit (i); } static void version (FILE *f, int i) { fprintf(f, _("Postpone %s - schedule a task to be executed later when a lockfile disappears\n" "Copyright (C) 1996-2003 Joost Witteveen\n" "Copyright (C) 2002-2004 Bill Allombert and Morten Brix Pedersen\n" "Copyright (C) 2007 Christoph Berg\n"), VERSION); exit (i); } static struct option long_options[] = { { "debian", no_argument, NULL, 'd' }, { "foreground", no_argument, NULL, 'f' }, { "help", no_argument, NULL, 'h' }, { "lock", required_argument, NULL, 'l' }, { "extra-lock", required_argument, NULL, 'L' }, { "output", required_argument, NULL, 'o' }, { "all-output", required_argument, NULL, 'O' }, { "verbose", no_argument, NULL, 'v' }, { "version", no_argument, NULL, 'V'}, { "wait", required_argument, NULL, 'w' }, { "version-string", no_argument, NULL, 'X'}, { NULL, 0, NULL, 0 } }; /* Parse command line parameters */ static void parse_params(int argc, char **argv) { while(1) { int c = getopt_long (argc, argv, "+dfl:L:o:O:vw:", long_options, NULL); if (c == -1) break; switch(c) { case 'd': if (n_waits < MAXLOCKS) waitlist[n_waits++] = DPKG_LOCKFILE; extra_lock = DEBIAN_EXTRA_LOCKFILE; break; case 'f': foreground = 1; break; case 'h': usage (stdout, 0); case 'l': postpone_lock = strdup (optarg); break; case 'L': extra_lock = strdup (optarg); break; case 'O': redirect_always = 1; /* FALLTHROUGH */ case 'o': output = strdup (optarg); break; case 'v': verbose++; break; case 'V': version (stdout, 0); case 'w': if (n_waits < MAXLOCKS) waitlist[n_waits++] = strdup (optarg); break; case 'X': puts (VERSION); exit (0); /* ignore unknown options for easier compatibility with future versions */ } } } int main (int argc, char **argv) { int lock = -1, pid, i; setlocale (LC_ALL, ""); bindtextdomain (PACKAGE, LOCALEDIR); textdomain (PACKAGE); parse_params (argc, argv); if (optind >= argc) { usage (stderr, 1); } if (postpone_lock) if ((lock = get_lock (postpone_lock)) == -1) { if (verbose) print (stdout, _("%s is already running in background.\n"), argv[optind]); exit (0); } if (wait_for_locks () == 0) { if (verbose > 1) print (stdout, _("No locks to wait for, running in foreground now.\n")); return run_command (argv + optind, 1); } if (verbose > 1) { print (stdout, _("Waiting for lock on")); for (i = 0; i < n_waits; i++) print (stdout, " %s", waitlist[i]); if (extra_lock) print (stdout, " %s", extra_lock); print (stdout, ".\n"); } if (!foreground) { if ((pid = fork ()) > 0) { /* parent */ if (verbose) print (stdout, _("Running %s in background.\n"), argv[optind]); exit (0); } else if (pid == -1) { if (postpone_lock) remove_lock (postpone_lock); perror ("fork"); exit (1); } /* child */ backgrounded = 1; /* Close all fds except the lock fd for daemon mode */ for (i = 0; i < 32; i++) { if (i != lock) close (i); } } /* wait for dpkg/other locks */ do { sleep (2); } while (wait_for_locks ()); return run_command (argv + optind, foreground); } /* vim:sw=4: */