/* wydawca - automatic release submission daemon Copyright (C) 2007, 2009-2011 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" int enable_locking = 1; char *lockdir; time_t lock_expire_time = 5*60; time_t lock_timeout = 60; #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 && lock_expire_time > 0) { 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); } static char * host_name () { static char *hostbuf = NULL; size_t size = 0; int rc; if (hostbuf) return hostbuf; do { if (!hostbuf) { size = 256; hostbuf = grecs_malloc (size); } else { size_t ns = size * 2; if (size < ns) grecs_alloc_die (); size = ns; hostbuf = grecs_realloc (hostbuf, size); } } while ((rc = gethostname (hostbuf, size )) == -1 && (errno == EINVAL #ifdef ENAMETOOLONG || errno == ENAMETOOLONG #endif )); if (rc) { logmsg (LOG_ERR, _("cannot get hostname: %s"), strerror (rc)); exit (EX_SOFTWARE); } return hostbuf; } int wydawca_lock (const char *lockname) { char *tempname = NULL; size_t size = 0; int rc; if (!enable_locking) return 0; expire_stale_lock (lockname); /* build the NFS hitching-post to the lock file */ grecs_asprintf (&tempname, &size, "%s.%lu.%lu.%s", lockname, (unsigned long) getpid (), (unsigned long) time (NULL), host_name ()); if (!tempname) return LOCK_FAILURE; if (lock_timeout) { time_t start = time (NULL); do { rc = _lock_internal (lockname, tempname); if (rc == LOCK_RETRY) sleep (1); else break; } while (time (NULL) - start < lock_timeout); } else 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 = grecs_strdup (tag); char *p; for (p = tagname; *p; p++) if (!isalnum (*p) && *p != '_' && *p != '-') *p = '_'; return tagname; } char * wydawca_lockname (const char *tag) { char *lockname = NULL; size_t size = 0; char *tagname = fix_tagname (tag); grecs_asprintf (&lockname, &size, "%s/LCK.%s", lockdir, tagname); if (!lockname) grecs_alloc_die (); free (tagname); return lockname; } void wydawca_lock_init () { if (enable_locking) { if (!lockdir) lockdir = grecs_strdup (LOCALSTATEDIR "/lock/" PACKAGE); if (create_hierarchy (lockdir, 0)) exit (EX_OSFILE); } }