]> 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 24576b139916e31bbbd8aa84d6e557e7a955f36f..ed25230b92913d8a7acaa3d12cfb46a369100796 100644 (file)
 #include "nls.h"
 #include "strutils.h"
 #include "closestream.h"
+#include "monotonic.h"
+#include "timer.h"
 
-static void __attribute__((__noreturn__)) usage(int ex)
+static void __attribute__((__noreturn__)) usage(void)
 {
-       fprintf(stderr, USAGE_HEADER);
-       fprintf(stderr,
-               _(" %1$s [options] <file|directory> <command> [command args]\n"
-                 " %1$s [options] <file|directory> -c <command>\n"
+       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_OPTIONS, stderr);
-       fputs(_(  " -s  --shared             get a shared lock\n"), stderr);
-       fputs(_(  " -x  --exclusive          get an exclusive lock (default)\n"), stderr);
-       fputs(_(  " -u  --unlock             remove a lock\n"), stderr);
-       fputs(_(  " -n  --nonblock           fail rather than wait\n"), stderr);
-       fputs(_(  " -w  --timeout <secs>     wait for a limited amount of time\n"), stderr);
-       fputs(_(  " -E  --conflict-exit-code <number>  exit code after conflict or timeout\n"), stderr);
-       fputs(_(  " -o  --close              close file descriptor before running command\n"), stderr);
-       fputs(_(  " -c  --command <command>  run a single command string through the shell\n"), stderr);
-       fprintf(stderr, USAGE_SEPARATOR);
-       fprintf(stderr, USAGE_HELP);
-       fprintf(stderr, USAGE_VERSION);
-       fprintf(stderr, USAGE_MAN_TAIL("flock(1)"));
-       exit(ex);
-}
-
-static sig_atomic_t timeout_expired = 0;
 
-static void timeout_handler(int sig __attribute__((__unused__)))
-{
-       timeout_expired = 1;
+       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 void strtotimeval(const char *str, struct timeval *tv)
-{
-       double user_input;
-
-       user_input = strtod_or_err(str, "bad number");
-       tv->tv_sec = (time_t) user_input;
-       tv->tv_usec = (long)((user_input - tv->tv_sec) * 1000000);
-       if ((tv->tv_sec + tv->tv_usec) == 0)
-               errx(EX_USAGE, _("timeout cannot be zero"));
-}
-
-static void setup_timer(struct itimerval *timer, struct itimerval *old_timer,
-                       struct sigaction *sa, struct sigaction *old_sa)
-{
-       memset(sa, 0, sizeof *sa);
-       sa->sa_handler = timeout_handler;
-       sa->sa_flags = SA_RESETHAND;
-       sigaction(SIGALRM, sa, old_sa);
-       setitimer(ITIMER_REAL, timer, old_timer);
-}
+static sig_atomic_t timeout_expired = 0;
 
-static void cancel_timer(struct itimerval *old_timer, struct sigaction *old_sa)
+static void timeout_handler(int sig __attribute__((__unused__)),
+                           siginfo_t *info,
+                           void *context __attribute__((__unused__)))
 {
-       setitimer(ITIMER_REAL, old_timer, NULL);
-       sigaction(SIGALRM, old_sa, NULL);
+       if (info->si_code == SI_TIMER)
+               timeout_expired = 1;
 }
 
 static int open_file(const char *filename, int *flags)
@@ -131,9 +114,18 @@ static int open_file(const char *filename, int *flags)
        return fd;
 }
 
+static void __attribute__((__noreturn__)) run_program(char **cmd_argv)
+{
+       execvp(cmd_argv[0], cmd_argv);
+
+       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;
+       static timer_t t_id;
+       struct itimerval timeout;
        int have_timeout = 0;
        int type = LOCK_EX;
        int block = 0;
@@ -141,7 +133,10 @@ int main(int argc, char *argv[])
        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
@@ -149,8 +144,9 @@ int main(int argc, char *argv[])
        int conflict_exit_code = 1;
        char **cmd_argv = NULL, *sh_c_argv[4];
        const char *filename = NULL;
-       struct sigaction sa, old_sa;
-
+       enum {
+               OPT_VERBOSE = CHAR_MAX + 1
+       };
        static const struct option long_options[] = {
                {"shared", no_argument, NULL, 's'},
                {"exclusive", no_argument, NULL, 'x'},
@@ -161,6 +157,8 @@ int main(int argc, char *argv[])
                {"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}
@@ -171,14 +169,18 @@ int main(int argc, char *argv[])
        textdomain(PACKAGE);
        atexit(close_stdout);
 
-       if (argc < 2)
-               usage(EX_USAGE);
+       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, "+sexnouw:E:hV?", long_options,
+               getopt_long(argc, argv, "+sexnoFuw:E:hV?", long_options,
                            &ix)) != EOF) {
                switch (opt) {
                case 's':
@@ -194,29 +196,38 @@ int main(int argc, char *argv[])
                case 'o':
                        do_close = 1;
                        break;
+               case 'F':
+                       no_fork = 1;
+                       break;
                case 'n':
                        block = LOCK_NB;
                        break;
                case 'w':
                        have_timeout = 1;
-                       strtotimeval(optarg, &timeout.it_value);
+                       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:
-                       /* optopt will be set if this was an unrecognized
-                        * option, i.e.  *not* 'h' or '?
-                        */
-                       usage(optopt ? EX_USAGE : 0);
-                       break;
+                       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") ||
@@ -231,7 +242,7 @@ int main(int argc, char *argv[])
                                cmd_argv[0] = _PATH_BSHELL;
                        cmd_argv[1] = "-c";
                        cmd_argv[2] = argv[optind + 2];
-                       cmd_argv[3] = 0;
+                       cmd_argv[3] = NULL;
                } else {
                        cmd_argv = &argv[optind + 1];
                }
@@ -241,7 +252,7 @@ int main(int argc, char *argv[])
 
        } else if (optind < argc) {
                /* Use provided file descriptor */
-               fd = (int)strtol_or_err(argv[optind], "bad number");
+               fd = strtos32_or_err(argv[optind], _("bad file descriptor"));
        } else {
                /* Bad options */
                errx(EX_USAGE, _("requires file descriptor, file or directory"));
@@ -257,27 +268,37 @@ int main(int argc, char *argv[])
                        have_timeout = 0;
                        block = LOCK_NB;
                } else
-                       setup_timer(&timeout, &old_timer, &sa, &old_sa);
+                       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)
+                       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);
@@ -287,7 +308,7 @@ int main(int argc, char *argv[])
                                if (open_flags & O_RDWR)
                                        break;
                        }
-                       /* go through */
+                       /* fallthrough */
                default:
                        /* Other errors */
                        if (filename)
@@ -300,43 +321,59 @@ int main(int argc, char *argv[])
        }
 
        if (have_timeout)
-               cancel_timer(&old_timer, &old_sa);
-
+               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);
-               f = fork();
+               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;
+                       }
 
-               if (f < 0) {
-                       err(EX_OSERR, _("fork failed"));
-               } else if (f == 0) {
-                       if (do_close)
-                               close(fd);
-                       execvp(cmd_argv[0], cmd_argv);
-                       /* execvp() failed */
-                       warn(_("failed to execute %s"), cmd_argv[0]);
-                       _exit((errno == ENOMEM) ? EX_OSERR : EX_UNAVAILABLE);
-               } 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;