fileio: add new read_boolean_file() helper (#41963)

This adds a new read_boolean_file() helper that combines
read_one_line_file() and parse_boolean() into one. 3 call sites are
converted to us it.
This commit is contained in:
Lennart Poettering
2026-05-11 14:16:05 +02:00
committed by GitHub
7 changed files with 180 additions and 36 deletions
+26 -6
View File
@@ -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);
+4
View File
@@ -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);
+14 -3
View File
@@ -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;
+1 -9
View File
@@ -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) {
+2 -8
View File
@@ -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;
}
+3 -10
View File
@@ -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)
+130
View File
@@ -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);