Blob


1 /*
2 * Copyright (c) 2022 Stefan Sperling <stsp@openbsd.org>
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 *
8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
15 */
17 #include <sys/queue.h>
18 #include <sys/socket.h>
19 #include <sys/un.h>
21 #include <err.h>
22 #include <event.h>
23 #include <imsg.h>
24 #include <limits.h>
25 #include <locale.h>
26 #include <sha1.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <getopt.h>
31 #include <unistd.h>
33 #include "got_error.h"
34 #include "got_version.h"
36 #include "got_lib_gitproto.h"
38 #include "gotd.h"
40 #ifndef nitems
41 #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
42 #endif
44 #define GOTCTL_CMD_INFO "info"
45 #define GOTCTL_CMD_STOP "stop"
47 struct gotctl_cmd {
48 const char *cmd_name;
49 const struct got_error *(*cmd_main)(int, char *[], int);
50 void (*cmd_usage)(void);
51 };
53 __dead static void usage(int, int);
55 __dead static void usage_info(void);
56 __dead static void usage_stop(void);
58 static const struct got_error* cmd_info(int, char *[], int);
59 static const struct got_error* cmd_stop(int, char *[], int);
61 static const struct gotctl_cmd gotctl_commands[] = {
62 { "info", cmd_info, usage_info },
63 { "stop", cmd_stop, usage_stop },
64 };
66 __dead static void
67 usage_info(void)
68 {
69 fprintf(stderr, "usage: %s info\n", getprogname());
70 exit(1);
71 }
73 static const struct got_error *
74 show_info(struct imsg *imsg)
75 {
76 struct gotd_imsg_info info;
77 size_t datalen;
79 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
80 if (datalen != sizeof(info))
81 return got_error(GOT_ERR_PRIVSEP_LEN);
82 memcpy(&info, imsg->data, sizeof(info));
84 printf("gotd PID: %d\n", info.pid);
85 printf("verbosity: %d\n", info.verbosity);
86 printf("number of repositories: %d\n", info.nrepos);
87 printf("number of connected clients: %d\n", info.nclients);
88 return NULL;
89 }
91 static const struct got_error *
92 show_repo_info(struct imsg *imsg)
93 {
94 struct gotd_imsg_info_repo info;
95 size_t datalen;
97 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
98 if (datalen != sizeof(info))
99 return got_error(GOT_ERR_PRIVSEP_LEN);
100 memcpy(&info, imsg->data, sizeof(info));
102 printf("repository \"%s\", path %s\n", info.repo_name, info.repo_path);
103 return NULL;
106 static const char *
107 get_state_name(enum gotd_client_state state)
109 static char unknown_state[64];
111 switch (state) {
112 case GOTD_STATE_EXPECT_LIST_REFS:
113 return "list-refs";
114 case GOTD_STATE_EXPECT_CAPABILITIES:
115 return "expect-capabilities";
116 case GOTD_STATE_EXPECT_WANT:
117 return "expect-want";
118 case GOTD_STATE_EXPECT_REF_UPDATE:
119 return "expect-ref-update";
120 case GOTD_STATE_EXPECT_MORE_REF_UPDATES:
121 return "expect-more-ref-updates";
122 case GOTD_STATE_EXPECT_HAVE:
123 return "expect-have";
124 case GOTD_STATE_EXPECT_PACKFILE:
125 return "expect-packfile";
126 case GOTD_STATE_EXPECT_DONE:
127 return "expect-done";
128 case GOTD_STATE_DONE:
129 return "done";
132 snprintf(unknown_state, sizeof(unknown_state),
133 "unknown state %d", state);
134 return unknown_state;
137 static const struct got_error *
138 show_client_info(struct imsg *imsg)
140 struct gotd_imsg_info_client info;
141 size_t datalen;
143 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
144 if (datalen != sizeof(info))
145 return got_error(GOT_ERR_PRIVSEP_LEN);
146 memcpy(&info, imsg->data, sizeof(info));
148 printf("client UID %d, GID %d, protocol state '%s', ",
149 info.euid, info.egid, get_state_name(info.state));
150 if (info.is_writing)
151 printf("writing to %s\n", info.repo_name);
152 else
153 printf("reading from %s\n", info.repo_name);
155 return NULL;
158 static const struct got_error *
159 show_capability(struct imsg *imsg)
161 struct gotd_imsg_capability icapa;
162 size_t datalen;
163 char *key, *value = NULL;
165 memset(&icapa, 0, sizeof(icapa));
167 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
168 if (datalen < sizeof(icapa))
169 return got_error(GOT_ERR_PRIVSEP_LEN);
170 memcpy(&icapa, imsg->data, sizeof(icapa));
172 if (datalen != sizeof(icapa) + icapa.key_len + icapa.value_len)
173 return got_error(GOT_ERR_PRIVSEP_LEN);
175 key = malloc(icapa.key_len + 1);
176 if (key == NULL)
177 return got_error_from_errno("malloc");
178 if (icapa.value_len > 0) {
179 value = malloc(icapa.value_len + 1);
180 if (value == NULL) {
181 free(key);
182 return got_error_from_errno("malloc");
186 memcpy(key, imsg->data + sizeof(icapa), icapa.key_len);
187 key[icapa.key_len] = '\0';
188 if (value) {
189 memcpy(value, imsg->data + sizeof(icapa) + icapa.key_len,
190 icapa.value_len);
191 value[icapa.value_len] = '\0';
194 if (strcmp(key, GOT_CAPA_AGENT) == 0)
195 printf(" client user agent: %s\n", value);
196 else if (value)
197 printf(" client supports %s=%s\n", key, value);
198 else
199 printf(" client supports %s\n", key);
201 free(key);
202 free(value);
203 return NULL;
206 static const struct got_error *
207 cmd_info(int argc, char *argv[], int gotd_sock)
209 const struct got_error *err;
210 struct imsgbuf ibuf;
211 struct imsg imsg;
213 imsg_init(&ibuf, gotd_sock);
215 if (imsg_compose(&ibuf, GOTD_IMSG_INFO, 0, 0, -1, NULL, 0) == -1)
216 return got_error_from_errno("imsg_compose INFO");
218 err = gotd_imsg_flush(&ibuf);
219 while (err == NULL) {
220 err = gotd_imsg_poll_recv(&imsg, &ibuf, 0);
221 if (err) {
222 if (err->code == GOT_ERR_EOF)
223 err = NULL;
224 break;
227 switch (imsg.hdr.type) {
228 case GOTD_IMSG_ERROR:
229 err = gotd_imsg_recv_error(NULL, &imsg);
230 break;
231 case GOTD_IMSG_INFO:
232 err = show_info(&imsg);
233 break;
234 case GOTD_IMSG_INFO_REPO:
235 err = show_repo_info(&imsg);
236 break;
237 case GOTD_IMSG_INFO_CLIENT:
238 err = show_client_info(&imsg);
239 break;
240 case GOTD_IMSG_CAPABILITY:
241 err = show_capability(&imsg);
242 break;
243 default:
244 err = got_error(GOT_ERR_PRIVSEP_MSG);
245 break;
248 imsg_free(&imsg);
251 imsg_clear(&ibuf);
252 return err;
255 __dead static void
256 usage_stop(void)
258 fprintf(stderr, "usage: %s stop\n", getprogname());
259 exit(1);
262 static const struct got_error *
263 cmd_stop(int argc, char *argv[], int gotd_sock)
265 const struct got_error *err;
266 struct imsgbuf ibuf;
267 struct imsg imsg;
269 imsg_init(&ibuf, gotd_sock);
271 if (imsg_compose(&ibuf, GOTD_IMSG_STOP, 0, 0, -1, NULL, 0) == -1)
272 return got_error_from_errno("imsg_compose STOP");
274 err = gotd_imsg_flush(&ibuf);
275 while (err == NULL) {
276 err = gotd_imsg_poll_recv(&imsg, &ibuf, 0);
277 if (err) {
278 if (err->code == GOT_ERR_EOF)
279 err = NULL;
280 break;
283 switch (imsg.hdr.type) {
284 case GOTD_IMSG_ERROR:
285 err = gotd_imsg_recv_error(NULL, &imsg);
286 break;
287 default:
288 err = got_error(GOT_ERR_PRIVSEP_MSG);
289 break;
292 imsg_free(&imsg);
295 imsg_clear(&ibuf);
296 return err;
299 static void
300 list_commands(FILE *fp)
302 size_t i;
304 fprintf(fp, "commands:");
305 for (i = 0; i < nitems(gotctl_commands); i++) {
306 const struct gotctl_cmd *cmd = &gotctl_commands[i];
307 fprintf(fp, " %s", cmd->cmd_name);
309 fputc('\n', fp);
312 __dead static void
313 usage(int hflag, int status)
315 FILE *fp = (status == 0) ? stdout : stderr;
317 fprintf(fp, "usage: %s [-hV] command [arg ...]\n",
318 getprogname());
319 if (hflag)
320 list_commands(fp);
321 exit(status);
324 static const struct got_error *
325 apply_unveil(const char *unix_socket_path)
327 #ifdef PROFILE
328 if (unveil("gmon.out", "rwc") != 0)
329 return got_error_from_errno2("unveil", "gmon.out");
330 #endif
331 if (unveil(unix_socket_path, "w") != 0)
332 return got_error_from_errno2("unveil", unix_socket_path);
334 if (unveil(NULL, NULL) != 0)
335 return got_error_from_errno("unveil");
337 return NULL;
340 static int
341 connect_gotd(const char *socket_path)
343 const struct got_error *error = NULL;
344 char unix_socket_path[PATH_MAX];
345 int gotd_sock = -1;
346 struct sockaddr_un sun;
348 if (socket_path) {
349 if (strlcpy(unix_socket_path, socket_path,
350 sizeof(unix_socket_path)) >= sizeof(unix_socket_path))
351 errx(1, "gotd socket path too long");
352 } else {
353 strlcpy(unix_socket_path, GOTD_UNIX_SOCKET,
354 sizeof(unix_socket_path));
357 error = apply_unveil(unix_socket_path);
358 if (error)
359 errx(1, "%s", error->msg);
361 #ifndef PROFILE
362 if (pledge("stdio unix", NULL) == -1)
363 err(1, "pledge");
364 #endif
365 if ((gotd_sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
366 err(1, "socket");
368 memset(&sun, 0, sizeof(sun));
369 sun.sun_family = AF_UNIX;
370 if (strlcpy(sun.sun_path, unix_socket_path,
371 sizeof(sun.sun_path)) >= sizeof(sun.sun_path))
372 errx(1, "gotd socket path too long");
373 if (connect(gotd_sock, (struct sockaddr *)&sun, sizeof(sun)) == -1)
374 err(1, "connect: %s", unix_socket_path);
376 #ifndef PROFILE
377 if (pledge("stdio", NULL) == -1)
378 err(1, "pledge");
379 #endif
381 return gotd_sock;
384 int
385 main(int argc, char *argv[])
387 const struct gotctl_cmd *cmd;
388 int gotd_sock = -1, i;
389 int ch;
390 int hflag = 0, Vflag = 0;
391 static const struct option longopts[] = {
392 { "version", no_argument, NULL, 'V' },
393 { NULL, 0, NULL, 0 }
394 };
395 const char *socket_path = NULL;
397 setlocale(LC_CTYPE, "");
399 #ifndef PROFILE
400 if (pledge("stdio unix unveil", NULL) == -1)
401 err(1, "pledge");
402 #endif
404 while ((ch = getopt_long(argc, argv, "+hf:V", longopts, NULL)) != -1) {
405 switch (ch) {
406 case 'h':
407 hflag = 1;
408 break;
409 case 'f':
410 socket_path = optarg;
411 break;
412 case 'V':
413 Vflag = 1;
414 break;
415 default:
416 usage(hflag, 1);
417 /* NOTREACHED */
421 argc -= optind;
422 argv += optind;
423 optind = 1;
424 optreset = 1;
426 if (Vflag) {
427 got_version_print_str();
428 return 0;
431 if (argc <= 0)
432 usage(hflag, hflag ? 0 : 1);
434 for (i = 0; i < nitems(gotctl_commands); i++) {
435 const struct got_error *error;
437 cmd = &gotctl_commands[i];
439 if (strncmp(cmd->cmd_name, argv[0], strlen(argv[0])) != 0)
440 continue;
442 if (hflag)
443 cmd->cmd_usage();
445 gotd_sock = connect_gotd(socket_path);
446 if (gotd_sock == -1)
447 return 1;
448 error = cmd->cmd_main(argc, argv, gotd_sock);
449 close(gotd_sock);
450 if (error) {
451 fprintf(stderr, "%s: %s\n", getprogname(), error->msg);
452 return 1;
455 return 0;
458 fprintf(stderr, "%s: unknown command '%s'\n", getprogname(), argv[0]);
459 list_commands(stderr);
460 return 1;