attempt to make cmdline tests involing http-server more reliable
[got-portable.git] / gotctl / gotctl.c
blobb24c2294b66990417a7111b39903692e8f4d83f4
1 /*
2 * Copyright (c) 2022 Stefan Sperling <stsp@openbsd.org>
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.
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.
17 #include "got_compat.h"
19 #include <sys/queue.h>
20 #include <sys/socket.h>
21 #include <sys/un.h>
23 #include <err.h>
24 #include <event.h>
25 #include <imsg.h>
26 #include <limits.h>
27 #include <locale.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <getopt.h>
32 #include <unistd.h>
34 #include "got_error.h"
35 #include "got_object.h"
36 #include "got_version.h"
37 #include "got_path.h"
39 #include "got_lib_gitproto.h"
41 #include "gotd.h"
43 #ifndef nitems
44 #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
45 #endif
47 #define GOTCTL_CMD_INFO "info"
48 #define GOTCTL_CMD_STOP "stop"
50 struct gotctl_cmd {
51 const char *cmd_name;
52 const struct got_error *(*cmd_main)(int, char *[], int);
53 void (*cmd_usage)(void);
56 __dead static void usage(int, int);
58 __dead static void usage_info(void);
59 __dead static void usage_stop(void);
61 static const struct got_error* cmd_info(int, char *[], int);
62 static const struct got_error* cmd_stop(int, char *[], int);
64 static const struct gotctl_cmd gotctl_commands[] = {
65 { "info", cmd_info, usage_info },
66 { "stop", cmd_stop, usage_stop },
69 __dead static void
70 usage_info(void)
72 fprintf(stderr, "usage: %s info\n", getprogname());
73 exit(1);
76 static const struct got_error *
77 show_info(struct imsg *imsg)
79 struct gotd_imsg_info info;
80 size_t datalen;
82 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
83 if (datalen != sizeof(info))
84 return got_error(GOT_ERR_PRIVSEP_LEN);
85 memcpy(&info, imsg->data, sizeof(info));
87 printf("gotd PID: %d\n", info.pid);
88 printf("verbosity: %d\n", info.verbosity);
89 printf("number of repositories: %d\n", info.nrepos);
90 printf("number of connected clients: %d\n", info.nclients);
91 return NULL;
94 static const struct got_error *
95 show_repo_info(struct imsg *imsg)
97 struct gotd_imsg_info_repo info;
98 size_t datalen;
100 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
101 if (datalen != sizeof(info))
102 return got_error(GOT_ERR_PRIVSEP_LEN);
103 memcpy(&info, imsg->data, sizeof(info));
105 printf("repository \"%s\", path %s\n", info.repo_name, info.repo_path);
106 return NULL;
109 static const struct got_error *
110 show_client_info(struct imsg *imsg)
112 struct gotd_imsg_info_client info;
113 size_t datalen;
115 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
116 if (datalen != sizeof(info))
117 return got_error(GOT_ERR_PRIVSEP_LEN);
118 memcpy(&info, imsg->data, sizeof(info));
120 printf("client UID %d, GID %d, ", info.euid, info.egid);
121 if (info.session_child_pid)
122 printf("session PID %ld, ", (long)info.session_child_pid);
123 if (info.repo_child_pid)
124 printf("repo PID %ld, ", (long)info.repo_child_pid);
125 if (info.is_writing)
126 printf("writing to %s\n", info.repo_name);
127 else
128 printf("reading from %s\n", info.repo_name);
130 return NULL;
133 static const struct got_error *
134 cmd_info(int argc, char *argv[], int gotd_sock)
136 const struct got_error *err;
137 struct imsgbuf ibuf;
138 struct imsg imsg;
140 if (imsgbuf_init(&ibuf, gotd_sock) == -1)
141 return got_error_from_errno("imsgbuf_init");
142 imsgbuf_allow_fdpass(&ibuf);
144 if (imsg_compose(&ibuf, GOTD_IMSG_INFO, 0, 0, -1, NULL, 0) == -1) {
145 imsgbuf_clear(&ibuf);
146 return got_error_from_errno("imsg_compose INFO");
149 err = gotd_imsg_flush(&ibuf);
150 while (err == NULL) {
151 err = gotd_imsg_poll_recv(&imsg, &ibuf, 0);
152 if (err) {
153 if (err->code == GOT_ERR_EOF)
154 err = NULL;
155 break;
158 switch (imsg.hdr.type) {
159 case GOTD_IMSG_ERROR:
160 err = gotd_imsg_recv_error(NULL, &imsg);
161 break;
162 case GOTD_IMSG_INFO:
163 err = show_info(&imsg);
164 break;
165 case GOTD_IMSG_INFO_REPO:
166 err = show_repo_info(&imsg);
167 break;
168 case GOTD_IMSG_INFO_CLIENT:
169 err = show_client_info(&imsg);
170 break;
171 default:
172 err = got_error(GOT_ERR_PRIVSEP_MSG);
173 break;
176 imsg_free(&imsg);
179 imsgbuf_clear(&ibuf);
180 return err;
183 __dead static void
184 usage_stop(void)
186 fprintf(stderr, "usage: %s stop\n", getprogname());
187 exit(1);
190 static const struct got_error *
191 cmd_stop(int argc, char *argv[], int gotd_sock)
193 const struct got_error *err;
194 struct imsgbuf ibuf;
195 struct imsg imsg;
197 if (imsgbuf_init(&ibuf, gotd_sock) == -1)
198 return got_error_from_errno("imsgbuf_init");
199 imsgbuf_allow_fdpass(&ibuf);
201 if (imsg_compose(&ibuf, GOTD_IMSG_STOP, 0, 0, -1, NULL, 0) == -1) {
202 imsgbuf_clear(&ibuf);
203 return got_error_from_errno("imsg_compose STOP");
206 err = gotd_imsg_flush(&ibuf);
207 while (err == NULL) {
208 err = gotd_imsg_poll_recv(&imsg, &ibuf, 0);
209 if (err) {
210 if (err->code == GOT_ERR_EOF)
211 err = NULL;
212 break;
215 switch (imsg.hdr.type) {
216 case GOTD_IMSG_ERROR:
217 err = gotd_imsg_recv_error(NULL, &imsg);
218 break;
219 default:
220 err = got_error(GOT_ERR_PRIVSEP_MSG);
221 break;
224 imsg_free(&imsg);
227 imsgbuf_clear(&ibuf);
228 return err;
231 static void
232 list_commands(FILE *fp)
234 size_t i;
236 fprintf(fp, "commands:");
237 for (i = 0; i < nitems(gotctl_commands); i++) {
238 const struct gotctl_cmd *cmd = &gotctl_commands[i];
239 fprintf(fp, " %s", cmd->cmd_name);
241 fputc('\n', fp);
244 __dead static void
245 usage(int hflag, int status)
247 FILE *fp = (status == 0) ? stdout : stderr;
249 fprintf(fp, "usage: %s [-hV] [-f path] command [arg ...]\n",
250 getprogname());
251 if (hflag)
252 list_commands(fp);
253 exit(status);
256 static const struct got_error *
257 apply_unveil(const char *unix_socket_path)
259 #ifdef PROFILE
260 if (unveil("gmon.out", "rwc") != 0)
261 return got_error_from_errno2("unveil", "gmon.out");
262 #endif
263 if (unveil(unix_socket_path, "w") != 0)
264 return got_error_from_errno2("unveil", unix_socket_path);
266 if (unveil(NULL, NULL) != 0)
267 return got_error_from_errno("unveil");
269 return NULL;
272 static int
273 connect_gotd(const char *socket_path)
275 const struct got_error *error = NULL;
276 int gotd_sock = -1;
277 struct sockaddr_un sun;
279 error = apply_unveil(socket_path);
280 if (error)
281 errx(1, "%s", error->msg);
283 #ifndef PROFILE
284 if (pledge("stdio unix", NULL) == -1)
285 err(1, "pledge");
286 #endif
287 if ((gotd_sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
288 err(1, "socket");
290 memset(&sun, 0, sizeof(sun));
291 sun.sun_family = AF_UNIX;
292 if (strlcpy(sun.sun_path, socket_path, sizeof(sun.sun_path)) >=
293 sizeof(sun.sun_path))
294 errx(1, "gotd socket path too long");
295 if (connect(gotd_sock, (struct sockaddr *)&sun, sizeof(sun)) == -1)
296 err(1, "connect: %s", socket_path);
298 #ifndef PROFILE
299 if (pledge("stdio", NULL) == -1)
300 err(1, "pledge");
301 #endif
303 return gotd_sock;
307 main(int argc, char *argv[])
309 const struct gotctl_cmd *cmd;
310 int gotd_sock = -1, i;
311 int ch;
312 int hflag = 0, Vflag = 0;
313 static const struct option longopts[] = {
314 { "version", no_argument, NULL, 'V' },
315 { NULL, 0, NULL, 0 }
317 const char *socket_path = GOTD_UNIX_SOCKET;
319 setlocale(LC_CTYPE, "");
321 #ifndef PROFILE
322 if (pledge("stdio unix unveil", NULL) == -1)
323 err(1, "pledge");
324 #endif
326 while ((ch = getopt_long(argc, argv, "+hf:V", longopts, NULL)) != -1) {
327 switch (ch) {
328 case 'h':
329 hflag = 1;
330 break;
331 case 'f':
332 socket_path = optarg;
333 break;
334 case 'V':
335 Vflag = 1;
336 break;
337 default:
338 usage(hflag, 1);
339 /* NOTREACHED */
343 argc -= optind;
344 argv += optind;
345 optind = 1;
346 optreset = 1;
348 if (Vflag) {
349 got_version_print_str();
350 return 0;
353 if (argc <= 0)
354 usage(hflag, hflag ? 0 : 1);
356 for (i = 0; i < nitems(gotctl_commands); i++) {
357 const struct got_error *error;
359 cmd = &gotctl_commands[i];
361 if (strncmp(cmd->cmd_name, argv[0], strlen(argv[0])) != 0)
362 continue;
364 if (hflag)
365 cmd->cmd_usage();
367 gotd_sock = connect_gotd(socket_path);
368 if (gotd_sock == -1)
369 return 1;
370 error = cmd->cmd_main(argc, argv, gotd_sock);
371 close(gotd_sock);
372 if (error) {
373 fprintf(stderr, "%s: %s\n", getprogname(), error->msg);
374 return 1;
377 return 0;
380 fprintf(stderr, "%s: unknown command '%s'\n", getprogname(), argv[0]);
381 list_commands(stderr);
382 return 1;