/* dotlock.c - dotfile locking * Copyright (C) 1998, 1999, 2000, 2001 Free Software Foundation, Inc. * * This file is part of GnuPG. * * GnuPG 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 2 of the License, or * (at your option) any later version. * * GnuPG 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 this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA */ #include #include #include #include #include #include #include #if !defined (HAVE_DOSISH_SYSTEM) #include #endif #include #ifndef _WIN32 #include #endif #include #include #include #include "types.h" #include "util.h" #include "memory.h" 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 */ }; static volatile DOTLOCK all_lockfiles; static int never_lock; static int read_lockfile( const char *name ); void disable_dotlock(void) { 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. * A cleanup routine gets installed to cleanup left over locks * or other files used together with the lockmechanism. * Althoug the function is called dotlock, this does not necessarily * mean that real lockfiles are used - the function may decide to * use fcntl locking. Calling the function with NULL only install * the atexit handler and maybe used to assure that the cleanup * is called after all other atexit handlers. * * Notes: This function creates a lock file in the same directory * as file_to_lock with the name "file_to_lock.lock" * A temporary file ".#lk..pid[.threadid] is used. * This function does nothing for Windoze. */ DOTLOCK create_dotlock( const char *file_to_lock ) { static int initialized; DOTLOCK h; int fd = -1; char pidstr[16]; #if !defined (HAVE_DOSISH_SYSTEM) struct utsname utsbuf; #endif const char *nodename; const char *dirpart; int dirpartlen; if( !initialized ) { atexit( remove_lockfiles ); initialized = 1; } if( !file_to_lock ) return NULL; h = m_alloc_clear( sizeof *h ); if( never_lock ) { h->disable = 1; #ifdef _REENTRANT /* fixme: aquire mutex on all_lockfiles */ #endif 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?) */ /* 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; h->tname = m_alloc( dirpartlen + 6+30+ strlen(nodename) + 11 ); #ifndef __riscos__ sprintf( h->tname, "%.*s/.#lk%p.%s.%d", dirpartlen, dirpart, h, nodename, (int)getpid() ); #else /* __riscos__ */ sprintf( h->tname, "%.*s.lk%p/%s/%d", dirpartlen, dirpart, h, nodename, (int)getpid() ); #endif /* __riscos__ */ do { 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)); m_free(h->tname); m_free(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); m_free(h->tname); m_free(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); m_free(h->tname); m_free(h); return NULL; } #ifdef _REENTRANT /* release mutex */ #endif #endif h->lockname = m_alloc( strlen(file_to_lock) + 6 ); strcpy(stpcpy(h->lockname, file_to_lock), EXTSEP_S "lock"); return h; } static int maybe_deadlock( DOTLOCK h ) { DOTLOCK r; for( r=all_lockfiles; r; r = r->next ) { if( r != h && r->locked ) return 1; } return 0; } /**************** * Do a lock on H. A TIMEOUT of 0 returns immediately, * -1 waits forever (hopefully not), other * values are timeouts in milliseconds. * Returns: 0 on success */ int make_dotlock( DOTLOCK h, long timeout ) { #if defined (HAVE_DOSISH_SYSTEM) return 0; #else int pid; const char *maybe_dead=""; int backoff=0; if( h->disable ) { return 0; } if( h->locked ) { #ifndef __riscos__ log_debug("oops, `%s' is already locked\n", h->lockname ); #endif /* !__riscos__ */ return 0; } 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; } 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( 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__ */ } 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 */ tv.tv_sec = 1 + backoff; tv.tv_usec = 0; select(0, NULL, NULL, NULL, &tv); if( backoff < 10 ) backoff++ ; } else return -1; } /*not reached */ #endif } /**************** * release a lock * Returns: 0 := success */ int release_dotlock( DOTLOCK h ) { #if defined (HAVE_DOSISH_SYSTEM) return 0; #else int pid; 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; #endif } /**************** * Read the lock file and return the pid, returns -1 on error. */ static int read_lockfile( const char *name ) { #if defined (HAVE_DOSISH_SYSTEM) return 0; #else int fd, pid; char pidstr[16]; 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( read(fd, pidstr, 10 ) != 10 ) { /* Read 10 digits w/o newline */ log_debug("error reading lockfile `%s'", name ); close(fd); errno = 0; return -1; } pidstr[10] = 0; /* terminate pid string */ close(fd); pid = atoi(pidstr); #ifndef __riscos__ if( !pid || pid == -1 ) { #else /* __riscos__ */ if( (!pid && riscos_getpid()) || pid == -1 ) { #endif /* __riscos__ */ log_error("invalid pid %d in lockfile `%s'", pid, name ); errno = 0; return -1; } return pid; #endif } void remove_lockfiles() { #if !defined (HAVE_DOSISH_SYSTEM) DOTLOCK h, h2; h = all_lockfiles; all_lockfiles = NULL; while( h ) { h2 = h->next; if( !h->disable ) { if( h->locked ) unlink( h->lockname ); unlink(h->tname); m_free(h->tname); m_free(h->lockname); } m_free(h); h = h2; } #endif }