diff options
author | Sergey Poznyakoff <gray@gnu.org> | 2021-04-29 19:03:26 +0300 |
---|---|---|
committer | Sergey Poznyakoff <gray@gnu.org> | 2021-04-29 19:11:20 +0300 |
commit | 9eca2fa180cb562128a9db1bad8c77d0b3bdc6a4 (patch) | |
tree | 76e402ef9ed270619d6618a060de75237f10bda1 /libmailutils | |
parent | 6a1152b883407a5cc7d77faa023d2948ac70e8de (diff) | |
download | mailutils-9eca2fa180cb562128a9db1bad8c77d0b3bdc6a4.tar.gz mailutils-9eca2fa180cb562128a9db1bad8c77d0b3bdc6a4.tar.bz2 |
Further improvements of the locker interface.
This fixes a bug that prevented previous versions of mailutils to
retry aquiring the lock.
Locking configuration (the "locking" section) is improved.
* dotlock/dotlock.c: New options: --retry (-r), --delay (-t),
--pid-check (-p).
Use the "locking" capability.
* libmailutils/base/locker.c: Remove the uses of assert().
(mu_locker_lock_mode): Fix the retry loop.
(lock_dotlock): Use mu_asprintf to create temporary lock name.
Return EAGAIN on temporary error conditions.
(lock_kernel): Fix typo (EACCES).
* libmailutils/cli/stdcapa.c (cb_locker_flags): Locker flags are
deprecated.
(cb_locker_retry_timeout,cb_locker_retry_count): Clear the
MU_LOCKER_RETRY flag, if supplied a zero value.
(cb_locker_external): Clear the MU_LOCKER_EXTERNAL flag (revert
to the dotlock locker type) if the value is a false boolean value.
* libmailutils/tests/Makefile.am: Add new test.
* libmailutils/tests/testsuite.at: Include new test,
* libmailutils/tests/lck.c: New test program.
* libmailutils/tests/lock.at: New test.
Diffstat (limited to 'libmailutils')
-rw-r--r-- | libmailutils/base/locker.c | 81 | ||||
-rw-r--r-- | libmailutils/cli/stdcapa.c | 89 | ||||
-rw-r--r-- | libmailutils/tests/.gitignore | 1 | ||||
-rw-r--r-- | libmailutils/tests/Makefile.am | 2 | ||||
-rw-r--r-- | libmailutils/tests/lck.c | 335 | ||||
-rw-r--r-- | libmailutils/tests/lock.at | 59 | ||||
-rw-r--r-- | libmailutils/tests/testsuite.at | 2 |
7 files changed, 496 insertions, 73 deletions
diff --git a/libmailutils/base/locker.c b/libmailutils/base/locker.c index 72634b0c9..ef26fc52b 100644 --- a/libmailutils/base/locker.c +++ b/libmailutils/base/locker.c @@ -19,7 +19,6 @@ # include <config.h> #endif -#include <assert.h> #include <errno.h> #include <fcntl.h> #include <limits.h> @@ -40,6 +39,7 @@ #include <mailutils/errno.h> #include <mailutils/locker.h> #include <mailutils/util.h> +#include <mailutils/io.h> #define LOCKFILE_ATTR 0644 @@ -57,17 +57,21 @@ struct _mu_locker int retries; int retry_sleep; - union lock_data { - struct { + union lock_data + { + struct + { char *dotlock; char *nfslock; } dot; - struct { + struct + { char *name; } external; - struct { + struct + { int fd; } kernel; } data; @@ -581,19 +585,19 @@ mu_locker_lock_mode (mu_locker_t lock, enum mu_locker_mode mode) { rc = locker_tab[type].lock (lock, mode); if (rc == EAGAIN && retries) - { - sleep (lock->retry_sleep); - continue; - } - - if (rc == 0) - lock->refcnt++; - - break; + sleep (lock->retry_sleep); + else + break; } + + if (rc == EAGAIN) + rc = MU_ERR_LOCK_CONFLICT; } else rc = 0; + + if (rc == 0) + lock->refcnt++; return rc; } @@ -746,9 +750,7 @@ lock_dotlock (mu_locker_t locker, enum mu_locker_mode mode) { int rc; char *host = NULL; - char pid[11]; /* 10 is strlen(2^32 = 4294967296) */ - char now[11]; - size_t sz = 0; + time_t now; int err = 0; int fd; @@ -766,29 +768,15 @@ lock_dotlock (mu_locker_t locker, enum mu_locker_mode mode) rc = mu_get_host_name (&host); if (rc) return rc; - - snprintf (now, sizeof (now), "%lu", (unsigned long) time (0)); - now[sizeof (now) - 1] = 0; - - snprintf (pid, sizeof (pid), "%lu", (unsigned long) getpid ()); - pid[sizeof (pid) - 1] = 0; - - sz = strlen (locker->file) + 1 /* "." */ - + strlen (pid) + 1 /* "." */ - + strlen (now) + 1 /* "." */ - + strlen (host) + 1; - - locker->data.dot.nfslock = malloc (sz); - - if (!locker->data.dot.nfslock) - { - free (host); - return ENOMEM; - } - - snprintf (locker->data.dot.nfslock, sz, "%s.%s.%s.%s", - locker->file, pid, now, host); + time (&now); + rc = mu_asprintf (&locker->data.dot.nfslock, + "%s.%lu.%lu.%s", + locker->file, + (unsigned long) getpid (), + (unsigned long) now, host); free (host); + if (rc) + return rc; fd = open (locker->data.dot.nfslock, O_WRONLY | O_CREAT | O_EXCL, LOCKFILE_ATTR); @@ -806,7 +794,7 @@ lock_dotlock (mu_locker_t locker, enum mu_locker_mode mode) { unlink (locker->data.dot.nfslock); if (errno == EEXIST) - return MU_ERR_LOCK_CONFLICT; + return EAGAIN; return errno; } @@ -827,9 +815,6 @@ lock_dotlock (mu_locker_t locker, enum mu_locker_mode mode) unlink (locker->data.dot.nfslock); - /* FIXME: If no errors, we have the lock. */ - assert (locker->refcnt == 0); - if (locker->flags & MU_LOCKER_PID) { char buf[16]; @@ -913,8 +898,8 @@ lock_kernel (mu_locker_t locker, enum mu_locker_mode mode) fl.l_len = 0; /* Lock entire file */ if (fcntl (fd, F_SETLK, &fl)) { -#ifdef EACCESS - if (errno == EACCESS) +#ifdef EACCES + if (errno == EACCES) return EAGAIN; #endif if (errno == EAGAIN) @@ -978,12 +963,6 @@ external_locker (mu_locker_t l, int lock) char aretry[3 + DEC_DIGS_PER_INT + 1]; int status = 0; - assert (l); - assert (l->flags & MU_LOCKER_EXTERNAL); - /* FIXME */ - assert (lock == !l->refcnt); - /* lock is true, refcnt is 0 or lock is false and refcnt is 1 */ - av[ac++] = l->data.external.name ? l->data.external.name : MU_LOCKER_EXTERNAL_PROGRAM; diff --git a/libmailutils/cli/stdcapa.c b/libmailutils/cli/stdcapa.c index afa5443e1..b72f024cb 100644 --- a/libmailutils/cli/stdcapa.c +++ b/libmailutils/cli/stdcapa.c @@ -30,6 +30,7 @@ #include <mailutils/locker.h> #include <mailutils/mu_auth.h> #include <mailutils/url.h> +#include <mailutils/kwd.h> /* ************************************************************************* * Logging section @@ -308,35 +309,44 @@ cb_locker_flags (void *data, mu_config_value_t *val) { int flags = 0; char const *s; - + static struct mu_kwd flag_tab[] = { + { "external-locker", 'E' }, + { "retry-count", 'R' }, + { "expire-timeout", 'T' }, + { "pid-check", 'P' }, + { NULL } + }; + if (mu_cfg_assert_value_type (val, MU_CFG_STRING)) return 1; for (s = val->v.string; *s; s++) { - switch (*s) + char const *kw; + if (mu_kwd_xlat_tok (flag_tab, *s, &kw)) { - case 'E': - flags |= MU_LOCKER_EXTERNAL; - break; - - case 'R': - flags |= MU_LOCKER_RETRY; - break; - - case 'T': - flags |= MU_LOCKER_TIME; - break; - - case 'P': - flags |= MU_LOCKER_PID; - break; - - default: mu_error (_("invalid lock flag `%c'"), *s); } + else if (*s == 'P') + { + /* TRANSLATORS: %c is replaced with the flag letter, and %s - with + the corresponding keyword. */ + mu_diag_output (MU_DIAG_WARNING, + _("applying legacy flag %c, use %s instead"), + *s, kw); + flags |= MU_LOCKER_PID; + } + else + { + /* TRANSLATORS: %c is replaced with the flag letter, and %s - with + the corresponding keyword. */ + mu_diag_output (MU_DIAG_WARNING, + _("ignoring legacy flag %c, use %s instead"), + *s, kw); + } } - mu_locker_set_default_flags (flags, mu_locker_assign); + if (flags) + mu_locker_set_default_flags (flags, mu_locker_assign); return 0; } @@ -356,6 +366,8 @@ cb_locker_retry_timeout (void *data, mu_config_value_t *val) mu_strerror (rc)); free (errmsg); } + else if (t == 0) + mu_locker_set_default_flags (MU_LOCKER_RETRY, mu_locker_clear_bit); else { mu_locker_set_default_retry_timeout (t); @@ -380,6 +392,8 @@ cb_locker_retry_count (void *data, mu_config_value_t *val) mu_strerror (rc)); free (errmsg); } + else if (n == 0) + mu_locker_set_default_flags (MU_LOCKER_RETRY, mu_locker_clear_bit); else { mu_locker_set_default_retry_count (n); @@ -404,6 +418,8 @@ cb_locker_expire_timeout (void *data, mu_config_value_t *val) mu_strerror (rc)); free (errmsg); } + else if (t == 0) + mu_locker_set_default_flags (MU_LOCKER_TIME, mu_locker_clear_bit); else { mu_locker_set_default_expire_timeout (t); @@ -415,13 +431,39 @@ cb_locker_expire_timeout (void *data, mu_config_value_t *val) static int cb_locker_external (void *data, mu_config_value_t *val) { + int t; + if (mu_cfg_assert_value_type (val, MU_CFG_STRING)) return 1; - mu_locker_set_default_external_program (val->v.string); - mu_locker_set_default_flags (MU_LOCKER_EXTERNAL, mu_locker_set_bit); + if (mu_str_to_c (val->v.string, mu_c_bool, &t, NULL) == 0 && t == 0) + { + mu_locker_set_default_flags (MU_LOCKER_EXTERNAL, mu_locker_clear_bit); + } + else + { + mu_locker_set_default_external_program (val->v.string); + mu_locker_set_default_flags (MU_LOCKER_EXTERNAL, mu_locker_set_bit); + } return 0; } +static int +cb_locker_pid_check (void *data, mu_config_value_t *val) +{ + int t; + + if (mu_cfg_assert_value_type (val, MU_CFG_STRING)) + return 1; + if (mu_str_to_c (val->v.string, mu_c_bool, &t, NULL)) + { + mu_error ("%s", _("not a boolean")); + return 1; + } + mu_locker_set_default_flags (MU_LOCKER_PID, + t ? mu_locker_set_bit : mu_locker_clear_bit); + return 0; +} + static struct mu_cfg_param locking_cfg[] = { /* FIXME: Flags are superfluous. */ { "flags", mu_cfg_callback, NULL, 0, cb_locker_flags, @@ -439,6 +481,9 @@ static struct mu_cfg_param locking_cfg[] = { { "external-locker", mu_cfg_callback, NULL, 0, cb_locker_external, N_("Use external locker program."), N_("prog: string") }, + { "pid-check", mu_cfg_callback, NULL, 0, cb_locker_pid_check, + N_("Check if PID of the lock owner is active."), + N_("arg: bool") }, { NULL, } }; diff --git a/libmailutils/tests/.gitignore b/libmailutils/tests/.gitignore index 4a215ccb8..9c0497402 100644 --- a/libmailutils/tests/.gitignore +++ b/libmailutils/tests/.gitignore @@ -21,6 +21,7 @@ fsfolder globtest hdrcpy imapio +lck listop listsort linetrack diff --git a/libmailutils/tests/Makefile.am b/libmailutils/tests/Makefile.am index 6e82124de..13f513c9d 100644 --- a/libmailutils/tests/Makefile.am +++ b/libmailutils/tests/Makefile.am @@ -43,6 +43,7 @@ noinst_PROGRAMS = \ globtest\ hdrcpy\ imapio\ + lck\ listop\ listsort\ linetrack\ @@ -120,6 +121,7 @@ TESTSUITE_AT += \ linecon.at\ list.at\ linetrack.at\ + lock.at\ logstr.at\ mailcap.at\ mimehdr.at\ diff --git a/libmailutils/tests/lck.c b/libmailutils/tests/lck.c new file mode 100644 index 000000000..fe7ac9213 --- /dev/null +++ b/libmailutils/tests/lck.c @@ -0,0 +1,335 @@ +/* + NAME + lck - mailutils locking test + + SYNOPSIS + lck [-akpu?] [-e COMMAND] [-H SECONDS] [-r N] [-t SECONDS] [-x SECONDS] + [--abandon] [--delay=SECONDS] [--expire=SECONDS] + [--external=COMMAND] [--help] [--hold=SECONDS] [--kernel] + [--pid-check] [--retry=N] [--show-config-options] [--unlock] + [--usage] FILE + + DESCRIPTION + Tests the mailutils locking mechanism. Unless --hold (-H) or --abandon + (-a) option is used, the tool locks the FILE and exits. If the --unlock + option is given, existing file lock is removed instead. + + The --hold and --abandon options are used to simulate locking conflict + conditions. Both options cause lck to fork a child process, which + will attempt to lock the file using the same options as the main (master) + process. After obtaining the lock, the child notifies the master process + and, if --hold=N option was given, sleeps for N seconds before releasing + the lock. If the --abandon option is given, the lock is not released. + After that, the child terminates. + + The master waits for child to successfully lock the file and attempts to + obtain the lock. If successful, it exits with the 0 status. On errors, + the termination status is 0. + + OPTIONS + Locking type (default: dotlock): + + -e, --external=COMMAND + Use the external locker command. + + -k, --kernel + Use kernel locking (fnctl). + + Locking parameters + + -p, --pid-check + Check if the PID of lock owner is still active. + + -r, --retry=N + Retry the lock N times. + + -t, --delay=SECONDS + Delay between two successive locking attempts. + + -x, --expire=SECONDS + Expire the lock after that many seconds. + + Child operation modifiers + + -a, --abandon + Abandon lock in child. + + -H, --hold=SECONDS + Hold the lock for that many seconds. + + Operation modifiers + + -u, --unlock + Release the existing lock. + + Informational options + + --show-config-options + Show compilation options. + + -?, --help + Give a short help list. + + --usage + Give a short usage message. + + AUTHOR + Sergey Poznyakoff <gray@gnu.org> + + LICENSE + This program is part of GNU Mailutils testsuite. + Copyright (C) 2020-2021 Free Software Foundation, Inc. + + GNU Mailutils 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 3, or (at your option) + any later version. + + GNU Mailutils 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. + + You should have received a copy of the GNU General Public License + along with GNU Mailutils. If not, see <http://www.gnu.org/licenses/>. + +*/ +#include <sys/types.h> +#include <sys/wait.h> +#include <signal.h> +#include <mailutils/mailutils.h> + +static int flags = 0; +static int unlock; +static unsigned retry_count; +static unsigned retry_sleep = 0; +static int pid_check; +static unsigned expire; +static char const *extlocker; +static unsigned hold_time; +static int abandon_lock; + +static void +cli_type (struct mu_parseopt *po, struct mu_option *opt, char const *arg) +{ + switch (opt->opt_short) + { + case 'k': + flags = MU_LOCKER_OPTIONS (flags) | MU_LOCKER_KERNEL; + break; + + case 'e': + flags = MU_LOCKER_OPTIONS (flags) | MU_LOCKER_EXTERNAL; + extlocker = arg; + break; + + default: + abort (); + } +} + +struct mu_option options[] = { + MU_OPTION_GROUP ("Locking type (default: dotlock)"), + + { "kernel", 'k', NULL, MU_OPTION_DEFAULT, + "use kernel locking", mu_c_void, NULL, cli_type }, + { "external", 'e', "COMMAND", MU_OPTION_DEFAULT, + "use external locker command", mu_c_void, NULL, cli_type }, + + MU_OPTION_GROUP ("Locking parameters"), + { "retry", 'r', "N", MU_OPTION_DEFAULT, + "retry the lock N times", + mu_c_uint, &retry_count }, + { "delay", 't', "SECONDS", MU_OPTION_DEFAULT, + "delay between two successive locking attempts (in seconds)", + mu_c_uint, &retry_sleep }, + { "pid-check", 'p', NULL, MU_OPTION_DEFAULT, + "check if the PID of lock owner is still active", + mu_c_bool, &pid_check }, + { "expire", 'x', "SECONDS", MU_OPTION_DEFAULT, + "expire the lock after that many seconds", + mu_c_uint, &expire }, + + MU_OPTION_GROUP ("Child operation modifiers"), + { "hold", 'H', "SECONDS", MU_OPTION_DEFAULT, + "hold lock for that many seconds", + mu_c_uint, &hold_time }, + { "abandon",'a', NULL, MU_OPTION_DEFAULT, + "abandon lock in child", + mu_c_bool, &abandon_lock }, + + MU_OPTION_GROUP ("Operation modifiers"), + { "unlock", 'u', NULL, MU_OPTION_DEFAULT, + "unlock", mu_c_bool, &unlock }, + MU_OPTION_END +}; + +static pid_t child_pid; +static int child_status; +static int time_out; + +void +sighan (int sig) +{ + switch (sig) + { + case SIGCHLD: + if (waitpid (child_pid, &child_status, WNOHANG) == child_pid) + child_pid = 0; + break; + + case SIGALRM: + time_out = 1; + break; + } +} + +int +main (int argc, char **argv) +{ + mu_locker_t lck; + char const *file; + int rc; + + mu_cli_simple (argc, argv, + MU_CLI_OPTION_OPTIONS, options, + MU_CLI_OPTION_PROG_DOC, "locking test tool", + MU_CLI_OPTION_PROG_ARGS, "FILE", + MU_CLI_OPTION_RETURN_ARGC, &argc, + MU_CLI_OPTION_RETURN_ARGV, &argv, + MU_CLI_OPTION_END); + + if (argc != 1) + { + mu_error ("bad arguments; try %s --help for more info", mu_program_name); + return 1; + } + file = argv[0]; + + if (expire) + flags |= MU_LOCKER_TIME; + if (retry_count || retry_sleep) + flags |= MU_LOCKER_RETRY; + if (pid_check) + flags |= MU_LOCKER_PID; + + MU_ASSERT (mu_locker_create (&lck, file, flags)); + if (expire) + mu_locker_set_expire_time (lck, expire); + if (retry_count) + mu_locker_set_retries (lck, retry_count); + if (retry_sleep) + mu_locker_set_retry_sleep (lck, retry_sleep); + if (extlocker) + mu_locker_set_external (lck, extlocker); + + if (hold_time || abandon_lock) + { + FILE *fp; + int p[2]; + + signal (SIGCHLD, sighan); + if (pipe (p)) + { + mu_diag_funcall (MU_DIAG_CRIT, "pipe", NULL, errno); + return 1; + } + + child_pid = fork (); + if (child_pid == -1) + { + mu_diag_funcall (MU_DIAG_CRIT, "fork", NULL, errno); + return 1; + } + + if (child_pid == 0) + { + /* child */ + signal (SIGCHLD, SIG_IGN); + + fp = fdopen (p[1], "w"); + close (p[0]); + + rc = mu_locker_lock (lck); + fprintf (fp, "L%d\n", rc); + fclose (fp); + if (rc) + abort (); + if (hold_time) + sleep (hold_time); + if (abandon_lock) + rc = 0; + else + rc = mu_locker_remove_lock (lck); + exit (rc != 0); + } + + /* master */ + fp = fdopen (p[0], "r"); + close (p[1]); + + signal (SIGALRM, sighan); + alarm (5); + if (fscanf (fp, "L%d", &rc) != 1) + { + if (time_out) + mu_error ("child didn't respond"); + else + mu_error ("bad response from child"); + if (child_pid) + kill (child_pid, SIGKILL); + return 1; + } + alarm (0); + + if (rc) + { + mu_error ("child lock failed"); + if (child_pid) + kill (child_pid, SIGKILL); + return 1; + } + } + + if (unlock) + rc = mu_locker_remove_lock (lck); + else + rc = mu_locker_lock (lck); + if (rc) + mu_diag_funcall (MU_DIAG_ERROR, + unlock ? "mu_locker_remove_lock" : "mu_locker_lock", + NULL, rc); + + if (child_pid > 0) + { + if (waitpid (child_pid, &child_status, WNOHANG) != child_pid) + { + if (child_pid) + kill (child_pid, SIGKILL); + waitpid (child_pid, &child_status, 0); + child_status = 0; + } + } + + if (WIFEXITED (child_status)) + { + int status = WEXITSTATUS (child_status); + if (status != 0) + { + mu_error ("child terminated with status %d", status); + return 1; + } + } + else if (WIFSIGNALED (child_status)) + { + mu_error ("child terminated on signal %d", WTERMSIG (child_status)); + return 1; + } + else + { + mu_error ("child terminated with unhandled status %d", child_status); + return 1; + } + + return rc != 0; +} diff --git a/libmailutils/tests/lock.at b/libmailutils/tests/lock.at new file mode 100644 index 000000000..0886390b9 --- /dev/null +++ b/libmailutils/tests/lock.at @@ -0,0 +1,59 @@ +# This file is part of GNU Mailutils. -*- Autotest -*- +# Copyright (C) 2010-2021 Free Software Foundation, Inc. +# +# GNU Mailutils 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 3, or (at +# your option) any later version. +# +# GNU Mailutils 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. +# +# You should have received a copy of the GNU General Public License +# along with GNU Mailutils. If not, see <http://www.gnu.org/licenses/>. + +AT_BANNER([Locking]) + +m4_pushdef([LCK_TEST],[ +AT_SETUP([$1]) +AT_KEYWORDS([lock]) +AT_CHECK([touch file +lck $2 file +], +m4_shift2($@)) +AT_CLEANUP +]) + +# Child holds lock for 4 seconds, master acquires it when child has +# released it. +LCK_TEST([retries], [--hold=4 --retry=10 --delay=1]) + +# Child holds lock for 4 seconds, master does two retries with one +# second interval and finishes before being able to acquire it. +LCK_TEST([conflict with previous locker], [--hold=4 --retry=2 --delay=1], + [1],[], + [lck: mu_locker_lock() failed: Conflict with previous locker +]) + +# Child abandons the lock; master is not able to acquire it. +LCK_TEST([abandoned lock], [--abandon --retry=4 --delay=1], + [1],[], + [lck: mu_locker_lock() failed: Conflict with previous locker +]) + +# Child abandons the lock; master asserts that its pid is not active and +# acquires it. +LCK_TEST([PID check],[--pid-check --abandon --retry=4 --delay=1]) + +# Child abandons the lock; master waits until it has expired and acquires +# it. +LCK_TEST([lock expiration], + [--abandon --expire=3 --retry=10 --delay=1]) + + +# Default settings correspond to --retry=10 --delay=1 --expire=600 +LCK_TEST([default settings], [--hold=2]) + +m4_popdef([LCK_TEST]) diff --git a/libmailutils/tests/testsuite.at b/libmailutils/tests/testsuite.at index 1e4f5dd98..915d00171 100644 --- a/libmailutils/tests/testsuite.at +++ b/libmailutils/tests/testsuite.at @@ -257,6 +257,8 @@ m4_include([msgset.at]) m4_include([globtest.at]) m4_include([linetrack.at]) + +m4_include([lock.at]) m4_popdef([MU_TEST_GROUP]) m4_popdef([MU_TEST_KEYWORDS]) |