From c9178a6fef0184baed0e8456bb6c6d4091b4997c Mon Sep 17 00:00:00 2001 From: Sergey Poznyakoff Date: Tue, 24 Feb 2009 18:45:51 +0200 Subject: Implement locking * src/lock.c: New file. * gnulib.modules (sysexits, xgethostname): New modules. * src/lock.c: New file. * src/Makefile.am: Add lock.c * src/config.c: Locking keywords. * src/job.c: Requeue jobs if locking fails. * src/wydawca.c (wydawca_uid): Rename to wydawca_set_uid (main): Implement restart. * src/process.c (scan_spool, scan_all_spools): Use locking, if configured. * src/directive.c, src/diskio.c, src/triplet.c: Rename wydawca_uid * src/exec.c, src/getopt.m4, src/net.c, src/pidfile.c: Use standard error codes from sysexits.h * tests/etc/wydawca.rcin: Disable locking. --- src/Makefile.am | 1 + src/config.c | 8 ++ src/directive.c | 4 +- src/diskio.c | 6 +- src/exec.c | 2 +- src/getopt.m4 | 2 +- src/job.c | 45 +++++++---- src/lock.c | 239 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/net.c | 14 ++-- src/pidfile.c | 10 +-- src/process.c | 25 +++++- src/triplet.c | 4 +- src/wydawca.c | 50 ++++++++++-- src/wydawca.h | 27 ++++++- 14 files changed, 386 insertions(+), 51 deletions(-) create mode 100644 src/lock.c (limited to 'src') diff --git a/src/Makefile.am b/src/Makefile.am index 903b754..fcfdb78 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -26,6 +26,7 @@ wydawca_SOURCES=\ gpg.c\ interval.c\ job.c\ + lock.c\ meta.c\ method.c\ net.c\ diff --git a/src/config.c b/src/config.c index facfcbc..bab1ed3 100644 --- a/src/config.c +++ b/src/config.c @@ -1237,6 +1237,14 @@ static struct gconf_keyword wydawca_kw[] = { gconf_type_string, &wakeup_interval, 0, cb_interval }, { "pidfile", N_("file"), N_("Set pid file name"), gconf_type_string, &pidfile }, + + { "locking", NULL, N_("Enable or disable locking"), + gconf_type_bool, &enable_locking }, + { "lockdir", N_("dir"), N_("Set directory for lock files"), + gconf_type_string, &lockdir }, + { "lock-expire-time", N_("interval"), N_("Define lock expiration interval"), + gconf_type_string, &lock_expire_time, 0, cb_interval }, + { "listen", N_("socket"), N_("Listen on this address"), gconf_type_sockaddr, &listen_sockaddr, }, { "mailer", N_("url"), N_("Set mailer URL"), diff --git a/src/directive.c b/src/directive.c index 37ce241..ccea1ef 100644 --- a/src/directive.c +++ b/src/directive.c @@ -337,9 +337,9 @@ process_directives (struct file_triplet *trp, const struct spool *spool) break; case filename_dir: - wydawca_uid (0); + wydawca_set_uid (0); rc = verify_detached_signature (trp, spool); - wydawca_uid (TRIPLET_UID (trp)); + wydawca_set_uid (TRIPLET_UID (trp)); if (rc == 0) { if (move_file (trp, spool, file_dist, relative_dir) diff --git a/src/diskio.c b/src/diskio.c index ccff5c2..c068e49 100644 --- a/src/diskio.c +++ b/src/diskio.c @@ -124,9 +124,9 @@ create_directory (const char *base, const char *name, uid_t uid, gid_t gid) if (!dry_run_mode) { int rc; - wydawca_uid (0); + wydawca_set_uid (0); rc = create_hierarchy (dir, baselen, uid, gid); - wydawca_uid (uid); + wydawca_set_uid (uid); if (rc) { free (dir); @@ -647,7 +647,7 @@ dir_symlink_file (struct file_triplet *trp, const struct spool *spool, { logmsg (LOG_EMERG, _("cannot restore current directory: %s"), strerror (errno)); - exit (1); + exit (EX_SOFTWARE); } free (src); diff --git a/src/exec.c b/src/exec.c index eb4c8c4..7293ca1 100644 --- a/src/exec.c +++ b/src/exec.c @@ -49,7 +49,7 @@ start_prog (int argc, const char **argv, pid_t *ppid) execvp (argv[0], (char**) argv); logmsg (LOG_CRIT, _("cannot run %s: %s"), argv[0], strerror (errno)); - exit (1); + exit (EX_UNAVAILABLE); case -1: logmsg (LOG_CRIT, _("cannot run `%s': fork failed: %s"), diff --git a/src/getopt.m4 b/src/getopt.m4 index 83cc45f..2cd9a0f 100644 --- a/src/getopt.m4 +++ b/src/getopt.m4 @@ -179,7 +179,7 @@ ifelse([<$#>],3,opterr = 0;) switch (c) { default: - ifelse([<$#>],3,$3,[]); + ifelse([<$#>],3,$3,[]); undivert(4) } diff --git a/src/job.c b/src/job.c index 545d270..2c177cb 100644 --- a/src/job.c +++ b/src/job.c @@ -38,6 +38,15 @@ size_t jobcnt; static struct spool fake_spool = { "all spools" }; +static int wakeup; + +RETSIGTYPE +queue_signal (int sig) +{ + wakeup = 1; + signal (sig, queue_signal); +} + struct job * job_locate (const struct spool *spool, uid_t uid) { @@ -59,21 +68,23 @@ job_active_count () return count; } -void +int wydawca_scanner (struct job *job) { + int rc; initstats(); timer_start ("wydawca"); if (job->spool == &fake_spool) - scan_all_spools (1, &job->uid); + rc = scan_all_spools (1, &job->uid); else { spool_create_timers (); - scan_spool (job->spool, 1, &job->uid); + rc = scan_spool (job->spool, 1, &job->uid); } timer_stop ("wydawca"); mail_finish (); logstats (); + return rc; } int @@ -92,15 +103,17 @@ job_start (struct job *job) if (single_process) { - wydawca_scanner (job); - return 0; + if (wydawca_scanner (job)) + job->state = STATE_QUEUED; + else + job->state = STATE_FINISHED; + wakeup = 1; } pid = fork (); if (pid == 0) { - wydawca_scanner (job); - exit (0); + exit (wydawca_scanner (job) ? WYDAWCA_EX_AGAIN : 0); } else if (pid == -1) { @@ -235,15 +248,6 @@ print_status (struct job *job, int expect_term) job->pid, job->spool->tag, pw->pw_name); } -static int wakeup; - -RETSIGTYPE -queue_signal (int sig) -{ - wakeup = 1; - signal (sig, queue_signal); -} - void job_queue_runner () { @@ -278,7 +282,14 @@ job_queue_runner () { print_status (job, 0); if ((job->state &= ~STATE_FINISHED) == 0) - job_remove (job); + { + if (WIFEXITED (job->exit_status) + && WEXITSTATUS (job->exit_status) == WYDAWCA_EX_AGAIN) + /* Re-queue the job */ + job->state = STATE_QUEUED; + else + job_remove (job); + } } if (job->state == STATE_QUEUED) if (job_start (job)) diff --git a/src/lock.c b/src/lock.c new file mode 100644 index 0000000..d94731c --- /dev/null +++ b/src/lock.c @@ -0,0 +1,239 @@ +/* wydawca - automatic release submission daemon + Copyright (C) 2007, 2009 Sergey Poznyakoff + + Wydawca 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 of the License, or (at your + option) any later version. + + Wydawca 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 wydawca. If not, see . */ + +#include "wydawca.h" +#include "c-ctype.h" +#include "xgethostname.h" + +int enable_locking = 1; +char *lockdir; +time_t lock_expire_time; + +#define LOCKFILE_MODE 0644 + +static int +stat_check (const char *file, int fd, int links) +{ + struct stat fn_stat; + struct stat fd_stat; + int err = 0; + int localfd = -1; + + if (fd == -1) + { + localfd = open (file, O_RDONLY); + + if (localfd == -1) + return errno; + fd = localfd; + } + + /* We should always be able to stat a valid fd, so this + is an error condition. */ + if (lstat (file, &fn_stat) || fstat (fd, &fd_stat)) + err = errno; + else + { + /* If the link and stat don't report the same info, or the + file is a symlink, fail the locking. */ +#define CHK(X) if(X) err = EINVAL + + CHK (!S_ISREG (fn_stat.st_mode)); + CHK (!S_ISREG (fd_stat.st_mode)); + CHK (fn_stat.st_nlink != links); + CHK (fn_stat.st_dev != fd_stat.st_dev); + CHK (fn_stat.st_ino != fd_stat.st_ino); + CHK (fn_stat.st_mode != fd_stat.st_mode); + CHK (fn_stat.st_nlink != fd_stat.st_nlink); + CHK (fn_stat.st_uid != fd_stat.st_uid); + CHK (fn_stat.st_gid != fd_stat.st_gid); + CHK (fn_stat.st_rdev != fd_stat.st_rdev); + +#undef CHK + } + if (localfd != -1) + close (localfd); + + return err; +} + +int +_lock_internal (const char *file, const char *fname) +{ + int err = 0; + int fd; + FILE *fp; + + fd = open (fname, O_WRONLY | O_CREAT | O_EXCL, LOCKFILE_MODE); + if (fd == -1) + { + if (errno == EEXIST) + return LOCK_RETRY; + else + { + logmsg (LOG_ERR, _("cannot create lock file %s: %s"), + fname, strerror (errno)); + return LOCK_FAILURE; + } + } + close (fd); + + /* Try to link to the lockfile. */ + if (link (fname, file) == -1) + { + unlink (fname); + if (errno == EEXIST) + return LOCK_RETRY; + else + { + logmsg (LOG_ERR, _("cannot create lock file %s: %s"), + file, strerror (errno)); + return LOCK_FAILURE; + } + } + + if ((fd = open (file, O_RDWR)) == -1) + { + unlink (fname); + logmsg (LOG_ERR, _("cannot open lock file %s: %s"), + fname, strerror (errno)); + return LOCK_FAILURE; + } + + err = stat_check (fname, fd, 2); + if (err) + { + unlink (fname); + logmsg (LOG_ERR, _("lock file check failed: %s"), strerror (errno)); + return (err == EINVAL) ? LOCK_INVALID : LOCK_FAILURE; + } + + unlink (fname); + + fp = fdopen (fd, "w"); + fprintf (fp, "%lu", (unsigned long) getpid ()); + fclose (fp); + + return 0; +} + +static void +expire_stale_lock (const char *file) +{ + int stale = 0; + char buf[80]; + int fd; + int len; + + fd = open (file, O_RDONLY); + if (fd == -1) + return; + + len = read (fd, buf, sizeof (buf) - 1); + if (len > 0) + { + pid_t pid; + + buf[len] = 0; + pid = strtoul (buf, NULL, 10); + if (pid > 0) + { + /* Process is gone so we try to remove the lock. */ + if (kill (pid, 0) == -1) + stale = 1; + } + else + stale = 1; /* Corrupted file, remove the lock. */ + } + + if (!stale) + { + struct stat stbuf; + fstat (fd, &stbuf); + /* The lock has expired. */ + if ((time (NULL) - stbuf.st_mtime) > lock_expire_time) + stale = 1; + } + + close (fd); + if (stale) + unlink (file); +} + +int +wydawca_lock (const char *lockname) +{ + char *tempname = NULL; + int rc; + + if (!enable_locking) + return 0; + expire_stale_lock (lockname); + + /* build the NFS hitching-post to the lock file */ + asprintf (&tempname, "%s.%lu.%lu.%s", + lockname, + (unsigned long) getpid (), + (unsigned long) time (NULL), xgethostname ()); + if (!tempname) + return LOCK_FAILURE; + rc = _lock_internal (lockname, tempname); + free (tempname); + return rc; +} + +void +wydawca_unlock (const char *lockfile) +{ + if (enable_locking) + unlink (lockfile); +} + +static char * +fix_tagname (const char *tag) +{ + char *tagname = xstrdup (tag); + char *p; + + for (p = tagname; *p; p++) + if (!c_isalnum (*p) && *p != '_' && *p != '-') + *p = '_'; + return tagname; +} + +char * +wydawca_lockname (const char *tag) +{ + char *lockname = NULL; + char *tagname = fix_tagname (tag); + asprintf (&lockname, "%s/LCK.%s", lockdir, tagname); + if (!lockname) + xalloc_die (); + free (tagname); + return lockname; +} + +void +wydawca_lock_init () +{ + if (enable_locking) + { + if (!lockdir) + lockdir = xstrdup (LOCALSTATEDIR "/lock/" PACKAGE); + if (create_hierarchy (lockdir, 0, getuid (), getgid ())) + exit (EX_OSFILE); + } +} diff --git a/src/net.c b/src/net.c index 03f9a5a..3874827 100644 --- a/src/net.c +++ b/src/net.c @@ -24,7 +24,7 @@ open_listener () if (listen_sockaddr.sa == NULL) { logmsg (LOG_CRIT, _("listener address is not configured")); - exit (1); + exit (EX_CONFIG); } fd = socket (listen_sockaddr.sa->sa_family, SOCK_STREAM, 0); @@ -32,7 +32,7 @@ open_listener () { logmsg (LOG_CRIT, _("cannot create socket: %s"), strerror(errno)); - exit (1); + exit (EX_OSERR); } if (listen_sockaddr.sa->sa_family == AF_UNIX) { @@ -44,7 +44,7 @@ open_listener () { logmsg (LOG_CRIT, _("%s: cannot stat socket: %s"), s_un->sun_path, strerror (errno)); - exit (1); + exit (errno == EACCES ? EX_NOPERM : EX_OSERR); } } else @@ -54,7 +54,7 @@ open_listener () { logmsg (LOG_CRIT, _("%s: not a socket"), s_un->sun_path, strerror (errno)); - exit (1); + exit (EX_OSFILE); } unlink (s_un->sun_path); } @@ -71,13 +71,13 @@ open_listener () logmsg (LOG_CRIT, _("cannot bind to local address: %s"), strerror (errno)); close (fd); - exit (1); + exit (EX_OSERR); } if (listen (fd, 8) == -1) { logmsg (LOG_CRIT, "listen: %s", strerror (errno)); close (fd); - exit (1); + exit (EX_OSERR); } return fd; @@ -155,7 +155,7 @@ handle_connection (FILE *fp) free (buf); } -static int reconfigure; +int reconfigure; static int terminate; RETSIGTYPE diff --git a/src/pidfile.c b/src/pidfile.c index 484cabd..6da84f1 100644 --- a/src/pidfile.c +++ b/src/pidfile.c @@ -38,7 +38,7 @@ check_pidfile () { logmsg (LOG_ERR, _("malformed pidfile %s"), pidfile); if (!force_startup) - exit (1); + exit (EX_UNAVAILABLE); } else { @@ -50,7 +50,7 @@ check_pidfile () _("cannot verify if PID %lu is running: %s"), pid, strerror (errno)); if (!force_startup) - exit (1); + exit (EX_UNAVAILABLE); } } else if (!force_startup) @@ -58,7 +58,7 @@ check_pidfile () logmsg (LOG_ERR, _("another wydawca instance may be running (PID %lu)"), pid); - exit (1); + exit (EX_UNAVAILABLE); } } logmsg (LOG_NOTICE, _("replacing pidfile %s (PID %lu)"), @@ -69,7 +69,7 @@ check_pidfile () { logmsg (LOG_ERR, _("cannot open pidfile %s: %s"), pidfile, strerror (errno)); - exit (1); + exit (EX_UNAVAILABLE); } else { @@ -79,7 +79,7 @@ check_pidfile () logmsg (LOG_ERR, _("cannot open pidfile %s for writing: %s"), pidfile, strerror (errno)); - exit (1); + exit (EX_UNAVAILABLE); } } fprintf (fp, "%lu\n", getpid ()); diff --git a/src/process.c b/src/process.c index 1a6b01d..d04121c 100644 --- a/src/process.c +++ b/src/process.c @@ -137,7 +137,7 @@ match_uid_p (uid_t uid, int uc, uid_t *uv) /* Scan upload directory from the DPAIR and register all files found there, forming triplets when possible */ void -scan_spool (const struct spool *spool, int uc, uid_t *uv) +scan_spool_unlocked (const struct spool *spool, int uc, uid_t *uv) { DIR *dir; struct dirent *ent; @@ -225,6 +225,20 @@ scan_spool (const struct spool *spool, int uc, uid_t *uv) timer_stop ("spool"); } +int +scan_spool (const struct spool *spool, int uc, uid_t *uv) +{ + char *lockfile = wydawca_lockname (spool->tag); + int rc = wydawca_lock (lockfile); + if (rc == LOCK_OK) + { + scan_spool_unlocked (spool, uc, uv); + wydawca_unlock (lockfile); + } + free (lockfile); + return rc; +} + static void close_methods (struct spool *spool) { @@ -234,19 +248,22 @@ close_methods (struct spool *spool) } /* Scan all configured update directories */ -void +int scan_all_spools (int uidc, uid_t *uidv) { struct spool_list *sp; - + int rc = 0; + timer_start ("wydawca"); for (sp = spool_list; sp; sp = sp->next) if (enabled_spool_p (&sp->spool)) - scan_spool (&sp->spool, uidc, uidv); + if (scan_spool (&sp->spool, uidc, uidv)) + rc++; for (sp = spool_list; sp; sp = sp->next) close_methods (&sp->spool); timer_stop ("wydawca"); + return rc; } void diff --git a/src/triplet.c b/src/triplet.c index 717fd2a..285c831 100644 --- a/src/triplet.c +++ b/src/triplet.c @@ -199,10 +199,10 @@ triplet_processor (void *data, void *proc_data) case triplet_complete: if (debug_level) logmsg (LOG_DEBUG, _("processing triplet `%s'"), trp->name); - if (wydawca_uid (TRIPLET_UID (trp)) == 0) + if (wydawca_set_uid (TRIPLET_UID (trp)) == 0) { process_directives (trp, spool); - wydawca_uid (0); + wydawca_set_uid (0); } return true; diff --git a/src/wydawca.c b/src/wydawca.c index f9818ed..b121959 100644 --- a/src/wydawca.c +++ b/src/wydawca.c @@ -255,7 +255,7 @@ collect_uids (int argc, char **argv) if (*p) { logmsg (LOG_ERR, _("no such user: %s"), argv[i]); - exit (1); + exit (EX_NOUSER); } uidv[i] = n; } @@ -266,7 +266,7 @@ collect_uids (int argc, char **argv) int -wydawca_uid (uid_t uid) +wydawca_set_uid (uid_t uid) { int rc; @@ -287,7 +287,32 @@ wydawca_uid (uid_t uid) return rc; } +int +wydawca_set_gid (gid_t gid) +{ + int rc; + + if (getuid () != 0) + return 0; +#if defined(HAVE_SETREGID) + rc = setregid (0, gid); +#elif defined(HAVE_SETRESGID) + rc = setresgid (-1, gid, -1); +#elif defined(HAVE_SETEGID) + rc = setegid (gid); +#else +# error "No way to reset user privileges?" +#endif + if (rc < 0) + logmsg (LOG_ERR, _("cannot switch to GID %d: %s (r=%d, e=%d)"), + gid, strerror (errno), getgid (), getegid ()); + return rc; +} + +char **x_argv; +extern int reconfigure; + void wydawca_daemon () { @@ -296,7 +321,7 @@ wydawca_daemon () if (!foreground && daemon (0, 0)) { logmsg (LOG_ERR, "%s", strerror (errno)); - exit (1); + exit (EX_OSERR); } wydawca_listener (); @@ -312,6 +337,7 @@ main (int argc, char **argv) mu_register_all_mailer_formats (); config_init (); + x_argv = argv; parse_options (argc, argv); argv += optind; @@ -321,9 +347,10 @@ main (int argc, char **argv) collect_uids (argc, argv); if (preprocess_only) - exit (gconf_preproc_run (conffile, gconf_preprocessor)); + exit (gconf_preproc_run (conffile, gconf_preprocessor) ? EX_CONFIG : 0); - gconf_parse (conffile); + if (gconf_parse (conffile)) + exit (EX_CONFIG); if (lint_mode) exit (0); @@ -346,7 +373,8 @@ main (int argc, char **argv) } mail_init (); - + wydawca_lock_init (); + logmsg (LOG_NOTICE, _("wydawca (%s) started"), PACKAGE_STRING); if (!daemon_mode) @@ -361,5 +389,15 @@ main (int argc, char **argv) mail_finish (); + if (reconfigure) + { + int i; + wydawca_set_uid (0); + for (i = getdtablesize (); i > 2; i--) + close (i); + remove_pidfile (); + execv (x_argv[0], x_argv); + } + exit (0); } diff --git a/src/wydawca.h b/src/wydawca.h index 6384738..f093504 100644 --- a/src/wydawca.h +++ b/src/wydawca.h @@ -40,6 +40,7 @@ #include #include #include +#include #include #include @@ -63,6 +64,8 @@ #define SP(s) ((s) ? (s) : "NONE") +#define WYDAWCA_EX_AGAIN 1 + /* The range of directive versions we accept (major * 100 + minor) */ #define MIN_DIRECTIVE_VERSION 101 #define MAX_DIRECTIVE_VERSION 101 @@ -319,6 +322,10 @@ extern char *admin_stat_message; extern char *pidfile; extern int force_startup; +extern char *lockdir; +extern time_t lock_expire_time; +extern int enable_locking; + extern int daemon_mode; extern time_t wakeup_interval; extern int foreground; @@ -364,8 +371,9 @@ enum exec_result wydawca_exec (int argc, const char **argv, int *retcode); /* Directory scanning and registering */ -void scan_spool (const struct spool *spool, int uc, uid_t *uv); -void scan_all_spools (int, uid_t *); +int scan_spool (const struct spool *spool, int uc, uid_t *uv); +int scan_all_spools (int, uid_t *); +void spool_create_timers (void); void register_spool (struct spool *spool); struct spool *wydawca_find_spool (const char *name); @@ -420,7 +428,7 @@ int process_directives (struct file_triplet *trp, int enabled_spool_p (const struct spool *spool); -int wydawca_uid (uid_t uid); +int wydawca_set_uid (uid_t uid); int parse_time_interval (const char *str, time_t *pint, const char **endp); @@ -504,7 +512,20 @@ void job_queue_runner (void); /* profile.c */ void check_pidfile (void); +void remove_pidfile (void); /* net.c */ void wydawca_listener (void); + + +#define LOCK_OK 0 +#define LOCK_CONFLICT 1 +#define LOCK_RETRY 2 +#define LOCK_INVALID 3 +#define LOCK_FAILURE 4 + +char *wydawca_lockname (const char *tag); +int wydawca_lock (const char *lockname); +void wydawca_unlock (const char *lockname); +void wydawca_lock_init (void); -- cgit v1.2.1