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