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 <sha2.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_version.h"
37 #include "got_lib_gitproto.h"
39 #include "gotd.h"
41 #ifndef nitems
42 #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
43 #endif
45 #define GOTCTL_CMD_INFO "info"
46 #define GOTCTL_CMD_STOP "stop"
48 struct gotctl_cmd {
49 const char *cmd_name;
50 const struct got_error *(*cmd_main)(int, char *[], int);
51 void (*cmd_usage)(void);
52 };
54 __dead static void usage(int, int);
56 __dead static void usage_info(void);
57 __dead static void usage_stop(void);
59 static const struct got_error* cmd_info(int, char *[], int);
60 static const struct got_error* cmd_stop(int, char *[], int);
62 static const struct gotctl_cmd gotctl_commands[] = {
63 { "info", cmd_info, usage_info },
64 { "stop", cmd_stop, usage_stop },
65 };
67 __dead static void
68 usage_info(void)
69 {
70 fprintf(stderr, "usage: %s info\n", getprogname());
71 exit(1);
72 }
74 static const struct got_error *
75 show_info(struct imsg *imsg)
76 {
77 struct gotd_imsg_info info;
78 size_t datalen;
80 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
81 if (datalen != sizeof(info))
82 return got_error(GOT_ERR_PRIVSEP_LEN);
83 memcpy(&info, imsg->data, sizeof(info));
85 printf("gotd PID: %d\n", info.pid);
86 printf("verbosity: %d\n", info.verbosity);
87 printf("number of repositories: %d\n", info.nrepos);
88 printf("number of connected clients: %d\n", info.nclients);
89 return NULL;
90 }
92 static const struct got_error *
93 show_repo_info(struct imsg *imsg)
94 {
95 struct gotd_imsg_info_repo info;
96 size_t datalen;
98 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
99 if (datalen != sizeof(info))
100 return got_error(GOT_ERR_PRIVSEP_LEN);
101 memcpy(&info, imsg->data, sizeof(info));
103 printf("repository \"%s\", path %s\n", info.repo_name, info.repo_path);
104 return NULL;
107 static const struct got_error *
108 show_client_info(struct imsg *imsg)
110 struct gotd_imsg_info_client info;
111 size_t datalen;
113 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
114 if (datalen != sizeof(info))
115 return got_error(GOT_ERR_PRIVSEP_LEN);
116 memcpy(&info, imsg->data, sizeof(info));
118 printf("client UID %d, GID %d, ", info.euid, info.egid);
119 if (info.session_child_pid)
120 printf("session PID %ld, ", (long)info.session_child_pid);
121 if (info.repo_child_pid)
122 printf("repo PID %ld, ", (long)info.repo_child_pid);
123 if (info.is_writing)
124 printf("writing to %s\n", info.repo_name);
125 else
126 printf("reading from %s\n", info.repo_name);
128 return NULL;
131 static const struct got_error *
132 cmd_info(int argc, char *argv[], int gotd_sock)
134 const struct got_error *err;
135 struct imsgbuf ibuf;
136 struct imsg imsg;
138 imsg_init(&ibuf, gotd_sock);
140 if (imsg_compose(&ibuf, GOTD_IMSG_INFO, 0, 0, -1, NULL, 0) == -1)
141 return got_error_from_errno("imsg_compose INFO");
143 err = gotd_imsg_flush(&ibuf);
144 while (err == NULL) {
145 err = gotd_imsg_poll_recv(&imsg, &ibuf, 0);
146 if (err) {
147 if (err->code == GOT_ERR_EOF)
148 err = NULL;
149 break;
152 switch (imsg.hdr.type) {
153 case GOTD_IMSG_ERROR:
154 err = gotd_imsg_recv_error(NULL, &imsg);
155 break;
156 case GOTD_IMSG_INFO:
157 err = show_info(&imsg);
158 break;
159 case GOTD_IMSG_INFO_REPO:
160 err = show_repo_info(&imsg);
161 break;
162 case GOTD_IMSG_INFO_CLIENT:
163 err = show_client_info(&imsg);
164 break;
165 default:
166 err = got_error(GOT_ERR_PRIVSEP_MSG);
167 break;
170 imsg_free(&imsg);
173 imsg_clear(&ibuf);
174 return err;
177 __dead static void
178 usage_stop(void)
180 fprintf(stderr, "usage: %s stop\n", getprogname());
181 exit(1);
184 static const struct got_error *
185 cmd_stop(int argc, char *argv[], int gotd_sock)
187 const struct got_error *err;
188 struct imsgbuf ibuf;
189 struct imsg imsg;
191 imsg_init(&ibuf, gotd_sock);
193 if (imsg_compose(&ibuf, GOTD_IMSG_STOP, 0, 0, -1, NULL, 0) == -1)
194 return got_error_from_errno("imsg_compose STOP");
196 err = gotd_imsg_flush(&ibuf);
197 while (err == NULL) {
198 err = gotd_imsg_poll_recv(&imsg, &ibuf, 0);
199 if (err) {
200 if (err->code == GOT_ERR_EOF)
201 err = NULL;
202 break;
205 switch (imsg.hdr.type) {
206 case GOTD_IMSG_ERROR:
207 err = gotd_imsg_recv_error(NULL, &imsg);
208 break;
209 default:
210 err = got_error(GOT_ERR_PRIVSEP_MSG);
211 break;
214 imsg_free(&imsg);
217 imsg_clear(&ibuf);
218 return err;
221 static void
222 list_commands(FILE *fp)
224 size_t i;
226 fprintf(fp, "commands:");
227 for (i = 0; i < nitems(gotctl_commands); i++) {
228 const struct gotctl_cmd *cmd = &gotctl_commands[i];
229 fprintf(fp, " %s", cmd->cmd_name);
231 fputc('\n', fp);
234 __dead static void
235 usage(int hflag, int status)
237 FILE *fp = (status == 0) ? stdout : stderr;
239 fprintf(fp, "usage: %s [-hV] [-f path] command [arg ...]\n",
240 getprogname());
241 if (hflag)
242 list_commands(fp);
243 exit(status);
246 static const struct got_error *
247 apply_unveil(const char *unix_socket_path)
249 #ifdef PROFILE
250 if (unveil("gmon.out", "rwc") != 0)
251 return got_error_from_errno2("unveil", "gmon.out");
252 #endif
253 if (unveil(unix_socket_path, "w") != 0)
254 return got_error_from_errno2("unveil", unix_socket_path);
256 if (unveil(NULL, NULL) != 0)
257 return got_error_from_errno("unveil");
259 return NULL;
262 static int
263 connect_gotd(const char *socket_path)
265 const struct got_error *error = NULL;
266 int gotd_sock = -1;
267 struct sockaddr_un sun;
269 error = apply_unveil(socket_path);
270 if (error)
271 errx(1, "%s", error->msg);
273 #ifndef PROFILE
274 if (pledge("stdio unix", NULL) == -1)
275 err(1, "pledge");
276 #endif
277 if ((gotd_sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
278 err(1, "socket");
280 memset(&sun, 0, sizeof(sun));
281 sun.sun_family = AF_UNIX;
282 if (strlcpy(sun.sun_path, socket_path, sizeof(sun.sun_path)) >=
283 sizeof(sun.sun_path))
284 errx(1, "gotd socket path too long");
285 if (connect(gotd_sock, (struct sockaddr *)&sun, sizeof(sun)) == -1)
286 err(1, "connect: %s", socket_path);
288 #ifndef PROFILE
289 if (pledge("stdio", NULL) == -1)
290 err(1, "pledge");
291 #endif
293 return gotd_sock;
296 int
297 main(int argc, char *argv[])
299 const struct gotctl_cmd *cmd;
300 int gotd_sock = -1, i;
301 int ch;
302 int hflag = 0, Vflag = 0;
303 static const struct option longopts[] = {
304 { "version", no_argument, NULL, 'V' },
305 { NULL, 0, NULL, 0 }
306 };
307 const char *socket_path = GOTD_UNIX_SOCKET;
309 setlocale(LC_CTYPE, "");
311 #ifndef PROFILE
312 if (pledge("stdio unix unveil", NULL) == -1)
313 err(1, "pledge");
314 #endif
316 while ((ch = getopt_long(argc, argv, "+hf:V", longopts, NULL)) != -1) {
317 switch (ch) {
318 case 'h':
319 hflag = 1;
320 break;
321 case 'f':
322 socket_path = optarg;
323 break;
324 case 'V':
325 Vflag = 1;
326 break;
327 default:
328 usage(hflag, 1);
329 /* NOTREACHED */
333 argc -= optind;
334 argv += optind;
335 optind = 1;
336 optreset = 1;
338 if (Vflag) {
339 got_version_print_str();
340 return 0;
343 if (argc <= 0)
344 usage(hflag, hflag ? 0 : 1);
346 for (i = 0; i < nitems(gotctl_commands); i++) {
347 const struct got_error *error;
349 cmd = &gotctl_commands[i];
351 if (strncmp(cmd->cmd_name, argv[0], strlen(argv[0])) != 0)
352 continue;
354 if (hflag)
355 cmd->cmd_usage();
357 gotd_sock = connect_gotd(socket_path);
358 if (gotd_sock == -1)
359 return 1;
360 error = cmd->cmd_main(argc, argv, gotd_sock);
361 close(gotd_sock);
362 if (error) {
363 fprintf(stderr, "%s: %s\n", getprogname(), error->msg);
364 return 1;
367 return 0;
370 fprintf(stderr, "%s: unknown command '%s'\n", getprogname(), argv[0]);
371 list_commands(stderr);
372 return 1;