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