]> git.ipfire.org Git - thirdparty/util-linux.git/blobdiff - sys-utils/flock.c
su: change error message
[thirdparty/util-linux.git] / sys-utils / flock.c
index a5003193ee117c673ead71a6003756a18c7b7987..ed25230b92913d8a7acaa3d12cfb46a369100796 100644 (file)
@@ -1,6 +1,4 @@
-/* ----------------------------------------------------------------------- *
- *
- *   Copyright 2003-2005 H. Peter Anvin - All Rights Reserved
+/*   Copyright 2003-2005 H. Peter Anvin - All Rights Reserved
  *
  *   Permission is hereby granted, free of charge, to any person
  *   obtaining a copy of this software and associated documentation
  *   WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  *   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
  *   OTHER DEALINGS IN THE SOFTWARE.
- *
- * ----------------------------------------------------------------------- */
+ */
 
+#include <ctype.h>
 #include <errno.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <unistd.h>
+#include <fcntl.h>
 #include <getopt.h>
+#include <paths.h>
 #include <signal.h>
-#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
 #include <string.h>
-#include <paths.h>
 #include <sysexits.h>
-#include <sys/types.h>
 #include <sys/file.h>
+#include <sys/stat.h>
 #include <sys/time.h>
+#include <sys/types.h>
 #include <sys/wait.h>
-#include <sys/stat.h>
-#include <fcntl.h>
+#include <unistd.h>
 
+#include "c.h"
 #include "nls.h"
+#include "strutils.h"
+#include "closestream.h"
+#include "monotonic.h"
+#include "timer.h"
 
-static const struct option long_options[] = {
-  { "shared",       0, NULL, 's' },
-  { "exclusive",    0, NULL, 'x' },
-  { "unlock",       0, NULL, 'u' },
-  { "nonblocking",  0, NULL, 'n' },
-  { "nb",           0, NULL, 'n' },
-  { "timeout",      1, NULL, 'w' },
-  { "wait",         1, NULL, 'w' },
-  { "close",        0, NULL, 'o' },
-  { "help",         0, NULL, 'h' },
-  { "version",      0, NULL, 'V' },
-  { 0, 0, 0, 0 }
-};
-
-const char *program;
-
-static void usage(int ex)
+static void __attribute__((__noreturn__)) usage(void)
 {
-  fputs(_("\nUsage:\n"), stderr);
-  fprintf(stderr,
-       _(" %1$s [-sxun][-w #] fd#\n"
-         " %1$s [-sxon][-w #] file [-c] command...\n"
-         " %1$s [-sxon][-w #] directory [-c] command...\n"), program);
-
-  fputs(_("\nOptions:\n"), stderr);
-  fputs(_(" -s  --shared     Get a shared lock\n"
-         " -x  --exclusive  Get an exclusive lock\n"
-         " -u  --unlock     Remove a lock\n"
-         " -n  --nonblock   Fail rather than wait\n"
-         " -w  --timeout    Wait for a limited amount of time\n"
-         " -o  --close      Close file descriptor before running command\n"
-         " -c  --command    Run a single command string through the shell\n"
-         " -h  --help       Display this text\n"
-         " -V  --version    Display version\n\n"), stderr);
-  exit(ex);
+       fputs(USAGE_HEADER, stdout);
+       printf(
+               _(" %1$s [options] <file>|<directory> <command> [<argument>...]\n"
+                 " %1$s [options] <file>|<directory> -c <command>\n"
+                 " %1$s [options] <file descriptor number>\n"),
+               program_invocation_short_name);
+
+       fputs(USAGE_SEPARATOR, stdout);
+       fputs(_("Manage file locks from shell scripts.\n"), stdout);
+
+       fputs(USAGE_OPTIONS, stdout);
+       fputs(_(  " -s, --shared             get a shared lock\n"), stdout);
+       fputs(_(  " -x, --exclusive          get an exclusive lock (default)\n"), stdout);
+       fputs(_(  " -u, --unlock             remove a lock\n"), stdout);
+       fputs(_(  " -n, --nonblock           fail rather than wait\n"), stdout);
+       fputs(_(  " -w, --timeout <secs>     wait for a limited amount of time\n"), stdout);
+       fputs(_(  " -E, --conflict-exit-code <number>  exit code after conflict or timeout\n"), stdout);
+       fputs(_(  " -o, --close              close file descriptor before running command\n"), stdout);
+       fputs(_(  " -c, --command <command>  run a single command string through the shell\n"), stdout);
+       fputs(_(  " -F, --no-fork            execute command without forking\n"), stdout);
+       fputs(_(  "     --verbose            increase verbosity\n"), stdout);
+       fputs(USAGE_SEPARATOR, stdout);
+       printf(USAGE_HELP_OPTIONS(26));
+       printf(USAGE_MAN_TAIL("flock(1)"));
+       exit(EXIT_SUCCESS);
 }
 
