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"
36 #include "got_path.h"
38 #include "got_lib_gitproto.h"
40 #include "gotd.h"
42 #ifndef nitems
43 #define nitems(_a) (sizeof((_a)) / sizeof((_a)[0]))
44 #endif
46 #define GOTCTL_CMD_INFO "info"
47 #define GOTCTL_CMD_STOP "stop"
49 struct gotctl_cmd {
50 const char *cmd_name;
51 const struct got_error *(*cmd_main)(int, char *[], int);
52 void (*cmd_usage)(void);
53 };
55 __dead static void usage(int, int);
57 __dead static void usage_info(void);
58 __dead static void usage_stop(void);
60 static const struct got_error* cmd_info(int, char *[], int);
61 static const struct got_error* cmd_stop(int, char *[], int);
63 static const struct gotctl_cmd gotctl_commands[] = {
64 { "info", cmd_info, usage_info },
65 { "stop", cmd_stop, usage_stop },
66 };
68 __dead static void
69 usage_info(void)
70 {
71 fprintf(stderr, "usage: %s info\n", getprogname());
72 exit(1);
73 }
75 static const struct got_error *
76 show_info(struct imsg *imsg)
77 {
78 struct gotd_imsg_info info;
79 size_t datalen;
81 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
82 if (datalen != sizeof(info))
83 return got_error(GOT_ERR_PRIVSEP_LEN);
84 memcpy(&info, imsg->data, sizeof(info));
86 printf("gotd PID: %d\n", info.pid);
87 printf("verbosity: %d\n", info.verbosity);
88 printf("number of repositories: %d\n", info.nrepos);
89 printf("number of connected clients: %d\n", info.nclients);
90 return NULL;
91 }
93 static const struct got_error *
94 show_repo_info(struct imsg *imsg)
95 {
96 struct gotd_imsg_info_repo info;
97 size_t datalen;
99 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
100 if (datalen != sizeof(info))
101 return got_error(GOT_ERR_PRIVSEP_LEN);
102 memcpy(&info, imsg->data, sizeof(info));
104 printf("repository \"%s\", path %s\n", info.repo_name, info.repo_path);
105 return NULL;
108 static const struct got_error *
109 show_client_info(struct imsg *imsg)
111 struct gotd_imsg_info_client info;
112 size_t datalen;
114 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
115 if (datalen != sizeof(info))
116 return got_error(GOT_ERR_PRIVSEP_LEN);
117 memcpy(&info, imsg->data, sizeof(info));
119 printf("client UID %d, GID %d, ", info.euid, info.egid);
120 if (info.session_child_pid)
121 printf("session PID %ld, ", (long)info.session_child_pid);
122 if (info.repo_child_pid)
123 printf("repo PID %ld, ", (long)info.repo_child_pid);
124 if (info.is_writing)
125 printf("writing to %s\n", info.repo_name);
126 else
127 printf("reading from %s\n", info.repo_name);
129 return NULL;
132 static const struct got_error *
133 cmd_info(int argc, char *argv[], int gotd_sock)
135 const struct got_error *err;
136 struct imsgbuf ibuf;
137 struct imsg imsg;
139 imsg_init(&ibuf, gotd_sock);
141 if (imsg_compose(&ibuf, GOTD_IMSG_INFO, 0, 0, -1, NULL, 0) == -1)
142 return got_error_from_errno("imsg_compose INFO");
144 err = gotd_imsg_flush(&ibuf);
145 while (err == NULL) {
146 err = gotd_imsg_poll_recv(&imsg, &ibuf, 0);
147 if (err) {
148 if (err->code == GOT_ERR_EOF)
149 err = NULL;
150 break;
153 switch (imsg.hdr.type) {
154 case GOTD_IMSG_ERROR:
155 err = gotd_imsg_recv_error(NULL, &imsg);
156 break;
157 case GOTD_IMSG_INFO:
158 err = show_info(&imsg);
159 break;
160 case GOTD_IMSG_INFO_REPO:
161 err = show_repo_info(&imsg);
162 break;
163 case GOTD_IMSG_INFO_CLIENT:
164 err = show_client_info(&imsg);
165 break;
166 default:
167 err = got_error(GOT_ERR_PRIVSEP_MSG);
168 break;
171 imsg_free(&imsg);
174 imsg_clear(&ibuf);
175 return err;
178 __dead static void
179 usage_stop(void)
181 fprintf(stderr, "usage: %s stop\n", getprogname());
182 exit(1);
185 static const struct got_error *
186 cmd_stop(int argc, char *argv[], int gotd_sock)
188 const struct got_error *err;
189 struct imsgbuf ibuf;
190 struct imsg imsg;
192 imsg_init(&ibuf, gotd_sock);
194 if (imsg_compose(&ibuf, GOTD_IMSG_STOP, 0, 0, -1, NULL, 0) == -1)
195 return got_error_from_errno("imsg_compose STOP");
197 err = gotd_imsg_flush(&ibuf);
198 while (err == NULL) {
199 err = gotd_imsg_poll_recv(&imsg, &ibuf, 0);
200 if (err) {
201 if (err->code == GOT_ERR_EOF)
202 err = NULL;
203 break;
206 switch (imsg.hdr.type) {
207 case GOTD_IMSG_ERROR:
208 err = gotd_imsg_recv_error(NULL, &imsg);
209 break;
210 default:
211 err = got_error(GOT_ERR_PRIVSEP_MSG);
212 break;
215 imsg_free(&imsg);
218 imsg_clear(&ibuf);
219 return err;
222 static void
223 list_commands(FILE *fp)
225 size_t i;
227 fprintf(fp, "commands:");
228 for (i = 0; i < nitems(gotctl_commands); i++) {
229 const struct gotctl_cmd *cmd = &gotctl_commands[i];
230 fprintf(fp, " %s", cmd->cmd_name);
232 fputc('\n', fp);
235 __dead static void
236 usage(int hflag, int status)
238 FILE *fp = (status == 0) ? stdout : stderr;
240 fprintf(fp, "usage: %s [-hV] [-f path] command [arg ...]\n",
241 getprogname());
242 if (hflag)
243 list_commands(fp);
244 exit(status);
247 static const struct got_error *
248 apply_unveil(const char *unix_socket_path)
250 #ifdef PROFILE
251 if (unveil("gmon.out", "rwc") != 0)
252 return got_error_from_errno2("unveil", "gmon.out");
253 #endif
254 if (unveil(unix_socket_path, "w") != 0)
255 return got_error_from_errno2("unveil", unix_socket_path);
257 if (unveil(NULL, NULL) != 0)
258 return got_error_from_errno("unveil");
260 return NULL;
263 static int
264 connect_gotd(const char *socket_path)
266 const struct got_error *error = NULL;
267 int gotd_sock = -1;
268 struct sockaddr_un sun;
270 error = apply_unveil(socket_path);
271 if (error)
272 errx(1, "%s", error->msg);
274 #ifndef PROFILE
275 if (pledge("stdio unix", NULL) == -1)
276 err(1, "pledge");
277 #endif
278 if ((gotd_sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
279 err(1, "socket");
281 memset(&sun, 0, sizeof(sun));
282 sun.sun_family = AF_UNIX;
283 if (strlcpy(sun.sun_path, socket_path, sizeof(sun.sun_path)) >=
284 sizeof(sun.sun_path))
285 errx(1, "gotd socket path too long");
286 if (connect(gotd_sock, (struct sockaddr *)&sun, sizeof(sun)) == -1)
287 err(1, "connect: %s", socket_path);
289 #ifndef PROFILE
290 if (pledge("stdio", NULL) == -1)
291 err(1, "pledge");
292 #endif
294 return gotd_sock;
297 int
298 main(int argc, char *argv[])
300 const struct gotctl_cmd *cmd;
301 int gotd_sock = -1, i;
302 int ch;
303 int hflag = 0, Vflag = 0;
304 static const struct option longopts[] = {
305 { "version", no_argument, NULL, 'V' },
306 { NULL, 0, NULL, 0 }
307 };
308 const char *socket_path = GOTD_UNIX_SOCKET;
310 setlocale(LC_CTYPE, "");
312 #ifndef PROFILE
313 if (pledge("stdio unix unveil", NULL) == -1)
314 err(1, "pledge");
315 #endif
317 while ((ch = getopt_long(argc, argv, "+hf:V", longopts, NULL)) != -1) {
318 switch (ch) {
319 case 'h':
320 hflag = 1;
321 break;
322 case 'f':
323 socket_path = optarg;
324 break;
325 case 'V':
326 Vflag = 1;
327 break;
328 default:
329 usage(hflag, 1);
330 /* NOTREACHED */
334 argc -= optind;
335 argv += optind;
336 optind = 1;
337 optreset = 1;
339 if (Vflag) {
340 got_version_print_str();
341 return 0;
344 if (argc <= 0)
345 usage(hflag, hflag ? 0 : 1);
347 for (i = 0; i < nitems(gotctl_commands); i++) {
348 const struct got_error *error;
350 cmd = &gotctl_commands[i];
352 if (strncmp(cmd->cmd_name, argv[0], strlen(argv[0])) != 0)
353 continue;
355 if (hflag)
356 cmd->cmd_usage();
358 gotd_sock = connect_gotd(socket_path);
359 if (gotd_sock == -1)
360 return 1;
361 error = cmd->cmd_main(argc, argv, gotd_sock);
362 close(gotd_sock);
363 if (error) {
364 fprintf(stderr, "%s: %s\n", getprogname(), error->msg);
365 return 1;
368 return 0;
371 fprintf(stderr, "%s: unknown command '%s'\n", getprogname(), argv[0]);
372 list_commands(stderr);
373 return 1;