1 /**
2  * \file lock.c cross-platform concurrency locking for fetchmail
3  *
4  * For license terms, see the file COPYING in this directory.
5  */
6 #include "config.h"
7 #include "fetchmail.h"
8 
9 #include <stdio.h>
10 #ifdef HAVE_STRING_H
11 #include <string.h> /* strcat() */
12 #endif
13 #if defined(STDC_HEADERS)
14 #include <stdlib.h>
15 #endif
16 #if defined(HAVE_UNISTD_H)
17 #include <unistd.h>
18 #endif
19 #include <errno.h>
20 #include <fcntl.h>
21 #include <signal.h>
22 
23 #include "i18n.h"
24 #include "lock.h"
25 
26 static char *lockfile;		/** name of lockfile */
27 static int lock_acquired;	/** flag if have we acquired a lock */
28 
fm_lock_setup(struct runctl * ctl)29 void fm_lock_setup(struct runctl *ctl)
30 /* set up the global lockfile name */
31 {
32     /* set up to do lock protocol */
33     const char *const FETCHMAIL_PIDFILE="fetchmail.pid";
34 
35     /* command-line option override */
36     if (ctl->pidfile) {
37 	lockfile = xstrdup(ctl->pidfile);
38 	return;
39     }
40 
41     /* defaults */
42     if (getuid() == ROOT_UID) {
43 	lockfile = (char *)xmalloc(strlen(PID_DIR)
44 		+ strlen(FETCHMAIL_PIDFILE) + 2); /* 2: "/" and NUL */
45 	strcpy(lockfile, PID_DIR);
46 	strcat(lockfile, "/");
47 	strcat(lockfile, FETCHMAIL_PIDFILE);
48     } else {
49 	lockfile = (char *)xmalloc(strlen(fmhome)
50 		+ strlen(FETCHMAIL_PIDFILE) + 3); /* 3: "/", "." and NUL */
51 	strcpy(lockfile, fmhome);
52 	strcat(lockfile, "/");
53 	if (at_home)
54 	   strcat(lockfile, ".");
55 	strcat(lockfile, FETCHMAIL_PIDFILE);
56     }
57 }
58 
unlockit(void)59 static void unlockit(void)
60 /* must-do actions for exit (but we can't count on being able to do malloc) */
61 {
62     if (lockfile && lock_acquired) {
63 	if (unlink(lockfile)) {
64 		(void)truncate(lockfile, (off_t)0);
65 	}
66     }
67 }
68 
fm_lock_dispose(void)69 void fm_lock_dispose(void)
70 /* arrange for a lock to be removed on process exit */
71 {
72 #ifdef HAVE_ATEXIT
73     atexit(unlockit);
74 #endif
75 }
76 
fm_lock_state(void)77 int fm_lock_state(void)
78 {
79     long	pid;
80     int		st;
81     FILE	*lockfp;
82     int		bkgd = FALSE;
83 
84     if ((lockfp = fopen(lockfile, "r")) != NULL)
85     {
86 	int args = fscanf(lockfp, "%ld %d", &pid, &st);
87 	bkgd = (args == 2);
88 
89 	if (ferror(lockfp)) {
90 	    report(stderr, GT_("fetchmail: error reading lockfile \"%s\": %s\n"),
91 		    lockfile, strerror(errno));
92 	    fclose(lockfp); /* not checking should be safe, file mode was "r" */
93 	    exit(PS_EXCLUDE);
94 	}
95 	fclose(lockfp); /* not checking should be safe, file mode was "r" */
96 
97 	if (args == EOF || args == 0 || kill(pid, 0) == -1) {
98 	    /* ^ could not read PID  || process does not exist */
99 	    /* => lockfile is stale, unlink it */
100 	    pid = 0;
101 	    report(stderr,GT_("fetchmail: removing stale lockfile \"%s\"\n"), lockfile);
102 	    if (unlink(lockfile)) {
103 	       if (errno != ENOENT) {
104 		   if (outlevel >= O_VERBOSE) {
105 		       report(stderr, GT_("fetchmail: cannot unlink lockfile \"%s\" (%s), trying to write to it\n"),
106 			       lockfile, strerror(errno));
107 		   }
108 		   /* we complain but we don't exit; it might be
109 		    * writable for us, but in a directory we cannot
110 		    * write to. This means we can write the new PID to
111 		    * the file. Truncate to be safe in case the PID is
112 		    * recycled by another process later.
113 		    * \bug we should use fcntl() style locks or
114 		    * something else instead in a future release. */
115 		   if (truncate(lockfile, (off_t)0)) {
116 		       /* but if we cannot truncate the file either,
117 			* assume that we cannot write to it later,
118 			* complain and quit. */
119 		       report(stderr, GT_("fetchmail: cannot write to lockfile \"%s\" (%s), exiting\n"),
120 			       lockfile, strerror(errno));
121 		       exit(PS_EXCLUDE);
122 		   }
123 	       }
124 	    }
125 	}
126     } else {
127 	pid = 0;
128 	if (errno != ENOENT) {
129 	    report(stderr, GT_("fetchmail: error opening lockfile \"%s\": %s\n"),
130 		    lockfile, strerror(errno));
131 	    exit(PS_EXCLUDE);
132 	}
133     }
134 
135     return(bkgd ? -pid : pid);
136 }
137 
fm_lock_assert(void)138 void fm_lock_assert(void)
139 /* assert that we already possess a lock */
140 {
141     lock_acquired = TRUE;
142 }
143 
fm_lock_or_die(void)144 void fm_lock_or_die(void)
145 /* get a lock on a given host or exit */
146 {
147     int fd;
148     char	tmpbuf[50];
149 
150     if (!lock_acquired) {
151 	int e = 0;
152 
153 	fd = open(lockfile, O_WRONLY|O_CREAT|O_EXCL, 0666);
154 	if (fd == -1 && EEXIST == errno) {
155 		fd = open(lockfile, O_WRONLY|O_TRUNC, 0666);
156 	}
157 	if (-1 != fd) {
158 	    ssize_t wr;
159 
160 	    snprintf(tmpbuf, sizeof(tmpbuf), "%ld\n", (long)getpid());
161 	    wr = write(fd, tmpbuf, strlen(tmpbuf));
162 	    if (wr == -1 || (size_t)wr != strlen(tmpbuf))
163 	        e = 1;
164 	    if (run.poll_interval)
165 	    {
166 		snprintf(tmpbuf, sizeof(tmpbuf), "%d\n", run.poll_interval);
167 		wr = write(fd, tmpbuf, strlen(tmpbuf));
168 		if (wr == -1 || (size_t)wr != strlen(tmpbuf))
169 		    e = 1;
170 	    }
171 	    if (fsync(fd)) e = 1;
172 	    if (close(fd)) e = 1;
173 	} else {
174 	    e = 1;
175 	}
176 	if (e == 0) {
177 	    lock_acquired = TRUE;
178 	} else {
179 	    report(stderr, GT_("fetchmail: lock creation failed, pidfile \"%s\": %s\n"), lockfile, strerror(errno));
180 	    exit(PS_EXCLUDE);
181 	}
182     }
183 }
184 
fm_lock_release(void)185 void fm_lock_release(void)
186 /* release a lock on a given host */
187 {
188     if (unlink(lockfile)) {
189 	    if (truncate(lockfile, (off_t)0)) {
190 		    report(stderr, GT_("fetchmail: cannot remove or truncate pidfile \"%s\": %s\n"), lockfile, strerror(errno));
191 	    }
192     }
193 }
194 /* lock.c ends here */
195