a50a0ee99b74c567b2965a5d1027db832613a66a
[gnupg.git] / jnlib / dotlock.c
1 /* dotlock.c - dotfile locking
2  *      Copyright (C) 1998,2000,2001,2003 Free Software Foundation, Inc.
3  *
4  * This file is part of GnuPG.
5  *
6  * GnuPG is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * GnuPG is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
19  */
20
21 #include <config.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <errno.h>
25 #include <ctype.h>
26 #include <errno.h>
27 #include <unistd.h>
28 #ifndef  HAVE_DOSISH_SYSTEM
29 #include <sys/utsname.h>
30 #endif
31 #include <sys/types.h>
32 #include <sys/time.h>
33 #include <sys/stat.h>
34 #include <fcntl.h>
35 #include <signal.h>
36
37 #include "libjnlib-config.h"
38 #include "dotlock.h"
39
40 #if !defined(DIRSEP_C) && !defined(EXTSEP_C) \
41     && !defined(DIRSEP_S) && !defined(EXTSEP_S)
42 #ifdef HAVE_DOSISH_SYSTEM
43 #define DIRSEP_C '\\'
44 #define EXTSEP_C '.'
45 #define DIRSEP_S "\\"
46 #define EXTSEP_S "."
47 #else
48 #define DIRSEP_C '/'
49 #define EXTSEP_C '.'
50 #define DIRSEP_S "/"
51 #define EXTSEP_S "."
52 #endif
53 #endif
54
55
56 struct dotlock_handle {
57     struct dotlock_handle *next;
58     char *tname;    /* name of lockfile template */
59     char *lockname; /* name of the real lockfile */
60     int locked;     /* lock status */
61     int disable;    /* locking */
62 };
63
64
65 static volatile DOTLOCK all_lockfiles;
66 static int never_lock;
67
68 static int read_lockfile( const char *name );
69
70 void
71 disable_dotlock(void)
72 {
73     never_lock = 1;
74 }
75
76 /****************
77  * Create a lockfile with the given name and return an object of
78  * type DOTLOCK which may be used later to actually do the lock.
79  * A cleanup routine gets installed to cleanup left over locks
80  * or other files used together with the lockmechanism.
81  * Althoug the function is called dotlock, this does not necessarily
82  * mean that real lockfiles are used - the function may decide to
83  * use fcntl locking.  Calling the function with NULL only install
84  * the atexit handler and maybe used to assure that the cleanup
85  * is called after all other atexit handlers.
86  *
87  * Notes: This function creates a lock file in the same directory
88  *        as file_to_lock with the name "file_to_lock.lock"
89  *        A temporary file ".#lk.<hostname>.pid[.threadid] is used.
90  *        This function does nothing for Windoze.
91  */
92 DOTLOCK
93 create_dotlock( const char *file_to_lock )
94 {
95     static int initialized;
96     DOTLOCK h;
97     int  fd = -1;
98     char pidstr[16];
99   #ifndef  HAVE_DOSISH_SYSTEM
100     struct utsname utsbuf;
101   #endif
102     const char *nodename;
103     const char *dirpart;
104     int dirpartlen;
105
106     if( !initialized ) {
107         atexit( dotlock_remove_lockfiles );
108         initialized = 1;
109     }
110     if( !file_to_lock )
111         return NULL;
112
113     h = jnlib_xcalloc( 1, sizeof *h );
114     if( never_lock ) {
115         h->disable = 1;
116 #ifdef _REENTRANT
117         /* fixme: aquire mutex on all_lockfiles */
118 #endif
119         h->next = all_lockfiles;
120         all_lockfiles = h;
121         return h;
122     }
123
124 #ifndef HAVE_DOSISH_SYSTEM
125     sprintf( pidstr, "%10d\n", (int)getpid() );
126     /* fixme: add the hostname to the second line (FQDN or IP addr?) */
127
128     /* create a temporary file */
129     if( uname( &utsbuf ) )
130         nodename = "unknown";
131     else
132         nodename = utsbuf.nodename;
133
134 #ifdef __riscos__
135     {
136         char *iter = (char *) nodename;
137         for (; iter[0]; iter++)
138             if (iter[0] == '.')
139                 iter[0] = '/';
140     }
141 #endif /* __riscos__ */
142
143     if( !(dirpart = strrchr( file_to_lock, DIRSEP_C )) ) {
144         dirpart = EXTSEP_S;
145         dirpartlen = 1;
146     }
147     else {
148         dirpartlen = dirpart - file_to_lock;
149         dirpart = file_to_lock;
150     }
151
152   #ifdef _REENTRANT
153     /* fixme: aquire mutex on all_lockfiles */
154   #endif
155     h->next = all_lockfiles;
156     all_lockfiles = h;
157
158     h->tname = jnlib_xmalloc( dirpartlen + 6+30+ strlen(nodename) + 11 );
159 #ifndef __riscos__
160      sprintf( h->tname, "%.*s/.#lk%p.%s.%d",
161              dirpartlen, dirpart, h, nodename, (int)getpid() );
162 #else /* __riscos__ */
163     sprintf( h->tname, "%.*s.lk%p/%s/%d",
164              dirpartlen, dirpart, h, nodename, (int)getpid() );
165 #endif /* __riscos__ */
166
167     do {
168         errno = 0;
169         fd = open( h->tname, O_WRONLY|O_CREAT|O_EXCL,
170                           S_IRUSR|S_IRGRP|S_IROTH|S_IWUSR );
171     } while( fd == -1 && errno == EINTR );
172     if( fd == -1 ) {
173         all_lockfiles = h->next;
174         log_error( "failed to create temporary file `%s': %s\n",
175                                             h->tname, strerror(errno));
176         jnlib_free(h->tname);
177         jnlib_free(h);
178         return NULL;
179     }
180     if( write(fd, pidstr, 11 ) != 11 ) {
181         all_lockfiles = h->next;
182       #ifdef _REENTRANT
183         /* release mutex */
184       #endif
185         log_fatal( "error writing to `%s': %s\n", h->tname, strerror(errno) );
186         close(fd);
187         unlink(h->tname);
188         jnlib_free(h->tname);
189         jnlib_free(h);
190         return NULL;
191     }
192     if( close(fd) ) {
193         all_lockfiles = h->next;
194       #ifdef _REENTRANT
195         /* release mutex */
196       #endif
197         log_fatal( "error writing to `%s': %s\n", h->tname, strerror(errno) );
198         close(fd);
199         unlink(h->tname);
200         jnlib_free(h->tname);
201         jnlib_free(h);
202         return NULL;
203     }
204
205   #ifdef _REENTRANT
206     /* release mutex */
207   #endif
208 #endif /* !HAVE_DOSISH_SYSTEM */
209     h->lockname = jnlib_xmalloc( strlen(file_to_lock) + 6 );
210     strcpy(stpcpy(h->lockname, file_to_lock), EXTSEP_S "lock");
211     return h;
212 }
213
214 static int
215 maybe_deadlock( DOTLOCK h )
216 {
217     DOTLOCK r;
218
219     for( r=all_lockfiles; r; r = r->next ) {
220         if( r != h && r->locked )
221             return 1;
222     }
223     return 0;
224 }
225
226 /****************
227  * Do a lock on H. A TIMEOUT of 0 returns immediately,
228  * -1 waits forever (hopefully not), other
229  * values are timeouts in milliseconds.
230  * Returns: 0 on success
231  */
232 int
233 make_dotlock( DOTLOCK h, long timeout )
234 {
235 #ifdef HAVE_DOSISH_SYSTEM
236     return 0;
237 #else
238     int  pid;
239     const char *maybe_dead="";
240     int backoff=0;
241
242     if( h->disable ) {
243         return 0;
244     }
245
246     if( h->locked ) {
247 #ifndef __riscos__
248         log_debug("oops, `%s' is already locked\n", h->lockname );
249 #endif /* !__riscos__ */
250         return 0;
251     }
252
253     for(;;) {
254 #ifndef __riscos__
255         if( !link(h->tname, h->lockname) ) {
256             /* fixme: better use stat to check the link count */
257             h->locked = 1;
258             return 0; /* okay */
259         }
260         if( errno != EEXIST ) {
261             log_error( "lock not made: link() failed: %s\n", strerror(errno) );
262             return -1;
263         }
264 #else /* __riscos__ */
265         if( !renamefile(h->tname, h->lockname) ) {
266             h->locked = 1;
267             return 0; /* okay */
268         }
269         if( errno != EEXIST ) {
270             log_error( "lock not made: rename() failed: %s\n", strerror(errno) );
271             return -1;
272         }
273 #endif /* __riscos__ */
274         if( (pid = read_lockfile(h->lockname)) == -1 ) {
275             if( errno != ENOENT ) {
276                 log_info("cannot read lockfile\n");
277                 return -1;
278             }
279             log_info( "lockfile disappeared\n");
280             continue;
281         }
282         else if( pid == getpid() ) {
283             log_info( "Oops: lock already held by us\n");
284             h->locked = 1;
285             return 0; /* okay */
286         }
287         else if( kill(pid, 0) && errno == ESRCH ) {
288 #ifndef __riscos__
289             maybe_dead = " - probably dead";
290 #if 0 /* we should not do this without checking the permissions */
291                /* and the hostname */
292             log_info( "removing stale lockfile (created by %d)", pid );
293 #endif
294 #else /* __riscos__ */
295             /* we are *pretty* sure that the other task is dead and therefore
296                we remove the other lock file */
297             maybe_dead = " - probably dead - removing lock";
298             unlink(h->lockname);
299 #endif /* __riscos__ */
300         }
301         if( timeout == -1 ) {
302             struct timeval tv;
303             log_info( "waiting for lock (held by %d%s) %s...\n",
304                       pid, maybe_dead, maybe_deadlock(h)? "(deadlock?) ":"");
305
306
307             /* can't use sleep, cause signals may be blocked */
308             tv.tv_sec = 1 + backoff;
309             tv.tv_usec = 0;
310             select(0, NULL, NULL, NULL, &tv);
311             if( backoff < 10 )
312                 backoff++ ;
313         }
314         else
315             return -1;
316     }
317     /*not reached */
318 #endif /* !HAVE_DOSISH_SYSTEM */
319 }
320
321
322 /****************
323  * release a lock
324  * Returns: 0 := success
325  */
326 int
327 release_dotlock( DOTLOCK h )
328 {
329 #ifdef HAVE_DOSISH_SYSTEM
330     return 0;
331 #else
332     int pid;
333
334     if( h->disable ) {
335         return 0;
336     }
337
338     if( !h->locked ) {
339         log_debug("oops, `%s' is not locked\n", h->lockname );
340         return 0;
341     }
342
343     pid = read_lockfile( h->lockname );
344     if( pid == -1 ) {
345         log_error( "release_dotlock: lockfile error\n");
346         return -1;
347     }
348     if( pid != getpid() ) {
349         log_error( "release_dotlock: not our lock (pid=%d)\n", pid);
350         return -1;
351     }
352 #ifndef __riscos__
353     if( unlink( h->lockname ) ) {
354         log_error( "release_dotlock: error removing lockfile `%s'",
355                                                         h->lockname);
356         return -1;
357     }
358 #else /* __riscos__ */
359     if( renamefile(h->lockname, h->tname) ) {
360         log_error( "release_dotlock: error renaming lockfile `%s' to `%s'",
361                                                         h->lockname, h->tname);
362         return -1;
363     }
364 #endif /* __riscos__ */
365     /* fixme: check that the link count is now 1 */
366     h->locked = 0;
367     return 0;
368 #endif /* !HAVE_DOSISH_SYSTEM */
369 }
370
371
372 /****************
373  * Read the lock file and return the pid, returns -1 on error.
374  */
375 static int
376 read_lockfile( const char *name )
377 {
378 #ifdef HAVE_DOSISH_SYSTEM
379     return 0;
380 #else
381     int fd, pid;
382     char pidstr[16];
383
384     if( (fd = open(name, O_RDONLY)) == -1 ) {
385         int e = errno;
386         log_debug("error opening lockfile `%s': %s\n", name, strerror(errno) );
387         errno = e;
388         return -1;
389     }
390     if( read(fd, pidstr, 10 ) != 10 ) {  /* Read 10 digits w/o newline */
391         log_debug("error reading lockfile `%s'", name );
392         close(fd);
393         errno = 0;
394         return -1;
395     }
396     pidstr[10] = 0;  /* terminate pid string */
397     close(fd);
398     pid = atoi(pidstr);
399 #ifndef __riscos__
400     if( !pid || pid == -1 ) {
401 #else /* __riscos__ */
402     if( (!pid && riscos_getpid()) || pid == -1 ) {
403 #endif /* __riscos__ */
404         log_error("invalid pid %d in lockfile `%s'", pid, name );
405         errno = 0;
406         return -1;
407     }
408     return pid;
409 #endif
410 }
411
412
413 void
414 dotlock_remove_lockfiles()
415 {
416 #ifndef HAVE_DOSISH_SYSTEM
417     DOTLOCK h, h2;
418
419     h = all_lockfiles;
420     all_lockfiles = NULL;
421
422     while( h ) {
423         h2 = h->next;
424         if (!h->disable ) {
425           if( h->locked )
426             unlink( h->lockname );
427           unlink(h->tname);
428           jnlib_free(h->tname);
429           jnlib_free(h->lockname);
430         }
431         jnlib_free(h);
432         h = h2;
433     }
434 #endif
435 }
436