From: Lennart Poettering Date: Fri, 17 Jul 2020 10:26:01 +0000 (+0200) Subject: fileio: add support for read_full_file() on AF_UNIX stream sockets X-Git-Tag: v246-rc2~29^2~3 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=b93d3f6b81118929f3750045150f1d69a5626234;p=thirdparty%2Fsystemd.git fileio: add support for read_full_file() on AF_UNIX stream sockets Optionally, teach read_full_file() the ability to connect to an AF_UNIX socket if the specified path points to one. --- diff --git a/src/basic/fileio.c b/src/basic/fileio.c index 6478a14097d..ba81868c64e 100644 --- a/src/basic/fileio.c +++ b/src/basic/fileio.c @@ -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); diff --git a/src/basic/fileio.h b/src/basic/fileio.h index 4ce51265157..0b88fdfdcb5 100644 --- a/src/basic/fileio.h +++ b/src/basic/fileio.h @@ -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); diff --git a/src/test/test-fileio.c b/src/test/test-fileio.c index af9696ef597..95dcd4fdb18 100644 --- a/src/test/test-fileio.c +++ b/src/test/test-fileio.c @@ -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; }