From: Tracey Emery Subject: Re: add gotctl(8) To: gameoftrees@openbsd.org, Stefan Sperling Date: Sat, 29 Oct 2022 08:39:21 -0600 On October 29, 2022 8:15:16 AM MDT, Stefan Sperling 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 >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 >+ * >+ * 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 >+#include >+#include >+ >+#include >+#include >+#include >+#include >+#include >+#include >+#include >+#include >+#include >+#include >+#include >+ >+#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.