See ChangeLog: Mon May 31 19:41:10 CEST 1999 Werner Koch
[gnupg.git] / util / dotlock.c
1 /* dotlock.c - dotfile locking
2  *      Copyright (C) 1998 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 #include "types.h"
37 #include "util.h"
38 #include "memory.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 uts;
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 = m_alloc_clear( 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( &uts ) )
97         nodename = "unknown";
98     else
99         nodename = uts.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 = m_alloc( 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         log_error( "failed to create temporary file `%s': %s\n",
127                                             h->tname, strerror(errno));
128         m_free(h->tname);
129         m_free(h);
130         return NULL;
131     }
132     if( write(fd, pidstr, 11 ) != 11 ) {
133         all_lockfiles = h->next;
134       #ifdef _REENTRANT
135         /* release mutex */
136       #endif
137         log_fatal( "error writing to `%s': %s\n", h->tname, strerror(errno) );
138         close(fd);
139         unlink(h->tname);
140         m_free(h->tname);
141         m_free(h);
142         return NULL;
143     }
144     if( close(fd) ) {
145         all_lockfiles = h->next;
146       #ifdef _REENTRANT
147         /* release mutex */
148       #endif
149         log_error( "error closing `%s': %s\n", h->tname, strerror(errno));
150         unlink(h->tname);
151         m_free(h->tname);
152         m_free(h);
153         return NULL;
154     }
155
156   #ifdef _REENTRANT
157     /* release mutex */
158   #endif
159 #endif /* !HAVE_DOSISH_SYSTEM */
160     h->lockname = m_alloc( strlen(file_to_lock) + 6 );
161     strcpy(stpcpy(h->lockname, file_to_lock), ".lock");
162     return h;
163 }
164
165 static int
166 maybe_deadlock( DOTLOCK h )
167 {
168     DOTLOCK r;
169
170     for( r=all_lockfiles; r; r = r->next ) {
171         if( r != h && r->locked )
172             return 1;
173     }
174     return 0;
175 }
176
177 /****************
178  * Do a lock on H. A TIMEOUT of 0 returns immediately,
179  * -1 waits forever (hopefully not), other
180  * values are timeouts in milliseconds.
181  * Returns: 0 on success
182  */
183 int
184 make_dotlock( DOTLOCK h, long timeout )
185 {
186 #ifdef HAVE_DOSISH_SYSTEM
187     return 0;
188 #else
189     int  pid;
190     const char *maybe_dead="";
191     int backoff=0;
192
193     if( h->locked ) {
194         log_debug("oops, `%s' is already locked\n", h->lockname );
195         return 0;
196     }
197
198     for(;;) {
199         if( !link(h->tname, h->lockname) ) {
200             /* fixme: better use stat to check the link count */
201             h->locked = 1;
202             return 0; /* okay */
203         }
204         if( errno != EEXIST ) {
205             log_error( "lock not made: link() failed: %s\n", strerror(errno) );
206             return -1;
207         }
208         if( (pid = read_lockfile(h->lockname)) == -1 ) {
209             if( errno != ENOENT ) {
210                 log_info("cannot read lockfile\n");
211                 return -1;
212             }
213             log_info( "lockfile disappeared\n");
214             continue;
215         }
216         else if( pid == getpid() ) {
217             log_info( "Oops: lock already hold by us\n");
218             h->locked = 1;
219             return 0; /* okay */
220         }
221         else if( kill(pid, 0) && errno == ESRCH ) {
222             maybe_dead = " - probably dead";
223          #if 0 /* we should not do this without checking the permissions */
224                /* and the hostname */
225             log_info( "removing stale lockfile (created by %d)", pid );
226          #endif
227         }
228         if( timeout == -1 ) {
229             struct timeval tv;
230             log_info( "waiting for lock (hold by %d%s) %s...\n",
231                       pid, maybe_dead, maybe_deadlock(h)? "(deadlock?) ":"");
232
233
234             /* can't use sleep, cause signals may be blocked */
235             tv.tv_sec = 1 + backoff;
236             tv.tv_usec = 0;
237             select(0, NULL, NULL, NULL, &tv);
238             if( backoff < 10 )
239                 backoff++ ;
240         }
241         else
242             return -1;
243     }
244     /*not reached */
245 #endif /* !HAVE_DOSISH_SYSTEM */
246 }
247
248
249 /****************
250  * release a lock
251  * Returns: 0 := success
252  */
253 int
254 release_dotlock( DOTLOCK h )
255 {
256 #ifdef HAVE_DOSISH_SYSTEM
257     return 0;
258 #else
259     int pid;
260
261     if( !h->locked ) {
262         log_debug("oops, `%s' is not locked\n", h->lockname );
263         return 0;
264     }
265
266     pid = read_lockfile( h->lockname );
267     if( pid == -1 ) {
268         log_error( "release_dotlock: lockfile error\n");
269         return -1;
270     }
271     if( pid != getpid() ) {
272         log_error( "release_dotlock: not our lock (pid=%d)\n", pid);
273         return -1;
274     }
275     if( unlink( h->lockname ) ) {
276         log_error( "release_dotlock: error removing lockfile `%s'",
277                                                         h->lockname);
278         return -1;
279     }
280     /* fixme: check that the link count is now 1 */
281     h->locked = 0;
282     return 0;
283 #endif /* !HAVE_DOSISH_SYSTEM */
284 }
285
286
287 /****************
288  * Read the lock file and return the pid, returns -1 on error.
289  */
290 static int
291 read_lockfile( const char *name )
292 {
293   #ifdef HAVE_DOSISH_SYSTEM
294     return 0;
295   #else
296     int fd, pid;
297     char pidstr[16];
298
299     if( (fd = open(name, O_RDONLY)) == -1 ) {
300         int e = errno;
301         log_debug("error opening lockfile `%s': %s\n", name, strerror(errno) );
302         errno = e;
303         return -1;
304     }
305     if( read(fd, pidstr, 10 ) != 10 ) {  /* Read 10 digits w/o newline */
306         log_debug("error reading lockfile `%s'", name );
307         close(fd);
308         errno = 0;
309         return -1;
310     }
311     pidstr[10] = 0;  /* terminate pid string */
312     close(fd);
313     pid = atoi(pidstr);
314     if( !pid || pid == -1 ) {
315         log_error("invalid pid %d in lockfile `%s'", pid, name );
316         errno = 0;
317         return -1;
318     }
319     return pid;
320   #endif
321 }
322
323
324 static void
325 remove_lockfiles()
326 {
327   #ifndef HAVE_DOSISH_SYSTEM
328     DOTLOCK h, h2;
329
330     h = all_lockfiles;
331     all_lockfiles = NULL;
332
333     while( h ) {
334         h2 = h->next;
335         if( h->locked )
336             unlink( h->lockname );
337         unlink(h->tname);
338         m_free(h->tname);
339         m_free(h->lockname);
340         m_free(h);
341         h = h2;
342     }
343   #endif
344 }
345