mirror of
https://github.com/morgan9e/systemd
synced 2026-04-15 17:06:39 +09:00
If we try to deserialize only a pidfd that points to a process that has been reaped, creating the pidref object will fail, which means that we'll try to create a pidref object from the serialized pid that comes next. If the pid has already been reused, this will succeed and we'll now have a pidref that points to a different process. Let's avoid this issue by serializing both the pidfd and the pid and creating the pidref object directly from both. This means we'll reuse the deserialized pidfd instead of opening a new one. We'll then immediately notice the pidfd is dead and do the appropriate follow up depending on the unit type.
586 lines
16 KiB
C
586 lines
16 KiB
C
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
|
|
|
#include <fcntl.h>
|
|
|
|
#include "alloc-util.h"
|
|
#include "env-util.h"
|
|
#include "escape.h"
|
|
#include "fd-util.h"
|
|
#include "fileio.h"
|
|
#include "hexdecoct.h"
|
|
#include "memfd-util.h"
|
|
#include "missing_mman.h"
|
|
#include "missing_syscall.h"
|
|
#include "parse-util.h"
|
|
#include "process-util.h"
|
|
#include "serialize.h"
|
|
#include "strv.h"
|
|
#include "tmpfile-util.h"
|
|
|
|
int serialize_item(FILE *f, const char *key, const char *value) {
|
|
assert(f);
|
|
assert(key);
|
|
|
|
if (!value)
|
|
return 0;
|
|
|
|
/* Make sure that anything we serialize we can also read back again with read_line() with a maximum line size
|
|
* of LONG_LINE_MAX. This is a safety net only. All code calling us should filter this out earlier anyway. */
|
|
if (strlen(key) + 1 + strlen(value) + 1 > LONG_LINE_MAX)
|
|
return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), "Attempted to serialize overly long item '%s', refusing.", key);
|
|
|
|
fputs(key, f);
|
|
fputc('=', f);
|
|
fputs(value, f);
|
|
fputc('\n', f);
|
|
|
|
return 1;
|
|
}
|
|
|
|
int serialize_item_escaped(FILE *f, const char *key, const char *value) {
|
|
_cleanup_free_ char *c = NULL;
|
|
|
|
assert(f);
|
|
assert(key);
|
|
|
|
if (!value)
|
|
return 0;
|
|
|
|
c = xescape(value, " ");
|
|
if (!c)
|
|
return log_oom();
|
|
|
|
return serialize_item(f, key, c);
|
|
}
|
|
|
|
int serialize_item_format(FILE *f, const char *key, const char *format, ...) {
|
|
_cleanup_free_ char *allocated = NULL;
|
|
char buf[256]; /* Something reasonably short that fits nicely on any stack (i.e. is considerably less
|
|
* than LONG_LINE_MAX (1MiB!) */
|
|
const char *b;
|
|
va_list ap;
|
|
int k;
|
|
|
|
assert(f);
|
|
assert(key);
|
|
assert(format);
|
|
|
|
/* First, let's try to format this into a stack buffer */
|
|
va_start(ap, format);
|
|
k = vsnprintf(buf, sizeof(buf), format, ap);
|
|
va_end(ap);
|
|
|
|
if (k < 0)
|
|
return log_warning_errno(errno, "Failed to serialize item '%s', ignoring: %m", key);
|
|
if (strlen(key) + 1 + k + 1 > LONG_LINE_MAX) /* See above */
|
|
return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), "Attempted to serialize overly long item '%s', refusing.", key);
|
|
|
|
if ((size_t) k < sizeof(buf))
|
|
b = buf; /* Yay, it fit! */
|
|
else {
|
|
/* So the string didn't fit in the short buffer above, but was not above our total limit,
|
|
* hence let's format it via dynamic memory */
|
|
|
|
va_start(ap, format);
|
|
k = vasprintf(&allocated, format, ap);
|
|
va_end(ap);
|
|
|
|
if (k < 0)
|
|
return log_warning_errno(errno, "Failed to serialize item '%s', ignoring: %m", key);
|
|
|
|
b = allocated;
|
|
}
|
|
|
|
fputs(key, f);
|
|
fputc('=', f);
|
|
fputs(b, f);
|
|
fputc('\n', f);
|
|
|
|
return 1;
|
|
}
|
|
|
|
int serialize_fd(FILE *f, FDSet *fds, const char *key, int fd) {
|
|
int copy;
|
|
|
|
assert(f);
|
|
assert(fds);
|
|
assert(key);
|
|
|
|
if (fd < 0)
|
|
return 0;
|
|
|
|
copy = fdset_put_dup(fds, fd);
|
|
if (copy < 0)
|
|
return log_error_errno(copy, "Failed to add file descriptor to serialization set: %m");
|
|
|
|
return serialize_item_format(f, key, "%i", copy);
|
|
}
|
|
|
|
int serialize_fd_many(FILE *f, FDSet *fds, const char *key, const int fd_array[], size_t n_fd_array) {
|
|
_cleanup_free_ char *t = NULL;
|
|
|
|
assert(f);
|
|
|
|
if (n_fd_array == 0)
|
|
return 0;
|
|
|
|
assert(fd_array);
|
|
|
|
for (size_t i = 0; i < n_fd_array; i++) {
|
|
int copy;
|
|
|
|
if (fd_array[i] < 0)
|
|
return -EBADF;
|
|
|
|
copy = fdset_put_dup(fds, fd_array[i]);
|
|
if (copy < 0)
|
|
return log_error_errno(copy, "Failed to add file descriptor to serialization set: %m");
|
|
|
|
if (strextendf_with_separator(&t, " ", "%i", copy) < 0)
|
|
return log_oom();
|
|
}
|
|
|
|
return serialize_item(f, key, t);
|
|
}
|
|
|
|
int serialize_usec(FILE *f, const char *key, usec_t usec) {
|
|
assert(f);
|
|
assert(key);
|
|
|
|
if (usec == USEC_INFINITY)
|
|
return 0;
|
|
|
|
return serialize_item_format(f, key, USEC_FMT, usec);
|
|
}
|
|
|
|
int serialize_dual_timestamp(FILE *f, const char *name, const dual_timestamp *t) {
|
|
assert(f);
|
|
assert(name);
|
|
assert(t);
|
|
|
|
if (!dual_timestamp_is_set(t))
|
|
return 0;
|
|
|
|
return serialize_item_format(f, name, USEC_FMT " " USEC_FMT, t->realtime, t->monotonic);
|
|
}
|
|
|
|
int serialize_strv(FILE *f, const char *key, char **l) {
|
|
int ret = 0, r;
|
|
|
|
/* Returns the first error, or positive if anything was serialized, 0 otherwise. */
|
|
|
|
STRV_FOREACH(i, l) {
|
|
r = serialize_item_escaped(f, key, *i);
|
|
if ((ret >= 0 && r < 0) ||
|
|
(ret == 0 && r > 0))
|
|
ret = r;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
int serialize_pidref(FILE *f, FDSet *fds, const char *key, PidRef *pidref) {
|
|
int r;
|
|
|
|
assert(f);
|
|
assert(fds);
|
|
|
|
if (!pidref_is_set(pidref))
|
|
return 0;
|
|
|
|
/* We always serialize the pid separately, to keep downgrades mostly working (older versions will
|
|
* deserialize the pid and silently fail to deserialize the pidfd). If we also have a pidfd, we
|
|
* serialize both the pid and pidfd, so that we can construct the exact same pidref after
|
|
* deserialization (this doesn't work with only the pidfd, as we can't retrieve the original pid
|
|
* from the pidfd anymore if the process is reaped). */
|
|
|
|
if (pidref->fd >= 0) {
|
|
int copy = fdset_put_dup(fds, pidref->fd);
|
|
if (copy < 0)
|
|
return log_error_errno(copy, "Failed to add file descriptor to serialization set: %m");
|
|
|
|
r = serialize_item_format(f, key, "@%i:" PID_FMT, copy, pidref->pid);
|
|
if (r < 0)
|
|
return r;
|
|
}
|
|
|
|
return serialize_item_format(f, key, PID_FMT, pidref->pid);
|
|
}
|
|
|
|
int serialize_ratelimit(FILE *f, const char *key, const RateLimit *rl) {
|
|
assert(rl);
|
|
|
|
return serialize_item_format(f, key,
|
|
USEC_FMT " " USEC_FMT " %u %u",
|
|
rl->begin,
|
|
rl->interval,
|
|
rl->num,
|
|
rl->burst);
|
|
}
|
|
|
|
int serialize_item_hexmem(FILE *f, const char *key, const void *p, size_t l) {
|
|
_cleanup_free_ char *encoded = NULL;
|
|
int r;
|
|
|
|
assert(f);
|
|
assert(key);
|
|
|
|
if (!p && l > 0)
|
|
return -EINVAL;
|
|
|
|
if (l == 0)
|
|
return 0;
|
|
|
|
encoded = hexmem(p, l);
|
|
if (!encoded)
|
|
return log_oom_debug();
|
|
|
|
r = serialize_item(f, key, encoded);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
return 1;
|
|
}
|
|
|
|
int serialize_item_base64mem(FILE *f, const char *key, const void *p, size_t l) {
|
|
_cleanup_free_ char *encoded = NULL;
|
|
ssize_t len;
|
|
int r;
|
|
|
|
assert(f);
|
|
assert(key);
|
|
|
|
if (!p && l > 0)
|
|
return -EINVAL;
|
|
|
|
if (l == 0)
|
|
return 0;
|
|
|
|
len = base64mem(p, l, &encoded);
|
|
if (len <= 0)
|
|
return log_oom_debug();
|
|
|
|
r = serialize_item(f, key, encoded);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
return 1;
|
|
}
|
|
|
|
int serialize_string_set(FILE *f, const char *key, Set *s) {
|
|
const char *e;
|
|
int r;
|
|
|
|
assert(f);
|
|
assert(key);
|
|
|
|
if (set_isempty(s))
|
|
return 0;
|
|
|
|
/* Serialize as individual items, as each element might contain separators and escapes */
|
|
|
|
SET_FOREACH(e, s) {
|
|
r = serialize_item(f, key, e);
|
|
if (r < 0)
|
|
return r;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
int serialize_image_policy(FILE *f, const char *key, const ImagePolicy *p) {
|
|
_cleanup_free_ char *policy = NULL;
|
|
int r;
|
|
|
|
assert(f);
|
|
assert(key);
|
|
|
|
if (!p)
|
|
return 0;
|
|
|
|
r = image_policy_to_string(p, /* simplify= */ false, &policy);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
r = serialize_item(f, key, policy);
|
|
if (r < 0)
|
|
return r;
|
|
|
|
return 1;
|
|
}
|
|
|
|
int deserialize_read_line(FILE *f, char **ret) {
|
|
_cleanup_free_ char *line = NULL;
|
|
int r;
|
|
|
|
assert(f);
|
|
assert(ret);
|
|
|
|
r = read_stripped_line(f, LONG_LINE_MAX, &line);
|
|
if (r < 0)
|
|
return log_error_errno(r, "Failed to read serialization line: %m");
|
|
if (r == 0) { /* eof */
|
|
*ret = NULL;
|
|
return 0;
|
|
}
|
|
|
|
if (isempty(line)) { /* End marker */
|
|
*ret = NULL;
|
|
return 0;
|
|
}
|
|
|
|
*ret = TAKE_PTR(line);
|
|
return 1;
|
|
}
|
|
|
|
int deserialize_fd(FDSet *fds, const char *value) {
|
|
_cleanup_close_ int our_fd = -EBADF;
|
|
int parsed_fd;
|
|
|
|
assert(value);
|
|
|
|
parsed_fd = parse_fd(value);
|
|
if (parsed_fd < 0)
|
|
return log_debug_errno(parsed_fd, "Failed to parse file descriptor serialization: %s", value);
|
|
|
|
our_fd = fdset_remove(fds, parsed_fd); /* Take possession of the fd */
|
|
if (our_fd < 0)
|
|
return log_debug_errno(our_fd, "Failed to acquire fd from serialization fds: %m");
|
|
|
|
return TAKE_FD(our_fd);
|
|
}
|
|
|
|
int deserialize_fd_many(FDSet *fds, const char *value, size_t n, int *ret) {
|
|
int r, *fd_array = NULL;
|
|
size_t m = 0;
|
|
|
|
assert(value);
|
|
|
|
fd_array = new(int, n);
|
|
if (!fd_array)
|
|
return -ENOMEM;
|
|
|
|
CLEANUP_ARRAY(fd_array, m, close_many_and_free);
|
|
|
|
for (;;) {
|
|
_cleanup_free_ char *w = NULL;
|
|
int fd;
|
|
|
|
r = extract_first_word(&value, &w, NULL, 0);
|
|
if (r < 0)
|
|
return r;
|
|
if (r == 0) {
|
|
if (m < n) /* Too few */
|
|
return -EINVAL;
|
|
|
|
break;
|
|
}
|
|
|
|
if (m >= n) /* Too many */
|
|
return -EINVAL;
|
|
|
|
fd = deserialize_fd(fds, w);
|
|
if (fd < 0)
|
|
return fd;
|
|
|
|
fd_array[m++] = fd;
|
|
}
|
|
|
|
memcpy(ret, fd_array, m * sizeof(int));
|
|
fd_array = mfree(fd_array);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int deserialize_strv(const char *value, char ***l) {
|
|
ssize_t unescaped_len;
|
|
char *unescaped;
|
|
|
|
assert(l);
|
|
assert(value);
|
|
|
|
unescaped_len = cunescape(value, 0, &unescaped);
|
|
if (unescaped_len < 0)
|
|
return unescaped_len;
|
|
|
|
return strv_consume(l, unescaped);
|
|
}
|
|
|
|
int deserialize_usec(const char *value, usec_t *ret) {
|
|
int r;
|
|
|
|
assert(value);
|
|
assert(ret);
|
|
|
|
r = safe_atou64(value, ret);
|
|
if (r < 0)
|
|
return log_debug_errno(r, "Failed to parse usec value \"%s\": %m", value);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int deserialize_dual_timestamp(const char *value, dual_timestamp *ret) {
|
|
uint64_t a, b;
|
|
int r, pos;
|
|
|
|
assert(value);
|
|
assert(ret);
|
|
|
|
pos = strspn(value, WHITESPACE);
|
|
if (value[pos] == '-')
|
|
return -EINVAL;
|
|
pos += strspn(value + pos, DIGITS);
|
|
pos += strspn(value + pos, WHITESPACE);
|
|
if (value[pos] == '-')
|
|
return -EINVAL;
|
|
|
|
r = sscanf(value, "%" PRIu64 "%" PRIu64 "%n", &a, &b, &pos);
|
|
if (r != 2)
|
|
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL),
|
|
"Failed to parse dual timestamp value \"%s\".",
|
|
value);
|
|
|
|
if (value[pos] != '\0')
|
|
/* trailing garbage */
|
|
return -EINVAL;
|
|
|
|
*ret = (dual_timestamp) {
|
|
.realtime = a,
|
|
.monotonic = b,
|
|
};
|
|
|
|
return 0;
|
|
}
|
|
|
|
int deserialize_environment(const char *value, char ***list) {
|
|
_cleanup_free_ char *unescaped = NULL;
|
|
ssize_t l;
|
|
int r;
|
|
|
|
assert(value);
|
|
assert(list);
|
|
|
|
/* Changes the *environment strv inline. */
|
|
|
|
l = cunescape(value, 0, &unescaped);
|
|
if (l < 0)
|
|
return log_error_errno(l, "Failed to unescape: %m");
|
|
|
|
r = strv_env_replace_consume(list, TAKE_PTR(unescaped));
|
|
if (r < 0)
|
|
return log_error_errno(r, "Failed to append environment variable: %m");
|
|
|
|
return 0;
|
|
}
|
|
|
|
int deserialize_pidref(FDSet *fds, const char *value, PidRef *ret) {
|
|
const char *e;
|
|
int r;
|
|
|
|
assert(value);
|
|
assert(ret);
|
|
|
|
e = startswith(value, "@");
|
|
if (e) {
|
|
_cleanup_free_ char *fdstr = NULL, *pidstr = NULL;
|
|
_cleanup_close_ int fd = -EBADF;
|
|
|
|
r = extract_many_words(&e, ":", /* flags = */ 0, &fdstr, &pidstr);
|
|
if (r < 0)
|
|
return log_debug_errno(r, "Failed to deserialize pidref '%s': %m", e);
|
|
if (r == 0)
|
|
return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot deserialize pidref from empty string.");
|
|
|
|
assert(r <= 2);
|
|
|
|
fd = deserialize_fd(fds, fdstr);
|
|
if (fd < 0)
|
|
return fd;
|
|
|
|
/* The serialization format changed after 255.4. In systemd <= 255.4 only pidfd is
|
|
* serialized, but that causes problems when reconstructing pidref (see serialize_pidref for
|
|
* details). After 255.4 the pid is serialized as well even if we have a pidfd, but we still
|
|
* need to support older format as we might be upgrading from a version that still uses the
|
|
* old format. */
|
|
if (pidstr) {
|
|
pid_t pid;
|
|
|
|
r = parse_pid(pidstr, &pid);
|
|
if (r < 0)
|
|
return log_debug_errno(r, "Failed to parse PID: %s", pidstr);
|
|
|
|
*ret = (PidRef) {
|
|
.pid = pid,
|
|
.fd = TAKE_FD(fd),
|
|
};
|
|
} else
|
|
r = pidref_set_pidfd_consume(ret, TAKE_FD(fd));
|
|
} else {
|
|
pid_t pid;
|
|
|
|
r = parse_pid(value, &pid);
|
|
if (r < 0)
|
|
return log_debug_errno(r, "Failed to parse PID: %s", value);
|
|
|
|
r = pidref_set_pid(ret, pid);
|
|
}
|
|
if (r < 0)
|
|
return log_debug_errno(r, "Failed to initialize pidref: %m");
|
|
|
|
return 0;
|
|
}
|
|
|
|
void deserialize_ratelimit(RateLimit *rl, const char *name, const char *value) {
|
|
usec_t begin, interval;
|
|
unsigned num, burst;
|
|
|
|
assert(rl);
|
|
assert(name);
|
|
assert(value);
|
|
|
|
if (sscanf(value, USEC_FMT " " USEC_FMT " %u %u", &begin, &interval, &num, &burst) != 4)
|
|
return log_notice("Failed to parse %s, ignoring: %s", name, value);
|
|
|
|
/* Preserve the counter only if the configuration didn't change. */
|
|
rl->num = (interval == rl->interval && burst == rl->burst) ? num : 0;
|
|
rl->begin = begin;
|
|
}
|
|
|
|
int open_serialization_fd(const char *ident) {
|
|
int fd;
|
|
|
|
fd = memfd_create_wrapper(ident, MFD_CLOEXEC | MFD_NOEXEC_SEAL);
|
|
if (fd < 0) {
|
|
const char *path;
|
|
|
|
path = getpid_cached() == 1 ? "/run/systemd" : "/tmp";
|
|
fd = open_tmpfile_unlinkable(path, O_RDWR|O_CLOEXEC);
|
|
if (fd < 0)
|
|
return fd;
|
|
|
|
log_debug("Serializing %s to %s.", ident, path);
|
|
} else
|
|
log_debug("Serializing %s to memfd.", ident);
|
|
|
|
return fd;
|
|
}
|
|
|
|
int open_serialization_file(const char *ident, FILE **ret) {
|
|
_cleanup_fclose_ FILE *f = NULL;
|
|
_cleanup_close_ int fd;
|
|
|
|
assert(ret);
|
|
|
|
fd = open_serialization_fd(ident);
|
|
if (fd < 0)
|
|
return fd;
|
|
|
|
f = take_fdopen(&fd, "w+");
|
|
if (!f)
|
|
return -errno;
|
|
|
|
*ret = TAKE_PTR(f);
|
|
|
|
return 0;
|
|
}
|