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>
5 #include "cmogstored.h"
6 static const char *pidfile
;
7 static bool pidfile_exists
;
8 static const char *old
;
14 static bool pid_is_running(pid_t pid
)
18 if (kill(pid
, 0) < 0 && errno
== ESRCH
)
23 /* sets errno on failure */
24 static bool pid_write(int fd
)
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
)
34 char buf
[sizeof(pid_t
) * 8 / 3 + 1];
40 r
= pread(fd
, buf
, sizeof(buf
), 0);
42 pid
= 0; /* empty file */
45 tmp
= strtol(buf
, &end
, 10);
47 if (*end
== '\n' && tmp
> 0 && tmp
< LONG_MAX
)
54 static void pidfile_cleanup(void)
57 if (getpid() == owner
) {
60 else if (pidfile_exists
)
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)
80 * int fd = mog_pidfile_open("/path/to/pid", &cur_pid);
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);
93 /* see if existing pidfile is valid */
94 pid
= pidfile_read(fd
);
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
))
105 /* old pidfile is still valid */
112 assert(pidfile
== NULL
&& "already opened pidfile for process");
113 pidfile
= canonicalize_filename_mode(path
, CAN_EXISTING
);
117 pidfile_exists
= true;
120 PRESERVE_ERRNO( close(fd
) );
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)");
136 if (!pid_write(fd
)) {
137 PRESERVE_ERRNO( close(fd
) );
139 PRESERVE_ERRNO( pidfile_cleanup() );
142 if (close(fd
) < 0 && errno
!= EINTR
)
146 atexit(pidfile_cleanup
);
151 int mog_pidfile_prepare(const char *path
)
154 int pid_fd
= mog_pidfile_open(path
, &cur_pid
);
159 die("already running on PID: %d", (int)cur_pid
);
161 die_errno("mog_pidfile_prepare failed");
165 /* returns true if successful (or path is non-existent) */
166 static bool unlink_if_owner_or_unused(const char *path
)
169 int fd
= open(path
, O_RDONLY
|O_CLOEXEC
);
172 /* somebody mistakenly removed path while we were running */
175 syslog(LOG_ERR
, "open(%s): %m failed", path
);
179 pid
= pidfile_read(fd
);
180 PRESERVE_ERRNO( mog_close(fd
) );
184 * existing path is empty, FS could've been full earlier,
185 * proceed assuming we can overwrite
187 } else if (pid
> 0) {
190 if (!pid_is_running(pid
))
193 "cannot unlink %s belongs to running PID:%d",
197 /* can't unlink pidfile safely */
198 syslog(LOG_ERR
, "failed to read/parse %s: %m", path
);
202 /* ENOENT: maybe somebody else just unlinked it */
203 if (unlink(path
) == 0 || errno
== ENOENT
)
206 syslog(LOG_ERR
, "failed to remove %s for upgrade: %m", path
);
210 /* replaces (non-atomically) current pidfile with pidfile.oldbin */
211 bool mog_pidfile_upgrade_prepare(void)
219 assert(owner
== getpid() &&
220 "mog_pidfile_upgrade_prepare called by non-owner");
222 if (!unlink_if_owner_or_unused(pidfile
))
225 assert(old
== NULL
&& "oldbin already registered");
226 old
= xasprintf("%s.oldbin", pidfile
);
227 fd
= open(old
, O_CREAT
|O_RDWR
|O_CLOEXEC
, 0666);
229 syslog(LOG_ERR
, "failed to open pidfile %s: %m", old
);
230 mog_free_and_null(&old
);
233 pid
= pidfile_read(fd
);
234 if (pid_is_running(pid
)) {
236 "upgrade failed, %s belongs to running PID:%d",
238 mog_free_and_null(&old
);
239 } else if (pid_write(fd
)) {
240 /* success writing, don't touch old */
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)
253 int fd
= open(pidfile
, O_RDONLY
|O_CLOEXEC
);
255 /* pidfile no longer exists, good */
259 pid
= pidfile_read(fd
);
260 PRESERVE_ERRNO( mog_close(fd
) );
262 /* save to overwrite */
263 if (!pid_is_running(pid
))
266 assert(old
&& "we are stuck on oldbin");
267 syslog(LOG_ERR
, "PID:%d of upgrade still running", pid
);
271 /* removes oldbin file and restores original pidfile */
272 void mog_pidfile_upgrade_abort(void)
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())
286 fd
= open(pidfile
, O_TRUNC
|O_CREAT
|O_WRONLY
|O_CLOEXEC
, 0666);
288 pidfile_exists
= true;
290 syslog(LOG_ERR
, "failed to write %s: %m", pidfile
);
292 if (unlink_if_owner_or_unused(old
))
293 mog_free_and_null(&old
);
295 /* we're pidless(!) */
296 syslog(LOG_ERR
, "failed to open %s for writing: %m", pidfile
);
297 pidfile_exists
= false;