patches and low-level development discussion
 help / color / mirror / code / Atom feed
From: Alyssa Ross <hi@alyssa.is>
To: devel@spectrum-os.org
Subject: [PATCH ucspi-vsock 7/7] Extract vsockserver-socketbinder and vsockserverd
Date: Fri, 19 Mar 2021 03:17:13 +0000	[thread overview]
Message-ID: <20210319031713.23600-1-hi@alyssa.is> (raw)
In-Reply-To: <20210319025349.8839-2-hi@alyssa.is>

vsockserver-socketbinder creates and listens on a socket, and
vsockserverd accepts connections and sets up file descriptors.

vsockserver previously did both of these things in one big program,
but now it just sets up a command line to run vsockserver-socketbinder
followed by vsockserverd.  Having two seperate programs allows one
program to be used in situations where the other is not
suitable (e.g. using vsockserver-socketbinder to create a socket in
situations where accept behaviour more complex than vsockserverd can
provide is required).

This design is taken from s6[1], which uses the same design for its
s6-ipcserver, s6-ipcserver-socketbinder, and s6-ipcserverd programs.

[1]: https://skarnet.org/software/s6/
---

Because vsockserver.c is completely rewritten in this patch, the diff
generated by git was a bit confusing, so the diff here has been
lovingly hand-edited to group the hunks together more and make it easy
to see the new code all at once.  Being able to do this is one of the
nice things about email patches. ;)

The new implementations are much more thoroughly commented.  Think
that's my new style. :)

 .gitignore                      |   2 +
 Makefile.in                     |  14 ++-
 vsockserver-socketbinder.c      |  86 ++++++++++++++++++
 vsockserver.c                   | 149 ++++++++++++++------------------
 vsockserver.c => vsockserverd.c |  65 ++++++--------
 5 files changed, 186 insertions(+), 130 deletions(-)
 create mode 100644 vsockserver-socketbinder.c
 copy vsockserver.c => vsockserverd.c (64%)

diff --git a/.gitignore b/.gitignore
index 89a0408..d29518a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,6 +4,8 @@
 *.o
 *.tmp
 vsockclient
+vsockserver-socketbinder
+vsockserverd
 vsockserver
 config.h
 Makefile
diff --git a/Makefile.in b/Makefile.in
index 3260e85..0e65f1f 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -10,7 +10,7 @@ INSTALL_PROGRAM = $(INSTALL)
 prefix = @PREFIX@
 bindir = @BINDIR@
 
-PROGRAMS = vsockclient vsockserver
+PROGRAMS = vsockclient vsockserver-socketbinder vsockserverd vsockserver
 
 all: $(PROGRAMS)
 .PHONY: all
@@ -26,11 +26,17 @@ config.h: configure
 
 vsockclient: vsockclient.o env.o log.o num.o vsock.o
 	$(CC) $(LDFLAGS) -o $@ $@.o env.o log.o num.o vsock.o $(LDLIBS)
-vsockserver: vsockserver.o env.o log.o num.o vsock.o
-	$(CC) $(LDFLAGS) -o $@ $@.o env.o log.o num.o vsock.o $(LDLIBS)
+vsockserver-socketbinder: vsockserver-socketbinder.o log.o num.o vsock.o
+	$(CC) $(LDFLAGS) -o $@ $@.o log.o num.o vsock.o $(LDLIBS)
+vsockserverd: vsockserverd.o env.o log.o vsock.o
+	$(CC) $(LDFLAGS) -o $@ $@.o env.o log.o vsock.o $(LDLIBS)
+vsockserver: vsockserver.o exec.o log.o
+	$(CC) $(LDFLAGS) -o $@ $@.o exec.o log.o $(LDLIBS)
 
 vsockclient.o: env.h log.h num.h vsock.h
-vsockserver.o: env.h log.h num.h vsock.h
+vsockserver-socketbinder.o: log.h num.h vsock.h
+vsockserverd.o: env.h log.h vsock.h
+vsockserver.o: config.h exec.h log.h
 
 clean:
 	rm -f $(PROGRAMS) *.o