-
 static sig_atomic_t timeout_expired = 0;
 
-static void timeout_handler(int sig)
+static void timeout_handler(int sig __attribute__((__unused__)),
+                           siginfo_t *info,
+                           void *context __attribute__((__unused__)))
 {
-  (void)sig;
-
-  timeout_expired = 1;
+       if (info->si_code == SI_TIMER)
+               timeout_expired = 1;
 }
 
-
-static char * strtotimeval(const char *str, struct timeval *tv)
+static int open_file(const char *filename, int *flags)
 {
-  char *s;
-  long fs;                     /* Fractional seconds */
-  int i;
 
-  tv->tv_sec = strtol(str, &s, 10);
-  fs = 0;
-
-  if ( *s == '.' ) {
-    s++;
-
-    for ( i = 0 ; i < 6 ; i++ ) {
-      if ( !isdigit(*s) )
-       break;
-
-      fs *= 10;
-      fs += *s++ - '0';
-    }
-
-    for ( ; i < 6; i++ )
-      fs *= 10;
+       int fd;
+       int fl = *flags == 0 ? O_RDONLY : *flags;
+
+       errno = 0;
+       fl |= O_NOCTTY | O_CREAT;
+       fd = open(filename, fl, 0666);
+
+       /* Linux doesn't like O_CREAT on a directory, even though it
+        * should be a no-op; POSIX doesn't allow O_RDWR or O_WRONLY
+        */
+       if (fd < 0 && errno == EISDIR) {
+               fl = O_RDONLY | O_NOCTTY;
+               fd = open(filename, fl);
+       }
+       if (fd < 0) {
+               warn(_("cannot open lock file %s"), filename);
+               if (errno == ENOMEM || errno == EMFILE || errno == ENFILE)
+                       exit(EX_OSERR);
+               if (errno == EROFS || errno == ENOSPC)
+                       exit(EX_CANTCREAT);
+               exit(EX_NOINPUT);
+       }
+       *flags = fl;
+       return fd;
+}
 
-    while ( isdigit(*s) )
-      s++;
-  }
+static void __attribute__((__noreturn__)) run_program(char **cmd_argv)
+{
+       execvp(cmd_argv[0], cmd_argv);
 
-  tv->tv_usec = fs;
-  return s;
+       warn(_("failed to execute %s"), cmd_argv[0]);
+       _exit((errno == ENOMEM) ? EX_OSERR : EX_UNAVAILABLE);
 }
 
 int main(int argc, char *argv[])
 {
-  struct itimerval timeout, old_timer;
-  int have_timeout = 0;
-  int type = LOCK_EX;
-  int block = 0;
-  int open_accmode;
-  int fd = -1;
-  int opt, ix;
-  int do_close = 0;
-  int err;
-  int status;
-  char *eon;
-  char **cmd_argv = NULL, *sh_c_argv[4];
-  const char *filename = NULL;
-  struct sigaction sa, old_sa;
-
-  setlocale(LC_ALL, "");
-  bindtextdomain(PACKAGE, LOCALEDIR);
-  textdomain(PACKAGE);
-
-  program = argv[0];
-
-  if ( argc < 2 )
-    usage(EX_USAGE);
-
-  memset(&timeout, 0, sizeof timeout);
-
-  optopt = 0;
-  while ( (opt = getopt_long(argc, argv, "+sexnouw:hV?", long_options, &ix)) != EOF ) {
-    switch(opt) {
-    case 's':
-      type = LOCK_SH;
-      break;
-    case 'e':
-    case 'x':
-      type = LOCK_EX;
-      break;
-    case 'u':
-      type = LOCK_UN;
-      break;
-    case 'o':
-      do_close = 1;
-      break;
-    case 'n':
-      block = LOCK_NB;
-      break;
-    case 'w':
-      have_timeout = 1;
-      eon = strtotimeval(optarg, &timeout.it_value);
-      if ( *eon )
-       usage(EX_USAGE);
-      break;
-    case 'V':
-      printf("flock (%s)\n", PACKAGE_STRING);
-      exit(0);
-    default:
-      /* optopt will be set if this was an unrecognized option, i.e. *not* 'h' or '?' */
-      usage(optopt ? EX_USAGE : 0);
-      break;
-    }
-  }
-
-  if ( argc > optind+1 ) {
-    /* Run command */
-
-    if ( !strcmp(argv[optind+1], "-c") ||
-        !strcmp(argv[optind+1], "--command") ) {
-
-      if ( argc != optind+3 ) {
-       fprintf(stderr, _("%s: %s requires exactly one command argument\n"),
-               program, argv[optind+1]);
-       exit(EX_USAGE);
-      }
-
-      cmd_argv = sh_c_argv;
-
-      cmd_argv[0] = getenv("SHELL");
-      if ( !cmd_argv[0] || !*cmd_argv[0] )
-       cmd_argv[0] = _PATH_BSHELL;
-
-      cmd_argv[1] = "-c";
-      cmd_argv[2] = argv[optind+2];
-      cmd_argv[3] = 0;
-    } else {
-      cmd_argv = &argv[optind+1];
-    }
-
-    filename = argv[optind];
-    open_accmode = ((type == LOCK_SH || access(filename, R_OK|W_OK) < 0) ?
-                    O_RDONLY : O_RDWR);
-    fd = open(filename, open_accmode|O_NOCTTY|O_CREAT, 0666);
-    /* Linux doesn't like O_CREAT on a directory, even though it should be a
-       no-op; POSIX doesn't allow O_RDWR or O_WRONLY */
-    if (fd < 0 && errno == EISDIR)
-        fd = open(filename, O_RDONLY|O_NOCTTY);
-
-    if ( fd < 0 ) {
-      err = errno;
-      fprintf(stderr, _("%s: cannot open lock file %s: %s\n"),
-             program, argv[optind], strerror(err));
-      exit((err == ENOMEM||err == EMFILE||err == ENFILE) ? EX_OSERR :
-          (err == EROFS||err == ENOSPC) ? EX_CANTCREAT :
-          EX_NOINPUT);
-    }
-
-  } else if (optind < argc) {
-    /* Use provided file descriptor */
-
-    fd = (int)strtol(argv[optind], &eon, 10);
-    if ( *eon || !argv[optind] ) {
-      fprintf(stderr, _("%s: bad number: %s\n"), program, argv[optind]);
-      exit(EX_USAGE);
-    }
-
-  } else {
-    /* Bad options */
-
-    fprintf(stderr, _("%s: requires file descriptor, file or directory\n"),
-               program);
-    exit(EX_USAGE);
-  }
-
-
-  if ( have_timeout ) {
-    if ( timeout.it_value.tv_sec == 0 &&
-        timeout.it_value.tv_usec == 0 ) {
-      /* -w 0 is equivalent to -n; this has to be special-cased
-        because setting an itimer to zero means disabled! */
-
-      have_timeout = 0;
-      block = LOCK_NB;
-    } else {
-      memset(&sa, 0, sizeof sa);
-
-      sa.sa_handler = timeout_handler;
-      sa.sa_flags   = SA_RESETHAND;
-      sigaction(SIGALRM, &sa, &old_sa);
-
-      setitimer(ITIMER_REAL, &timeout, &old_timer);
-    }
-  }
-
-  while ( flock(fd, type|block) ) {
-    switch( (err = errno) ) {
-    case EWOULDBLOCK:          /* -n option set and failed to lock */
-      exit(1);
-    case EINTR:                        /* Signal received */
-      if ( timeout_expired )
-       exit(1);                /* -w option set and failed to lock */
-      continue;                        /* otherwise try again */
-    default:                   /* Other errors */
-      if ( filename )
-       fprintf(stderr, "%s: %s: %s\n", program, filename, strerror(err));
-      else
-       fprintf(stderr, "%s: %d: %s\n", program, fd, strerror(err));
-      exit((err == ENOLCK||err == ENOMEM) ? EX_OSERR : EX_DATAERR);
-    }
-  }
-
-  if ( have_timeout ) {
-    setitimer(ITIMER_REAL, &old_timer, NULL); /* Cancel itimer */
-    sigaction(SIGALRM, &old_sa, NULL); /* Cancel signal handler */
-  }
-
-  status = 0;
-
-  if ( cmd_argv ) {
-    pid_t w, f;
-
-    /* Clear any inherited settings */
-    signal(SIGCHLD, SIG_DFL);
-    f = fork();
-
-    if ( f < 0 ) {
-      err = errno;
-      fprintf(stderr, _("%s: fork failed: %s\n"), program, strerror(err));
-      exit(EX_OSERR);
-    } else if ( f == 0 ) {
-      if ( do_close )
-       close(fd);
-      execvp(cmd_argv[0], cmd_argv);
-      err = errno;
-      /* execvp() failed */
-      fprintf(stderr, "%s: %s: %s\n", program, cmd_argv[0], strerror(err));
-      _exit((err == ENOMEM) ? EX_OSERR: EX_UNAVAILABLE);
-    } else {
-      do {
-       w = waitpid(f, &status, 0);
-       if (w == -1 && errno != EINTR)
-         break;
-      } while ( w != f );
-
-      if (w == -1) {
-       err = errno;
-       status = EXIT_FAILURE;
-       fprintf(stderr, "%s: waitpid failed: %s\n", program, strerror(err));
-      } else if ( WIFEXITED(status) )
-       status = WEXITSTATUS(status);
-      else if ( WIFSIGNALED(status) )
-       status = WTERMSIG(status) + 128;
-      else
-       status = EX_OSERR;      /* WTF? */
-    }
-  }
-
-  return status;
+       static timer_t t_id;
+       struct itimerval timeout;
+       int have_timeout = 0;
+       int type = LOCK_EX;
+       int block = 0;
+       int open_flags = 0;
+       int fd = -1;
+       int opt, ix;
+       int do_close = 0;
+       int no_fork = 0;
+       int status;
+       int verbose = 0;
+       struct timeval time_start, time_done;
+       /*
+        * The default exit code for lock conflict or timeout
+        * is specified in man flock.1
+        */
+       int conflict_exit_code = 1;
+       char **cmd_argv = NULL, *sh_c_argv[4];
+       const char *filename = NULL;
+       enum {
+               OPT_VERBOSE = CHAR_MAX + 1
+       };
+       static const struct option long_options[] = {
+               {"shared", no_argument, NULL, 's'},
+               {"exclusive", no_argument, NULL, 'x'},
+               {"unlock", no_argument, NULL, 'u'},
+               {"nonblocking", no_argument, NULL, 'n'},
+               {"nb", no_argument, NULL, 'n'},
+               {"timeout", required_argument, NULL, 'w'},
+               {"wait", required_argument, NULL, 'w'},
+               {"conflict-exit-code", required_argument, NULL, 'E'},
+               {"close", no_argument, NULL, 'o'},
+               {"no-fork", no_argument, NULL, 'F'},
+               {"verbose", no_argument, NULL, OPT_VERBOSE},
+               {"help", no_argument, NULL, 'h'},
+               {"version", no_argument, NULL, 'V'},
+               {NULL, 0, NULL, 0}
+       };
+
+       setlocale(LC_ALL, "");
+       bindtextdomain(PACKAGE, LOCALEDIR);
+       textdomain(PACKAGE);
+       atexit(close_stdout);
+
+       strutils_set_exitcode(EX_USAGE);
+
+       if (argc < 2) {
+               warnx(_("not enough arguments"));
+               errtryhelp(EX_USAGE);
+       }
+
+       memset(&timeout, 0, sizeof timeout);
+
+       optopt = 0;
+       while ((opt =
+               getopt_long(argc, argv, "+sexnoFuw:E:hV?", long_options,
+                           &ix)) != EOF) {
+               switch (opt) {
+               case 's':
+                       type = LOCK_SH;
+                       break;
+               case 'e':
+               case 'x':
+                       type = LOCK_EX;
+                       break;
+               case 'u':
+                       type = LOCK_UN;
+                       break;
+               case 'o':
+                       do_close = 1;
+                       break;
+               case 'F':
+                       no_fork = 1;
+                       break;
+               case 'n':
+                       block = LOCK_NB;
+                       break;
+               case 'w':
+                       have_timeout = 1;
+                       strtotimeval_or_err(optarg, &timeout.it_value,
+                               _("invalid timeout value"));
+                       break;
+               case 'E':
+                       conflict_exit_code = strtos32_or_err(optarg,
+                               _("invalid exit code"));
+                       break;
+               case OPT_VERBOSE:
+                       verbose = 1;
+                       break;
+               case 'V':
+                       printf(UTIL_LINUX_VERSION);
+                       exit(EX_OK);
+               case 'h':
+                       usage();
+               default:
+                       errtryhelp(EX_USAGE);
+               }
+       }
+
+       if (no_fork && do_close)
+               errx(EX_USAGE,
+                       _("the --no-fork and --close options are incompatible"));
+
+       if (argc > optind + 1) {
+               /* Run command */
+               if (!strcmp(argv[optind + 1], "-c") ||
+                   !strcmp(argv[optind + 1], "--command")) {
+                       if (argc != optind + 3)
+                               errx(EX_USAGE,
+                                    _("%s requires exactly one command argument"),
+                                    argv[optind + 1]);
+                       cmd_argv = sh_c_argv;
+                       cmd_argv[0] = getenv("SHELL");
+                       if (!cmd_argv[0] || !*cmd_argv[0])
+                               cmd_argv[0] = _PATH_BSHELL;
+                       cmd_argv[1] = "-c";
+                       cmd_argv[2] = argv[optind + 2];
+                       cmd_argv[3] = NULL;
+               } else {
+                       cmd_argv = &argv[optind + 1];
+               }
+
+               filename = argv[optind];
+               fd = open_file(filename, &open_flags);
+
+       } else if (optind < argc) {
+               /* Use provided file descriptor */
+               fd = strtos32_or_err(argv[optind], _("bad file descriptor"));
+       } else {
+               /* Bad options */
+               errx(EX_USAGE, _("requires file descriptor, file or directory"));
+       }
+
+       if (have_timeout) {
+               if (timeout.it_value.tv_sec == 0 &&
+                   timeout.it_value.tv_usec == 0) {
+                       /* -w 0 is equivalent to -n; this has to be
+                        * special-cased because setting an itimer to zero
+                        * means disabled!
+                        */
+                       have_timeout = 0;
+                       block = LOCK_NB;
+               } else
+                       if (setup_timer(&t_id, &timeout, &timeout_handler))
+                               err(EX_OSERR, _("cannot set up timer"));
+       }
+
+       if (verbose)
+               gettime_monotonic(&time_start);
+       while (flock(fd, type | block)) {
+               switch (errno) {
+               case EWOULDBLOCK:
+                       /* -n option set and failed to lock. */
+                       if (verbose)
+                               warnx(_("failed to get lock"));
+                       exit(conflict_exit_code);
+               case EINTR:
+                       /* Signal received */
+                       if (timeout_expired) {
+                               /* -w option set and failed to lock. */
+                               if (verbose)
+                                       warnx(_("timeout while waiting to get lock"));
+                               exit(conflict_exit_code);
+                       }
+                       /* otherwise try again */
+                       continue;
+               case EIO:
+               case EBADF:             /* since Linux 3.4 (commit 55725513) */
+                       /* Probably NFSv4 where flock() is emulated by fcntl().
+                        * Let's try to reopen in read-write mode.
+                        */
+                       if (!(open_flags & O_RDWR) &&
+                           type != LOCK_SH &&
+                           filename &&
+                           access(filename, R_OK | W_OK) == 0) {
+
+                               close(fd);
+                               open_flags = O_RDWR;
+                               fd = open_file(filename, &open_flags);
+
+                               if (open_flags & O_RDWR)
+                                       break;
+                       }
+                       /* fallthrough */
+               default:
+                       /* Other errors */
+                       if (filename)
+                               warn("%s", filename);
+                       else
+                               warn("%d", fd);
+                       exit((errno == ENOLCK
+                             || errno == ENOMEM) ? EX_OSERR : EX_DATAERR);
+               }
+       }
+
+       if (have_timeout)
+               cancel_timer(&t_id);
+       if (verbose) {
+               struct timeval delta;
+
+               gettime_monotonic(&time_done);
+               timersub(&time_done, &time_start, &delta);
+               printf(_("%s: getting lock took %ld.%06ld seconds\n"),
+                      program_invocation_short_name, delta.tv_sec,
+                      delta.tv_usec);
+       }
+       status = EX_OK;
+
+       if (cmd_argv) {
+               pid_t w, f;
+               /* Clear any inherited settings */
+               signal(SIGCHLD, SIG_DFL);
+               if (verbose)
+                       printf(_("%s: executing %s\n"), program_invocation_short_name, cmd_argv[0]);
+
+               if (!no_fork) {
+                       f = fork();
+                       if (f < 0)
+                               err(EX_OSERR, _("fork failed"));
+
+                       /* child */
+                       else if (f == 0) {
+                               if (do_close)
+                                       close(fd);
+                               run_program(cmd_argv);
+
+                       /* parent */
+                       } else {
+                               do {
+                                       w = waitpid(f, &status, 0);
+                                       if (w == -1 && errno != EINTR)
+                                               break;
+                               } while (w != f);
+
+                               if (w == -1) {
+                                       status = EXIT_FAILURE;
+                                       warn(_("waitpid failed"));
+                               } else if (WIFEXITED(status))
+                                       status = WEXITSTATUS(status);
+                               else if (WIFSIGNALED(status))
+                                       status = WTERMSIG(status) + 128;
+                               else
+                                       /* WTF? */
+                                       status = EX_OSERR;
+                       }
+
+               } else
+                       /* no-fork execution */
+                       run_program(cmd_argv);
+       }
+
+       return status;
 }
-