See ChangeLog: Fri Jul 14 19:38:23 CEST 2000 Werner Koch
[gnupg.git] / jnlib / dotlock.c
1 /* dotlock.c - dotfile locking
2  *      Copyright (C) 1998, 2000 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 struct dotlock_handle {
41     struct dotlock_handle *next;
42     char *tname;    /* name of lockfile template */
43     char *lockname; /* name of the real lockfile */
44     int locked;     /* lock status */
45     int disable;    /* locking */
46 };
47
48
49 static DOTLOCK all_lockfiles;
50 static int never_lock;
51
52 static int read_lockfile( const char *name );
53 static void remove_lockfiles(void);
54
55 void
56 disable_dotlock(void)
57 {
58     never_lock = 1;
59 }
60
61 /****************
62  * Create a lockfile with the given name and return an object of
63  * type DOTLOCK which may be used later to actually do the lock.
64  * A cleanup routine gets installed to cleanup left over locks
65  * or other files used together with the lockmechanism.
66  * Althoug the function is called dotlock, this does not necessarily
67  * mean that real lockfiles are used - the function may decide to
68  * use fcntl locking.  Calling the function with NULL only install
69  * the atexit handler and maybe used to assure that the cleanup
70  * is called after all other atexit handlers.
71  *
72  * Notes: This function creates a lock file in the same directory
73  *        as file_to_lock with the name "file_to_lock.lock"
74  *        A temporary file ".#lk.<hostname>.pid[.threadid] is used.
75  *        This function does nothing for Windoze.
76  */
77 DOTLOCK
78 create_dotlock( const char *file_to_lock )
79 {
80     static int initialized;
81     DOTLOCK h;
82     int  fd = -1;
83     char pidstr[16];
84   #ifndef  HAVE_DOSISH_SYSTEM
85     struct utsname utsbuf;
86   #endif
87     const char *nodename;
88     const char *dirpart;
89     int dirpartlen;
90
91     if( !initialized ) {
92         atexit( remove_lockfiles );
93         initialized = 1;
94     }
95     if( !file_to_lock )
96         return NULL;
97
98     h = jnlib_xcalloc( 1, sizeof *h );
99     if( never_lock ) {
100         h->disable = 1;
101       #ifdef _REENTRANT
102         /* fixme: aquire mutex on all_lockfiles */
103       #endif
104         h->next = all_lockfiles;
105         all_lockfiles = h;
106         return h;
107     }
108
109 #ifndef HAVE_DOSISH_SYSTEM
110     sprintf( pidstr, "%10d\n", (int)getpid() );
111     /* fixme: add the hostname to the second line (FQDN or IP addr?) */
112
113     /* create a temporary file */
114     if( uname( &utsbuf ) )
115         nodename = "unknown";
116     else
117         nodename = utsbuf.nodename;
118
119     if( !(dirpart = strrchr( file_to_lock, '/' )) ) {
120         dirpart = ".";
121         dirpartlen = 1;
122     }
123     else {
124         dirpartlen = dirpart - file_to_lock;
125         dirpart = file_to_lock;
126     }
127
128   #ifdef _REENTRANT
129     /* fixme: aquire mutex on all_lockfiles */
130   #endif
131     h->next = all_lockfiles;
132     all_lockfiles = h;
133
134     h->tname = jnlib_xmalloc( dirpartlen + 6+30+ strlen(nodename) + 11 );
135     sprintf( h->tname, "%.*s/.#lk%p.%s.%d",
136              dirpartlen, dirpart, h, nodename, (int)getpid() );
137
138     do {
139         errno = 0;
140         fd = open( h->tname, O_WRONLY|O_CREAT|O_EXCL,
141                           S_IRUSR|S_IRGRP|S_IROTH|S_IWUSR );
142     } while( fd == -1 && errno == EINTR );
143     if( fd == -1 ) {
144         all_lockfiles = h->next;
145         log_error( "failed to create temporary file `%s': %s\n",
146                                             h->tname, strerror(errno));
147         jnlib_free(h->tname);
148         jnlib_free(h);
149         return NULL;
150     }
151     if( write(fd, pidstr, 11 ) != 11 ) {
152         all_lockfiles = h->next;
153       #ifdef _REENTRANT
154         /* release mutex */
155       #endif
156         log_fatal( "error writing to `%s': %s\n", h->tname, strerror(errno) );
157         close(fd);
158         unlink(h->tname);
159         jnlib_free(h->tname);
160         jnlib_free(h);
161         return NULL;
162     }
163     if( close(fd) ) {
164         all_lockfiles = h->next;
165       #ifdef _REENTRANT
166         /* release mutex */
167       #endif
168         log_error( "error closing `%s': %s\n", h->tname, strerror(errno));
169         unlink(h->tname);
170         jnlib_free(h->tname);
171         jnlib_free(h);
172         return NULL;
173     }
174
175   #ifdef _REENTRANT
176     /* release mutex */
177   #endif
178 #endif /* !HAVE_DOSISH_SYSTEM */
179     h->lockname = jnlib_xmalloc( strlen(file_to_lock) + 6 );
180     strcpy(stpcpy(h->lockname, file_to_lock), ".lock");
181     return h;
182 }
183
184 static int
185 maybe_deadlock( DOTLOCK h )
186 {
187     DOTLOCK r;
188
189     for( r=all_lockfiles; r; r = r->next ) {
190         if( r != h && r->locked )
191             return 1;
192     }
193     return 0;
194 }
195
196 /****************
197  * Do a lock on H. A TIMEOUT of 0 returns immediately,
198  * -1 waits forever (hopefully not), other
199  * values are timeouts in milliseconds.
200  * Returns: 0 on success
201  */
202 int
203 make_dotlock( DOTLOCK h, long timeout )
204 {
205 #ifdef HAVE_DOSISH_SYSTEM
206     return 0;
207 #else
208     int  pid;
209     const char *maybe_dead="";
210     int backoff=0;
211
212     if( h->disable ) {
213         return 0;
214     }
215
216     if( h->locked ) {
217         log_debug("oops, `%s' is already locked\n", h->lockname );
218         return 0;
219     }
220
221     for(;;) {
222         if( !link(h->tname, h->lockname) ) {
223             /* fixme: better use stat to check the link count */
224             h->locked = 1;
225             return 0; /* okay */
226         }
227         if( errno != EEXIST ) {
228             log_error( "lock not made: link() failed: %s\n", strerror(errno) );
229             return -1;
230         }
231         if( (pid = read_lockfile(h->lockname)) == -1 ) {
232             if( errno != ENOENT ) {
233                 log_info("cannot read lockfile\n");
234                 return -1;
235             }
236             log_info( "lockfile disappeared\n");
237             continue;
238         }
239         else if( pid == getpid() ) {
240             log_info( "Oops: lock already hold by us\n");
241             h->locked = 1;
242             return 0; /* okay */
243         }
244         else if( kill(pid, 0) && errno == ESRCH ) {
245             maybe_dead = " - probably dead";
246          #if 0 /* we should not do this without checking the permissions */
247                /* and the hostname */
248             log_info( "removing stale lockfile (created by %d)", pid );
249          #endif
250         }
251         if( timeout == -1 ) {
252             struct timeval tv;
253             log_info( "waiting for lock (hold by %d%s) %s...\n",
254                       pid, maybe_dead, maybe_deadlock(h)? "(deadlock?) ":"");
255
256
257             /* can't use sleep, cause signals may be blocked */
258             tv.tv_sec = 1 + backoff;
259             tv.tv_usec = 0;
260             select(0, NULL, NULL, NULL, &tv);
261             if( backoff < 10 )
262                 backoff++ ;
263         }
264         else
265             return -1;
266     }
267     /*not reached */
268 #endif /* !HAVE_DOSISH_SYSTEM */
269 }
270
271
272 /****************
273  * release a lock
274  * Returns: 0 := success
275  */
276 int
277 release_dotlock( DOTLOCK h )
278 {
279 #ifdef HAVE_DOSISH_SYSTEM
280     return 0;
281 #else
282     int pid;
283
284     if( h->disable ) {
285         return 0;
286     }
287
288     if( !h->locked ) {
289         log_debug("oops, `%s' is not locked\n", h->lockname );
290         return 0;
291     }
292
293     pid = read_lockfile( h->lockname );
294     if( pid == -1 ) {
295         log_error( "release_dotlock: lockfile error\n");
296         return -1;
297     }
298     if( pid != getpid() ) {
299         log_error( "release_dotlock: not our lock (pid=%d)\n", pid);
300         return -1;
301     }
302     if( unlink( h->lockname ) ) {
303         log_error( "release_dotlock: error removing lockfile `%s'",
304                                                         h->lockname);
305         return -1;
306     }
307     /* fixme: check that the link count is now 1 */
308     h->locked = 0;
309     return 0;
310 #endif /* !HAVE_DOSISH_SYSTEM */
311 }
312
313
314 /****************
315  * Read the lock file and return the pid, returns -1 on error.
316  */
317 static int
318 read_lockfile( const char *name )
319 {
320   #ifdef HAVE_DOSISH_SYSTEM
321     return 0;
322   #else
323     int fd, pid;
324     char pidstr[16];
325
326     if( (fd = open(name, O_RDONLY)) == -1 ) {
327         int e = errno;
328         log_debug("error opening lockfile `%s': %s\n", name, strerror(errno) );
329         errno = e;
330         return -1;
331     }
332     if( read(fd, pidstr, 10 ) != 10 ) {  /* Read 10 digits w/o newline */
333         log_debug("error reading lockfile `%s'", name );
334         close(fd);
335         errno = 0;
336         return -1;
337     }
338     pidstr[10] = 0;  /* terminate pid string */
339     close(fd);
340     pid = atoi(pidstr);
341     if( !pid || pid == -1 ) {
342         log_error("invalid pid %d in lockfile `%s'", pid, name );
343         errno = 0;
344         return -1;
345     }
346     return pid;
347   #endif
348 }
349
350
351 static void
352 remove_lockfiles()
353 {
354   #ifndef HAVE_DOSISH_SYSTEM
355     DOTLOCK h, h2;
356
357     h = all_lockfiles;
358     all_lockfiles = NULL;
359
360     while( h ) {
361         h2 = h->next;
362         if( !h->disable ) {
363             if( h->locked )
364                 unlink( h->lockname );
365             unlink(h->tname);
366             jnlib_free(h->tname);
367             jnlib_free(h->lockname);
368         }
369         jnlib_free(h);
370         h = h2;
371     }
372   #endif
373 }
374