diff --git a/21-sendfd/client.c b/21-sendfd/client.c
index d93f588..70c6888 100644
--- a/21-sendfd/client.c
+++ b/21-sendfd/client.c
@@ -19,12 +19,88 @@
// buf, bufsize: Buffer for the received message
// fd: Where to store the file descriptor
int recvfd(int sock_fd, char *buf, size_t bufsize, int *fd) {
- return 0;
+ // We prepare the msghdr like we do in the server, but do not
+ // fill the cmsg of the msghdr. For details, please look at server.c
+ struct iovec data = { .iov_base = buf, .iov_len = bufsize };
+ union {
+ char buf[CMSG_SPACE(sizeof(*fd))];
+ struct cmsghdr align;
+ } auxdata;
+
+ struct msghdr msgh = {
+ .msg_iov = &data,
+ .msg_iovlen = 1,
+ .msg_control = auxdata.buf,
+ .msg_controllen = sizeof(auxdata.buf),
+ };
+
+ // We received from our socket a message (with space for an
+ // auxiliary cmsg). As we use SOCK_SEQPACKET, this will either receive the whole message or fail.
+ int len = recvmsg(sock_fd, &msgh, 0);
+ if (len < 0) die("recvmsg");
+
+ // We check that the received auxiliary data is of the correct
+ // type and that contains exactly one file descriptor.
+ struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msgh);
+ if ( cmsg->cmsg_level != SOL_SOCKET
+ || cmsg->cmsg_type != SCM_RIGHTS
+ || cmsg->cmsg_len != CMSG_LEN(sizeof(*fd)))
+ die("recvmsg/SOL_SOCKET");
+
+ // Extract the file descriptor and store it to the user provided
+ // location.
+ int *cmsg_fd = (int*)CMSG_DATA(cmsg);
+ *fd = *cmsg_fd;
+
+ // Length of message in buf. len <= bufsize
+ return len;
}
int main(int argc, char *argv[]) {
- // FIXME: Create a socket(2) with AF_UNIX, SOCK_SEQPACKET
- // FIXME: Connect to the domain socket "./socket"
- // FIXME: Receive the file descriptor with recvfd()
- // FIXME: Add a read/write loop that transfers data from your stdin to the received file descriptor (like cat)
+ // Create an connection-oriented AF_UNIX socket that preserves
+ // message boundaries.
+ int sock_fd = socket(AF_UNIX, SOCK_SEQPACKET, 0);
+ if (sock_fd < 0) die("socket");
+
+ // Create an socket address that points to the UNIX socket file
+ char *sock_name = "./socket";
+ struct sockaddr_un addr = { .sun_family = AF_UNIX };
+ strncpy(addr.sun_path, sock_name, sizeof(addr.sun_path)-1);
+
+ // Connect to that address on our socket
+ if (connect(sock_fd, (struct sockaddr*)&addr, sizeof(addr)) == -1)
+ die("connect");
+
+ // Receive message and file descriptor from the server
+ int stdout_fd;
+ char buf[4096];
+ int len = recvfd(sock_fd, buf, sizeof(buf)-1, &stdout_fd);
+
+ // Truncate the message until the first null byte and print out some information about the descriptor
+ printf("Received message: `%s'", buf);
+
+ // Resolve the link /proc/self/fd/<NUMBER> to give the user an idea what we have received
+ len = snprintf(buf, sizeof(buf), "/proc/self/fd/%d", stdout_fd);
+ char *linkname = buf + len + 1;
+ if (readlink(buf, linkname, sizeof(buf) - (linkname - buf)) < 0)
+ die("readlink");
+ printf(" with fd=%d: %s -> %s\n", stdout_fd, buf, linkname);
+
+ // In an endless loop, we copy data from our stdin to the received file descriptor.
+ while (true) {
+ // We read a buffer full of data
+ size_t len = read(STDIN_FILENO, buf, sizeof(buf));
+ if (len < 0) die("read");
+ if (len == 0) break;
+
+ // As writes can be short, we issue writes until our buffer
+ // becomes empty.
+ int to_write = len;
+ char *ptr = buf;
+ do {
+ int written = write(stdout_fd, ptr, to_write);
+ to_write -= written;
+ ptr += len;
+ } while (to_write > 0);
+ }
}
diff --git a/21-sendfd/server.c b/21-sendfd/server.c
index 5fce383..0733ef1 100644
--- a/21-sendfd/server.c
+++ b/21-sendfd/server.c
@@ -20,14 +20,83 @@
// buf, buflen: Message to send, arbitrary data
// fd: file descriptor to transfer
void sendfd(int sockfd, void *buf, size_t buflen, int fd) {
- // FIXME: Prepare a struct msghdr with an msg_control buffer
- // FIXME: Attach the file descriptor as cmsg (see cmsg(3), unix(7))
- // FIXME: Use sendmsg(2) to send the file descriptor and the message to the other process.
+ // We use sendmsg, which requires an iovec to describe the data
+ // sent. We already know this structure from the writev exercise
+ struct iovec data = { .iov_base = buf, .iov_len = buflen};
+
+ // Ancillary data buffer. We wrap it into an anonymous union to
+ // ensure correct alignment.
+ union {
+ // Buffer with enough space to hold a single descriptor
+ char buf[CMSG_SPACE(sizeof(fd))];
+ struct cmsghdr align;
+ } auxdata;
+
+ // We create a message header that points to our data buffer and
+ // to our auxdata buffer.
+ struct msghdr msgh = {
+ // Normal data to send
+ .msg_iov = &data,
+ .msg_iovlen = 1,
+ // Buffer for control data. See cmsg(3) for more details
+ .msg_control = auxdata.buf,
+ .msg_controllen = sizeof(auxdata.buf),
+ };
+
+ // We prepare the auxiliary data
+ struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msgh);
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_RIGHTS; // See unix(7), SCM_RIGHTS
+ cmsg->cmsg_len = CMSG_LEN(sizeof(fd));
+
+ // Set the file descriptor into the cmsg data
+ int *cmsg_fd = (int*)CMSG_DATA(cmsg);
+ *cmsg_fd = fd;
+
+ // Send our prepared message with sendmsg(2)
+ if (sendmsg(sockfd, &msgh, 0) == -1)
+ die("sendmsg");
}
int main() {
- // FIXME: Create an socket with AF_UNIX and SOCK_SEQPACKET
- // FIXME: Bind it to a filename and listen
- // FIXME: Accept clients, send your STDOUT, and directly close the connection again
+ // Create a new UNIX domain socket. We use the SOCK_SEQPACKET
+ // socket type as it is a connection oriented socket (others
+ // connect to it), but it ensures message boundaries. Otherwise,
+ // the other side has to read exactly as many bytes as we write in
+ // order to get the auxiliary data correctly.
+ int sock_fd = socket(AF_UNIX, SOCK_SEQPACKET, 0);
+ if (sock_fd < 0) die("socket");
+
+ // Bind the socket to a filename. We already have seen that in the
+ // postbox exercise.
+ char *sock_name = "./socket";
+ struct sockaddr_un sockaddr = {.sun_family = AF_UNIX };
+ strcpy(sockaddr.sun_path, sock_name);
+
+ int rc = unlink(sock_name);
+ if (rc < 0 && errno != ENOENT) die("unlink/socket");
+
+ rc = bind(sock_fd, (struct sockaddr *) &sockaddr, sizeof(sockaddr));
+ if (rc == -1) die("bind/socket");
+
+ // As we are connection oriented, we listen on the socket.
+ rc = listen(sock_fd, 10);
+ if (rc < 0) die("listen/socket");
+
+ printf("Please connect: ./client\n");
+
+ while (true) {
+ // We wait for a client and establish a connection.
+ int client_fd = accept(sock_fd, NULL, NULL);
+ if (client_fd < 0) die("accept");
+
+ printf("Client on fd=%d. Sending STDOUT\n", client_fd);
+
+ // Send our STDOUT file descriptor with an accompanying message
+ sendfd(client_fd, "STDOUT", strlen("STDOUT"), STDOUT_FILENO);
+
+ // Directly close the client fd again.
+ close(client_fd);
+ }
}