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