Merge pull request #28557 from bluca/utils
utils: add serialize_bool_elide() and send/receive helpers for FD array
This commit is contained in:
@@ -41,6 +41,11 @@
|
||||
# define IDN_FLAGS 0
|
||||
#endif
|
||||
|
||||
/* From the kernel's include/net/scm.h */
|
||||
#ifndef SCM_MAX_FD
|
||||
# define SCM_MAX_FD 253
|
||||
#endif
|
||||
|
||||
static const char* const socket_address_type_table[] = {
|
||||
[SOCK_STREAM] = "Stream",
|
||||
[SOCK_DGRAM] = "Datagram",
|
||||
@@ -951,6 +956,53 @@ int getpeergroups(int fd, gid_t **ret) {
|
||||
return (int) n;
|
||||
}
|
||||
|
||||
ssize_t send_many_fds_iov_sa(
|
||||
int transport_fd,
|
||||
int *fds_array, size_t n_fds_array,
|
||||
const struct iovec *iov, size_t iovlen,
|
||||
const struct sockaddr *sa, socklen_t len,
|
||||
int flags) {
|
||||
|
||||
_cleanup_free_ struct cmsghdr *cmsg = NULL;
|
||||
struct msghdr mh = {
|
||||
.msg_name = (struct sockaddr*) sa,
|
||||
.msg_namelen = len,
|
||||
.msg_iov = (struct iovec *)iov,
|
||||
.msg_iovlen = iovlen,
|
||||
};
|
||||
ssize_t k;
|
||||
|
||||
assert(transport_fd >= 0);
|
||||
assert(fds_array || n_fds_array == 0);
|
||||
|
||||
/* The kernel will reject sending more than SCM_MAX_FD FDs at once */
|
||||
if (n_fds_array > SCM_MAX_FD)
|
||||
return -E2BIG;
|
||||
|
||||
/* We need either an FD array or data to send. If there's nothing, return an error. */
|
||||
if (n_fds_array == 0 && !iov)
|
||||
return -EINVAL;
|
||||
|
||||
if (n_fds_array > 0) {
|
||||
mh.msg_controllen = CMSG_SPACE(sizeof(int) * n_fds_array);
|
||||
mh.msg_control = cmsg = malloc(mh.msg_controllen);
|
||||
if (!cmsg)
|
||||
return -ENOMEM;
|
||||
|
||||
*cmsg = (struct cmsghdr) {
|
||||
.cmsg_len = CMSG_LEN(sizeof(int) * n_fds_array),
|
||||
.cmsg_level = SOL_SOCKET,
|
||||
.cmsg_type = SCM_RIGHTS,
|
||||
};
|
||||
memcpy(CMSG_DATA(cmsg), fds_array, sizeof(int) * n_fds_array);
|
||||
}
|
||||
k = sendmsg(transport_fd, &mh, MSG_NOSIGNAL | flags);
|
||||
if (k < 0)
|
||||
return (ssize_t) -errno;
|
||||
|
||||
return k;
|
||||
}
|
||||
|
||||
ssize_t send_one_fd_iov_sa(
|
||||
int transport_fd,
|
||||
int fd,
|
||||
@@ -1006,6 +1058,78 @@ int send_one_fd_sa(
|
||||
return (int) send_one_fd_iov_sa(transport_fd, fd, NULL, 0, sa, len, flags);
|
||||
}
|
||||
|
||||
ssize_t receive_many_fds_iov(
|
||||
int transport_fd,
|
||||
struct iovec *iov, size_t iovlen,
|
||||
int **ret_fds_array, size_t *ret_n_fds_array,
|
||||
int flags) {
|
||||
|
||||
CMSG_BUFFER_TYPE(CMSG_SPACE(sizeof(int) * SCM_MAX_FD)) control;
|
||||
struct msghdr mh = {
|
||||
.msg_control = &control,
|
||||
.msg_controllen = sizeof(control),
|
||||
.msg_iov = iov,
|
||||
.msg_iovlen = iovlen,
|
||||
};
|
||||
_cleanup_free_ int *fds_array = NULL;
|
||||
size_t n_fds_array = 0;
|
||||
struct cmsghdr *cmsg;
|
||||
ssize_t k;
|
||||
|
||||
assert(transport_fd >= 0);
|
||||
assert(ret_fds_array);
|
||||
assert(ret_n_fds_array);
|
||||
|
||||
/*
|
||||
* Receive many FDs via @transport_fd. We don't care for the transport-type. We retrieve all the FDs
|
||||
* at once. This is best used in combination with send_many_fds().
|
||||
*/
|
||||
|
||||
k = recvmsg_safe(transport_fd, &mh, MSG_CMSG_CLOEXEC | flags);
|
||||
if (k < 0)
|
||||
return k;
|
||||
|
||||
CMSG_FOREACH(cmsg, &mh)
|
||||
if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) {
|
||||
size_t n = (cmsg->cmsg_len - CMSG_LEN(0)) / sizeof(int);
|
||||
|
||||
fds_array = GREEDY_REALLOC(fds_array, n_fds_array + n);
|
||||
if (!fds_array) {
|
||||
cmsg_close_all(&mh);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
memcpy(fds_array + n_fds_array, CMSG_TYPED_DATA(cmsg, int), sizeof(int) * n);
|
||||
n_fds_array += n;
|
||||
}
|
||||
|
||||
if (n_fds_array == 0) {
|
||||
cmsg_close_all(&mh);
|
||||
|
||||
/* If didn't receive an FD or any data, return an error. */
|
||||
if (k == 0)
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
*ret_fds_array = TAKE_PTR(fds_array);
|
||||
*ret_n_fds_array = n_fds_array;
|
||||
|
||||
return k;
|
||||
}
|
||||
|
||||
int receive_many_fds(int transport_fd, int **ret_fds_array, size_t *ret_n_fds_array, int flags) {
|
||||
ssize_t k;
|
||||
|
||||
k = receive_many_fds_iov(transport_fd, NULL, 0, ret_fds_array, ret_n_fds_array, flags);
|
||||
if (k == 0)
|
||||
return 0;
|
||||
|
||||
/* k must be negative, since receive_many_fds_iov() only returns a positive value if data was received
|
||||
* through the iov. */
|
||||
assert(k < 0);
|
||||
return (int) k;
|
||||
}
|
||||
|
||||
ssize_t receive_one_fd_iov(
|
||||
int transport_fd,
|
||||
struct iovec *iov, size_t iovlen,
|
||||
|
||||
@@ -153,6 +153,28 @@ int getpeercred(int fd, struct ucred *ucred);
|
||||
int getpeersec(int fd, char **ret);
|
||||
int getpeergroups(int fd, gid_t **ret);
|
||||
|
||||
ssize_t send_many_fds_iov_sa(
|
||||
int transport_fd,
|
||||
int *fds_array, size_t n_fds_array,
|
||||
const struct iovec *iov, size_t iovlen,
|
||||
const struct sockaddr *sa, socklen_t len,
|
||||
int flags);
|
||||
static inline ssize_t send_many_fds_iov(
|
||||
int transport_fd,
|
||||
int *fds_array, size_t n_fds_array,
|
||||
const struct iovec *iov, size_t iovlen,
|
||||
int flags) {
|
||||
|
||||
return send_many_fds_iov_sa(transport_fd, fds_array, n_fds_array, iov, iovlen, NULL, 0, flags);
|
||||
}
|
||||
static inline int send_many_fds(
|
||||
int transport_fd,
|
||||
int *fds_array,
|
||||
size_t n_fds_array,
|
||||
int flags) {
|
||||
|
||||
return send_many_fds_iov_sa(transport_fd, fds_array, n_fds_array, NULL, 0, NULL, 0, flags);
|
||||
}
|
||||
ssize_t send_one_fd_iov_sa(
|
||||
int transport_fd,
|
||||
int fd,
|
||||
@@ -167,6 +189,8 @@ int send_one_fd_sa(int transport_fd,
|
||||
#define send_one_fd(transport_fd, fd, flags) send_one_fd_iov_sa(transport_fd, fd, NULL, 0, NULL, 0, flags)
|
||||
ssize_t receive_one_fd_iov(int transport_fd, struct iovec *iov, size_t iovlen, int flags, int *ret_fd);
|
||||
int receive_one_fd(int transport_fd, int flags);
|
||||
ssize_t receive_many_fds_iov(int transport_fd, struct iovec *iov, size_t iovlen, int **ret_fds_array, size_t *ret_n_fds_array, int flags);
|
||||
int receive_many_fds(int transport_fd, int **ret_fds_array, size_t *ret_n_fds_array, int flags);
|
||||
|
||||
ssize_t next_datagram_size_fd(int fd);
|
||||
|
||||
|
||||
@@ -19,6 +19,9 @@ int serialize_strv(FILE *f, const char *key, char **l);
|
||||
static inline int serialize_bool(FILE *f, const char *key, bool b) {
|
||||
return serialize_item(f, key, yes_no(b));
|
||||
}
|
||||
static inline int serialize_bool_elide(FILE *f, const char *key, bool b) {
|
||||
return b ? serialize_item(f, key, yes_no(b)) : 0;
|
||||
}
|
||||
|
||||
int deserialize_usec(const char *value, usec_t *timestamp);
|
||||
int deserialize_dual_timestamp(const char *value, dual_timestamp *t);
|
||||
|
||||
@@ -22,19 +22,23 @@ TEST(serialize_item) {
|
||||
assert_se(serialize_item(f, "a", NULL) == 0);
|
||||
assert_se(serialize_item(f, "a", "bbb") == 1);
|
||||
assert_se(serialize_item(f, "a", "bbb") == 1);
|
||||
assert_se(serialize_bool_elide(f, "c", true) == 1);
|
||||
assert_se(serialize_bool_elide(f, "d", false) == 0);
|
||||
assert_se(serialize_item(f, "a", long_string) == -EINVAL);
|
||||
assert_se(serialize_item(f, long_string, "a") == -EINVAL);
|
||||
assert_se(serialize_item(f, long_string, long_string) == -EINVAL);
|
||||
|
||||
rewind(f);
|
||||
|
||||
_cleanup_free_ char *line1 = NULL, *line2 = NULL, *line3 = NULL;
|
||||
_cleanup_free_ char *line1 = NULL, *line2 = NULL, *line3 = NULL, *line4 = NULL;
|
||||
assert_se(read_line(f, LONG_LINE_MAX, &line1) > 0);
|
||||
assert_se(streq(line1, "a=bbb"));
|
||||
assert_se(read_line(f, LONG_LINE_MAX, &line2) > 0);
|
||||
assert_se(streq(line2, "a=bbb"));
|
||||
assert_se(read_line(f, LONG_LINE_MAX, &line3) == 0);
|
||||
assert_se(streq(line3, ""));
|
||||
assert_se(read_line(f, LONG_LINE_MAX, &line3) > 0);
|
||||
assert_se(streq(line3, "c=yes"));
|
||||
assert_se(read_line(f, LONG_LINE_MAX, &line4) == 0);
|
||||
assert_se(streq(line4, ""));
|
||||
}
|
||||
|
||||
TEST(serialize_item_escaped) {
|
||||
|
||||
@@ -310,6 +310,71 @@ TEST(passfd_contents_read) {
|
||||
assert_se(streq(buf, file_contents));
|
||||
}
|
||||
|
||||
TEST(pass_many_fds_contents_read) {
|
||||
_cleanup_close_pair_ int pair[2];
|
||||
static const char file_contents[][STRLEN("test contents in the fileX") + 1] = {
|
||||
"test contents in the file0",
|
||||
"test contents in the file1",
|
||||
"test contents in the file2"
|
||||
};
|
||||
static const char wire_contents[] = "test contents on the wire";
|
||||
int r;
|
||||
|
||||
assert_se(socketpair(AF_UNIX, SOCK_DGRAM, 0, pair) >= 0);
|
||||
|
||||
r = safe_fork("(passfd_contents_read)", FORK_DEATHSIG|FORK_LOG|FORK_WAIT, NULL);
|
||||
assert_se(r >= 0);
|
||||
|
||||
if (r == 0) {
|
||||
/* Child */
|
||||
struct iovec iov = IOVEC_MAKE_STRING(wire_contents);
|
||||
char tmpfile[][STRLEN("/tmp/test-socket-util-passfd-contents-read-XXXXXX") + 1] = {
|
||||
"/tmp/test-socket-util-passfd-contents-read-XXXXXX",
|
||||
"/tmp/test-socket-util-passfd-contents-read-XXXXXX",
|
||||
"/tmp/test-socket-util-passfd-contents-read-XXXXXX"
|
||||
};
|
||||
int tmpfds[3] = { -EBADF, -EBADF, -EBADF };
|
||||
|
||||
pair[0] = safe_close(pair[0]);
|
||||
|
||||
for (size_t i = 0; i < 3; ++i) {
|
||||
assert_se(write_tmpfile(tmpfile[i], file_contents[i]) == 0);
|
||||
tmpfds[i] = open(tmpfile[i], O_RDONLY);
|
||||
assert_se(tmpfds[i] >= 0);
|
||||
assert_se(unlink(tmpfile[i]) == 0);
|
||||
}
|
||||
|
||||
assert_se(send_many_fds_iov(pair[1], tmpfds, 3, &iov, 1, MSG_DONTWAIT) > 0);
|
||||
close_many(tmpfds, 3);
|
||||
_exit(EXIT_SUCCESS);
|
||||
}
|
||||
|
||||
/* Parent */
|
||||
char buf[64];
|
||||
struct iovec iov = IOVEC_MAKE(buf, sizeof(buf)-1);
|
||||
_cleanup_free_ int *fds = NULL;
|
||||
size_t n_fds = 0;
|
||||
ssize_t k;
|
||||
|
||||
pair[1] = safe_close(pair[1]);
|
||||
|
||||
k = receive_many_fds_iov(pair[0], &iov, 1, &fds, &n_fds, MSG_DONTWAIT);
|
||||
assert_se(k > 0);
|
||||
buf[k] = 0;
|
||||
assert_se(streq(buf, wire_contents));
|
||||
|
||||
assert_se(n_fds == 3);
|
||||
|
||||
for (size_t i = 0; i < 3; ++i) {
|
||||
assert_se(fds[i] >= 0);
|
||||
r = read(fds[i], buf, sizeof(buf)-1);
|
||||
assert_se(r >= 0);
|
||||
buf[r] = 0;
|
||||
assert_se(streq(buf, file_contents[i]));
|
||||
safe_close(fds[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(receive_nopassfd) {
|
||||
_cleanup_close_pair_ int pair[2];
|
||||
static const char wire_contents[] = "no fd passed here";
|
||||
|
||||
Reference in New Issue
Block a user