diff options
Diffstat (limited to 'common/dotlock.c')
-rw-r--r-- | common/dotlock.c | 713 |
1 files changed, 713 insertions, 0 deletions
diff --git a/common/dotlock.c b/common/dotlock.c new file mode 100644 index 000000000..0d5a7bce9 --- /dev/null +++ b/common/dotlock.c @@ -0,0 +1,713 @@ +/* dotlock.c - dotfile locking + * Copyright (C) 1998, 2000, 2001, 2003, 2004, + * 2005, 2006, 2008 Free Software Foundation, Inc. + * + * This file is part of JNLIB. + * + * JNLIB 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 3 of + * the License, or (at your option) any later version. + * + * JNLIB 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 program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <ctype.h> +#include <errno.h> +#include <unistd.h> +#ifdef HAVE_DOSISH_SYSTEM +# define WIN32_LEAN_AND_MEAN +# include <windows.h> +#else +# include <sys/utsname.h> +#endif +#include <sys/types.h> +#include <sys/time.h> +#include <sys/stat.h> +#include <fcntl.h> +#ifdef HAVE_SIGNAL_H +# include <signal.h> +#endif + +#include "libjnlib-config.h" +#include "stringhelp.h" +#include "dotlock.h" +#include "utf8conv.h" + +#if !defined(DIRSEP_C) && !defined(EXTSEP_C) \ + && !defined(DIRSEP_S) && !defined(EXTSEP_S) +#ifdef HAVE_DOSISH_SYSTEM +#define DIRSEP_C '\\' +#define EXTSEP_C '.' +#define DIRSEP_S "\\" +#define EXTSEP_S "." +#else +#define DIRSEP_C '/' +#define EXTSEP_C '.' +#define DIRSEP_S "/" +#define EXTSEP_S "." +#endif +#endif + + +/* The object describing a lock. */ +struct dotlock_handle +{ + struct dotlock_handle *next; + char *lockname; /* Name of the actual lockfile. */ + int locked; /* Lock status. */ + int disable; /* If true, locking is disabled. */ + +#ifdef HAVE_DOSISH_SYSTEM + HANDLE lockhd; /* The W32 handle of the lock file. */ +#else + char *tname; /* Name of the lockfile template. */ + size_t nodename_off; /* Offset in TNAME of the nodename part. */ + size_t nodename_len; /* Length of the nodename part. */ +#endif /* HAVE_DOSISH_SYSTEM */ +}; + + +/* A list of of all lock handles. */ +static volatile dotlock_t all_lockfiles; + +/* If this has the value true all locking is disabled. */ +static int never_lock; + + +/* Local protototypes. */ +#ifndef HAVE_DOSISH_SYSTEM +static int read_lockfile (dotlock_t h, int *same_node); +#endif /*!HAVE_DOSISH_SYSTEM*/ + + + + +/* Entirely disable all locking. This function should be called + before any locking is done. It may be called right at startup of + the process as it only sets a global value. */ +void +disable_dotlock(void) +{ + never_lock = 1; +} + + + +/* Create a lockfile for a file name FILE_TO_LOCK and returns an + object of type dotlock_t which may be used later to actually acquire + the lock. A cleanup routine gets installed to cleanup left over + locks or other files used internally by the lock mechanism. + + Calling this function with NULL does only install the atexit + handler and may thus be used to assure that the cleanup is called + after all other atexit handlers. + + This function creates a lock file in the same directory as + FILE_TO_LOCK using that name and a suffix of ".lock". Note that on + POSIX systems a temporary file ".#lk.<hostname>.pid[.threadid] is + used. + + The function returns an new handle which needs to be released using + destroy_dotlock but gets also released at the termination of the + process. On error NULL is returned. + */ +dotlock_t +create_dotlock (const char *file_to_lock) +{ + static int initialized; + dotlock_t h; +#ifndef HAVE_DOSISH_SYSTEM + int fd = -1; + char pidstr[16]; + const char *nodename; + const char *dirpart; + int dirpartlen; + struct utsname utsbuf; + size_t tnamelen; +#endif + + if ( !initialized ) + { + atexit (dotlock_remove_lockfiles); + initialized = 1; + } + + if ( !file_to_lock ) + return NULL; /* Only initialization was requested. */ + + h = jnlib_calloc (1, sizeof *h); + if (!h) + return NULL; + + if (never_lock) + { + h->disable = 1; +#ifdef _REENTRANT + /* fixme: aquire mutex on all_lockfiles */ +#endif + h->next = all_lockfiles; + all_lockfiles = h; + return h; + } + +#ifndef HAVE_DOSISH_SYSTEM + /* + This is the POSIX version which uses a temporary file and the + link system call to make locking an atomic operation. + */ + + snprintf (pidstr, sizeof pidstr, "%10d\n", (int)getpid() ); + + /* Create a temporary file. */ + if ( uname ( &utsbuf ) ) + nodename = "unknown"; + else + nodename = utsbuf.nodename; + +#ifdef __riscos__ + { + char *iter = (char *) nodename; + for (; iter[0]; iter++) + if (iter[0] == '.') + iter[0] = '/'; + } +#endif /* __riscos__ */ + + if ( !(dirpart = strrchr (file_to_lock, DIRSEP_C)) ) + { + dirpart = EXTSEP_S; + dirpartlen = 1; + } + else + { + dirpartlen = dirpart - file_to_lock; + dirpart = file_to_lock; + } + +#ifdef _REENTRANT + /* fixme: aquire mutex on all_lockfiles */ +#endif + h->next = all_lockfiles; + all_lockfiles = h; + + tnamelen = dirpartlen + 6 + 30 + strlen(nodename) + 10; + h->tname = jnlib_malloc (tnamelen + 1); + if (!h->tname) + { + all_lockfiles = h->next; + jnlib_free (h); + return NULL; + } + h->nodename_len = strlen (nodename); + +#ifndef __riscos__ + snprintf (h->tname, tnamelen, "%.*s/.#lk%p.", dirpartlen, dirpart, h ); + h->nodename_off = strlen (h->tname); + snprintf (h->tname+h->nodename_off, tnamelen - h->nodename_off, + "%s.%d", nodename, (int)getpid ()); +#else /* __riscos__ */ + snprintf (h->tname, tnamelen, "%.*s.lk%p/", dirpartlen, dirpart, h ); + h->nodename_off = strlen (h->tname); + snprintf (h->tname+h->nodename_off, tnamelen - h->modename_off, + "%s/%d", nodename, (int)getpid () ); +#endif /* __riscos__ */ + + do + { + jnlib_set_errno (0); + fd = open (h->tname, O_WRONLY|O_CREAT|O_EXCL, + S_IRUSR|S_IRGRP|S_IROTH|S_IWUSR ); + } + while (fd == -1 && errno == EINTR); + + if ( fd == -1 ) + { + all_lockfiles = h->next; + log_error (_("failed to create temporary file `%s': %s\n"), + h->tname, strerror(errno)); + jnlib_free (h->tname); + jnlib_free (h); + return NULL; + } + if ( write (fd, pidstr, 11 ) != 11 ) + goto write_failed; + if ( write (fd, nodename, strlen (nodename) ) != strlen (nodename) ) + goto write_failed; + if ( write (fd, "\n", 1 ) != 1 ) + goto write_failed; + if ( close (fd) ) + goto write_failed; + +# ifdef _REENTRANT + /* release mutex */ +# endif + h->lockname = jnlib_malloc ( strlen (file_to_lock) + 6 ); + if (!h->lockname) + { + all_lockfiles = h->next; + unlink (h->tname); + jnlib_free (h->tname); + jnlib_free (h); + return NULL; + } + strcpy (stpcpy (h->lockname, file_to_lock), EXTSEP_S "lock"); + return h; + + write_failed: + all_lockfiles = h->next; +# ifdef _REENTRANT + /* fixme: release mutex */ +# endif + log_error ( _("error writing to `%s': %s\n"), h->tname, strerror(errno) ); + close (fd); + unlink (h->tname); + jnlib_free (h->tname); + jnlib_free (h); + return NULL; + +#else /* HAVE_DOSISH_SYSTEM */ + + /* The Windows version does not need a temporary file but uses the + plain lock file along with record locking. We create this file + here so that we later do only need to do the file locking. For + error reporting it is useful to keep the name of the file in the + handle. */ + h->next = all_lockfiles; + all_lockfiles = h; + + h->lockname = jnlib_malloc ( strlen (file_to_lock) + 6 ); + if (!h->lockname) + { + all_lockfiles = h->next; + jnlib_free (h); + return NULL; + } + strcpy (stpcpy(h->lockname, file_to_lock), EXTSEP_S "lock"); + + /* If would be nice if we would use the FILE_FLAG_DELETE_ON_CLOSE + along with FILE_SHARE_DELETE but that does not work due to a race + condition: Despite the OPEN_ALWAYS flag CreateFile may return an + error and we can't reliable create/open the lock file unless we + would wait here until it works - however there are other valid + reasons why a lock file can't be created and thus the process + would not stop as expected but spin til until Windows crashes. + Our solution is to keep the lock file open; that does not + harm. */ + { +#ifdef HAVE_W32CE_SYSTEM + wchar_t *wname = utf8_to_wchar (h->lockname); + + h->lockhd = INVALID_HANDLE_VALUE; + if (wname) + h->lockhd = CreateFile (wname, +#else + h->lockhd = CreateFile (h->lockname, +#endif + GENERIC_READ|GENERIC_WRITE, + FILE_SHARE_READ|FILE_SHARE_WRITE, + NULL, OPEN_ALWAYS, 0, NULL); +#ifdef HAVE_W32CE_SYSTEM + jnlib_free (wname); +#endif + } + if (h->lockhd == INVALID_HANDLE_VALUE) + { + log_error (_("can't create `%s': %s\n"), h->lockname, w32_strerror (-1)); + all_lockfiles = h->next; + jnlib_free (h->lockname); + jnlib_free (h); + return NULL; + } + return h; + +#endif /* HAVE_DOSISH_SYSTEM */ +} + + +/* Destroy the local handle H and release the lock. */ +void +destroy_dotlock (dotlock_t h) +{ + dotlock_t hprev, htmp; + + if ( !h ) + return; + + /* First remove the handle from our global list of all locks. */ + for (hprev=NULL, htmp=all_lockfiles; htmp; hprev=htmp, htmp=htmp->next) + if (htmp == h) + { + if (hprev) + hprev->next = htmp->next; + else + all_lockfiles = htmp->next; + h->next = NULL; + break; + } + + /* Then destroy the lock. */ + if (!h->disable) + { +#ifdef HAVE_DOSISH_SYSTEM + if (h->locked) + { + UnlockFile (h->lockhd, 0, 0, 1, 0); + } + CloseHandle (h->lockhd); +#else /* !HAVE_DOSISH_SYSTEM */ + if (h->locked && h->lockname) + unlink (h->lockname); + if (h->tname) + unlink (h->tname); + jnlib_free (h->tname); +#endif /* HAVE_DOSISH_SYSTEM */ + jnlib_free (h->lockname); + } + jnlib_free(h); +} + + +#ifndef HAVE_DOSISH_SYSTEM +static int +maybe_deadlock (dotlock_t h) +{ + dotlock_t r; + + for ( r=all_lockfiles; r; r = r->next ) + { + if ( r != h && r->locked ) + return 1; + } + return 0; +} +#endif /*!HAVE_DOSISH_SYSTEM*/ + + + +/* Do a lock on H. A TIMEOUT of 0 returns immediately, -1 waits + forever (hopefully not), other values are reserved (should then be + timeouts in milliseconds). Returns: 0 on success */ +int +make_dotlock (dotlock_t h, long timeout) +{ + int backoff = 0; +#ifndef HAVE_DOSISH_SYSTEM + int pid; + const char *maybe_dead=""; + int same_node; +#endif /*!HAVE_DOSISH_SYSTEM*/ + + if ( h->disable ) + return 0; /* Locks are completely disabled. Return success. */ + + if ( h->locked ) + { +#ifndef __riscos__ + log_debug ("Oops, `%s' is already locked\n", h->lockname); +#endif /* !__riscos__ */ + return 0; + } + + for (;;) + { +#ifndef HAVE_DOSISH_SYSTEM +# ifndef __riscos__ + if ( !link(h->tname, h->lockname) ) + { + /* fixme: better use stat to check the link count */ + h->locked = 1; + return 0; /* okay */ + } + if ( errno != EEXIST ) + { + log_error ( "lock not made: link() failed: %s\n", strerror(errno) ); + return -1; + } +# else /* __riscos__ */ + if ( !renamefile(h->tname, h->lockname) ) + { + h->locked = 1; + return 0; /* okay */ + } + if ( errno != EEXIST ) + { + log_error( "lock not made: rename() failed: %s\n", strerror(errno) ); + return -1; + } +# endif /* __riscos__ */ + + if ( (pid = read_lockfile (h, &same_node)) == -1 ) + { + if ( errno != ENOENT ) + { + log_info ("cannot read lockfile\n"); + return -1; + } + log_info( "lockfile disappeared\n"); + continue; + } + else if ( pid == getpid() && same_node ) + { + log_info( "Oops: lock already held by us\n"); + h->locked = 1; + return 0; /* okay */ + } + else if ( same_node && kill (pid, 0) && errno == ESRCH ) + { +# ifndef __riscos__ + log_info (_("removing stale lockfile (created by %d)\n"), pid ); + unlink (h->lockname); + continue; +# else /* __riscos__ */ + /* Under RISCOS we are *pretty* sure that the other task + is dead and therefore we remove the stale lock file. */ + maybe_dead = _(" - probably dead - removing lock"); + unlink(h->lockname); +# endif /* __riscos__ */ + } + + if ( timeout == -1 ) + { + /* Wait until lock has been released. */ + struct timeval tv; + + log_info (_("waiting for lock (held by %d%s) %s...\n"), + pid, maybe_dead, maybe_deadlock(h)? _("(deadlock?) "):""); + + + /* We can't use sleep, cause signals may be blocked. */ + tv.tv_sec = 1 + backoff; + tv.tv_usec = 0; + select(0, NULL, NULL, NULL, &tv); + if ( backoff < 10 ) + backoff++ ; + } + else + return -1; +#else /*HAVE_DOSISH_SYSTEM*/ + int w32err; + + if (LockFile (h->lockhd, 0, 0, 1, 0)) + { + h->locked = 1; + return 0; /* okay */ + } + w32err = GetLastError (); + if (w32err != ERROR_LOCK_VIOLATION) + { + log_error (_("lock `%s' not made: %s\n"), + h->lockname, w32_strerror (w32err)); + return -1; + } + + if ( timeout == -1 ) + { + /* Wait until lock has been released. */ + log_info (_("waiting for lock %s...\n"), h->lockname); + Sleep ((1 + backoff)*1000); + if ( backoff < 10 ) + backoff++ ; + } + else + return -1; +#endif /*HAVE_DOSISH_SYSTEM*/ + } + /*NOTREACHED*/ +} + + +/* Release a lock. Returns 0 on success. */ +int +release_dotlock (dotlock_t h) +{ +#ifndef HAVE_DOSISH_SYSTEM + int pid, same_node; +#endif + + /* To avoid atexit race conditions we first check whether there are + any locks left. It might happen that another atexit handler + tries to release the lock while the atexit handler of this module + already ran and thus H is undefined. */ + if (!all_lockfiles) + return 0; + + if ( h->disable ) + return 0; + + if ( !h->locked ) + { + log_debug("Oops, `%s' is not locked\n", h->lockname); + return 0; + } + +#ifdef HAVE_DOSISH_SYSTEM + if (!UnlockFile (h->lockhd, 0, 0, 1, 0)) + { + log_error ("release_dotlock: error removing lockfile `%s': %s\n", + h->lockname, w32_strerror (-1)); + return -1; + } +#else + + pid = read_lockfile (h, &same_node); + if ( pid == -1 ) + { + log_error( "release_dotlock: lockfile error\n"); + return -1; + } + if ( pid != getpid() || !same_node ) + { + log_error( "release_dotlock: not our lock (pid=%d)\n", pid); + return -1; + } + +#ifndef __riscos__ + if ( unlink( h->lockname ) ) + { + log_error ("release_dotlock: error removing lockfile `%s'\n", + h->lockname); + return -1; + } + /* Fixme: As an extra check we could check whether the link count is + now really at 1. */ +#else /* __riscos__ */ + if ( renamefile (h->lockname, h->tname) ) + { + log_error ("release_dotlock: error renaming lockfile `%s' to `%s'\n", + h->lockname, h->tname); + return -1; + } +#endif /* __riscos__ */ + +#endif /* !HAVE_DOSISH_SYSTEM */ + h->locked = 0; + return 0; +} + + +/* Read the lock file and return the pid, returns -1 on error. True + will be stored in the integer at address SAME_NODE if the lock file + has been created on the same node. */ +#ifndef HAVE_DOSISH_SYSTEM +static int +read_lockfile (dotlock_t h, int *same_node ) +{ + char buffer_space[10+1+70+1]; /* 70 is just an estimated value; node + name are usually shorter. */ + int fd; + int pid = -1; + char *buffer, *p; + size_t expected_len; + int res, nread; + + *same_node = 0; + expected_len = 10 + 1 + h->nodename_len + 1; + if ( expected_len >= sizeof buffer_space) + { + buffer = jnlib_malloc (expected_len); + if (!buffer) + return -1; + } + else + buffer = buffer_space; + + if ( (fd = open (h->lockname, O_RDONLY)) == -1 ) + { + int e = errno; + log_info ("error opening lockfile `%s': %s\n", + h->lockname, strerror(errno) ); + if (buffer != buffer_space) + jnlib_free (buffer); + jnlib_set_errno (e); /* Need to return ERRNO here. */ + return -1; + } + + p = buffer; + nread = 0; + do + { + res = read (fd, p, expected_len - nread); + if (res == -1 && errno == EINTR) + continue; + if (res < 0) + { + log_info ("error reading lockfile `%s'", h->lockname ); + close (fd); + if (buffer != buffer_space) + jnlib_free (buffer); + jnlib_set_errno (0); /* Do not return an inappropriate ERRNO. */ + return -1; + } + p += res; + nread += res; + } + while (res && nread != expected_len); + close(fd); + + if (nread < 11) + { + log_info ("invalid size of lockfile `%s'", h->lockname ); + if (buffer != buffer_space) + jnlib_free (buffer); + jnlib_set_errno (0); /* Better don't return an inappropriate ERRNO. */ + return -1; + } + + if (buffer[10] != '\n' + || (buffer[10] = 0, pid = atoi (buffer)) == -1 +#ifndef __riscos__ + || !pid +#else /* __riscos__ */ + || (!pid && riscos_getpid()) +#endif /* __riscos__ */ + ) + { + log_error ("invalid pid %d in lockfile `%s'", pid, h->lockname ); + if (buffer != buffer_space) + jnlib_free (buffer); + jnlib_set_errno (0); + return -1; + } + + if (nread == expected_len + && !memcmp (h->tname+h->nodename_off, buffer+11, h->nodename_len) + && buffer[11+h->nodename_len] == '\n') + *same_node = 1; + + if (buffer != buffer_space) + jnlib_free (buffer); + return pid; +} +#endif /* !HAVE_DOSISH_SYSTEM */ + + +/* Remove all lockfiles. This is usually called by the atexit handler + installed by this module but may also be called by other + termination handlers. */ +void +dotlock_remove_lockfiles (void) +{ + dotlock_t h, h2; + + h = all_lockfiles; + all_lockfiles = NULL; + + while ( h ) + { + h2 = h->next; + destroy_dotlock (h); + h = h2; + } +} + |