diff --git a/src/basic/fileio.c b/src/basic/fileio.c index 661667a6b2..8149bb8b0d 100644 --- a/src/basic/fileio.c +++ b/src/basic/fileio.c @@ -420,17 +420,31 @@ int read_one_line_file_at(int dir_fd, const char *filename, char **ret) { _cleanup_fclose_ FILE *f = NULL; int r; - assert(dir_fd >= 0 || dir_fd == AT_FDCWD); + assert(dir_fd >= 0 || IN_SET(dir_fd, AT_FDCWD, XAT_FDROOT)); assert(filename); assert(ret); - r = fopen_unlocked_at(dir_fd, filename, "re", 0, &f); + r = fopen_unlocked_at(dir_fd, filename, "re", /* open_flags= */ 0, &f); if (r < 0) return r; return read_line(f, LONG_LINE_MAX, ret); } +int read_boolean_file_at(int dir_fd, const char *filename) { + _cleanup_free_ char *s = NULL; + int r; + + assert(dir_fd >= 0 || IN_SET(dir_fd, AT_FDCWD, XAT_FDROOT)); + assert(filename); + + r = read_one_line_file_at(dir_fd, filename, &s); + if (r < 0) + return r; + + return parse_boolean(s); +} + int verify_file_at(int dir_fd, const char *fn, const char *blob, bool accept_extra_nl) { _cleanup_fclose_ FILE *f = NULL; _cleanup_free_ char *buf = NULL; @@ -1010,13 +1024,19 @@ static int xfopenat_regular(int dir_fd, const char *path, const char *mode, int /* A combination of fopen() with openat() */ - assert(dir_fd >= 0 || dir_fd == AT_FDCWD); + assert(dir_fd >= 0 || IN_SET(dir_fd, AT_FDCWD, XAT_FDROOT)); assert(mode); assert(ret); if (dir_fd == AT_FDCWD && path && open_flags == 0) f = fopen(path, mode); - else { + else if (dir_fd == XAT_FDROOT && path && open_flags == 0) { + _cleanup_free_ char *j = strjoin("/", path); + if (!j) + return -ENOMEM; + + f = fopen(j, mode); + } else { _cleanup_close_ int fd = -EBADF; int mode_flags; @@ -1051,7 +1071,7 @@ static int xfopenat_unix_socket(int dir_fd, const char *path, const char *bind_n FILE *f; int r; - assert(dir_fd >= 0 || dir_fd == AT_FDCWD); + assert(dir_fd >= 0 || IN_SET(dir_fd, AT_FDCWD, XAT_FDROOT)); assert(ret); sk = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0); @@ -1099,7 +1119,7 @@ int xfopenat_full( FILE *f = NULL; /* avoid false maybe-uninitialized warning */ int r; - assert(dir_fd >= 0 || dir_fd == AT_FDCWD); + assert(dir_fd >= 0 || IN_SET(dir_fd, AT_FDCWD, XAT_FDROOT)); assert(mode); assert(ret); diff --git a/src/basic/fileio.h b/src/basic/fileio.h index 274fdfbd7c..5e6e3de1fe 100644 --- a/src/basic/fileio.h +++ b/src/basic/fileio.h @@ -63,6 +63,10 @@ int read_one_line_file_at(int dir_fd, const char *filename, char **ret); static inline int read_one_line_file(const char *filename, char **ret) { return read_one_line_file_at(AT_FDCWD, filename, ret); } +int read_boolean_file_at(int dir_fd, const char *filename); +static inline int read_boolean_file(const char *filename) { + return read_boolean_file_at(AT_FDCWD, filename); +} int read_full_file_full(int dir_fd, const char *filename, uint64_t offset, size_t size, ReadFullFileFlags flags, const char *bind_name, char **ret_contents, size_t *ret_size); static inline int read_full_file_at(int dir_fd, const char *filename, char **ret_contents, size_t *ret_size) { return read_full_file_full(dir_fd, filename, UINT64_MAX, SIZE_MAX, 0, NULL, ret_contents, ret_size); diff --git a/src/basic/socket-util.c b/src/basic/socket-util.c index 2e0ee684ff..d53208f138 100644 --- a/src/basic/socket-util.c +++ b/src/basic/socket-util.c @@ -1619,13 +1619,17 @@ int connect_unix_path(int fd, int dir_fd, const char *path) { _cleanup_close_ int inode_fd = -EBADF; assert(fd >= 0); - assert(dir_fd == AT_FDCWD || dir_fd >= 0); + assert(IN_SET(dir_fd, AT_FDCWD, XAT_FDROOT) || dir_fd >= 0); /* Connects to the specified AF_UNIX socket in the file system. Works around the 108 byte size limit * in sockaddr_un, by going via O_PATH if needed. This hence works for any kind of path. */ - if (!path) + if (!path) { + if (dir_fd < 0) + return -EISDIR; + return connect_unix_inode(fd, dir_fd); /* If no path is specified, then dir_fd refers to the socket inode to connect to. */ + } /* Refuse zero length path early, to make sure AF_UNIX stack won't mistake this for an abstract * namespace path, since first char is NUL */ @@ -1640,7 +1644,14 @@ int connect_unix_path(int fd, int dir_fd, const char *path) { * exist. If the path is too long, we also need to take the indirect route, since we can't fit this * into a sockaddr_un directly. */ - inode_fd = openat(dir_fd, path, O_PATH|O_CLOEXEC); + if (dir_fd == XAT_FDROOT) { + _cleanup_free_ char *j = strjoin("/", path); + if (!j) + return -ENOMEM; + + inode_fd = open(j, O_PATH|O_CLOEXEC); + } else + inode_fd = openat(dir_fd, path, O_PATH|O_CLOEXEC); if (inode_fd < 0) return -errno; diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c index d241f5e7f8..5b3fc8990d 100644 --- a/src/basic/terminal-util.c +++ b/src/basic/terminal-util.c @@ -869,16 +869,8 @@ int vt_disallocate(const char *tty_path) { } static int vt_default_utf8(void) { - _cleanup_free_ char *b = NULL; - int r; - /* Read the default VT UTF8 setting from the kernel */ - - r = read_one_line_file("/sys/module/vt/parameters/default_utf8", &b); - if (r < 0) - return r; - - return parse_boolean(b); + return read_boolean_file("/sys/module/vt/parameters/default_utf8"); } static int vt_reset_keyboard(int fd) { diff --git a/src/journal/journald-console.c b/src/journal/journald-console.c index 0f376f9e5a..0cd2215bd2 100644 --- a/src/journal/journald-console.c +++ b/src/journal/journald-console.c @@ -12,7 +12,6 @@ #include "journald-console.h" #include "journald-manager.h" #include "log.h" -#include "parse-util.h" #include "process-util.h" #include "stdio-util.h" #include "terminal-util.h" @@ -22,13 +21,8 @@ static bool prefix_timestamp(void) { static int cached_printk_time = -1; - if (_unlikely_(cached_printk_time < 0)) { - _cleanup_free_ char *p = NULL; - - cached_printk_time = - read_one_line_file("/sys/module/printk/parameters/time", &p) >= 0 - && parse_boolean(p) > 0; - } + if (_unlikely_(cached_printk_time < 0)) + cached_printk_time = read_boolean_file("/sys/module/printk/parameters/time") > 0; return cached_printk_time; } diff --git a/src/shared/apparmor-util.c b/src/shared/apparmor-util.c index 5f01bfae01..b784d26c5b 100644 --- a/src/shared/apparmor-util.c +++ b/src/shared/apparmor-util.c @@ -9,9 +9,7 @@ #include "sd-dlopen.h" -#include "alloc-util.h" #include "fileio.h" -#include "parse-util.h" static void *libapparmor_dl = NULL; @@ -31,18 +29,13 @@ bool mac_apparmor_use(void) { if (cached_use >= 0) return cached_use; - _cleanup_free_ char *p = NULL; - r = read_one_line_file("/sys/module/apparmor/parameters/enabled", &p); + r = read_boolean_file("/sys/module/apparmor/parameters/enabled"); if (r < 0) { if (r != -ENOENT) - log_debug_errno(r, "Failed to read /sys/module/apparmor/parameters/enabled, assuming AppArmor is not available: %m"); + log_debug_errno(r, "Failed to read and parse /sys/module/apparmor/parameters/enabled, assuming AppArmor is not available: %m"); return (cached_use = false); } - - r = parse_boolean(p); - if (r < 0) - log_debug_errno(r, "Failed to parse /sys/module/apparmor/parameters/enabled, assuming AppArmor is not available: %m"); - if (r <= 0) + if (r == 0) return (cached_use = false); if (dlopen_libapparmor(LOG_DEBUG) < 0) diff --git a/src/test/test-fileio.c b/src/test/test-fileio.c index a752b1c7cd..d050522028 100644 --- a/src/test/test-fileio.c +++ b/src/test/test-fileio.c @@ -747,4 +747,134 @@ TEST(write_data_file_atomic_at) { ASSERT_OK_ERRNO(rmdir("/tmp/zzz")); } +TEST(read_one_line_file_at_xat_fdroot) { + _cleanup_(rm_rf_physical_and_freep) char *t = NULL; + _cleanup_free_ char *fn = NULL, *buf = NULL; + + ASSERT_OK(mkdtemp_malloc("/tmp/test-r1lf-xatfd-XXXXXX", &t)); + ASSERT_TRUE(path_is_absolute(t)); + + ASSERT_NOT_NULL(fn = path_join(t, "hello")); + ASSERT_OK(write_string_file(fn, "first line\nsecond line", WRITE_STRING_FILE_CREATE)); + + /* XAT_FDROOT is supposed to root the path at the host's "/"; the implementation prepends a "/" so + * we pass the path without leading slash. */ + ASSERT_OK_EQ(read_one_line_file_at(XAT_FDROOT, fn + 1, &buf), (int) STRLEN("first line\n")); + ASSERT_STREQ(buf, "first line"); + buf = mfree(buf); + + /* Sanity check: AT_FDCWD with the absolute path gives the same result. */ + ASSERT_OK_EQ(read_one_line_file_at(AT_FDCWD, fn, &buf), (int) STRLEN("first line\n")); + ASSERT_STREQ(buf, "first line"); + buf = mfree(buf); + + /* /proc/version should always be readable via XAT_FDROOT (some build envs may restrict it; tolerate + * that). */ + int r = read_one_line_file_at(XAT_FDROOT, "proc/version", &buf); + if (!ERRNO_IS_NEG_PRIVILEGE(r)) { + ASSERT_OK(r); + ASSERT_FALSE(isempty(buf)); + buf = mfree(buf); + } + + /* Non-existent path through XAT_FDROOT should yield -ENOENT. */ + ASSERT_ERROR(read_one_line_file_at(XAT_FDROOT, "tmp/this/path/really/should/not/exist", &buf), ENOENT); + + /* Now create a Unix socket in the same temp dir, and verify that read_one_line_file_at() returns + * -ENXIO when pointed at it via XAT_FDROOT — read_one_line_file_at() does not enable the socket + * fallback. */ + _cleanup_free_ char *sockpath = NULL; + ASSERT_NOT_NULL(sockpath = path_join(t, "socket")); + + _cleanup_close_ int listener = -EBADF; + ASSERT_OK(listener = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, /* protocol= */ 0)); + + union sockaddr_union sa; + ASSERT_OK(sockaddr_un_set_path(&sa.un, sockpath)); + ASSERT_OK_ERRNO(bind(listener, &sa.sa, sockaddr_un_len(&sa.un))); + ASSERT_OK_ERRNO(listen(listener, 1)); + + ASSERT_ERROR(read_one_line_file_at(XAT_FDROOT, sockpath + 1, &buf), ENXIO); + + /* But read_full_file_full() with READ_FULL_FILE_CONNECT_SOCKET *does* enable the socket fallback, + * which routes through xfopenat_unix_socket() and connect_unix_path() — both now teach the + * XAT_FDROOT codepath. Use that to exercise the socket open via XAT_FDROOT. */ + static const char test_sock_str[] = "hello via xat_fdroot socket\n"; + + _cleanup_(pidref_done) PidRef pidref = PIDREF_NULL; + int pr = ASSERT_OK(pidref_safe_fork("(server)", FORK_DEATHSIG_SIGTERM|FORK_LOG, &pidref)); + if (pr == 0) { + _cleanup_close_ int rfd = -EBADF; + ASSERT_OK(rfd = accept4(listener, /* addr= */ NULL, /* addrlen= */ NULL, SOCK_CLOEXEC)); + ASSERT_OK_EQ_ERRNO(write(rfd, test_sock_str, sizeof(test_sock_str) - 1), + (ssize_t) sizeof(test_sock_str) - 1); + _exit(EXIT_SUCCESS); + } + + _cleanup_free_ char *data = NULL; + size_t size; + ASSERT_OK(read_full_file_full(XAT_FDROOT, sockpath + 1, + /* offset= */ UINT64_MAX, /* size= */ SIZE_MAX, + READ_FULL_FILE_CONNECT_SOCKET, /* bind_name= */ NULL, + &data, &size)); + ASSERT_EQ(size, sizeof(test_sock_str) - 1); + ASSERT_STREQ(data, test_sock_str); + + ASSERT_OK(pidref_wait_for_terminate_and_check("(server)", &pidref, WAIT_LOG)); +} + +TEST(read_boolean_file) { + _cleanup_(unlink_tempfilep) char fn[] = "/tmp/test-read-boolean-file-XXXXXX"; + _cleanup_close_ int fd = -EBADF, dfd = -EBADF; + const char *rel; + + ASSERT_OK(fd = mkostemp_safe(fn)); + + ASSERT_OK(write_string_file(fn, "yes", WRITE_STRING_FILE_TRUNCATE)); + ASSERT_OK_EQ(read_boolean_file(fn), true); + ASSERT_OK_EQ(read_boolean_file_at(AT_FDCWD, fn), true); + + ASSERT_OK(write_string_file(fn, "0", WRITE_STRING_FILE_TRUNCATE)); + ASSERT_OK_EQ(read_boolean_file(fn), false); + ASSERT_OK_EQ(read_boolean_file_at(AT_FDCWD, fn), false); + + ASSERT_OK(write_string_file(fn, "true\nignored\n", WRITE_STRING_FILE_TRUNCATE)); + ASSERT_OK_EQ(read_boolean_file(fn), true); + + ASSERT_OK(write_string_file(fn, "garbage", WRITE_STRING_FILE_TRUNCATE)); + ASSERT_ERROR(read_boolean_file(fn), EINVAL); + + ASSERT_ERROR(read_boolean_file("/tmp/this-file-better-not-exist-XXX"), ENOENT); + + /* Now test XAT_FDROOT: filename is relative, looked up against "/" */ + ASSERT_TRUE(path_startswith(fn, "/")); + rel = fn + 1; + + ASSERT_OK(write_string_file(fn, "on", WRITE_STRING_FILE_TRUNCATE)); + ASSERT_OK_EQ(read_boolean_file_at(XAT_FDROOT, rel), true); + + ASSERT_OK(write_string_file(fn, "off", WRITE_STRING_FILE_TRUNCATE)); + ASSERT_OK_EQ(read_boolean_file_at(XAT_FDROOT, rel), false); + + ASSERT_ERROR(read_boolean_file_at(XAT_FDROOT, "tmp/this-file-better-not-exist-XXX"), ENOENT); + + /* And confirm XAT_FDROOT ignores the cwd: chdir somewhere unrelated, then look up + * the same relative-to-/ path. */ + _cleanup_free_ char *cwd = NULL; + ASSERT_OK(safe_getcwd(&cwd)); + ASSERT_OK_ERRNO(chdir("/usr")); + + ASSERT_OK(write_string_file(fn, "yes", WRITE_STRING_FILE_TRUNCATE)); + ASSERT_OK_EQ(read_boolean_file_at(XAT_FDROOT, rel), true); + + ASSERT_OK_ERRNO(chdir(cwd)); + + /* Also test the dir_fd >= 0 path using an actual fd for /tmp. */ + ASSERT_OK(dfd = open("/tmp", O_DIRECTORY|O_CLOEXEC)); + ASSERT_OK(write_string_file(fn, "1", WRITE_STRING_FILE_TRUNCATE)); + _cleanup_free_ char *bn = NULL; + ASSERT_OK(path_extract_filename(fn, &bn)); + ASSERT_OK_EQ(read_boolean_file_at(dfd, bn), true); +} + DEFINE_TEST_MAIN(LOG_DEBUG);