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