From: Stefan Sperling Subject: Re: add gotctl(8) To: gameoftrees@openbsd.org Date: Sat, 29 Oct 2022 16:15:16 +0200 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. 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; +}