diff --git a/vsockserver-socketbinder.c b/vsockserver-socketbinder.c
new file mode 100644
index 0000000..598c01c
--- /dev/null
+++ b/vsockserver-socketbinder.c
@@ -0,0 +1,86 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: 2020-2021 Alyssa Ross <hi@alyssa.is>
+
+#define _GNU_SOURCE
+
+#include <errno.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdnoreturn.h>
+#include <string.h>
+#include <sysexits.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include <linux/vm_sockets.h>
+
+#include "log.h"
+#include "num.h"
+#include "vsock.h"
+
+noreturn static void ex_usage(void)
+{
+	if (verbosity)
+		fprintf(stderr, "Usage: %s cid port prog...\n",
+			program_invocation_short_name);
+	exit(EX_USAGE);
+}
+
+int main(int argc, char *argv[])
+{
+	int opt, fd;
+	uint32_t cid, port;
+
+	// A skeleton of an option parser to reject any options that
+	// are given, so we can add options in the future without
+	// worrying about breaking backwards compatibility because
+	// they were previously interpreted as a first argument.
+	while ((opt = getopt(argc, argv, "+")) != -1) {
+		switch (opt) {
+		default:
+			ex_usage();
+		}
+	}
+
+	// Check there are enough positional arguments (two for the
+	// address and at least one to exec into).
+	if (optind > argc - 3)
+		ex_usage();
+
+	// Parse the `cid' argument.
+	if (!strcmp(argv[optind], "-1"))
+		cid = VMADDR_CID_ANY;
+	else if (getu32(argv[optind], 0,  UINT32_MAX, &cid))
+		ex_usage();
+	optind++;
+
+	// Parse the `port' argument.
+	if (!strcmp(argv[optind], "-1"))
+		port = VMADDR_PORT_ANY;
+	else if (getu32(argv[optind], 0, UINT32_MAX, &port))
+		ex_usage();
+	optind++;
+
+	// Set up the socket.
+	fd = socket(AF_VSOCK, SOCK_STREAM, 0);
+	if (fd == -1)
+		diee(EX_OSERR, "socket");
+	if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1)
+		diee(EX_OSERR, "fcntl");
+	if (vsock_bind(fd, cid, port) == -1)
+		diee(EX_OSERR, "bind");
+	if (listen(fd ,40) == -1)
+		diee(EX_OSERR, "listen");
+
+	// Place the socket at stdout.
+	if (dup2(fd, STDIN_FILENO) == -1)
+		diee(EX_OSERR, "dup2");
+	if (fd != STDIN_FILENO)
+		close(fd);
+
+	// Finally, exec into `prog'.
+	execvp(argv[optind], &argv[optind]);
+	diee(EX_OSERR, "execvp");
+}
diff --git a/vsockserver.c b/vsockserver.c
index 43307d2..f740a8a 100644
--- a/vsockserver.c
+++ b/vsockserver.c
@@ -1,28 +1,20 @@
 // SPDX-License-Identifier: GPL-2.0-or-later
-// SPDX-FileCopyrightText: 2020-2021 Alyssa Ross <hi@alyssa.is>
+// SPDX-FileCopyrightText: 2021 Alyssa Ross <hi@alyssa.is>
 
 #define _GNU_SOURCE
 
+#include <argz.h>
 #include <errno.h>
-#include <fcntl.h>
-#include <inttypes.h>
-#include <poll.h>
 #include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <stdnoreturn.h>
-#include <string.h>
-#include <sys/socket.h>
-#include <sys/wait.h>
 #include <sysexits.h>
 #include <unistd.h>
 
-#include <linux/vm_sockets.h>
-
-#include "env.h"
+#include "config.h"
+#include "exec.h"
 #include "log.h"
