Merge pull request #8620 from poettering/portablectl
an implementation of the "portable services" concept
This commit is contained in:
+86
-42
@@ -21,18 +21,28 @@
|
||||
#include "macro.h"
|
||||
#include "missing.h"
|
||||
#include "path-util.h"
|
||||
#include "set.h"
|
||||
#include "stat-util.h"
|
||||
#include "string-util.h"
|
||||
#include "strv.h"
|
||||
#include "terminal-util.h"
|
||||
#include "util.h"
|
||||
|
||||
static int files_add(Hashmap *h, const char *suffix, const char *root, unsigned flags, const char *path) {
|
||||
static int files_add(
|
||||
Hashmap *h,
|
||||
Set *masked,
|
||||
const char *suffix,
|
||||
const char *root,
|
||||
unsigned flags,
|
||||
const char *path) {
|
||||
|
||||
_cleanup_closedir_ DIR *dir = NULL;
|
||||
const char *dirpath;
|
||||
struct dirent *de;
|
||||
int r;
|
||||
|
||||
assert(h);
|
||||
assert((flags & CONF_FILES_FILTER_MASKED) == 0 || masked);
|
||||
assert(path);
|
||||
|
||||
dirpath = prefix_roota(root, path);
|
||||
@@ -41,62 +51,89 @@ static int files_add(Hashmap *h, const char *suffix, const char *root, unsigned
|
||||
if (!dir) {
|
||||
if (errno == ENOENT)
|
||||
return 0;
|
||||
return -errno;
|
||||
|
||||
return log_debug_errno(errno, "Failed to open directory '%s': %m", dirpath);
|
||||
}
|
||||
|
||||
FOREACH_DIRENT(de, dir, return -errno) {
|
||||
char *p;
|
||||
struct stat st;
|
||||
char *p, *key;
|
||||
|
||||
if (!dirent_is_file_with_suffix(de, suffix)) {
|
||||
log_debug("Ignoring %s/%s, because it's not a regular file with suffix %s.", dirpath, de->d_name, strna(suffix));
|
||||
/* Does this match the suffix? */
|
||||
if (suffix && !endswith(de->d_name, suffix))
|
||||
continue;
|
||||
|
||||
/* Has this file already been found in an earlier directory? */
|
||||
if (hashmap_contains(h, de->d_name)) {
|
||||
log_debug("Skipping overridden file '%s/%s'.", dirpath, de->d_name);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (flags & CONF_FILES_EXECUTABLE) {
|
||||
struct stat st;
|
||||
|
||||
/* As requested: check if the file is marked exectuable. Note that we don't check access(X_OK)
|
||||
* here, as we care about whether the file is marked executable at all, and not whether it is
|
||||
* executable for us, because if such errors are stuff we should log about. */
|
||||
/* Has this been masked in an earlier directory? */
|
||||
if ((flags & CONF_FILES_FILTER_MASKED) && set_contains(masked, de->d_name)) {
|
||||
log_debug("File '%s/%s' is masked by previous entry.", dirpath, de->d_name);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Read file metadata if we shall validate the check for file masks, for node types or whether the node is marked executable. */
|
||||
if (flags & (CONF_FILES_FILTER_MASKED|CONF_FILES_REGULAR|CONF_FILES_DIRECTORY|CONF_FILES_EXECUTABLE))
|
||||
if (fstatat(dirfd(dir), de->d_name, &st, 0) < 0) {
|
||||
log_debug_errno(errno, "Failed to stat %s/%s, ignoring: %m", dirpath, de->d_name);
|
||||
log_debug_errno(errno, "Failed to stat '%s/%s', ignoring: %m", dirpath, de->d_name);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!null_or_empty(&st)) {
|
||||
/* A mask is a symlink to /dev/null or an empty file. It does not even
|
||||
* have to be executable. Other entries must be regular executable files
|
||||
* or symlinks to them. */
|
||||
if (S_ISREG(st.st_mode)) {
|
||||
if ((st.st_mode & 0111) == 0) { /* not executable */
|
||||
log_debug("Ignoring %s/%s, as it is not marked executable.",
|
||||
dirpath, de->d_name);
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
log_debug("Ignoring %s/%s, as it is neither a regular file nor a mask.",
|
||||
dirpath, de->d_name);
|
||||
continue;
|
||||
}
|
||||
/* Is this a masking entry? */
|
||||
if ((flags & CONF_FILES_FILTER_MASKED))
|
||||
if (null_or_empty(&st)) {
|
||||
/* Mark this one as masked */
|
||||
r = set_put_strdup(masked, de->d_name);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
log_debug("File '%s/%s' is a mask.", dirpath, de->d_name);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Does this node have the right type? */
|
||||
if (flags & (CONF_FILES_REGULAR|CONF_FILES_DIRECTORY))
|
||||
if (!((flags & CONF_FILES_DIRECTORY) && S_ISDIR(st.st_mode)) &&
|
||||
!((flags & CONF_FILES_REGULAR) && S_ISREG(st.st_mode))) {
|
||||
log_debug("Ignoring '%s/%s', as it is not a of the right type.", dirpath, de->d_name);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Does this node have the executable bit set? */
|
||||
if (flags & CONF_FILES_EXECUTABLE)
|
||||
/* As requested: check if the file is marked exectuable. Note that we don't check access(X_OK)
|
||||
* here, as we care about whether the file is marked executable at all, and not whether it is
|
||||
* executable for us, because if so, such errors are stuff we should log about. */
|
||||
|
||||
if ((st.st_mode & 0111) == 0) { /* not executable */
|
||||
log_debug("Ignoring '%s/%s', as it is not marked executable.", dirpath, de->d_name);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (flags & CONF_FILES_BASENAME) {
|
||||
p = strdup(de->d_name);
|
||||
if (!p)
|
||||
return -ENOMEM;
|
||||
|
||||
key = p;
|
||||
} else {
|
||||
p = strjoin(dirpath, "/", de->d_name);
|
||||
if (!p)
|
||||
return -ENOMEM;
|
||||
|
||||
key = basename(p);
|
||||
}
|
||||
|
||||
p = strjoin(dirpath, "/", de->d_name);
|
||||
if (!p)
|
||||
return -ENOMEM;
|
||||
|
||||
r = hashmap_put(h, basename(p), p);
|
||||
if (r == -EEXIST) {
|
||||
log_debug("Skipping overridden file: %s.", p);
|
||||
free(p);
|
||||
} else if (r < 0) {
|
||||
free(p);
|
||||
return r;
|
||||
} else if (r == 0) {
|
||||
log_debug("Duplicate file %s", p);
|
||||
r = hashmap_put(h, key, p);
|
||||
if (r < 0) {
|
||||
free(p);
|
||||
return log_debug_errno(r, "Failed to add item to hashmap: %m");
|
||||
}
|
||||
|
||||
assert(r > 0);
|
||||
}
|
||||
|
||||
return 0;
|
||||
@@ -112,6 +149,7 @@ static int base_cmp(const void *a, const void *b) {
|
||||
|
||||
static int conf_files_list_strv_internal(char ***strv, const char *suffix, const char *root, unsigned flags, char **dirs) {
|
||||
_cleanup_hashmap_free_ Hashmap *fh = NULL;
|
||||
_cleanup_set_free_free_ Set *masked = NULL;
|
||||
char **files, **p;
|
||||
int r;
|
||||
|
||||
@@ -121,12 +159,18 @@ static int conf_files_list_strv_internal(char ***strv, const char *suffix, const
|
||||
if (!path_strv_resolve_uniq(dirs, root))
|
||||
return -ENOMEM;
|
||||
|
||||
fh = hashmap_new(&string_hash_ops);
|
||||
fh = hashmap_new(&path_hash_ops);
|
||||
if (!fh)
|
||||
return -ENOMEM;
|
||||
|
||||
if (flags & CONF_FILES_FILTER_MASKED) {
|
||||
masked = set_new(&path_hash_ops);
|
||||
if (!masked)
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
STRV_FOREACH(p, dirs) {
|
||||
r = files_add(fh, suffix, root, flags, *p);
|
||||
r = files_add(fh, masked, suffix, root, flags, *p);
|
||||
if (r == -ENOMEM)
|
||||
return r;
|
||||
if (r < 0)
|
||||
|
||||
@@ -9,7 +9,11 @@
|
||||
***/
|
||||
|
||||
enum {
|
||||
CONF_FILES_EXECUTABLE = 1,
|
||||
CONF_FILES_EXECUTABLE = 1U << 0,
|
||||
CONF_FILES_REGULAR = 1U << 1,
|
||||
CONF_FILES_DIRECTORY = 1U << 2,
|
||||
CONF_FILES_BASENAME = 1U << 3,
|
||||
CONF_FILES_FILTER_MASKED = 1U << 4,
|
||||
};
|
||||
|
||||
int conf_files_list(char ***ret, const char *suffix, const char *root, unsigned flags, const char *dir, ...);
|
||||
|
||||
@@ -89,7 +89,7 @@ static int do_execute(
|
||||
* If callbacks is nonnull, execution is serial. Otherwise, we default to parallel.
|
||||
*/
|
||||
|
||||
r = conf_files_list_strv(&paths, NULL, NULL, CONF_FILES_EXECUTABLE, (const char* const*) directories);
|
||||
r = conf_files_list_strv(&paths, NULL, NULL, CONF_FILES_EXECUTABLE|CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED, (const char* const*) directories);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
|
||||
@@ -12,10 +12,13 @@
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "copy.h"
|
||||
#include "dirent-util.h"
|
||||
#include "fd-util.h"
|
||||
#include "fileio.h"
|
||||
#include "fs-util.h"
|
||||
#include "io-util.h"
|
||||
#include "macro.h"
|
||||
#include "memfd-util.h"
|
||||
#include "missing.h"
|
||||
@@ -562,6 +565,202 @@ try_dev_shm_without_o_tmpfile:
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
/* When the data is smaller or equal to 64K, try to place the copy in a memfd/pipe */
|
||||
#define DATA_FD_MEMORY_LIMIT (64U*1024U)
|
||||
|
||||
/* If memfd/pipe didn't work out, then let's use a file in /tmp up to a size of 1M. If it's large than that use /var/tmp instead. */
|
||||
#define DATA_FD_TMP_LIMIT (1024U*1024U)
|
||||
|
||||
int fd_duplicate_data_fd(int fd) {
|
||||
|
||||
_cleanup_close_ int copy_fd = -1, tmp_fd = -1;
|
||||
_cleanup_free_ void *remains = NULL;
|
||||
_cleanup_free_ char *t = NULL;
|
||||
size_t remains_size = 0;
|
||||
const char *td;
|
||||
struct stat st;
|
||||
int r;
|
||||
|
||||
/* Creates a 'data' fd from the specified source fd, containing all the same data in a read-only fashion, but
|
||||
* independent of it (i.e. the source fd can be closed and unmounted after this call succeeded). Tries to be
|
||||
* somewhat smart about where to place the data. In the best case uses a memfd(). If memfd() are not supported
|
||||
* uses a pipe instead. For larger data will use an unlinked file in /tmp, and for even larger data one in
|
||||
* /var/tmp. */
|
||||
|
||||
if (fstat(fd, &st) < 0)
|
||||
return -errno;
|
||||
|
||||
/* For now, let's only accept regular files, sockets, pipes and char devices */
|
||||
if (S_ISDIR(st.st_mode))
|
||||
return -EISDIR;
|
||||
if (S_ISLNK(st.st_mode))
|
||||
return -ELOOP;
|
||||
if (!S_ISREG(st.st_mode) && !S_ISSOCK(st.st_mode) && !S_ISFIFO(st.st_mode) && !S_ISCHR(st.st_mode))
|
||||
return -EBADFD;
|
||||
|
||||
/* If we have reason to believe the data is bounded in size, then let's use memfds or pipes as backing fd. Note
|
||||
* that we use the reported regular file size only as a hint, given that there are plenty special files in
|
||||
* /proc and /sys which report a zero file size but can be read from. */
|
||||
|
||||
if (!S_ISREG(st.st_mode) || st.st_size < DATA_FD_MEMORY_LIMIT) {
|
||||
|
||||
/* Try a memfd first */
|
||||
copy_fd = memfd_new("data-fd");
|
||||
if (copy_fd >= 0) {
|
||||
off_t f;
|
||||
|
||||
r = copy_bytes(fd, copy_fd, DATA_FD_MEMORY_LIMIT, 0);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
f = lseek(copy_fd, 0, SEEK_SET);
|
||||
if (f != 0)
|
||||
return -errno;
|
||||
|
||||
if (r == 0) {
|
||||
/* Did it fit into the limit? If so, we are done. */
|
||||
r = memfd_set_sealed(copy_fd);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return TAKE_FD(copy_fd);
|
||||
}
|
||||
|
||||
/* Hmm, pity, this didn't fit. Let's fall back to /tmp then, see below */
|
||||
|
||||
} else {
|
||||
_cleanup_(close_pairp) int pipefds[2] = { -1, -1 };
|
||||
int isz;
|
||||
|
||||
/* If memfds aren't available, use a pipe. Set O_NONBLOCK so that we will get EAGAIN rather
|
||||
* then block indefinitely when we hit the pipe size limit */
|
||||
|
||||
if (pipe2(pipefds, O_CLOEXEC|O_NONBLOCK) < 0)
|
||||
return -errno;
|
||||
|
||||
isz = fcntl(pipefds[1], F_GETPIPE_SZ, 0);
|
||||
if (isz < 0)
|
||||
return -errno;
|
||||
|
||||
/* Try to enlarge the pipe size if necessary */
|
||||
if ((size_t) isz < DATA_FD_MEMORY_LIMIT) {
|
||||
|
||||
(void) fcntl(pipefds[1], F_SETPIPE_SZ, DATA_FD_MEMORY_LIMIT);
|
||||
|
||||
isz = fcntl(pipefds[1], F_GETPIPE_SZ, 0);
|
||||
if (isz < 0)
|
||||
return -errno;
|
||||
}
|
||||
|
||||
if ((size_t) isz >= DATA_FD_MEMORY_LIMIT) {
|
||||
|
||||
r = copy_bytes_full(fd, pipefds[1], DATA_FD_MEMORY_LIMIT, 0, &remains, &remains_size);
|
||||
if (r < 0 && r != -EAGAIN)
|
||||
return r; /* If we get EAGAIN it could be because of the source or because of
|
||||
* the destination fd, we can't know, as sendfile() and friends won't
|
||||
* tell us. Hence, treat this as reason to fall back, just to be
|
||||
* sure. */
|
||||
if (r == 0) {
|
||||
/* Everything fit in, yay! */
|
||||
(void) fd_nonblock(pipefds[0], false);
|
||||
|
||||
return TAKE_FD(pipefds[0]);
|
||||
}
|
||||
|
||||
/* Things didn't fit in. But we read data into the pipe, let's remember that, so that
|
||||
* when writing the new file we incorporate this first. */
|
||||
copy_fd = TAKE_FD(pipefds[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* If we have reason to believe this will fit fine in /tmp, then use that as first fallback. */
|
||||
if ((!S_ISREG(st.st_mode) || st.st_size < DATA_FD_TMP_LIMIT) &&
|
||||
(DATA_FD_MEMORY_LIMIT + remains_size) < DATA_FD_TMP_LIMIT) {
|
||||
off_t f;
|
||||
|
||||
tmp_fd = open_tmpfile_unlinkable(NULL /* NULL as directory means /tmp */, O_RDWR|O_CLOEXEC);
|
||||
if (tmp_fd < 0)
|
||||
return tmp_fd;
|
||||
|
||||
if (copy_fd >= 0) {
|
||||
/* If we tried a memfd/pipe first and it ended up being too large, then copy this into the
|
||||
* temporary file first. */
|
||||
|
||||
r = copy_bytes(copy_fd, tmp_fd, UINT64_MAX, 0);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
assert(r == 0);
|
||||
}
|
||||
|
||||
if (remains_size > 0) {
|
||||
/* If there were remaining bytes (i.e. read into memory, but not written out yet) from the
|
||||
* failed copy operation, let's flush them out next. */
|
||||
|
||||
r = loop_write(tmp_fd, remains, remains_size, false);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
r = copy_bytes(fd, tmp_fd, DATA_FD_TMP_LIMIT - DATA_FD_MEMORY_LIMIT - remains_size, COPY_REFLINK);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r == 0)
|
||||
goto finish; /* Yay, it fit in */
|
||||
|
||||
/* It didn't fit in. Let's not forget to use what we already used */
|
||||
f = lseek(tmp_fd, 0, SEEK_SET);
|
||||
if (f != 0)
|
||||
return -errno;
|
||||
|
||||
safe_close(copy_fd);
|
||||
copy_fd = TAKE_FD(tmp_fd);
|
||||
|
||||
remains = mfree(remains);
|
||||
remains_size = 0;
|
||||
}
|
||||
|
||||
/* As last fallback use /var/tmp */
|
||||
r = var_tmp_dir(&td);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
tmp_fd = open_tmpfile_unlinkable(td, O_RDWR|O_CLOEXEC);
|
||||
if (tmp_fd < 0)
|
||||
return tmp_fd;
|
||||
|
||||
if (copy_fd >= 0) {
|
||||
/* If we tried a memfd/pipe first, or a file in /tmp, and it ended up being too large, than copy this
|
||||
* into the temporary file first. */
|
||||
r = copy_bytes(copy_fd, tmp_fd, UINT64_MAX, COPY_REFLINK);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
assert(r == 0);
|
||||
}
|
||||
|
||||
if (remains_size > 0) {
|
||||
/* Then, copy in any read but not yet written bytes. */
|
||||
r = loop_write(tmp_fd, remains, remains_size, false);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
/* Copy in the rest */
|
||||
r = copy_bytes(fd, tmp_fd, UINT64_MAX, COPY_REFLINK);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
assert(r == 0);
|
||||
|
||||
finish:
|
||||
/* Now convert the O_RDWR file descriptor into an O_RDONLY one (and as side effect seek to the beginning of the
|
||||
* file again */
|
||||
|
||||
return fd_reopen(tmp_fd, O_RDONLY|O_CLOEXEC);
|
||||
}
|
||||
|
||||
int fd_move_above_stdio(int fd) {
|
||||
int flags, copy;
|
||||
PROTECT_ERRNO;
|
||||
|
||||
@@ -81,6 +81,8 @@ enum {
|
||||
|
||||
int acquire_data_fd(const void *data, size_t size, unsigned flags);
|
||||
|
||||
int fd_duplicate_data_fd(int fd);
|
||||
|
||||
/* Hint: ENETUNREACH happens if we try to connect to "non-existing" special IP addresses, such as ::5 */
|
||||
#define ERRNO_IS_DISCONNECT(r) \
|
||||
IN_SET(r, ENOTCONN, ECONNRESET, ECONNREFUSED, ECONNABORTED, EPIPE, ENETUNREACH)
|
||||
|
||||
+44
-18
@@ -263,29 +263,35 @@ int verify_file(const char *fn, const char *blob, bool accept_extra_nl) {
|
||||
}
|
||||
|
||||
int read_full_stream(FILE *f, char **contents, size_t *size) {
|
||||
size_t n, l;
|
||||
_cleanup_free_ char *buf = NULL;
|
||||
struct stat st;
|
||||
size_t n, l;
|
||||
int fd;
|
||||
|
||||
assert(f);
|
||||
assert(contents);
|
||||
|
||||
if (fstat(fileno(f), &st) < 0)
|
||||
return -errno;
|
||||
|
||||
n = LINE_MAX;
|
||||
|
||||
if (S_ISREG(st.st_mode)) {
|
||||
fd = fileno(f);
|
||||
if (fd >= 0) { /* If the FILE* object is backed by an fd (as opposed to memory or such, see fmemopen(), let's
|
||||
* optimize our buffering) */
|
||||
|
||||
/* Safety check */
|
||||
if (st.st_size > READ_FULL_BYTES_MAX)
|
||||
return -E2BIG;
|
||||
if (fstat(fileno(f), &st) < 0)
|
||||
return -errno;
|
||||
|
||||
/* Start with the right file size, but be prepared for files from /proc which generally report a file
|
||||
* size of 0. Note that we increase the size to read here by one, so that the first read attempt
|
||||
* already makes us notice the EOF. */
|
||||
if (st.st_size > 0)
|
||||
n = st.st_size + 1;
|
||||
if (S_ISREG(st.st_mode)) {
|
||||
|
||||
/* Safety check */
|
||||
if (st.st_size > READ_FULL_BYTES_MAX)
|
||||
return -E2BIG;
|
||||
|
||||
/* Start with the right file size, but be prepared for files from /proc which generally report a file
|
||||
* size of 0. Note that we increase the size to read here by one, so that the first read attempt
|
||||
* already makes us notice the EOF. */
|
||||
if (st.st_size > 0)
|
||||
n = st.st_size + 1;
|
||||
}
|
||||
}
|
||||
|
||||
l = 0;
|
||||
@@ -676,21 +682,41 @@ static int parse_env_file_push(
|
||||
return 0;
|
||||
}
|
||||
|
||||
int parse_env_file(
|
||||
int parse_env_filev(
|
||||
FILE *f,
|
||||
const char *fname,
|
||||
const char *newline, ...) {
|
||||
const char *newline,
|
||||
va_list ap) {
|
||||
|
||||
va_list ap;
|
||||
int r, n_pushed = 0;
|
||||
va_list aq;
|
||||
|
||||
if (!newline)
|
||||
newline = NEWLINE;
|
||||
|
||||
va_copy(aq, ap);
|
||||
r = parse_env_file_internal(f, fname, newline, parse_env_file_push, &aq, &n_pushed);
|
||||
va_end(aq);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return n_pushed;
|
||||
}
|
||||
|
||||
int parse_env_file(
|
||||
FILE *f,
|
||||
const char *fname,
|
||||
const char *newline,
|
||||
...) {
|
||||
|
||||
va_list ap;
|
||||
int r;
|
||||
|
||||
va_start(ap, newline);
|
||||
r = parse_env_file_internal(NULL, fname, newline, parse_env_file_push, &ap, &n_pushed);
|
||||
r = parse_env_filev(f, fname, newline, ap);
|
||||
va_end(ap);
|
||||
|
||||
return r < 0 ? r : n_pushed;
|
||||
return r;
|
||||
}
|
||||
|
||||
static int load_env_file_push(
|
||||
|
||||
+2
-1
@@ -45,7 +45,8 @@ int read_full_stream(FILE *f, char **contents, size_t *size);
|
||||
|
||||
int verify_file(const char *fn, const char *blob, bool accept_extra_nl);
|
||||
|
||||
int parse_env_file(const char *fname, const char *separator, ...) _sentinel_;
|
||||
int parse_env_filev(FILE *f, const char *fname, const char *separator, va_list ap);
|
||||
int parse_env_file(FILE *f, const char *fname, const char *separator, ...) _sentinel_;
|
||||
int load_env_file(FILE *f, const char *fname, const char *separator, char ***l);
|
||||
int load_env_file_pairs(FILE *f, const char *fname, const char *separator, char ***l);
|
||||
|
||||
|
||||
@@ -131,6 +131,8 @@ basic_sources = files('''
|
||||
ordered-set.h
|
||||
pager.c
|
||||
pager.h
|
||||
os-util.c
|
||||
os-util.h
|
||||
parse-util.c
|
||||
parse-util.h
|
||||
path-util.c
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "fd-util.h"
|
||||
#include "fs-util.h"
|
||||
#include "macro.h"
|
||||
#include "os-util.h"
|
||||
#include "strv.h"
|
||||
#include "fileio.h"
|
||||
#include "string-util.h"
|
||||
|
||||
int path_is_os_tree(const char *path) {
|
||||
int r;
|
||||
|
||||
assert(path);
|
||||
|
||||
/* Does the path exist at all? If not, generate an error immediately. This is useful so that a missing root dir
|
||||
* always results in -ENOENT, and we can properly distuingish the case where the whole root doesn't exist from
|
||||
* the case where just the os-release file is missing. */
|
||||
if (laccess(path, F_OK) < 0)
|
||||
return -errno;
|
||||
|
||||
/* We use {/etc|/usr/lib}/os-release as flag file if something is an OS */
|
||||
r = open_os_release(path, NULL, NULL);
|
||||
if (r == -ENOENT) /* We got nothing */
|
||||
return 0;
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int open_os_release(const char *root, char **ret_path, int *ret_fd) {
|
||||
_cleanup_free_ char *q = NULL;
|
||||
const char *p;
|
||||
int k;
|
||||
|
||||
FOREACH_STRING(p, "/etc/os-release", "/usr/lib/os-release") {
|
||||
k = chase_symlinks(p, root, CHASE_PREFIX_ROOT|(ret_fd ? CHASE_OPEN : 0), (ret_path ? &q : NULL));
|
||||
if (k != -ENOENT)
|
||||
break;
|
||||
}
|
||||
if (k < 0)
|
||||
return k;
|
||||
|
||||
if (ret_fd) {
|
||||
int real_fd;
|
||||
|
||||
/* Convert the O_PATH fd into a proper, readable one */
|
||||
real_fd = fd_reopen(k, O_RDONLY|O_CLOEXEC|O_NOCTTY);
|
||||
safe_close(k);
|
||||
if (real_fd < 0)
|
||||
return real_fd;
|
||||
|
||||
*ret_fd = real_fd;
|
||||
}
|
||||
|
||||
if (ret_path)
|
||||
*ret_path = TAKE_PTR(q);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fopen_os_release(const char *root, char **ret_path, FILE **ret_file) {
|
||||
_cleanup_free_ char *p = NULL;
|
||||
_cleanup_close_ int fd = -1;
|
||||
FILE *f;
|
||||
int r;
|
||||
|
||||
if (!ret_file)
|
||||
return open_os_release(root, ret_path, NULL);
|
||||
|
||||
r = open_os_release(root, ret_path ? &p : NULL, &fd);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
f = fdopen(fd, "re");
|
||||
if (!f)
|
||||
return -errno;
|
||||
fd = -1;
|
||||
|
||||
*ret_file = f;
|
||||
|
||||
if (ret_path)
|
||||
*ret_path = TAKE_PTR(p);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int parse_os_release(const char *root, ...) {
|
||||
_cleanup_fclose_ FILE *f = NULL;
|
||||
_cleanup_free_ char *p = NULL;
|
||||
va_list ap;
|
||||
int r;
|
||||
|
||||
r = fopen_os_release(root, &p, &f);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
va_start(ap, root);
|
||||
r = parse_env_filev(f, p, NEWLINE, ap);
|
||||
va_end(ap);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
int load_os_release_pairs(const char *root, char ***ret) {
|
||||
_cleanup_fclose_ FILE *f = NULL;
|
||||
_cleanup_free_ char *p = NULL;
|
||||
int r;
|
||||
|
||||
r = fopen_os_release(root, &p, &f);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return load_env_file_pairs(f, p, NEWLINE, ret);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1+ */
|
||||
#pragma once
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
int path_is_os_tree(const char *path);
|
||||
|
||||
int open_os_release(const char *root, char **ret_path, int *ret_fd);
|
||||
int fopen_os_release(const char *root, char **ret_path, FILE **ret_file);
|
||||
|
||||
int parse_os_release(const char *root, ...);
|
||||
int load_os_release_pairs(const char *root, char ***ret);
|
||||
@@ -17,6 +17,7 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/mount.h>
|
||||
#include <sys/personality.h>
|
||||
#include <sys/prctl.h>
|
||||
#include <sys/types.h>
|
||||
@@ -1344,6 +1345,16 @@ int safe_fork_full(
|
||||
}
|
||||
}
|
||||
|
||||
if ((flags & (FORK_NEW_MOUNTNS|FORK_MOUNTNS_SLAVE)) == (FORK_NEW_MOUNTNS|FORK_MOUNTNS_SLAVE)) {
|
||||
|
||||
/* Optionally, make sure we never propagate mounts to the host. */
|
||||
|
||||
if (mount(NULL, "/", NULL, MS_SLAVE | MS_REC, NULL) < 0) {
|
||||
log_full_errno(prio, errno, "Failed to remount root directory as MS_SLAVE: %m");
|
||||
_exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
if (flags & FORK_CLOSE_ALL_FDS) {
|
||||
/* Close the logs here in case it got reopened above, as close_all_fds() would close them for us */
|
||||
log_close();
|
||||
|
||||
@@ -163,6 +163,7 @@ typedef enum ForkFlags {
|
||||
FORK_LOG = 1U << 5,
|
||||
FORK_WAIT = 1U << 6,
|
||||
FORK_NEW_MOUNTNS = 1U << 7,
|
||||
FORK_MOUNTNS_SLAVE = 1U << 8,
|
||||
} ForkFlags;
|
||||
|
||||
int safe_fork_full(const char *name, const int except_fds[], size_t n_except_fds, ForkFlags flags, pid_t *ret_pid);
|
||||
|
||||
@@ -132,32 +132,6 @@ int path_is_read_only_fs(const char *path) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int path_is_os_tree(const char *path) {
|
||||
int r;
|
||||
|
||||
assert(path);
|
||||
|
||||
/* Does the path exist at all? If not, generate an error immediately. This is useful so that a missing root dir
|
||||
* always results in -ENOENT, and we can properly distuingish the case where the whole root doesn't exist from
|
||||
* the case where just the os-release file is missing. */
|
||||
if (laccess(path, F_OK) < 0)
|
||||
return -errno;
|
||||
|
||||
/* We use /usr/lib/os-release as flag file if something is an OS */
|
||||
r = chase_symlinks("/usr/lib/os-release", path, CHASE_PREFIX_ROOT, NULL);
|
||||
if (r == -ENOENT) {
|
||||
|
||||
/* Also check for the old location in /etc, just in case. */
|
||||
r = chase_symlinks("/etc/os-release", path, CHASE_PREFIX_ROOT, NULL);
|
||||
if (r == -ENOENT)
|
||||
return 0; /* We got nothing */
|
||||
}
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int files_same(const char *filea, const char *fileb, int flags) {
|
||||
struct stat a, b;
|
||||
|
||||
|
||||
@@ -35,7 +35,6 @@ int null_or_empty_path(const char *fn);
|
||||
int null_or_empty_fd(int fd);
|
||||
|
||||
int path_is_read_only_fs(const char *path);
|
||||
int path_is_os_tree(const char *path);
|
||||
|
||||
int files_same(const char *filea, const char *fileb, int flags);
|
||||
|
||||
|
||||
+1
-1
@@ -264,7 +264,7 @@ int container_get_leader(const char *machine, pid_t *pid) {
|
||||
return -EINVAL;
|
||||
|
||||
p = strjoina("/run/systemd/machines/", machine);
|
||||
r = parse_env_file(p, NEWLINE, "LEADER", &s, "CLASS", &class, NULL);
|
||||
r = parse_env_file(NULL, p, NEWLINE, "LEADER", &s, "CLASS", &class, NULL);
|
||||
if (r == -ENOENT)
|
||||
return -EHOSTDOWN;
|
||||
if (r < 0)
|
||||
|
||||
Reference in New Issue
Block a user