diff --git a/NEWS b/NEWS index befd14812..e76bcdb08 100644 --- a/NEWS +++ b/NEWS @@ -4,6 +4,8 @@ Noteworthy changes in version 1.4.10 (unreleased) * The algorithm to compute the SIG_ID status has been changed to match the one from 2.0.10. + * Improved file locking. Implemented it for W32. + Noteworthy changes in version 1.4.9 (2008-03-26) ------------------------------------------------ diff --git a/g10/ChangeLog b/g10/ChangeLog index 52696c43c..7b5b5464d 100644 --- a/g10/ChangeLog +++ b/g10/ChangeLog @@ -1,12 +1,12 @@ 2009-05-05 Werner Koch - * keygen.c (output_control_s): s/create/creation/. + * keygen.c (read_parameter_file): Add keyword "Creation-Date". + (output_control_s): s/create/creation/. (enum para_name): Add pCREATIONDATE, pKEYCREATIONDATE. Remove pCREATETIME. (generate_keypair): Do not set old pCREATETIME. (parse_creation_string): New. (proc_parameter_file): Set pCREATIONDATE. - (read_parameter_file): Add keyword "Creation-Date". (do_generate_keypair): Remove arg TIMESTAMP. Set it using pKEYCREATIONDATE. (get_parameter_u32): Set a default pKEYCREATIONDATE. diff --git a/util/ChangeLog b/util/ChangeLog index f99f0a461..85d5a49fc 100644 --- a/util/ChangeLog +++ b/util/ChangeLog @@ -1,5 +1,8 @@ 2009-05-05 Werner Koch + * dotlock.c: Merged changes from GnuPG-2. Better detection of + stale lockfiles and actual locking support on W32. Fixes bug#1028. + * miscutil.c (isotime2seconds): New. 2009-04-05 David Shaw diff --git a/util/dotlock.c b/util/dotlock.c index 8369af730..ccbb6e89d 100644 --- a/util/dotlock.c +++ b/util/dotlock.c @@ -1,6 +1,6 @@ /* dotlock.c - dotfile locking * Copyright (C) 1998, 1999, 2000, 2001, 2004, - * 2005 Free Software Foundation, Inc. + * 2005, 2009 Free Software Foundation, Inc. * * This file is part of GnuPG. * @@ -25,8 +25,11 @@ #include #include #include -#if !defined (HAVE_DOSISH_SYSTEM) -#include +#ifdef HAVE_DOSISH_SYSTEM +# define WIN32_LEAN_AND_MEAN +# include +#else +# include #endif #include #ifndef _WIN32 @@ -38,25 +41,50 @@ #include "types.h" #include "util.h" #include "memory.h" +#include "i18n.h" + +/* The object describing a lock. */ struct dotlock_handle { struct dotlock_handle *next; char *tname; /* name of lockfile template */ char *lockname; /* name of the real lockfile */ int locked; /* lock status */ int disable; /* locking */ +#ifdef HAVE_DOSISH_SYSTEM + HANDLE lockhd; /* The W32 handle of the lock file. */ +#else + size_t nodename_off; /* Offset in TNAME to the nodename part. */ + size_t nodename_len; /* Length of the nodename part. */ +#endif }; +/* A list of of all lock handles. */ static volatile DOTLOCK all_lockfiles; + +/* If this has the value true all locking is disabled. */ static int never_lock; + +#ifdef _REENTRANT + /* fixme: acquire mutex on all_lockfiles */ +# define lock_all_lockfiles(h) do { } while (0) +# define unlock_all_lockfiles(h) do { } while (0) +#else +# define lock_all_lockfiles(h) do { } while (0) +# define unlock_all_lockfiles(h) do { } while (0) +#endif + + + void disable_dotlock(void) { - never_lock = 1; + never_lock = 1; } + /**************** * Create a lockfile with the given name and return an object of * type DOTLOCK which may be used later to actually do the lock. @@ -78,14 +106,16 @@ create_dotlock( const char *file_to_lock ) { static int initialized; DOTLOCK h; -#if !defined (HAVE_DOSISH_SYSTEM) +#ifndef HAVE_DOSISH_SYSTEM int fd = -1; char pidstr[16]; struct utsname utsbuf; const char *nodename; const char *dirpart; int dirpartlen; -#endif + size_t tnamelen; + int n; +#endif /*!HAVE_DOSISH_SYSTEM*/ if( !initialized ) { atexit( remove_lockfiles ); @@ -97,18 +127,15 @@ create_dotlock( const char *file_to_lock ) h = xmalloc_clear( sizeof *h ); if( never_lock ) { h->disable = 1; -#ifdef _REENTRANT - /* fixme: aquire mutex on all_lockfiles */ -#endif + lock_all_lockfiles (h); h->next = all_lockfiles; all_lockfiles = h; return h; } -#if !defined (HAVE_DOSISH_SYSTEM) - sprintf( pidstr, "%10d\n", (int)getpid() ); - /* fixme: add the hostname to the second line (FQDN or IP addr?) */ +#ifndef HAVE_DOSISH_SYSTEM + snprintf (pidstr, sizeof pidstr, "%10d\n", (int)getpid() ); /* create a temporary file */ if( uname( &utsbuf ) ) @@ -116,14 +143,14 @@ create_dotlock( const char *file_to_lock ) else nodename = utsbuf.nodename; -#ifdef __riscos__ +# ifdef __riscos__ { char *iter = (char *) nodename; for (; iter[0]; iter++) if (iter[0] == '.') iter[0] = '/'; } -#endif /* __riscos__ */ +# endif /* __riscos__ */ if( !(dirpart = strrchr( file_to_lock, DIRSEP_C )) ) { dirpart = EXTSEP_S; @@ -134,25 +161,27 @@ create_dotlock( const char *file_to_lock ) dirpart = file_to_lock; } -#ifdef _REENTRANT - /* fixme: aquire mutex on all_lockfiles */ -#endif + lock_all_lockfiles (h); h->next = all_lockfiles; all_lockfiles = h; - h->tname = xmalloc( dirpartlen + 6+30+ strlen(nodename) + 11 ); -#ifndef __riscos__ - sprintf( h->tname, "%.*s/.#lk%p.%s.%d", - dirpartlen, dirpart, (void *)h, nodename, (int)getpid() ); -#else /* __riscos__ */ - sprintf( h->tname, "%.*s.lk%p/%s/%d", - dirpartlen, dirpart, (void *)h, nodename, (int)getpid() ); -#endif /* __riscos__ */ + tnamelen = dirpartlen + 6+30+ strlen(nodename) + 10; + h->tname = xmalloc (tnamelen + 1); + h->nodename_len = strlen (nodename); + +# ifndef __riscos__ + snprintf (h->tname, tnamelen, "%.*s/.#lk%p.%n%s.%d", + dirpartlen, dirpart, (void *)h, &n, nodename, (int)getpid ()); +# else /* __riscos__ */ + snprintf (h->tname, tnamelen, "%.*s.lk%p/%n%s/%d", + dirpartlen, dirpart, (void *)h, &n, nodename, (int)getpid ()); +# endif /* __riscos__ */ + h->nodename_off = n; do { errno = 0; - fd = open( h->tname, O_WRONLY|O_CREAT|O_EXCL, - S_IRUSR|S_IRGRP|S_IROTH|S_IWUSR ); + 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; @@ -162,78 +191,110 @@ create_dotlock( const char *file_to_lock ) xfree(h); return NULL; } - if( write(fd, pidstr, 11 ) != 11 ) { - all_lockfiles = h->next; -#ifdef _REENTRANT - /* release mutex */ -#endif - log_fatal( "error writing to `%s': %s\n", h->tname, strerror(errno) ); - close(fd); - unlink(h->tname); - xfree(h->tname); - xfree(h); - return NULL; - } - if( close(fd) ) { - all_lockfiles = h->next; -#ifdef _REENTRANT - /* release mutex */ -#endif - log_error( "error closing `%s': %s\n", h->tname, strerror(errno)); - unlink(h->tname); - xfree(h->tname); - xfree(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 -#endif + unlock_all_lockfiles (h); h->lockname = xmalloc( strlen(file_to_lock) + 6 ); strcpy(stpcpy(h->lockname, file_to_lock), EXTSEP_S "lock"); return h; + + write_failed: + all_lockfiles = h->next; + unlock_all_lockfiles (h); + log_error ("error writing to `%s': %s\n", h->tname, strerror(errno)); + close (fd); + unlink (h->tname); + xfree (h->tname); + xfree (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 = xmalloc ( strlen (file_to_lock) + 6 ); + 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 until Windows crashes. Our + solution is to keep the lock file open; that does not harm. */ + h->lockhd = CreateFile (h->lockname, + GENERIC_READ|GENERIC_WRITE, + FILE_SHARE_READ|FILE_SHARE_WRITE, + NULL, OPEN_ALWAYS, 0, NULL); + if (h->lockhd == INVALID_HANDLE_VALUE) + { + log_error (_("can't create `%s': %s\n"), h->lockname, w32_strerror (-1)); + all_lockfiles = h->next; + xfree (h->lockname); + xfree (h); + return NULL; + } + return h; + +#endif /* HAVE_DOSISH_SYSTEM */ } void destroy_dotlock ( DOTLOCK h ) { -#if !defined (HAVE_DOSISH_SYSTEM) - if ( h ) + DOTLOCK 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) { - DOTLOCK hprev, htmp; - - /* 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; - } - - /* Second destroy the lock. */ - if (!h->disable) - { - if (h->locked && h->lockname) - unlink (h->lockname); - if (h->tname) - unlink (h->tname); - xfree (h->tname); - xfree (h->lockname); - } - xfree(h); - + if (hprev) + hprev->next = htmp->next; + else + all_lockfiles = htmp->next; + h->next = NULL; + break; } + + /* Second destroy the lock. */ + if (!h->disable) + { +#ifdef HAVE_DOSISH_SYSTEM + if (h->locked) + UnlockFile (h->lockhd, 0, 0, 1, 0); + CloseHandle (h->lockhd); +#else + if (h->locked && h->lockname) + unlink (h->lockname); + if (h->tname) + unlink (h->tname); + xfree (h->tname); #endif + xfree (h->lockname); + } + xfree(h); } -#ifndef HAVE_DOSISH_SYSTEM +#ifndef HAVE_DOSISH_SYSTEM static int maybe_deadlock( DOTLOCK h ) { @@ -245,44 +306,101 @@ maybe_deadlock( DOTLOCK h ) } return 0; } +#endif /* !HAVE_DOSISH_SYSTEM */ -/**************** - * Read the lock file and return the pid, returns -1 on error. - */ + +/* 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( const char *name ) +read_lockfile (DOTLOCK h, int *same_node ) { - int fd, pid; - char pidstr[16]; + char buffer_space[10+1+70+1]; /* 70 is just an estimated value; node + names 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 = xmalloc (expected_len); + else + buffer = buffer_space; - if( (fd = open(name, O_RDONLY)) == -1 ) { - int e = errno; - log_debug("error opening lockfile `%s': %s\n", name, strerror(errno) ); - errno = e; - return -1; + 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) + xfree (buffer); + errno = e; /* Need to return ERRNO here. */ + return -1; } - if( read(fd, pidstr, 10 ) != 10 ) { /* Read 10 digits w/o newline */ - log_debug("error reading lockfile `%s'", name ); - close(fd); - errno = 0; - 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) + xfree (buffer); + errno = 0; /* Do not return an inappropriate ERRNO. */ + return -1; + } + p += res; + nread += res; } - pidstr[10] = 0; /* terminate pid string */ - close(fd); - pid = atoi(pidstr); + while (res && nread != expected_len); + close (fd); + + if (nread < 11) + { + log_info ("invalid size of lockfile `%s'", h->lockname ); + if (buffer != buffer_space) + xfree (buffer); + errno = 0; /* Better don't return an inappropriate ERRNO. */ + return -1; + } + + if (buffer[10] != '\n' + || (buffer[10] = 0, pid = atoi (buffer)) == -1 #ifndef __riscos__ - if( !pid || pid == -1 ) { + || !pid #else /* __riscos__ */ - if( (!pid && riscos_getpid()) || pid == -1 ) { + || (!pid && riscos_getpid()) #endif /* __riscos__ */ - log_error("invalid pid %d in lockfile `%s'", pid, name ); - errno = 0; - return -1; + ) + { + log_error ("invalid pid %d in lockfile `%s'", pid, h->lockname ); + if (buffer != buffer_space) + xfree (buffer); + errno = 0; + return -1; } - return pid; + + 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) + xfree (buffer); + return pid; } #endif /* !HAVE_DOSISH_SYSTEM */ + /**************** * Do a lock on H. A TIMEOUT of 0 returns immediately, * -1 waits forever (hopefully not), other @@ -292,90 +410,129 @@ read_lockfile( const char *name ) int make_dotlock( DOTLOCK h, long timeout ) { -#if defined (HAVE_DOSISH_SYSTEM) + int backoff=0; +#ifndef HAVE_DOSISH_SYSTEM + int pid; + const char *maybe_dead=""; + int same_node; +#endif + + if (h->disable) return 0; -#else - int pid; - const char *maybe_dead=""; - int backoff=0; - if( h->disable ) { - return 0; - } - - if( h->locked ) { + if (h->locked) + { #ifndef __riscos__ - log_debug("oops, `%s' is already locked\n", h->lockname ); + log_debug ("oops, `%s' is already locked\n", h->lockname); #endif /* !__riscos__ */ - return 0; + 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 ( !riscos_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__ */ - for(;;) { -#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( !riscos_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->lockname)) == -1 ) { - if( errno != ENOENT ) { - log_info("cannot read lockfile\n"); - return -1; + 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; + log_info ("lockfile disappeared\n"); + continue; } - else if( pid == getpid() ) { - log_info( "Oops: lock already held by us\n"); - h->locked = 1; - return 0; /* okay */ + else if (pid == getpid ()) + { + log_info ("Oops: lock already held by us\n"); + h->locked = 1; + return 0; /* okay */ } - else if( kill(pid, 0) && errno == ESRCH ) { -#ifndef __riscos__ - maybe_dead = " - probably dead"; -#if 0 /* we should not do this without checking the permissions */ - /* and the hostname */ - log_info( "removing stale lockfile (created by %d)", pid ); -#endif -#else /* __riscos__ */ - /* we are *pretty* sure that the other task is dead and therefore - we remove the other lock file */ - maybe_dead = " - probably dead - removing lock"; - unlink(h->lockname); -#endif /* __riscos__ */ + 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__ */ + /* We are *pretty* sure that the other task is dead and + therefore we remove the other lock file. */ + maybe_dead = " - probably dead - removing lock"; + unlink (h->lockname); +# endif /* __riscos__ */ } - if( timeout == -1 ) { - struct timeval tv; - log_info( "waiting for lock (held by %d%s) %s...\n", - pid, maybe_dead, maybe_deadlock(h)? "(deadlock?) ":""); + + if (timeout == -1) + { + struct timeval tv; + + log_info ("waiting for lock (held by %d%s) %s...\n", + pid, maybe_dead, maybe_deadlock(h)? "(deadlock?) ":""); - /* can't use sleep, cause signals may be blocked */ + /* We can't use sleep, because signals may be blocked. */ tv.tv_sec = 1 + backoff; tv.tv_usec = 0; - select(0, NULL, NULL, NULL, &tv); - if( backoff < 10 ) - backoff++ ; + select (0, NULL, NULL, NULL, &tv); + if (backoff < 10) + backoff++ ; } - else - return -1; + 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*/ } - /*not reached */ -#endif + /*NOTREACHED */ } @@ -384,56 +541,71 @@ make_dotlock( DOTLOCK h, long timeout ) * Returns: 0 := success */ int -release_dotlock( DOTLOCK h ) +release_dotlock (DOTLOCK h) { -#if defined (HAVE_DOSISH_SYSTEM) - return 0; -#else - int pid; - - /* 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; - } - - pid = read_lockfile( h->lockname ); - if( pid == -1 ) { - log_error( "release_dotlock: lockfile error\n"); - return -1; - } - if( pid != getpid() ) { - 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'", - h->lockname); - return -1; - } -#else /* __riscos__ */ - if( riscos_renamefile(h->lockname, h->tname) ) { - log_error( "release_dotlock: error renaming lockfile `%s' to `%s'", - h->lockname, h->tname); - return -1; - } -#endif /* __riscos__ */ - /* fixme: check that the link count is now 1 */ - h->locked = 0; - return 0; +#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; + } + +#ifndef HAVE_DOSISH_SYSTEM + 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'", + h->lockname); + return -1; + } +# else /* __riscos__ */ + if( riscos_renamefile(h->lockname, h->tname) ) + { + log_error( "release_dotlock: error renaming lockfile `%s' to `%s'", + h->lockname, h->tname); + return -1; + } +# endif /* __riscos__ */ + +#else /*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; + } +#endif /*HAVE_DOSISH_SYSTEM*/ + + h->locked = 0; + return 0; } + void remove_lockfiles() {