-#include "num.h"
-#include "vsock.h"
 
 noreturn static void ex_usage(void)
 {
@@ -34,102 +26,87 @@ noreturn static void ex_usage(void)
 
 int main(int argc, char *argv[])
 {
-	bool notify = false;
 	int opt;
-	pid_t child;
-	uint32_t lcid, lport, rcid, rport;
+	bool alloc_failed = false;
+	size_t binder_opts_len = 0, daemon_opts_len = 0;
+	char *binder_opts = NULL, *daemon_opts = NULL;
+
+	// The heavy lifting here is done by vsockserver-socketbinder
+	// and vsockserverd.  All this program does is munge argv
+	// appropriately to set the right options for each of those
+	// programs, and then exec into vsockserver-socketbinder.
+
+	// If allocation fails, we need to keep going until after
+	// we've parsed the arguments, so we know what our verbosity
+	// setting is, and consequently whether we should print an
+	// error message about the allocation failure.
+	alloc_failed |=
+		argz_add(&binder_opts, &binder_opts_len, BINDIR "/vsockserver-socketbinder") ||
+		argz_add(&daemon_opts, &daemon_opts_len, BINDIR "/vsockserverd");
 
 	while ((opt = getopt(argc, argv, "+1qQv")) != -1) {
+		char *arg = NULL;
+
 		switch (opt) {
 		case '1':
-			notify = true;
-			break;
 		case 'q':
 		case 'Q':
 		case 'v':
 			set_verbosity(opt);
+			alloc_failed |=
+				asprintf(&arg, "-%c", opt) == -1 ||
+				argz_add(&daemon_opts, &daemon_opts_len, arg);
+			free(arg);
 			break;
 		default:
 			ex_usage();
 		}
 	}
 
+	// Now that verbosity is set, we can whether we were sitting
+	// on an allocation failure.
+	if (alloc_failed)
+		diee(EX_OSERR, "malloc");
+	// Now we don't have to keep checking alloc_failed before
+	// doing anything, and we can deal with allocation failures
+	// after this point by just terminating immediately.
+
-	// Check there are enough positional arguments (two for the
-	// address and at least one to exec into).
+	// Check there are `cid' and `port' arguments to pass to
+	// vsockserver-socketbinder, and at least one `prog' argument
+	// to pass to vsockserverd.
 	if (optind > argc - 3)
 		ex_usage();
 
-	if (!strcmp(argv[optind], "-1"))
-		lcid = VMADDR_CID_ANY;
-	else if (getu32(argv[optind], 0, UINT32_MAX, &lcid))
-		ex_usage();
-	optind++;
-
-	if (!strcmp(argv[optind], "-1"))
-		lport = VMADDR_PORT_ANY;
-	else if (getu32(argv[optind], 0, UINT32_MAX, &lport))
-		ex_usage();
-	optind++;
-
-	int fd = socket(AF_VSOCK, SOCK_STREAM, 0);
-	if (fd == -1)
-		diee(EX_OSERR, "socket");
-
-	if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1)
-		diee(EX_OSERR, "fcntl");
-
-	if (vsock_bind(fd, lcid, lport) == -1)
-		diee(EX_OSERR, "bind");
-
-	if (listen(fd, 40) == -1)
-		diee(EX_OSERR, "listen");
-
-	if (lport == VMADDR_PORT_ANY)
-		if (vsock_get_cid_and_port(fd, NULL, &lport) == -1)
-			diee(EX_OSERR, "getsockname");
-
-	if (notify) {
-		printf("%" PRIu32 "\n", lport);
-		fclose(stdout);
-	}
-
-	setenvf("VSOCKLOCALCID", 1, "%" PRIu32, lcid);
-	setenvf("VSOCKLOCALPORT", 1, "%" PRIu32, lport);
-
-	ilog("listening as %" PRIu32 " on port %" PRIu32, lcid, lport);
-
-	struct pollfd poll_fd = { .fd = fd, .events = POLL_IN, .revents = 0 };
-	while (poll(&poll_fd, 1, -1) != -1) {
-		// On Linux, conn will be blocking.  On other
-		// platforms, this may not be the case.  If other
-		// platforms are to be supported, we'd probably want
-		// to set O_NONBLOCK here.
-		int conn = vsock_accept(fd, &rcid, &rport);
-		if (conn == -1)
-			diee(EX_OSERR, "accept");
-
-		setenvf("VSOCKREMOTECID", 1, "%" PRIu32, rcid);
-		setenvf("VSOCKREMOTEPORT", 1, "%" PRIu32, rport);
-
-		ilog("connection from %" PRIu32 " port %" PRIu32, rcid, rport);
-
-		switch (child = fork()) {
-		case -1: diee(EX_OSERR, "fork");
-		case 0:
-			if (dup2(conn, STDIN_FILENO) == -1)
-				diee(EX_OSERR, "dup2");
-			if (dup2(conn, STDOUT_FILENO) == -1)
-				diee(EX_OSERR, "dup2");
-			if (conn != STDIN_FILENO && conn != STDOUT_FILENO)
-				close(conn);
-			execvp(argv[optind], &argv[optind]);
-			diee(EX_OSERR, "exec");
-		}
-
-		if (waitpid(child, NULL, 0) == -1)
-			diee(EX_OSERR, "waitpid");
-
-		close(conn);
-	}
-	diee(EX_OSERR, "poll");
+	// Add `cid' and `port' arguments to binder options.
+	if (argz_add(&binder_opts, &binder_opts_len, "--") ||
+	    argz_add(&binder_opts, &binder_opts_len, argv[optind++]) ||
+	    argz_add(&binder_opts, &binder_opts_len, argv[optind++]))
+		diee(EX_OSERR, "malloc");
+
+	// Add all of daemon_opts onto the end of binder_opts.  It's
+	// okay to multiply to find the size because if it would
+	// overflow calloc would have failed earlier.
+	if (argz_append(&binder_opts, &binder_opts_len, daemon_opts, daemon_opts_len))
+		diee(EX_OSERR, "malloc");
+	free(daemon_opts);
+
+	// Append `prog' to binder_opts.  Technically this should be
+	// part of daemon_opts, but then we'd just be copying it into
+	// one place to immediately copy it elsewhere.
+	for (int i = optind; i < argc; i++)
+		if (argz_add(&binder_opts, &binder_opts_len, argv[i]))
+			diee(EX_OSERR, "malloc");
+
+	if (verbosity == all) {
+		char *opt;
+
+		// Log the full argv before we exec it.
+		fprintf(stderr, "%s: executing", program_invocation_short_name);
+		while ((opt = argz_next(binder_opts, binder_opts_len, opt)))
+			fprintf(stderr, " %s", opt);
+		fputc('\n', stderr);
+	}
+
+	execzp(binder_opts, binder_opts, binder_opts_len);
+	diee(EX_OSERR, "execvp");
 }
