cmogstored 1.8.1 - use default system stack size
[cmogstored.git] / pidfile.c
blob44784de18ae0c5c439b742b20230f1882ac35619
1 /*
2 * Copyright (C) 2012-2020 all contributors <cmogstored-public@yhbt.net>
3 * License: GPL-3.0+ <https://www.gnu.org/licenses/gpl-3.0.txt>
4 */
5 #include "cmogstored.h"
6 static const char *pidfile;
7 static bool pidfile_exists;
8 static const char *old;
9 static pid_t owner;
10 #ifndef O_CLOEXEC
11 #define O_CLOEXEC (0)
12 #endif
14 static bool pid_is_running(pid_t pid)
16 if (pid <= 0)
17 return false;
18 if (kill(pid, 0) < 0 && errno == ESRCH)
19 return false;
20 return true;
23 /* sets errno on failure */
24 static bool pid_write(int fd)
26 errno = 0;
27 return !(dprintf(fd, "%d\n", (int)getpid()) <= 1 || errno == ENOSPC);
30 /* returns 0 if pidfile is empty, -1 on error, pid value on success */
31 static pid_t pidfile_read(int fd)
33 pid_t pid = -1;
34 char buf[sizeof(pid_t) * 8 / 3 + 1];
35 ssize_t r;
36 char *end;
37 long tmp;
39 errno = 0;
40 r = pread(fd, buf, sizeof(buf), 0);
41 if (r == 0)
42 pid = 0; /* empty file */
43 if (r > 0) {
44 errno = 0;
45 tmp = strtol(buf, &end, 10);
47 if (*end == '\n' && tmp > 0 && tmp < LONG_MAX)
48 pid = (pid_t)tmp;
51 return pid;
54 static void pidfile_cleanup(void)
56 if (pidfile) {
57 if (getpid() == owner) {
58 if (old)
59 unlink(old);
60 else if (pidfile_exists)
61 unlink(pidfile);
63 /* else: don't unlink if it does not belong to us */
64 mog_free_and_null(&pidfile);
65 mog_free_and_null(&old);
70 * opens a pid file and returns a file descriptor for it
71 * mog_pidfile_commit() should be used on the fd returned by
72 * this function (often in a separate process)
73 * returns < 0 if there is an error and sets errno=EAGAIN
74 * if a pid already exists
77 * Example: (error checking is left as an exercise to the reader)
79 * pid_t cur_pid;
80 * int fd = mog_pidfile_open("/path/to/pid", &cur_pid);
81 * daemon(0, 0);
82 * mog_pidfile_commit(fd);
84 static int mog_pidfile_open(const char *path, pid_t *cur)
86 int fd = open(path, O_RDWR|O_CREAT, 0666);
87 pid_t pid;
89 *cur = -1;
90 if (fd < 0)
91 return fd;
93 /* see if existing pidfile is valid */
94 pid = pidfile_read(fd);
95 if (pid == 0) {
97 * existing pidfile is empty, FS could've been full earlier,
98 * proceed assuming we can overwrite
100 } else if (pid > 0) {
101 /* can't signal it, (likely) safe to overwrite */
102 if (!pid_is_running(pid))
103 goto out;
105 /* old pidfile is still valid */
106 errno = EAGAIN;
107 *cur = pid;
108 goto err;
111 out:
112 assert(pidfile == NULL && "already opened pidfile for process");
113 pidfile = canonicalize_filename_mode(path, CAN_EXISTING);
114 if (!pidfile)
115 goto err;
117 pidfile_exists = true;
118 return fd;
119 err:
120 PRESERVE_ERRNO( close(fd) );
121 return -1;
125 * commits the pidfile pointed to by the given fd
126 * and closes the given fd on success.
127 * returns -1 on error and sets errno
128 * fd should be the return value of mog_pidfile_open();
130 int mog_pidfile_commit(int fd)
132 assert(lseek(fd, 0, SEEK_CUR) == 0 && "pidfile offset != 0");
133 assert(pidfile && "mog_pidfile_open not called (or unsuccessful)");
135 errno = 0;
136 if (!pid_write(fd)) {
137 PRESERVE_ERRNO( close(fd) );
138 if (errno == ENOSPC)
139 PRESERVE_ERRNO( pidfile_cleanup() );
140 return -1;
142 if (close(fd) < 0 && errno != EINTR)
143 return -1;
145 owner = getpid();
146 atexit(pidfile_cleanup);
148 return 0;
151 int mog_pidfile_prepare(const char *path)
153 pid_t cur_pid = -1;
154 int pid_fd = mog_pidfile_open(path, &cur_pid);
156 if (pid_fd >= 0)
157 return pid_fd;
158 if (errno == EAGAIN)
159 die("already running on PID: %d", (int)cur_pid);
160 else
161 die_errno("mog_pidfile_prepare failed");
162 return -1;
165 /* returns true if successful (or path is non-existent) */
166 static bool unlink_if_owner_or_unused(const char *path)
168 pid_t pid;
169 int fd = open(path, O_RDONLY|O_CLOEXEC);
171 if (fd < 0) {
172 /* somebody mistakenly removed path while we were running */
173 if (errno == ENOENT)
174 return true;
175 syslog(LOG_ERR, "open(%s): %m failed", path);
176 return false;
179 pid = pidfile_read(fd);
180 PRESERVE_ERRNO( mog_close(fd) );
182 if (pid == 0) {
184 * existing path is empty, FS could've been full earlier,
185 * proceed assuming we can overwrite
187 } else if (pid > 0) {
188 if (pid == getpid())
189 goto do_unlink;
190 if (!pid_is_running(pid))
191 goto do_unlink;
192 syslog(LOG_ERR,
193 "cannot unlink %s belongs to running PID:%d",
194 path, (int)pid);
195 return false;
196 } else {
197 /* can't unlink pidfile safely */
198 syslog(LOG_ERR, "failed to read/parse %s: %m", path);
199 return false;
201 do_unlink:
202 /* ENOENT: maybe somebody else just unlinked it */
203 if (unlink(path) == 0 || errno == ENOENT)
204 return true;
206 syslog(LOG_ERR, "failed to remove %s for upgrade: %m", path);
207 return false;
210 /* replaces (non-atomically) current pidfile with pidfile.oldbin */
211 bool mog_pidfile_upgrade_prepare(void)
213 pid_t pid = -1;
214 int fd;
216 if (!pidfile)
217 return true;
219 assert(owner == getpid() &&
220 "mog_pidfile_upgrade_prepare called by non-owner");
222 if (!unlink_if_owner_or_unused(pidfile))
223 return false;
225 assert(old == NULL && "oldbin already registered");
226 old = xasprintf("%s.oldbin", pidfile);
227 fd = open(old, O_CREAT|O_RDWR|O_CLOEXEC, 0666);
228 if (fd < 0) {
229 syslog(LOG_ERR, "failed to open pidfile %s: %m", old);
230 mog_free_and_null(&old);
231 return false;
233 pid = pidfile_read(fd);
234 if (pid_is_running(pid)) {
235 syslog(LOG_ERR,
236 "upgrade failed, %s belongs to running PID:%d",
237 old, (int)pid);
238 mog_free_and_null(&old);
239 } else if (pid_write(fd)) {
240 /* success writing, don't touch old */
241 } else {
242 syslog(LOG_ERR, "failed to write pidfile %s: %m", old);
243 mog_free_and_null(&old);
246 PRESERVE_ERRNO( mog_close(fd) );
247 return old ? true : false;
250 static bool upgrade_failed(void)
252 pid_t pid;
253 int fd = open(pidfile, O_RDONLY|O_CLOEXEC);
255 /* pidfile no longer exists, good */
256 if (fd < 0)
257 return true;
259 pid = pidfile_read(fd);
260 PRESERVE_ERRNO( mog_close(fd) );
262 /* save to overwrite */
263 if (!pid_is_running(pid))
264 return true;
266 assert(old && "we are stuck on oldbin");
267 syslog(LOG_ERR, "PID:%d of upgrade still running", pid);
268 return false;
271 /* removes oldbin file and restores original pidfile */
272 void mog_pidfile_upgrade_abort(void)
274 int fd;
276 if (!pidfile)
277 return;
279 assert(owner == getpid() &&
280 "mog_pidfile_upgrade_abort called by non-owner");
282 /* ensure the pidfile of the upgraded process is really invalid */
283 if (!upgrade_failed())
284 return;
286 fd = open(pidfile, O_TRUNC|O_CREAT|O_WRONLY|O_CLOEXEC, 0666);
287 if (fd >= 0) {
288 pidfile_exists = true;
289 if (!pid_write(fd))
290 syslog(LOG_ERR, "failed to write %s: %m", pidfile);
291 mog_close(fd);
292 if (unlink_if_owner_or_unused(old))
293 mog_free_and_null(&old);
294 } else {
295 /* we're pidless(!) */
296 syslog(LOG_ERR, "failed to open %s for writing: %m", pidfile);
297 pidfile_exists = false;