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