Fix for bug 537
[gnupg.git] / jnlib / dotlock.c
1 /* dotlock.c - dotfile locking
2  * Copyright (C) 1998, 2000, 2001, 2003, 2004, 
3  *               2005, 2006 Free Software Foundation, Inc.
4  *
5  * This file is part of JNLIB.
6  *
7  * JNLIB is free software; you can redistribute it and/or modify it
8  * under the terms of the GNU Lesser General Public License as
9  * published by the Free Software Foundation; either version 2.1 of
10  * the License, or (at your option) any later version.
11  *
12  * JNLIB is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
20  * 02110-1301, USA.
21  */
22
23 #include <config.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <errno.h>
27 #include <ctype.h>
28 #include <errno.h>
29 #include <unistd.h>
30 #ifndef  HAVE_DOSISH_SYSTEM
31 #include <sys/utsname.h>
32 #endif
33 #include <sys/types.h>
34 #include <sys/time.h>
35 #include <sys/stat.h>
36 #include <fcntl.h>
37 #include <signal.h>
38
39 #include "libjnlib-config.h"
40 #include "dotlock.h"
41
42 #if !defined(DIRSEP_C) && !defined(EXTSEP_C) \
43     && !defined(DIRSEP_S) && !defined(EXTSEP_S)
44 #ifdef HAVE_DOSISH_SYSTEM
45 #define DIRSEP_C '\\'
46 #define EXTSEP_C '.'
47 #define DIRSEP_S "\\"
48 #define EXTSEP_S "."
49 #else
50 #define DIRSEP_C '/'
51 #define EXTSEP_C '.'
52 #define DIRSEP_S "/"
53 #define EXTSEP_S "."
54 #endif
55 #endif
56
57
58 struct dotlock_handle 
59 {
60   struct dotlock_handle *next;
61   char *tname;    /* Name of lockfile template.  */
62   size_t nodename_off; /* Offset in TNAME of the nodename part. */
63   size_t nodename_len; /* Length of the nodename part. */
64   char *lockname; /* Name of the real lockfile.  */
65   int locked;     /* Lock status.  */
66   int disable;    /* When true, locking is disabled.  */
67 };
68
69
70 static volatile DOTLOCK all_lockfiles;
71 static int never_lock;
72
73 static int read_lockfile (DOTLOCK h, int *same_node);
74
75 void
76 disable_dotlock(void)
77 {
78   never_lock = 1;
79 }
80
81 /****************
82  * Create a lockfile with the given name and return an object of
83  * type DOTLOCK which may be used later to actually do the lock.
84  * A cleanup routine gets installed to cleanup left over locks
85  * or other files used together with the lock mechanism.
86  * Although the function is called dotlock, this does not necessarily
87  * mean that real lockfiles are used - the function may decide to
88  * use fcntl locking.  Calling the function with NULL only install
89  * the atexit handler and maybe used to assure that the cleanup
90  * is called after all other atexit handlers.
91  *
92  * Notes: This function creates a lock file in the same directory
93  *        as file_to_lock with the name "file_to_lock.lock"
94  *        A temporary file ".#lk.<hostname>.pid[.threadid] is used.
95  *        This function does nothing for Windoze.
96  */
97 DOTLOCK
98 create_dotlock( const char *file_to_lock )
99 {
100   static int initialized;
101   DOTLOCK h;
102   int  fd = -1;
103   char pidstr[16];
104   const char *nodename;
105   const char *dirpart;
106   int dirpartlen;
107 #ifndef  HAVE_DOSISH_SYSTEM
108   struct utsname utsbuf;
109 #endif
110
111   if ( !initialized )
112     {
113       atexit( dotlock_remove_lockfiles );
114       initialized = 1;
115     }
116   if ( !file_to_lock )
117     return NULL;  /* Only initialization was requested.  */
118
119   h = jnlib_xcalloc ( 1, sizeof *h );
120   if( never_lock )
121     {
122       h->disable = 1;
123 #ifdef _REENTRANT
124       /* fixme: aquire mutex on all_lockfiles */
125 #endif
126       h->next = all_lockfiles;
127       all_lockfiles = h;
128       return h;
129     }
130
131 #ifndef HAVE_DOSISH_SYSTEM
132   sprintf (pidstr, "%10d\n", (int)getpid() );
133   /* fixme: add the hostname to the second line (FQDN or IP addr?) */
134
135   /* Create a temporary file. */
136   if ( uname ( &utsbuf ) )
137     nodename = "unknown";
138   else
139     nodename = utsbuf.nodename;
140   
141 #ifdef __riscos__
142   {
143     char *iter = (char *) nodename;
144     for (; iter[0]; iter++)
145       if (iter[0] == '.')
146         iter[0] = '/';
147   }
148 #endif /* __riscos__ */
149
150   if ( !(dirpart = strrchr ( file_to_lock, DIRSEP_C )) )
151     {
152       dirpart = EXTSEP_S;
153       dirpartlen = 1;
154     }
155   else
156     {
157       dirpartlen = dirpart - file_to_lock;
158       dirpart = file_to_lock;
159     }
160
161 #ifdef _REENTRANT
162     /* fixme: aquire mutex on all_lockfiles */
163 #endif
164   h->next = all_lockfiles;
165   all_lockfiles = h;
166
167   h->tname = jnlib_xmalloc ( dirpartlen + 6+30+ strlen(nodename) + 11 );
168   h->nodename_len = strlen (nodename);
169 #ifndef __riscos__
170   sprintf (h->tname, "%.*s/.#lk%p.", dirpartlen, dirpart, h );
171   h->nodename_off = strlen (h->tname);
172   sprintf (h->tname+h->nodename_off, "%s.%d", nodename, (int)getpid ());
173 #else /* __riscos__ */
174   sprintf (h->tname, "%.*s.lk%p/", dirpartlen, dirpart, h );
175   h->nodename_off = strlen (h->tname);
176   sprintf (h->tname+h->nodename_off, "%s/%d", nodename, (int)getpid () );
177 #endif /* __riscos__ */
178
179   do 
180     {
181       errno = 0;
182       fd = open (h->tname, O_WRONLY|O_CREAT|O_EXCL,
183                  S_IRUSR|S_IRGRP|S_IROTH|S_IWUSR );
184     } 
185   while (fd == -1 && errno == EINTR);
186
187   if ( fd == -1 ) 
188     {
189       all_lockfiles = h->next;
190       log_error ( "failed to create temporary file `%s': %s\n",
191                   h->tname, strerror(errno));
192       jnlib_free(h->tname);
193       jnlib_free(h);
194       return NULL;
195     }
196   if ( write (fd, pidstr, 11 ) != 11 )
197     goto write_failed;
198   if ( write (fd, nodename, strlen (nodename) ) != strlen (nodename) )
199     goto write_failed;
200   if ( write (fd, "\n", 1 ) != 1 )
201     goto write_failed;
202   if ( close (fd) )
203     goto write_failed;
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  write_failed:
213   all_lockfiles = h->next;
214 # ifdef _REENTRANT
215   /* fixme: release mutex */
216 # endif
217   log_error ( "error writing to `%s': %s\n", h->tname, strerror(errno) );
218   close(fd);
219   unlink(h->tname);
220   jnlib_free(h->tname);
221   jnlib_free(h);
222   return NULL;
223 }
224
225
226 void
227 destroy_dotlock ( DOTLOCK h )
228 {
229 #ifndef HAVE_DOSISH_SYSTEM
230   if ( h )
231     {
232       DOTLOCK hprev, htmp;
233       
234       /* First remove the handle from our global list of all locks. */
235       for (hprev=NULL, htmp=all_lockfiles; htmp; hprev=htmp, htmp=htmp->next)
236         if (htmp == h)
237           {
238             if (hprev)
239               hprev->next = htmp->next;
240             else
241               all_lockfiles = htmp->next;
242             h->next = NULL;
243             break;
244           }
245       
246       /* Second destroy the lock. */
247       if (!h->disable)
248         {
249           if (h->locked && h->lockname)
250             unlink (h->lockname);
251           if (h->tname)
252               unlink (h->tname);
253           jnlib_free (h->tname);
254           jnlib_free (h->lockname);
255         }
256       jnlib_free(h);
257     }
258 #endif /*!HAVE_DOSISH_SYSTEM*/
259 }
260
261
262
263 static int
264 maybe_deadlock( DOTLOCK h )
265 {
266   DOTLOCK r;
267
268   for ( r=all_lockfiles; r; r = r->next )
269     {
270       if ( r != h && r->locked )
271         return 1;
272     }
273   return 0;
274 }
275
276 /****************
277  * Do a lock on H. A TIMEOUT of 0 returns immediately, -1 waits
278  * forever (hopefully not), other values are reserved (should then be
279  * timeouts in milliseconds).  Returns: 0 on success
280  */
281 int
282 make_dotlock( DOTLOCK h, long timeout )
283 {
284 #ifdef HAVE_DOSISH_SYSTEM
285   return 0;
286 #else
287   int  pid;
288   const char *maybe_dead="";
289   int backoff=0;
290   int same_node;
291
292   if ( h->disable )
293     return 0; /* Locks are completely disabled.  Return success. */
294
295   if ( h->locked ) 
296     {
297 #ifndef __riscos__
298       log_debug("oops, `%s' is already locked\n", h->lockname );
299 #endif /* !__riscos__ */
300       return 0;
301     }
302
303   for(;;)
304     {
305 #ifndef __riscos__
306       if ( !link(h->tname, h->lockname) )
307         {
308           /* fixme: better use stat to check the link count */
309           h->locked = 1;
310           return 0; /* okay */
311         }
312       if ( errno != EEXIST )
313         {
314           log_error( "lock not made: link() failed: %s\n", strerror(errno) );
315           return -1;
316         }
317 #else /* __riscos__ */
318       if ( !renamefile(h->tname, h->lockname) ) 
319         {
320           h->locked = 1;
321           return 0; /* okay */
322         }
323       if ( errno != EEXIST ) 
324         {
325           log_error( "lock not made: rename() failed: %s\n", strerror(errno) );
326           return -1;
327         }
328 #endif /* __riscos__ */
329
330       if ( (pid = read_lockfile (h, &same_node)) == -1 ) 
331         {
332           if ( errno != ENOENT )
333             {
334               log_info ("cannot read lockfile\n");
335               return -1;
336             }
337           log_info( "lockfile disappeared\n");
338           continue;
339         }
340       else if ( pid == getpid() && same_node )
341         {
342           log_info( "Oops: lock already held by us\n");
343           h->locked = 1;
344           return 0; /* okay */
345         }
346       else if ( same_node && kill (pid, 0) && errno == ESRCH )
347         {
348 #ifndef __riscos__
349           log_info ("removing stale lockfile (created by %d)", pid );
350           unlink (h->lockname);
351           continue;
352 #else /* __riscos__ */
353           /* Under RISCOS we are *pretty* sure that the other task
354              is dead and therefore we remove the stale lock file. */
355           maybe_dead = " - probably dead - removing lock";
356           unlink(h->lockname);
357 #endif /* __riscos__ */
358         }
359
360       if ( timeout == -1 ) 
361         {
362           /* Wait until lock has been released. */
363           struct timeval tv;
364           
365           log_info ("waiting for lock (held by %d%s) %s...\n",
366                     pid, maybe_dead, maybe_deadlock(h)? "(deadlock?) ":"");
367
368
369           /* We can't use sleep, cause signals may be blocked. */
370           tv.tv_sec = 1 + backoff;
371           tv.tv_usec = 0;
372           select(0, NULL, NULL, NULL, &tv);
373           if ( backoff < 10 )
374             backoff++ ;
375         }
376       else
377         return -1;
378     }
379     /*NOTREACHED*/
380 #endif /* !HAVE_DOSISH_SYSTEM */
381 }
382
383
384 /****************
385  * release a lock
386  * Returns: 0 := success
387  */
388 int
389 release_dotlock( DOTLOCK h )
390 {
391 #ifdef HAVE_DOSISH_SYSTEM
392   return 0;
393 #else
394   int pid, same_node;
395
396   /* To avoid atexit race conditions we first check whether there are
397      any locks left.  It might happen that another atexit handler
398      tries to release the lock while the atexit handler of this module
399      already ran and thus H is undefined.  */
400   if (!all_lockfiles)
401     return 0;
402
403   if ( h->disable )
404     return 0;
405
406   if ( !h->locked )
407     {
408       log_debug("oops, `%s' is not locked\n", h->lockname );
409       return 0;
410     }
411
412   pid = read_lockfile (h, &same_node);
413   if ( pid == -1 ) 
414     {
415       log_error( "release_dotlock: lockfile error\n");
416       return -1;
417     }
418   if ( pid != getpid() || !same_node )
419     {
420       log_error( "release_dotlock: not our lock (pid=%d)\n", pid);
421       return -1;
422     }
423 #ifndef __riscos__
424   if ( unlink( h->lockname ) )
425     {
426       log_error( "release_dotlock: error removing lockfile `%s'",
427                  h->lockname);
428       return -1;
429     }
430 #else /* __riscos__ */
431   if ( renamefile(h->lockname, h->tname) ) 
432     {
433       log_error( "release_dotlock: error renaming lockfile `%s' to `%s'",
434                  h->lockname, h->tname);
435       return -1;
436     }
437 #endif /* __riscos__ */
438   /* fixme: check that the link count is now 1 */
439   h->locked = 0;
440   return 0;
441 #endif /* !HAVE_DOSISH_SYSTEM */
442 }
443
444
445 /*
446    Read the lock file and return the pid, returns -1 on error.  True
447    will be stored at SAME_NODE if the lock file has been created on
448    the same node.
449  */
450 static int
451 read_lockfile (DOTLOCK h, int *same_node )
452 {
453 #ifdef HAVE_DOSISH_SYSTEM
454   return 0;
455 #else
456   char buffer_space[10+1+70+1]; /* 70 is just an estimated value; node
457                                    name are usually shorter. */
458   int fd, pid;
459   char *buffer, *p;
460   size_t expected_len;
461   int res, nread;
462   
463   *same_node = 0;
464   expected_len = 10 + 1 + h->nodename_len + 1;
465   if ( expected_len >= sizeof buffer_space)
466     buffer = jnlib_xmalloc (expected_len);
467   else
468     buffer = buffer_space;
469
470   if ( (fd = open (h->lockname, O_RDONLY)) == -1 )
471     {
472       int e = errno;
473       log_info ("error opening lockfile `%s': %s\n",
474                 h->lockname, strerror(errno) );
475       if (buffer != buffer_space)
476         jnlib_free (buffer);
477       errno = e; /* Need to return ERRNO here. */
478       return -1;
479     }
480
481   p = buffer;
482   nread = 0;
483   do
484     {
485       res = read (fd, p, expected_len - nread);
486       if (res == -1 && errno == EINTR)
487         continue;
488       if (res < 0)
489         {
490           log_info ("error reading lockfile `%s'", h->lockname );
491           close (fd); 
492           if (buffer != buffer_space)
493             jnlib_free (buffer);
494           errno = 0; /* Do not return an inappropriate ERRNO. */
495           return -1;
496         }
497       p += res;
498       nread += res;
499     }
500   while (res && nread != expected_len);
501   close(fd);
502
503   if (nread < 11)
504     {
505       log_info ("invalid size of lockfile `%s'", h->lockname );
506       if (buffer != buffer_space)
507         jnlib_free (buffer);
508       errno = 0; /* Do not return an inappropriate ERRNO. */
509       return -1;
510     }
511
512   if (buffer[10] != '\n'
513       || (buffer[10] = 0, pid = atoi (buffer)) == -1
514 #ifndef __riscos__
515       || !pid 
516 #else /* __riscos__ */
517       || (!pid && riscos_getpid())
518 #endif /* __riscos__ */
519       )
520     {
521       log_error ("invalid pid %d in lockfile `%s'", pid, h->lockname );
522       if (buffer != buffer_space)
523         jnlib_free (buffer);
524       errno = 0;
525       return -1;
526     }
527
528   if (nread == expected_len
529       && !memcmp (h->tname+h->nodename_off, buffer+11, h->nodename_len) 
530       && buffer[11+h->nodename_len] == '\n')
531     *same_node = 1;
532
533   if (buffer != buffer_space)
534     jnlib_free (buffer);
535   return pid;
536 #endif
537 }
538
539
540 void
541 dotlock_remove_lockfiles()
542 {
543 #ifndef HAVE_DOSISH_SYSTEM
544   DOTLOCK h, h2;
545   
546   h = all_lockfiles;
547   all_lockfiles = NULL;
548     
549   while ( h )
550     {
551       h2 = h->next;
552       destroy_dotlock (h);
553       h = h2;
554     }
555 #endif
556 }
557