]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
fileio: add support for read_full_file() on AF_UNIX stream sockets
authorLennart Poettering <lennart@poettering.net>
Fri, 17 Jul 2020 10:26:01 +0000 (12:26 +0200)
committerLennart Poettering <lennart@poettering.net>
Tue, 21 Jul 2020 08:23:44 +0000 (10:23 +0200)
Optionally, teach read_full_file() the ability to connect to an AF_UNIX
socket if the specified path points to one.

src/basic/fileio.c
src/basic/fileio.h
src/test/test-fileio.c

index 6478a14097de28f2fc1cf2fae3bc99451f9997d5..ba81868c64efcf78e01489856498545df93bb0d5 100644 (file)
@@ -22,6 +22,7 @@
 #include "mkdir.h"
 #include "parse-util.h"
 #include "path-util.h"
+#include "socket-util.h"
 #include "stdio-util.h"
 #include "string-util.h"
 #include "tmpfile-util.h"
@@ -535,21 +536,18 @@ int read_full_stream_full(
 
                 errno = 0;
                 k = fread(buf + l, 1, n - l, f);
-                if (k > 0)
-                        l += k;
+
+                assert(k <= n - l);
+                l += k;
 
                 if (ferror(f)) {
                         r = errno_or_else(EIO);
                         goto finalize;
                 }
-
                 if (feof(f))
                         break;
 
-                /* We aren't expecting fread() to return a short read outside
-                 * of (error && eof), assert buffer is full and enlarge buffer.
-                 */
-                assert(l == n);
+                assert(k > 0); /* we can't have read zero bytes because that would have been EOF */
 
                 /* Safety check */
                 if (n >= READ_FULL_BYTES_MAX) {
@@ -603,8 +601,54 @@ int read_full_file_full(int dir_fd, const char *filename, ReadFullFileFlags flag
         assert(contents);
 
         r = xfopenat(dir_fd, filename, "re", 0, &f);
-        if (r < 0)
-                return r;
+        if (r < 0) {
+                _cleanup_close_ int dfd = -1, sk = -1;
+                union sockaddr_union sa;
+
+                /* ENXIO is what Linux returns if we open a node that is an AF_UNIX socket */
+                if (r != -ENXIO)
+                        return r;
+
+                /* If this is enabled, let's try to connect to it */
+                if (!FLAGS_SET(flags, READ_FULL_FILE_CONNECT_SOCKET))
+                        return -ENXIO;
+
+                if (dir_fd == AT_FDCWD)
+                        r = sockaddr_un_set_path(&sa.un, filename);
+                else {
+                        char procfs_path[STRLEN("/proc/self/fd/") + DECIMAL_STR_MAX(int)];
+
+                        /* If we shall operate relative to some directory, then let's use O_PATH first to
+                         * open the socket inode, and then connect to it via /proc/self/fd/. We have to do
+                         * this since there's not connectat() that takes a directory fd as first arg. */
+
+                        dfd = openat(dir_fd, filename, O_PATH|O_CLOEXEC);
+                        if (dfd < 0)
+                                return -errno;
+
+                        xsprintf(procfs_path, "/proc/self/fd/%i", dfd);
+                        r = sockaddr_un_set_path(&sa.un, procfs_path);
+                }
+                if (r < 0)
+                        return r;
+
+                sk = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
+                if (sk < 0)
+                        return -errno;
+
+                if (connect(sk, &sa.sa, SOCKADDR_UN_LEN(sa.un)) < 0)
+                        return errno == ENOTSOCK ? -ENXIO : -errno; /* propagate original error if this is
+                                                                     * not a socket after all */
+
+                if (shutdown(sk, SHUT_WR) < 0)
+                        return -errno;
+
+                f = fdopen(sk, "r");
+                if (!f)
+                        return -errno;
+
+                TAKE_FD(sk);
+        }
 
         (void) __fsetlocking(f, FSETLOCKING_BYCALLER);
 
index 4ce51265157ab350a7ca11ccafe124246e4a7dec..0b88fdfdcb5763533b6e3832f420238967c2f110 100644 (file)
@@ -36,6 +36,7 @@ typedef enum {
         READ_FULL_FILE_UNBASE64            = 1 << 1,
         READ_FULL_FILE_UNHEX               = 1 << 2,
         READ_FULL_FILE_WARN_WORLD_READABLE = 1 << 3,
+        READ_FULL_FILE_CONNECT_SOCKET      = 1 << 4,
 } ReadFullFileFlags;
 
 int fopen_unlocked(const char *path, const char *options, FILE **ret);
index af9696ef597f3e00e5020180397b5f19bdee4a6a..95dcd4fdb180af9ef9c397d92e0e0f1719641b57 100644 (file)
@@ -15,6 +15,8 @@
 #include "io-util.h"
 #include "parse-util.h"
 #include "process-util.h"
+#include "rm-rf.h"
+#include "socket-util.h"
 #include "string-util.h"
 #include "strv.h"
 #include "tests.h"
@@ -842,6 +844,53 @@ static void test_read_nul_string(void) {
         assert_se(read_nul_string(f, LONG_LINE_MAX, &s) == 0 && streq_ptr(s, ""));
 }
 
+static void test_read_full_file_socket(void) {
+        _cleanup_(rm_rf_physical_and_freep) char *z = NULL;
+        _cleanup_close_ int listener = -1;
+        _cleanup_free_ char *data = NULL;
+        union sockaddr_union sa;
+        const char *j;
+        size_t size;
+        pid_t pid;
+        int r;
+
+        log_info("/* %s */", __func__);
+
+        listener = socket(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC, 0);
+        assert_se(listener >= 0);
+
+        assert_se(mkdtemp_malloc(NULL, &z) >= 0);
+        j = strjoina(z, "/socket");
+
+        assert_se(sockaddr_un_set_path(&sa.un, j) >= 0);
+
+        assert_se(bind(listener, &sa.sa, SOCKADDR_UN_LEN(sa.un)) >= 0);
+        assert_se(listen(listener, 1) >= 0);
+
+        r = safe_fork("(server)", FORK_DEATHSIG|FORK_LOG, &pid);
+        assert_se(r >= 0);
+        if (r == 0) {
+                _cleanup_close_ int rfd = -1;
+                /* child */
+
+                rfd = accept4(listener, NULL, 0, SOCK_CLOEXEC);
+                assert_se(rfd >= 0);
+
+#define TEST_STR "This is a test\nreally."
+
+                assert_se(write(rfd, TEST_STR, strlen(TEST_STR)) == strlen(TEST_STR));
+                _exit(EXIT_SUCCESS);
+        }
+
+        assert_se(read_full_file_full(AT_FDCWD, j, 0, &data, &size) == -ENXIO);
+        assert_se(read_full_file_full(AT_FDCWD, j, READ_FULL_FILE_CONNECT_SOCKET, &data, &size) >= 0);
+        assert_se(size == strlen(TEST_STR));
+        assert_se(streq(data, TEST_STR));
+
+        assert_se(wait_for_terminate_and_check("(server)", pid, WAIT_LOG) >= 0);
+#undef TEST_STR
+}
+
 int main(int argc, char *argv[]) {
         test_setup_logging(LOG_DEBUG);
 
@@ -867,6 +916,7 @@ int main(int argc, char *argv[]) {
         test_read_line3();
         test_read_line4();
         test_read_nul_string();
+        test_read_full_file_socket();
 
         return 0;
 }