1
0
mirror of git://git.gnupg.org/gnupg.git synced 2024-06-08 23:37:47 +02:00

Improve dotlocking.

Implement locking for W32.
This commit is contained in:
Werner Koch 2009-05-05 15:32:16 +00:00
parent 631a0de335
commit 418e61a824
4 changed files with 401 additions and 224 deletions

2
NEWS
View File

@ -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)
------------------------------------------------

View File

@ -1,12 +1,12 @@
2009-05-05 Werner Koch <wk@g10code.com>
* 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.

View File

@ -1,5 +1,8 @@
2009-05-05 Werner Koch <wk@g10code.com>
* 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 <dshaw@jabberwocky.com>

View File

@ -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 <ctype.h>
#include <errno.h>
#include <unistd.h>
#if !defined (HAVE_DOSISH_SYSTEM)
#include <sys/utsname.h>
#ifdef HAVE_DOSISH_SYSTEM
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
#else
# include <sys/utsname.h>
#endif
#include <sys/types.h>
#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()
{