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.
This commit is contained in:
+53
-9
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user