]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
fileio: teach read_one_line_file_at() XAT_FDROOT support
authorLennart Poettering <lennart@amutable.com>
Mon, 11 May 2026 09:34:22 +0000 (11:34 +0200)
committerLennart Poettering <lennart@amutable.com>
Mon, 11 May 2026 10:04:02 +0000 (12:04 +0200)
src/basic/fileio.c
src/basic/socket-util.c
src/test/test-fileio.c

index 661667a6b2a1ebfb1f7f8726a62bf7c8e0b6a50c..31e9af2e4e03f1212fce0380518f5989969e78fa 100644 (file)
@@ -420,11 +420,11 @@ 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;
 
@@ -1010,13 +1010,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 +1057,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 +1105,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);
 
index 2e0ee684ff98bcfe392d7a91b4406ac7c814c514..d53208f1389909c4817cf5cfc1a4b1837ac634f8 100644 (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;
 
index a752b1c7cda23261b081de4bdd68353996cc6a1f..d27bf7ab5d8243ce9f0782fdd109bf0a8fbb4f0a 100644 (file)
@@ -747,4 +747,80 @@ 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));
+}
+
 DEFINE_TEST_MAIN(LOG_DEBUG);