diff --git a/vsockserver.c b/vsockserverd.c
similarity index 64%
copy from vsockserver.c
copy to vsockserverd.c
index 43307d2..1abfff9 100644
--- a/vsockserver.c
+++ b/vsockserverd.c
@@ -4,30 +4,25 @@
 #define _GNU_SOURCE
 
 #include <errno.h>
-#include <fcntl.h>
 #include <inttypes.h>
 #include <poll.h>
 #include <stdbool.h>
+#include <stdint.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <stdnoreturn.h>
-#include <string.h>
-#include <sys/socket.h>
 #include <sys/wait.h>
 #include <sysexits.h>
 #include <unistd.h>
 
-#include <linux/vm_sockets.h>
-
 #include "env.h"
 #include "log.h"
-#include "num.h"
 #include "vsock.h"
 
 noreturn static void ex_usage(void)
 {
 	if (verbosity)
-		fprintf(stderr, "Usage: %s [ -1 ] [ -q | -Q | -v ] cid port prog...\n",
+		fprintf(stderr, "Usage: %s [ -1 ] [ -q | -Q | -v ] prog...\n",
 			program_invocation_short_name);
 	exit(EX_USAGE);
 }
@@ -38,6 +33,8 @@ int main(int argc, char *argv[])
 	int opt;
 	pid_t child;
 	uint32_t lcid, lport, rcid, rport;
+	struct pollfd poll_fd =
+		{ .fd = STDIN_FILENO, .events = POLLIN, .revents = 0 };
 
 	while ((opt = getopt(argc, argv, "+1qQv")) != -1) {
 		switch (opt) {
@@ -54,60 +51,42 @@ int main(int argc, char *argv[])
 		}
 	}
 
-	// Check there are enough positional arguments (two for the
-	// address and at least one to exec into).
-	if (optind > argc - 3)
+	// Check that there is something for us to exec into.
+	if (optind > argc - 1)
 		ex_usage();
 
-	if (!strcmp(argv[optind], "-1"))
-		lcid = VMADDR_CID_ANY;
-	else if (getu32(argv[optind], 0, UINT32_MAX, &lcid))
-		ex_usage();
-	optind++;
-
-	if (!strcmp(argv[optind], "-1"))
-		lport = VMADDR_PORT_ANY;
-	else if (getu32(argv[optind], 0, UINT32_MAX, &lport))
-		ex_usage();
-	optind++;
-
-	int fd = socket(AF_VSOCK, SOCK_STREAM, 0);
-	if (fd == -1)
-		diee(EX_OSERR, "socket");
-
-	if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1)
-		diee(EX_OSERR, "fcntl");
-
-	if (vsock_bind(fd, lcid, lport) == -1)
-		diee(EX_OSERR, "bind");
-
-	if (listen(fd, 40) == -1)
-		diee(EX_OSERR, "listen");
-
-	if (lport == VMADDR_PORT_ANY)
-		if (vsock_get_cid_and_port(fd, NULL, &lport) == -1)
-			diee(EX_OSERR, "getsockname");
+	// Find out the CID and port of the socket.
+	if (vsock_get_cid_and_port(STDIN_FILENO, &lcid, &lport)) {
+		if (errno == ENOTSOCK || errno == EPROTOTYPE)
+			die(EX_USAGE, "stdin is not an AF_VSOCK socket");
+		diee(EX_OSERR, "getsockname");
+	}
 
