1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343
|
/** \file lockfile.c
* library module to safely create a lock file.
* \author Matthias Andree
* \date 2001 - 2006
*
* Copyright (C) 2001 - 2006 Matthias Andree <matthias.andree@gmx.de>
*
* This library is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2 of the
* License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA
*/
#include "leafnode.h"
#include "ln_log.h"
#include "critmem.h"
#include "validatefqdn.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#ifdef WITH_DMALLOC
#include <dmalloc.h>
#endif
#include <signal.h> /* for kill */
/** get hard link count of open file pointed to by filedes.
* uses fstat(2)
* \return 0 in case of trouble
* (which is also logged), the count of hard links otherwise
*
* NOTE: you cannot distinguish "fstat error" from "deleted file".
*/
static nlink_t
fd_st_nlink(const int fd /** open file descriptor */ )
{
struct stat st;
if (fstat(fd, &st)) {
ln_log(LNLOG_SERR, LNLOG_CTOP, "Cannot fstat %d: %m", fd);
return 0;
}
return st.st_nlink;
}
/** Check if the lock file given by "name" is stale and if so, erase it.
* A lock is stale when the process to which it belongs, is dead.
*
* \bug cannot detect if lock files held by other hosts are stale.
*
* \return
* - -1 for error "failure"
* - 0 if lock file is still in use or held by another host "failure"
* - 1 if lock file is stale and has been erased "success" */
static int
lock_is_stale(
/** file name of lock file */
const char *const name,
/** quiet flag */
const int quiet)
{
char buf[512];
int fd;
int len;
char *pid;
char *host;
char *tmp;
unsigned long npid;
fd = open(name, O_RDONLY, 0);
if (fd < 0) {
if (errno == ENOENT) {
/* file has just disappeared, thus it's not stale */
return 0;
} else {
ln_log(LNLOG_SERR, LNLOG_CTOP,
"cannot open %s for reading: %m", name);
return -1;
}
}
if ((len = read(fd, buf, sizeof(buf) - 1)) < 0) {
ln_log(LNLOG_SERR, LNLOG_CTOP, "read error on %s: %m", name);
(void)close(fd);
return -1;
}
if (close(fd) < 0) {
ln_log(LNLOG_SERR, LNLOG_CTOP, "read error on %s: %m", name);
return -1;
}
/* read pid and host */
buf[len - 1] = '\0';
pid = host = buf;
/* we expect a single \n here */
host += strcspn(host, "\n");
*(host++) = '\0';
/* kill trailing \n */
tmp = host;
tmp += strcspn(tmp, "\n");
*tmp = '\0';
npid = strtoul(pid, 0, 10);
if (npid == ULONG_MAX && errno == ERANGE) {
/* overflow error, should not happen, bail out */
ln_log(LNLOG_SERR, LNLOG_CTOP, "bogus pid in %s: %m", name);
return -1;
}
if (strcasecmp(host, fqdn)) {
if (!quiet)
ln_log(LNLOG_SERR, LNLOG_CTOP,
"lockfile held by pid %lu on host %s, we're %s",
npid, host, fqdn);
return 0; /* other host holds the lock */
}
/* okay, we can see if there's still a process with that pid active */
if (kill((pid_t)npid, 0) && errno == ESRCH) {
/* no such process, good */
if (!unlink(name)) {
ln_log(LNLOG_SNOTICE, LNLOG_CTOP,
"erased stale pid %lu host %s lockfile %s",
npid, host, name);
return 1;
} else {
if (!quiet)
ln_log(LNLOG_SERR, LNLOG_CTOP,
"unable to erase stale pid %lu host %s lockfile %s",
npid, host, name);
return 0;
}
}
/* there is a process active */
return 0;
}
/** Safe mkstemp replacement.
* Ensures the file is only read- and writable by its owner; some systems
* create these with 0777 or 0666 permissions. */
int
safe_mkstemp(
/** template to build filename upon, as for mkstemp */
char *templ)
{
mode_t oldmask;
int ret;
oldmask = umask(077);
ret = mkstemp(templ);
(void)umask(oldmask);
return ret;
}
/**
* Try to set a lockfile, blocking or non-blocking.
* Checks if the lockfile exists and is active.
*
* requires: atomic link(2) syscall.
*
* features:
* - NFS safe (but leafnode does not work distributed)
* - stale detection by PID in lock file (may be fooled)
*
* \bug Cannot remove stale lock on other machine.
*
* \bug Stale detection may be fooled if another process has been
* assigned the PID that the last caller had.
*
* \return
* - 0: if locking succeeded
* - 1: if locking failed because the lock is held by someone else and
* isn't stale
* - -1: for other errors
*/
int
try_lock(
/** Timeout, if nonzero, wait at most this many seconds. */
unsigned long timeout)
{
const int block = 1;
char *l2, *pid;
int fd;
int have_lock = 0;
int quiet = 0;
const char *const append = ".XXXXXXXXXX";
const int have_timeout = (timeout != 0);
if (debugmode)
syslog(LOG_DEBUG,
"try_lock(timeout=%lu), fqdn=\"%s\"",
timeout, fqdn);
if (verbose)
printf("try_lock(timeout=%lu), fqdn=\"%s\"\n",
timeout, fqdn);
/* kill bogus fqdn */
if (!is_validfqdn(fqdn)) {
ln_log(LNLOG_SCRIT, LNLOG_CTOP,
"Internal error: "
"must not try to lock with local host name \"%s\"", fqdn);
return -1;
}
l2 = (char *)critmalloc(strlen(lockfile) + strlen(append) + 1,
"try_lock");
pid = (char *)critmalloc(strlen(fqdn) + sizeof(unsigned long) * 4 + 4,
"try_lock");
strcpy(l2, lockfile); /* RATS: ignore */
strcat(l2, append); /* RATS: ignore */
/* make a temporary file */
fd = safe_mkstemp(l2);
if (fd < 0) {
ln_log(LNLOG_SERR, LNLOG_CTOP, "mkstemp(%s) failed: %m", l2);
free(l2);
free(pid);
return -1;
}
/* write our PID and host into it (stale detection) */
sprintf(pid, "%lu\n%s\n", (unsigned long)getpid(), fqdn);
/* safe, see malloc above */
if (writes(fd, pid) < 0 || fsync(fd) < 0) {
ln_log(LNLOG_SERR, LNLOG_CTOP, "cannot write to %s: %m", l2);
if (unlink(l2))
ln_log(LNLOG_SERR, LNLOG_CTOP,
"Cannot remove lock helper file %s: %m", l2);
free(l2);
free(pid);
return -1;
}
/* and try to finally lock */
while (!have_lock) {
if (0 == link(l2, lockfile)) {
/* link succeeded. good. */
have_lock = 1;
break;
} else {
if (2 == fd_st_nlink(fd)) {
/* link failed, but st_nlink has increased to 2, good. */
have_lock = 1;
} else {
int stale;
struct timeval tv = { 1, 0 };
/* Could not create link. Check if the lock file is stale. */
stale = lock_is_stale(lockfile, quiet);
/* if we have a stale file, it's just been removed.
retry, don't care for what block says */
if (stale == 1)
continue;
quiet = 1;
/* if we have a problem with stale detection, or
if we are in non-blocking mode, abort */
if (stale == -1 || !block)
break;
if (have_timeout) {
if (timeout == 0)
break;
--timeout;
}
/* retry after a second, select does not interfere w/ alarm */
if (select(0, NULL, NULL, NULL, &tv) < 0) {
/* must not happen */
ln_log(LNLOG_SERR, LNLOG_CTOP,
"try_lock: select failed: %m");
break;
}
}
}
}
if (close(fd) < 0) {
ln_log(LNLOG_SERR, LNLOG_CTOP, "cannot write to %s: %m", l2);
have_lock = 0;
}
/* unlink l2, but just log if unable to unlink, ignore otherwise */
if (unlink(l2))
ln_log(LNLOG_SERR, LNLOG_CTOP,
"Cannot remove lock helper file %s: %m", l2);
/* clean up */
free(l2);
free(pid);
/* mind the return logic */
return have_lock ? 0 : 1;
}
/** Tries to hand over lock to the process with the given pid. \return
* 0 for success, nonzero for failure -- check errno for details in
* case of error.
*/
int handover_lock(pid_t pid) {
int fd = open(lockfile, O_RDWR|O_TRUNC, (mode_t)0600);
char *buf = (char *)critmalloc(strlen(fqdn) + sizeof(unsigned long) * 4 + 4,
"handover_lock");
if (fd < 0) { free(buf); return fd; }
sprintf(buf, "%lu\n%s\n", (unsigned long)pid, fqdn);
if (-1 == writes(fd, buf)) goto close_bail;
if (-1 == fsync(fd)) goto close_bail;
if (-1 == close(fd)) goto close_bail;
free(buf);
return 0;
close_bail:
(void)close(fd);
free(buf);
return -1;
}
|