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 struct got_error *
107 show_client_info(struct imsg *imsg)
109 struct gotd_imsg_info_client info;
110 size_t datalen;
112 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
113 if (datalen != sizeof(info))
114 return got_error(GOT_ERR_PRIVSEP_LEN);
115 memcpy(&info, imsg->data, sizeof(info));
117 printf("client UID %d, GID %d, ", info.euid, info.egid);
118 if (info.session_child_pid)
119 printf("session PID %ld, ", (long)info.session_child_pid);
120 if (info.repo_child_pid)
121 printf("repo PID %ld, ", (long)info.repo_child_pid);
122 if (info.is_writing)
123 printf("writing to %s\n", info.repo_name);
124 else
125 printf("reading from %s\n", info.repo_name);
127 return NULL;
130 static const struct got_error *
131 cmd_info(int argc, char *argv[], int gotd_sock)
133 const struct got_error *err;
134 struct imsgbuf ibuf;
135 struct imsg imsg;
137 imsg_init(&ibuf, gotd_sock);
139 if (imsg_compose(&ibuf, GOTD_IMSG_INFO, 0, 0, -1, NULL, 0) == -1)
140 return got_error_from_errno("imsg_compose INFO");
142 err = gotd_imsg_flush(&ibuf);
143 while (err == NULL) {
144 err = gotd_imsg_poll_recv(&imsg, &ibuf, 0);
145 if (err) {
146 if (err->code == GOT_ERR_EOF)
147 err = NULL;
148 break;
151 switch (imsg.hdr.type) {
152 case GOTD_IMSG_ERROR:
153 err = gotd_imsg_recv_error(NULL, &imsg);
154 break;
155 case GOTD_IMSG_INFO:
156 err = show_info(&imsg);
157 break;
158 case GOTD_IMSG_INFO_REPO:
159 err = show_repo_info(&imsg);
160 break;
161 case GOTD_IMSG_INFO_CLIENT:
162 err = show_client_info(&imsg);
163 break;
164 default:
165 err = got_error(GOT_ERR_PRIVSEP_MSG);
166 break;
169 imsg_free(&imsg);
172 imsg_clear(&ibuf);
173 return err;
176 __dead static void
177 usage_stop(void)
179 fprintf(stderr, "usage: %s stop\n", getprogname());
180 exit(1);
183 static const struct got_error *
184 cmd_stop(int argc, char *argv[], int gotd_sock)
186 const struct got_error *err;
187 struct imsgbuf ibuf;
188 struct imsg imsg;
190 imsg_init(&ibuf, gotd_sock);
192 if (imsg_compose(&ibuf, GOTD_IMSG_STOP, 0, 0, -1, NULL, 0) == -1)
193 return got_error_from_errno("imsg_compose STOP");
195 err = gotd_imsg_flush(&ibuf);
196 while (err == NULL) {
197 err = gotd_imsg_poll_recv(&imsg, &ibuf, 0);
198 if (err) {
199 if (err->code == GOT_ERR_EOF)
200 err = NULL;
201 break;
204 switch (imsg.hdr.type) {
205 case GOTD_IMSG_ERROR:
206 err = gotd_imsg_recv_error(NULL, &imsg);
207 break;
208 default:
209 err = got_error(GOT_ERR_PRIVSEP_MSG);
210 break;
213 imsg_free(&imsg);
216 imsg_clear(&ibuf);
217 return err;
220 static void
221 list_commands(FILE *fp)
223 size_t i;
225 fprintf(fp, "commands:");
226 for (i = 0; i < nitems(gotctl_commands); i++) {
227 const struct gotctl_cmd *cmd = &gotctl_commands[i];
228 fprintf(fp, " %s", cmd->cmd_name);
230 fputc('\n', fp);
233 __dead static void
234 usage(int hflag, int status)
236 FILE *fp = (status == 0) ? stdout : stderr;
238 fprintf(fp, "usage: %s [-hV] [-f path] command [arg ...]\n",
239 getprogname());
240 if (hflag)
241 list_commands(fp);
242 exit(status);
245 static const struct got_error *
246 apply_unveil(const char *unix_socket_path)
248 #ifdef PROFILE
249 if (unveil("gmon.out", "rwc") != 0)
250 return got_error_from_errno2("unveil", "gmon.out");
251 #endif
252 if (unveil(unix_socket_path, "w") != 0)
253 return got_error_from_errno2("unveil", unix_socket_path);
255 if (unveil(NULL, NULL) != 0)
256 return got_error_from_errno("unveil");
258 return NULL;
261 static int
262 connect_gotd(const char *socket_path)
264 const struct got_error *error = NULL;
265 int gotd_sock = -1;
266 struct sockaddr_un sun;
268 error = apply_unveil(socket_path);
269 if (error)
270 errx(1, "%s", error->msg);
272 #ifndef PROFILE
273 if (pledge("stdio unix", NULL) == -1)
274 err(1, "pledge");
275 #endif
276 if ((gotd_sock = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
277 err(1, "socket");
279 memset(&sun, 0, sizeof(sun));
280 sun.sun_family = AF_UNIX;
281 if (strlcpy(sun.sun_path, socket_path, sizeof(sun.sun_path)) >=
282 sizeof(sun.sun_path))
283 errx(1, "gotd socket path too long");
284 if (connect(gotd_sock, (struct sockaddr *)&sun, sizeof(sun)) == -1)
285 err(1, "connect: %s", socket_path);
287 #ifndef PROFILE
288 if (pledge("stdio", NULL) == -1)
289 err(1, "pledge");
290 #endif
292 return gotd_sock;
295 int
296 main(int argc, char *argv[])
298 const struct gotctl_cmd *cmd;
299 int gotd_sock = -1, i;
300 int ch;
301 int hflag = 0, Vflag = 0;
302 static const struct option longopts[] = {
303 { "version", no_argument, NULL, 'V' },
304 { NULL, 0, NULL, 0 }
305 };
306 const char *socket_path = GOTD_UNIX_SOCKET;
308 setlocale(LC_CTYPE, "");
310 #ifndef PROFILE
311 if (pledge("stdio unix unveil", NULL) == -1)
312 err(1, "pledge");
313 #endif
315 while ((ch = getopt_long(argc, argv, "+hf:V", longopts, NULL)) != -1) {
316 switch (ch) {
317 case 'h':
318 hflag = 1;
319 break;
320 case 'f':
321 socket_path = optarg;
322 break;
323 case 'V':
324 Vflag = 1;
325 break;
326 default:
327 usage(hflag, 1);
328 /* NOTREACHED */
332 argc -= optind;
333 argv += optind;
334 optind = 1;
335 optreset = 1;
337 if (Vflag) {
338 got_version_print_str();
339 return 0;
342 if (argc <= 0)
343 usage(hflag, hflag ? 0 : 1);
345 for (i = 0; i < nitems(gotctl_commands); i++) {
346 const struct got_error *error;
348 cmd = &gotctl_commands[i];
350 if (strncmp(cmd->cmd_name, argv[0], strlen(argv[0])) != 0)
351 continue;
353 if (hflag)
354 cmd->cmd_usage();
356 gotd_sock = connect_gotd(socket_path);
357 if (gotd_sock == -1)
358 return 1;
359 error = cmd->cmd_main(argc, argv, gotd_sock);
360 close(gotd_sock);
361 if (error) {
362 fprintf(stderr, "%s: %s\n", getprogname(), error->msg);
363 return 1;
366 return 0;
369 fprintf(stderr, "%s: unknown command '%s'\n", getprogname(), argv[0]);
370 list_commands(stderr);
371 return 1;