+	// Notify the user of our port and indicate readiness.
 	if (notify) {
 		printf("%" PRIu32 "\n", lport);
 		fclose(stdout);
 	}
 
+	// Set our CID and port as environment variables so `prog' can
+	// know them.
 	setenvf("VSOCKLOCALCID", 1, "%" PRIu32, lcid);
 	setenvf("VSOCKLOCALPORT", 1, "%" PRIu32, lport);
 
 	ilog("listening as %" PRIu32 " on port %" PRIu32, lcid, lport);
 
-	struct pollfd poll_fd = { .fd = fd, .events = POLL_IN, .revents = 0 };
+	// Wait for a connection.
 	while (poll(&poll_fd, 1, -1) != -1) {
 		// On Linux, conn will be blocking.  On other
 		// platforms, this may not be the case.  If other
 		// platforms are to be supported, we'd probably want
 		// to set O_NONBLOCK here.
-		int conn = vsock_accept(fd, &rcid, &rport);
+		int conn = vsock_accept(STDIN_FILENO, &rcid, &rport);
 		if (conn == -1)
 			diee(EX_OSERR, "accept");
 
+		// Set the client's CID and port as environment
+		// variables so `prog' can know them.
 		setenvf("VSOCKREMOTECID", 1, "%" PRIu32, rcid);
 		setenvf("VSOCKREMOTEPORT", 1, "%" PRIu32, rport);
 
@@ -116,12 +95,18 @@ int main(int argc, char *argv[])
 		switch (child = fork()) {
 		case -1: diee(EX_OSERR, "fork");
 		case 0:
+			// Set up the connection socket on prog's
+			// stdin and stdout.  This has the happy side
+			// effect of closing the listening socket in
+			// the child, since it's also on stdin.
 			if (dup2(conn, STDIN_FILENO) == -1)
 				diee(EX_OSERR, "dup2");
 			if (dup2(conn, STDOUT_FILENO) == -1)
 				diee(EX_OSERR, "dup2");
 			if (conn != STDIN_FILENO && conn != STDOUT_FILENO)
 				close(conn);
+
+			// exec into `prog'.
 			execvp(argv[optind], &argv[optind]);
 			diee(EX_OSERR, "exec");
 		}
-- 
2.30.0

  parent reply	other threads:[~2021-03-19  3:17 UTC|newest]

Thread overview: 15+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-03-19  3:23 Broken threading fix Alyssa Ross
2021-03-19  2:56 ` [PATCH ucspi-vsock 1/7] vsock: get cid and port instead of just cid Alyssa Ross
2021-03-19  2:53   ` [PATCH ucspi-vsock 0/7] Extract vsockserver-socketbinder and vsockserverd Alyssa Ross
2021-03-19  3:19     ` Alyssa Ross
2021-03-19  2:56 ` [PATCH ucspi-vsock 2/7] vsock: check socket family before reading sockaddr Alyssa Ross
2021-03-19  2:56 ` [PATCH ucspi-vsock 3/7] vsockserver: switch to a non-blocking socket Alyssa Ross
2021-03-19  2:56 ` [PATCH ucspi-vsock 4/7] configure: create, to generate config.h Alyssa Ross
2021-03-19  2:56 ` [PATCH ucspi-vsock 5/7] log: add die function Alyssa Ross
2021-03-19  2:56 ` [PATCH ucspi-vsock 6/7] exec: add execzp() Alyssa Ross
2021-03-19  3:17 ` Alyssa Ross [this message]
2021-03-19  3:39   ` [PATCH ucspi-vsock 7/7] Extract vsockserver-socketbinder and vsockserverd Cole Helbling
2021-03-20 20:24     ` Alyssa Ross
2021-03-21  2:52       ` Cole Helbling
2021-03-21 14:33         ` Alyssa Ross
2021-03-21 14:38           ` Alyssa Ross

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20210319031713.23600-1-hi@alyssa.is \
    --to=hi@alyssa.is \
    --cc=devel@spectrum-os.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this public inbox

	https://spectrum-os.org/git/crosvm
	https://spectrum-os.org/git/doc
	https://spectrum-os.org/git/mktuntap
	https://spectrum-os.org/git/nixpkgs
	https://spectrum-os.org/git/spectrum
	https://spectrum-os.org/git/ucspi-vsock
	https://spectrum-os.org/git/www

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).