"GOT", but the "O" is a cute, smiling pufferfish. Index | Thread | Search

From:
Tracey Emery <tracey@traceyemery.net>
Subject:
Re: add gotctl(8)
To:
gameoftrees@openbsd.org, Stefan Sperling <stsp@stsp.name>
Date:
Sat, 29 Oct 2022 08:39:21 -0600

Download raw body.

Thread
On October 29, 2022 8:15:16 AM MDT, Stefan Sperling <stsp@stsp.name> wrote:
>On Sat, Oct 29, 2022 at 09:05:49AM -0400, Josiah Frentsos wrote:
>> The second .Bl should be removed.
>
>> That should be "Ds".
>
>> There's a missing .El here.
>
>Thanks! Updated diff below. I have also removed some log_debug() leftovers.
>

I looked this over on my phone, since I'm afk, but it looks ok to me. Cool idea!! I wasn't sure if we'd need a control or not. :)

>diff refs/heads/main refs/heads/gotctl
>commit - d815102a02118e65c84d562a312d18f27303c85f
>commit + 2e9dee2446b72f3695924f4dae2c5286b16b5c3b
>blob - dfc19a3497e88dd48a01d757c267998ea8be1241
>blob + fe7db9a3d673041a04256447d4b529fc4109ed8c
>--- Makefile
>+++ Makefile
>@@ -46,10 +46,12 @@ server:
> 	${MAKE} -C gotwebd install
> 
> server:
>+	${MAKE} -C gotctl
> 	${MAKE} -C gotd
> 	${MAKE} -C gotsh
> 
> server-install:
>+	${MAKE} -C gotctl install
> 	${MAKE} -C gotd install
> 	${MAKE} -C gotsh install
> 
>blob - 89c442c3a1faa744027e0511d4fd421f9474ee20
>blob + b970ee903b827d35f5b176c68200ec092a13d4e8
>--- README
>+++ README
>@@ -66,12 +66,14 @@ This will install the following commands:
> This will install the following commands:
> 
>  gotd, the repository server program
>+ gotctl, the server control utility
>  gotsh, the login shell for users accessing the server via the network
> 
> See the following manual page files for information about server setup:
> 
>  $ man -l gotd/gotd.8
>  $ man -l gotd/gotd.conf.5
>+ $ man -l gotctl/gotctl.8
>  $ man -l gotsh/gotsh.1
> 
> 
>blob - e3a20027002c07e25791ee436b0838e10613c439
>blob + 8f699ed8dfc370daa7619bed3732d46c81c4c22b
>--- gotd/gotd.c
>+++ gotd/gotd.c
>@@ -95,6 +95,7 @@ void gotd_sighdlr(int sig, short event, void *arg);
> static struct gotd gotd;
> 
> void gotd_sighdlr(int sig, short event, void *arg);
>+static void gotd_shutdown(void);
> 
> __dead static void
> usage()
>@@ -264,10 +265,7 @@ get_client_proc(struct gotd_client *client)
> 		return client->repo_read;
> 	else if (client->repo_write)
> 		return client->repo_write;
>-	else {
>-		fatal("uid %d is neither reading nor writing", client->euid);
>-		/* NOTREACHED */
>-	}
>+
> 	return NULL;
> }
> 
>@@ -341,11 +339,12 @@ disconnect(struct gotd_client *client)
> 	log_debug("uid %d: disconnecting", client->euid);
> 
> 	idisconnect.client_id = client->id;
>-	if (gotd_imsg_compose_event(&proc->iev,
>-	    GOTD_IMSG_DISCONNECT, PROC_GOTD, -1,
>-	    &idisconnect, sizeof(idisconnect)) == -1)
>-		log_warn("imsg compose DISCONNECT");
>-
>+	if (proc) {
>+		if (gotd_imsg_compose_event(&proc->iev,
>+		    GOTD_IMSG_DISCONNECT, PROC_GOTD, -1,
>+		    &idisconnect, sizeof(idisconnect)) == -1)
>+			log_warn("imsg compose DISCONNECT");
>+	}
> 	slot = client_hash(client->id) % nitems(gotd_clients);
> 	STAILQ_REMOVE(&gotd_clients[slot], client, gotd_client, entry);
> 	imsg_clear(&client->iev.ibuf);
>@@ -384,6 +383,167 @@ static struct gotd_child_proc *
> 	disconnect(client);
> }
> 
>+static const struct got_error *
>+send_repo_info(struct gotd_imsgev *iev, struct gotd_repo *repo)
>+{
>+	const struct got_error *err = NULL;
>+	struct gotd_imsg_info_repo irepo;
>+
>+	memset(&irepo, 0, sizeof(irepo));
>+
>+	if (strlcpy(irepo.repo_name, repo->name, sizeof(irepo.repo_name))
>+	    >= sizeof(irepo.repo_name))
>+		return got_error_msg(GOT_ERR_NO_SPACE, "repo name too long");
>+	if (strlcpy(irepo.repo_path, repo->path, sizeof(irepo.repo_path))
>+	    >= sizeof(irepo.repo_path))
>+		return got_error_msg(GOT_ERR_NO_SPACE, "repo path too long");
>+
>+	if (gotd_imsg_compose_event(iev, GOTD_IMSG_INFO_REPO, PROC_GOTD, -1,
>+	    &irepo, sizeof(irepo)) == -1) {
>+		err = got_error_from_errno("imsg compose INFO_REPO");
>+		if (err)
>+			return err;
>+	}
>+
>+	return NULL;
>+}
>+
>+static const struct got_error *
>+send_capability(struct gotd_client_capability *capa, struct gotd_imsgev* iev)
>+{
>+	const struct got_error *err = NULL;
>+	struct gotd_imsg_capability icapa;
>+	size_t len;
>+	struct ibuf *wbuf;
>+
>+	memset(&icapa, 0, sizeof(icapa));
>+
>+	icapa.key_len = strlen(capa->key);
>+	len = sizeof(icapa) + icapa.key_len;
>+	if (capa->value) {
>+		icapa.value_len = strlen(capa->value);
>+		len += icapa.value_len;
>+	}
>+
>+	wbuf = imsg_create(&iev->ibuf, GOTD_IMSG_CAPABILITY, 0, 0, len);
>+	if (wbuf == NULL) {
>+		err = got_error_from_errno("imsg_create CAPABILITY");
>+		return err;
>+	}
>+
>+	if (imsg_add(wbuf, &icapa, sizeof(icapa)) == -1)
>+		return got_error_from_errno("imsg_add CAPABILITY");
>+	if (imsg_add(wbuf, capa->key, icapa.key_len) == -1)
>+		return got_error_from_errno("imsg_add CAPABILITY");
>+	if (capa->value) {
>+		if (imsg_add(wbuf, capa->value, icapa.value_len) == -1)
>+			return got_error_from_errno("imsg_add CAPABILITY");
>+	}
>+
>+	wbuf->fd = -1;
>+	imsg_close(&iev->ibuf, wbuf);
>+
>+	gotd_imsg_event_add(iev);
>+
>+	return NULL;
>+}
>+
>+static const struct got_error *
>+send_client_info(struct gotd_imsgev *iev, struct gotd_client *client)
>+{
>+	const struct got_error *err = NULL;
>+	struct gotd_imsg_info_client iclient;
>+	struct gotd_child_proc *proc;
>+	size_t i;
>+
>+	memset(&iclient, 0, sizeof(iclient));
>+	iclient.euid = client->euid;
>+	iclient.egid = client->egid;
>+
>+	proc = get_client_proc(client);
>+	if (proc) {
>+		if (strlcpy(iclient.repo_name, proc->chroot_path,
>+		    sizeof(iclient.repo_name)) >= sizeof(iclient.repo_name)) {
>+			return got_error_msg(GOT_ERR_NO_SPACE,
>+			    "repo name too long");
>+		}
>+		if (client_is_writing(client))
>+			iclient.is_writing = 1;
>+	}
>+
>+	iclient.state = client->state;
>+	iclient.ncapabilities = client->ncapabilities;
>+
>+	if (gotd_imsg_compose_event(iev, GOTD_IMSG_INFO_CLIENT, PROC_GOTD, -1,
>+	    &iclient, sizeof(iclient)) == -1) {
>+		err = got_error_from_errno("imsg compose INFO_CLIENT");
>+		if (err)
>+			return err;
>+	}
>+
>+	for (i = 0; i < client->ncapabilities; i++) {
>+		struct gotd_client_capability *capa;
>+		capa = &client->capabilities[i];
>+		err = send_capability(capa, iev);
>+		if (err)
>+			return err;
>+	}
>+
>+	return NULL;
>+}
>+
>+static const struct got_error *
>+send_info(struct gotd_client *client)
>+{
>+	const struct got_error *err = NULL;
>+	struct gotd_imsg_info info;
>+	uint64_t slot;
>+	struct gotd_repo *repo;
>+
>+	info.pid = gotd.pid;
>+	info.verbosity = gotd.verbosity;
>+	info.nrepos = gotd.nrepos;
>+	info.nclients = client_cnt - 1;
>+
>+	if (gotd_imsg_compose_event(&client->iev, GOTD_IMSG_INFO, PROC_GOTD, -1,
>+	    &info, sizeof(info)) == -1) {
>+		err = got_error_from_errno("imsg compose INFO");
>+		if (err)
>+			return err;
>+	}
>+
>+	TAILQ_FOREACH(repo, &gotd.repos, entry) {
>+		err = send_repo_info(&client->iev, repo);
>+		if (err)
>+			return err;
>+	}
>+
>+	for (slot = 0; slot < nitems(gotd_clients); slot++) {
>+		struct gotd_client *c;
>+		STAILQ_FOREACH(c, &gotd_clients[slot], entry) {
>+			if (c->id == client->id)
>+				continue;
>+			err = send_client_info(&client->iev, c);
>+			if (err)
>+				return err;
>+		}
>+	}
>+
>+	return NULL;
>+}
>+
>+static const struct got_error *
>+stop_gotd(struct gotd_client *client)
>+{
>+
>+	if (client->euid != 0)
>+		return got_error_set_errno(EPERM, "stop");
>+
>+	gotd_shutdown();
>+	/* NOTREACHED */
>+	return NULL;
>+}
>+
> static struct gotd_child_proc *
> find_proc_by_repo_name(enum gotd_procid proc_id, const char *repo_name)
> {
>@@ -465,6 +625,8 @@ forward_list_refs_request(struct gotd_client *client, 
> 		return got_error_from_errno("dup");
> 
> 	proc = get_client_proc(client);
>+	if (proc == NULL)
>+		fatalx("no process found for uid %d", client->euid);
> 	if (gotd_imsg_compose_event(&proc->iev,
> 	    GOTD_IMSG_LIST_REFS_INTERNAL, PROC_GOTD, fd,
> 	    &ilref, sizeof(ilref)) == -1) {
>@@ -838,6 +1000,12 @@ gotd_request(int fd, short events, void *arg)
> 				return;
> 			}
> 		}
>+
>+		/* Disconnect gotctl(8) now that messages have been sent. */
>+		if (!client_is_reading(client) && !client_is_writing(client)) {
>+			disconnect(client);
>+			return;
>+		}
> 	}
> 
> 	if ((events & EV_READ) == 0)
>@@ -856,6 +1024,12 @@ gotd_request(int fd, short events, void *arg)
> 		evtimer_del(&client->tmo);
> 
> 		switch (imsg.hdr.type) {
>+		case GOTD_IMSG_INFO:
>+			err = send_info(client);
>+			break;
>+		case GOTD_IMSG_STOP:
>+			err = stop_gotd(client);
>+			break;
> 		case GOTD_IMSG_LIST_REFS:
> 			if (client->state != GOTD_STATE_EXPECT_LIST_REFS) {
> 				err = got_error_msg(GOT_ERR_BAD_REQUEST,
>@@ -1246,6 +1420,9 @@ verify_imsg_src(struct gotd_client *client, struct got
> 	int ret = 0;
> 
> 	client_proc = get_client_proc(client);
>+	if (client_proc == NULL)
>+		fatalx("no process found for uid %d", client->euid);
>+
> 	if (proc->pid != client_proc->pid) {
> 		kill_proc(proc, 1);
> 		log_warnx("received message from PID %d for uid %d, while "
>blob - 181834ce44e984caf7169755b86ad50d182325fd
>blob + bf91df1c5d10c70c9dd79eae37b18f6f1016bb4e
>--- gotd/gotd.h
>+++ gotd/gotd.h
>@@ -103,6 +103,12 @@ enum gotd_imsg_type {
> 	/* An error occured while processing a request. */
> 	GOTD_IMSG_ERROR,
> 
>+	/* Commands used by gotctl(8). */
>+	GOTD_IMSG_INFO,
>+	GOTD_IMSG_INFO_REPO,
>+	GOTD_IMSG_INFO_CLIENT,
>+	GOTD_IMSG_STOP,
>+
> 	/* Request a list of references. */
> 	GOTD_IMSG_LIST_REFS,
> 	GOTD_IMSG_LIST_REFS_INTERNAL,
>@@ -155,6 +161,35 @@ struct gotd_imsg_error {
> 	char msg[GOT_ERR_MAX_MSG_SIZE];
> } __attribute__((__packed__));
> 
>+/* Structure for GOTD_IMSG_INFO. */
>+struct gotd_imsg_info {
>+	pid_t pid;
>+	int verbosity;
>+	int nrepos;
>+	int nclients;
>+
>+	/* Followed by nclients GOTD_IMSG_INFO_REPO messages. */
>+	/* Followed by nclients GOTD_IMSG_INFO_CLIENT messages. */
>+};
>+
>+/* Structure for GOTD_IMSG_INFO_REPO. */
>+struct gotd_imsg_info_repo {
>+	char repo_name[NAME_MAX];
>+	char repo_path[PATH_MAX];
>+};
>+
>+/* Structure for GOTD_IMSG_INFO_CLIENT */
>+struct gotd_imsg_info_client {
>+	uid_t euid;
>+	gid_t egid;
>+	char repo_name[NAME_MAX];
>+	int is_writing;
>+	enum gotd_client_state state;
>+	size_t ncapabilities;
>+
>+	/* Followed by ncapabilities GOTD_IMSG_CAPABILITY. */
>+};
>+
> /* Structure for GOTD_IMSG_LIST_REFS. */
> struct gotd_imsg_list_refs {
> 	char repo_name[NAME_MAX];
>blob - /dev/null
>blob + 200e34d3d28dabf795f51d6137af53cbe1e40c5a (mode 644)
>--- /dev/null
>+++ gotctl/Makefile
>@@ -0,0 +1,27 @@
>+.PATH:${.CURDIR}/../lib ${.CURDIR}/../gotd
>+
>+.include "../got-version.mk"
>+
>+PROG=		gotctl
>+SRCS=		gotctl.c error.c imsg.c pollfd.c sha1.c
>+
>+MAN =		${PROG}.1
>+
>+CPPFLAGS = -I${.CURDIR}/../include -I${.CURDIR}/../lib -I${.CURDIR}/../gotd
>+
>+.if defined(PROFILE)
>+LDADD = -lutil_p -lz_p -lm_p -lc_p -levent_p
>+.else
>+LDADD = -lutil -lz -lm -levent
>+.endif
>+DPADD = ${LIBZ} ${LIBUTIL}
>+
>+.if ${GOT_RELEASE} != "Yes"
>+NOMAN = Yes
>+.endif
>+
>+realinstall:
>+	${INSTALL} ${INSTALL_COPY} -o ${BINOWN} -g ${BINGRP} \
>+	-m ${BINMODE} ${PROG} ${BINDIR}/${PROG}
>+
>+.include <bsd.prog.mk>
>blob - /dev/null
>blob + d41a6e1a949b1556352b5d6b79871935dd15e95a (mode 644)
>--- /dev/null
>+++ gotctl/gotctl.8
>@@ -0,0 +1,71 @@
>+.\"
>+.\" Copyright (c) 2022 Stefan Sperling
>+.\"
>+.\" Permission to use, copy, modify, and distribute this software for any
>+.\" purpose with or without fee is hereby granted, provided that the above
>+.\" copyright notice and this permission notice appear in all copies.
>+.\"
>+.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
>+.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
>+.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
>+.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
>+.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
>+.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
>+.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
>+.\"
>+.Dd $Mdocdate$
>+.Dt GOTCTL 8
>+.Os
>+.Sh NAME
>+.Nm gotctl
>+.Nd control the Game of Trees Daemon
>+.Sh SYNOPSIS
>+.Nm
>+.Op Fl hV
>+.Op Fl f Ar path
>+.Ar command
>+.Op Ar arg ...
>+.Sh DESCRIPTION
>+.Nm
>+controls the
>+.Xr gotd 8
>+daemon.
>+.Pp
>+The options for
>+.Nm
>+are as follows:
>+.Bl -tag -width Ds
>+.It Fl h
>+Display usage information and exit immediately.
>+.It Fl f Ar path
>+Set the
>+.Ar path
>+to the unix socket which
>+.Xr gotd 8
>+is listening on.
>+If not specified, the default path
>+.Pa /var/run/gotd.sock
>+will be used.
>+.It Fl V , -version
>+Display program version and exit immediately.
>+.El
>+.Pp
>+The commands for
>+.Nm
>+are as follows:
>+.Bl -tag -width Ds
>+.It Cm info
>+Display information about a running
>+.Xr gotd 8
>+instance.
>+.It Cm stop
>+Stop a running
>+.Xr gotd 8
>+instance.
>+This operation requires root privileges.
>+.El
>+.Sh SEE ALSO
>+.Xr got 1 ,
>+.Xr gotd 8
>+.Sh AUTHORS
>+.An Stefan Sperling Aq Mt stsp@openbsd.org
>blob - /dev/null
>blob + 821e75bd339a40b002fc603fcf67ccd42e2a0578 (mode 644)
>--- /dev/null
>+++ gotctl/gotctl.c
>@@ -0,0 +1,461 @@
>+/*
>+ * Copyright (c) 2022 Stefan Sperling <stsp@openbsd.org>
>+ *
>+ * Permission to use, copy, modify, and distribute this software for any
>+ * purpose with or without fee is hereby granted, provided that the above
>+ * copyright notice and this permission notice appear in all copies.
>+ *
>+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
>+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
>+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
>+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
>+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
>+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
>+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
>+ */
>+
>+#include <sys/queue.h>
>+#include <sys/socket.h>
>+#include <sys/un.h>
>+
>+#include <err.h>
>+#include <event.h>
>+#include <imsg.h>
>+#include <limits.h>
>+#include <locale.h>
>+#include <sha1.h>
>+#include <stdio.h>
>+#include <stdlib.h>
>+#include <string.h>
>+#include <getopt.h>
>+#include <unistd.h>
>+
>+#include "got_error.h"
>+#include "got_version.h"
>+
>+#include "got_lib_gitproto.h"
>+
>+#include "gotd.h"
>+
>+#ifndef nitems
>+#define nitems(_a)	(sizeof((_a)) / sizeof((_a)[0]))
>+#endif
>+
>+#define GOTCTL_CMD_INFO "info"
>+#define GOTCTL_CMD_STOP "stop"
>+
>+struct gotctl_cmd {
>+	const char	*cmd_name;
>+	const struct got_error *(*cmd_main)(int, char *[], int);
>+	void		(*cmd_usage)(void);
>+};
>+
>+__dead static void	usage(int, int);
>+
>+__dead static void	usage_info(void);
>+__dead static void	usage_stop(void);
>+
>+static const struct got_error*		cmd_info(int, char *[], int);
>+static const struct got_error*		cmd_stop(int, char *[], int);
>+
>+static const struct gotctl_cmd gotctl_commands[] = {
>+	{ "info",	cmd_info,	usage_info },
>+	{ "stop",	cmd_stop,	usage_stop },
>+};
>+
>+__dead static void
>+usage_info(void)
>+{
>+	fprintf(stderr, "usage: %s info\n", getprogname());
>+	exit(1);
>+}
>+
>+static const struct got_error *
>+show_info(struct imsg *imsg)
>+{
>+	struct gotd_imsg_info info;
>+	size_t datalen;
>+
>+	datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
>+	if (datalen != sizeof(info))
>+		return got_error(GOT_ERR_PRIVSEP_LEN);
>+	memcpy(&info, imsg->data, sizeof(info));
>+
>+	printf("gotd PID: %d\n", info.pid);
>+	printf("verbosity: %d\n", info.verbosity);
>+	printf("number of repositories: %d\n", info.nrepos);
>+	printf("number of connected clients: %d\n", info.nclients);
>+	return NULL;
>+}
>+
>+static const struct got_error *
>+show_repo_info(struct imsg *imsg)
>+{
>+	struct gotd_imsg_info_repo info;
>+	size_t datalen;
>+
>+	datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
>+	if (datalen != sizeof(info))
>+		return got_error(GOT_ERR_PRIVSEP_LEN);
>+	memcpy(&info, imsg->data, sizeof(info));
>+
>+	printf("repository \"%s\", path %s\n", info.repo_name, info.repo_path);
>+	return NULL;
>+}
>+
>+static const char *
>+get_state_name(enum gotd_client_state state)
>+{
>+	static char unknown_state[64];
>+
>+	switch (state) {
>+	case GOTD_STATE_EXPECT_LIST_REFS:
>+		return "list-refs";
>+	case GOTD_STATE_EXPECT_CAPABILITIES:
>+		return "expect-capabilities";
>+	case GOTD_STATE_EXPECT_WANT:
>+		return "expect-want";
>+	case GOTD_STATE_EXPECT_REF_UPDATE:
>+		return "expect-ref-update";
>+	case GOTD_STATE_EXPECT_MORE_REF_UPDATES:
>+		return "expect-more-ref-updates";
>+	case GOTD_STATE_EXPECT_HAVE:
>+		return "expect-have";
>+	case GOTD_STATE_EXPECT_PACKFILE:
>+		return "expect-packfile";
>+	case GOTD_STATE_EXPECT_DONE:
>+		return "expect-done";
>+	case GOTD_STATE_DONE:
>+		return "done";
>+	}
>+
>+	snprintf(unknown_state, sizeof(unknown_state),
>+	    "unknown state %d", state);
>+	return unknown_state;
>+}
>+
>+static const struct got_error *
>+show_client_info(struct imsg *imsg)
>+{
>+	struct gotd_imsg_info_client info;
>+	size_t datalen;
>+
>+	datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
>+	if (datalen != sizeof(info))
>+		return got_error(GOT_ERR_PRIVSEP_LEN);
>+	memcpy(&info, imsg->data, sizeof(info));
>+
>+	printf("client UID %d, GID %d, protocol state '%s', ",
>+	    info.euid, info.egid, get_state_name(info.state));
>+	if (info.is_writing)
>+		printf("writing to %s\n", info.repo_name);
>+	else
>+		printf("reading from %s\n", info.repo_name);
>+
>+	return NULL;
>+}
>+
>+static const struct got_error *
>+show_capability(struct imsg *imsg)
>+{
>+	struct gotd_imsg_capability icapa;
>+	size_t datalen;
>+	char *key, *value = NULL;
>+
>+	memset(&icapa, 0, sizeof(icapa));
>+
>+	datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
>+	if (datalen < sizeof(icapa))
>+		return got_error(GOT_ERR_PRIVSEP_LEN);
>+	memcpy(&icapa, imsg->data, sizeof(icapa));
>+
>+	if (datalen != sizeof(icapa) + icapa.key_len + icapa.value_len)
>+		return got_error(GOT_ERR_PRIVSEP_LEN);
>+
>+	key = malloc(icapa.key_len + 1);
>+	if (key == NULL)
>+		return got_error_from_errno("malloc");
>+	if (icapa.value_len > 0) {
>+		value = malloc(icapa.value_len + 1);
>+		if (value == NULL) {
>+			free(key);
>+			return got_error_from_errno("malloc");
>+		}
>+	}
>+
>+	memcpy(key, imsg->data + sizeof(icapa), icapa.key_len);
>+	key[icapa.key_len] = '\0';
>+	if (value) {
>+		memcpy(value, imsg->data + sizeof(icapa) + icapa.key_len,
>+		    icapa.value_len);
>+		value[icapa.value_len] = '\0';
>+	}
>+
>+	if (strcmp(key, GOT_CAPA_AGENT) == 0)
>+		printf("  client user agent: %s\n", value);
>+	else if (value)
>+		printf("  client supports %s=%s\n", key, value);
>+	else
>+		printf("  client supports %s\n", key);
>+
>+	free(key);
>+	free(value);
>+	return NULL;
>+}
>+
>+static const struct got_error *
>+cmd_info(int argc, char *argv[], int gotd_sock)
>+{
>+	const struct got_error *err;
>+	struct imsgbuf ibuf;
>+	struct imsg imsg;
>+
>+	imsg_init(&ibuf, gotd_sock);
>+
>+	if (imsg_compose(&ibuf, GOTD_IMSG_INFO, 0, 0, -1, NULL, 0) == -1)
>+		return got_error_from_errno("imsg_compose INFO");
>+
>+	err = gotd_imsg_flush(&ibuf);
>+	while (err == NULL) {
>+		err = gotd_imsg_poll_recv(&imsg, &ibuf, 0);
>+		if (err) {
>+			if (err->code == GOT_ERR_EOF)
>+				err = NULL;
>+			break;
>+		}
>+		
>+		switch (imsg.hdr.type) {
>+		case GOTD_IMSG_ERROR:
>+			err = gotd_imsg_recv_error(NULL, &imsg);
>+			break;
>+		case GOTD_IMSG_INFO:
>+			err = show_info(&imsg);
>+			break;
>+		case GOTD_IMSG_INFO_REPO:
>+			err = show_repo_info(&imsg);
>+			break;
>+		case GOTD_IMSG_INFO_CLIENT:
>+			err = show_client_info(&imsg);
>+			break;
>+		case GOTD_IMSG_CAPABILITY:
>+			err = show_capability(&imsg);
>+			break;
>+		default:
>+			err = got_error(GOT_ERR_PRIVSEP_MSG);
>+			break;
>+		}
>+
>+		imsg_free(&imsg);
>+	}
>+
>+	imsg_clear(&ibuf);
>+	return err;
>+}
>+
>+__dead static void
>+usage_stop(void)
>+{
>+	fprintf(stderr, "usage: %s stop\n", getprogname());
>+	exit(1);
>+}
>+
>+static const struct got_error *
>+cmd_stop(int argc, char *argv[], int gotd_sock)
>+{
>+	const struct got_error *err;
>+	struct imsgbuf ibuf;
>+	struct imsg imsg;
>+
>+	imsg_init(&ibuf, gotd_sock);
>+
>+	if (imsg_compose(&ibuf, GOTD_IMSG_STOP, 0, 0, -1, NULL, 0) == -1)
>+		return got_error_from_errno("imsg_compose STOP");
>+
>+	err = gotd_imsg_flush(&ibuf);
>+	while (err == NULL) {
>+		err = gotd_imsg_poll_recv(&imsg, &ibuf, 0);
>+		if (err) {
>+			if (err->code == GOT_ERR_EOF)
>+				err = NULL;
>+			break;
>+		}
>+		
>+		switch (imsg.hdr.type) {
>+		case GOTD_IMSG_ERROR:
>+			err = gotd_imsg_recv_error(NULL, &imsg);
>+			break;
>+		default:
>+			err = got_error(GOT_ERR_PRIVSEP_MSG);
>+			break;
>+		}
>+
>+		imsg_free(&imsg);
>+	}
>+
>+	imsg_clear(&ibuf);
>+	return err;
>+}
>+
>+static void
>+list_commands(FILE *fp)
>+{
>+	size_t i;
>+
>+	fprintf(fp, "commands:");
>+	for (i = 0; i < nitems(gotctl_commands); i++) {
>+		const struct gotctl_cmd *cmd = &gotctl_commands[i];
>+		fprintf(fp, " %s", cmd->cmd_name);
>+	}
>+	fputc('\n', fp);
>+}
>+
>+__dead static void
>+usage(int hflag, int status)
>+{
>+	FILE *fp = (status == 0) ? stdout : stderr;
>+
>+	fprintf(fp, "usage: %s [-hV] command [arg ...]\n",
>+	    getprogname());
>+	if (hflag)
>+		list_commands(fp);
>+	exit(status);
>+}
>+
>+static const struct got_error *
>+apply_unveil(const char *unix_socket_path)
>+{
>+#ifdef PROFILE
>+	if (unveil("gmon.out", "rwc") != 0)
>+		return got_error_from_errno2("unveil", "gmon.out");
>+#endif
>+	if (unveil(unix_socket_path, "w") != 0)
>+		return got_error_from_errno2("unveil", unix_socket_path);
>+
>+	if (unveil(NULL, NULL) != 0)
>+		return got_error_from_errno("unveil");
>+
>+	return NULL;
>+}
>+
>+static int
>+connect_gotd(const char *socket_path)
>+{
>+	const struct got_error *error = NULL;
>+	char unix_socket_path[PATH_MAX];
>+	int gotd_sock = -1;
>+	struct sockaddr_un sun;
>+
>+	if (socket_path) {
>+		if (strlcpy(unix_socket_path, socket_path,
>+		    sizeof(unix_socket_path)) >= sizeof(unix_socket_path)) 
>+			errx(1, "gotd socket path too long");
>+	} else {
>+		strlcpy(unix_socket_path, GOTD_UNIX_SOCKET,
>+		    sizeof(unix_socket_path));
>+	}
>+
>+	error = apply_unveil(unix_socket_path);
>+	if (error)
>+		errx(1, "%s", error->msg);
>+
>+#ifndef PROFILE
>+	if (pledge("stdio unix", NULL) == -1)
>+		err(1, "pledge");
>+#endif
>+	if ((gotd_sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
>+		err(1, "socket");
>+
>+	memset(&sun, 0, sizeof(sun));
>+	sun.sun_family = AF_UNIX;
>+	if (strlcpy(sun.sun_path, unix_socket_path,
>+	    sizeof(sun.sun_path)) >= sizeof(sun.sun_path))
>+		errx(1, "gotd socket path too long");
>+	if (connect(gotd_sock, (struct sockaddr *)&sun, sizeof(sun)) == -1)
>+		err(1, "connect: %s", unix_socket_path);
>+
>+#ifndef PROFILE
>+	if (pledge("stdio", NULL) == -1)
>+		err(1, "pledge");
>+#endif
>+
>+	return gotd_sock;
>+}
>+
>+int
>+main(int argc, char *argv[])
>+{
>+	const struct gotctl_cmd *cmd;
>+	int gotd_sock = -1, i;
>+	int ch;
>+	int hflag = 0, Vflag = 0;
>+	static const struct option longopts[] = {
>+	    { "version", no_argument, NULL, 'V' },
>+	    { NULL, 0, NULL, 0 }
>+	};
>+	const char *socket_path = NULL;
>+
>+	setlocale(LC_CTYPE, "");
>+
>+#ifndef PROFILE
>+	if (pledge("stdio unix unveil", NULL) == -1)
>+		err(1, "pledge");
>+#endif
>+
>+	while ((ch = getopt_long(argc, argv, "+hf:V", longopts, NULL)) != -1) {
>+		switch (ch) {
>+		case 'h':
>+			hflag = 1;
>+			break;
>+		case 'f':
>+			socket_path = optarg;
>+			break;
>+		case 'V':
>+			Vflag = 1;
>+			break;
>+		default:
>+			usage(hflag, 1);
>+			/* NOTREACHED */
>+		}
>+	}
>+
>+	argc -= optind;
>+	argv += optind;
>+	optind = 1;
>+	optreset = 1;
>+
>+	if (Vflag) {
>+		got_version_print_str();
>+		return 0;
>+	}
>+
>+	if (argc <= 0)
>+		usage(hflag, hflag ? 0 : 1);
>+
>+	for (i = 0; i < nitems(gotctl_commands); i++) {
>+		const struct got_error *error;
>+
>+		cmd = &gotctl_commands[i];
>+
>+		if (strncmp(cmd->cmd_name, argv[0], strlen(argv[0])) != 0)
>+			continue;
>+
>+		if (hflag)
>+			cmd->cmd_usage();
>+
>+		gotd_sock = connect_gotd(socket_path);
>+		if (gotd_sock == -1)
>+			return 1;
>+		error = cmd->cmd_main(argc, argv, gotd_sock);
>+		close(gotd_sock);
>+		if (error) {
>+			fprintf(stderr, "%s: %s\n", getprogname(), error->msg);
>+			return 1;
>+		}
>+
>+		return 0;
>+	}
>+
>+	fprintf(stderr, "%s: unknown command '%s'\n", getprogname(), argv[0]);
>+	list_commands(stderr);
>+	return 1;
>+}
>


-- 
Tracey Emery
Sent from my phone.