Blob


1 /*
2 * Copyright (c) 2022, 2023 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/types.h>
18 #include <sys/queue.h>
19 #include <sys/tree.h>
20 #include <sys/socket.h>
21 #include <sys/stat.h>
22 #include <sys/uio.h>
24 #include <errno.h>
25 #include <event.h>
26 #include <limits.h>
27 #include <sha1.h>
28 #include <sha2.h>
29 #include <signal.h>
30 #include <stdint.h>
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <imsg.h>
35 #include <unistd.h>
37 #include "got_error.h"
38 #include "got_repository.h"
39 #include "got_object.h"
40 #include "got_path.h"
41 #include "got_reference.h"
42 #include "got_opentemp.h"
44 #include "got_lib_hash.h"
45 #include "got_lib_delta.h"
46 #include "got_lib_object.h"
47 #include "got_lib_object_cache.h"
48 #include "got_lib_pack.h"
49 #include "got_lib_repository.h"
50 #include "got_lib_gitproto.h"
52 #include "gotd.h"
53 #include "log.h"
54 #include "session_read.h"
56 enum gotd_session_read_state {
57 GOTD_STATE_EXPECT_LIST_REFS,
58 GOTD_STATE_EXPECT_CAPABILITIES,
59 GOTD_STATE_EXPECT_WANT,
60 GOTD_STATE_EXPECT_HAVE_OR_DONE,
61 GOTD_STATE_DONE,
62 };
64 static struct gotd_session_read {
65 pid_t pid;
66 const char *title;
67 struct got_repository *repo;
68 struct gotd_repo *repo_cfg;
69 int *pack_fds;
70 int *temp_fds;
71 struct gotd_imsgev parent_iev;
72 struct gotd_imsgev notifier_iev;
73 struct timeval request_timeout;
74 enum gotd_session_read_state state;
75 struct gotd_imsgev repo_child_iev;
76 } gotd_session;
78 static struct gotd_session_client {
79 struct gotd_client_capability *capabilities;
80 size_t ncapa_alloc;
81 size_t ncapabilities;
82 uint32_t id;
83 int fd;
84 int delta_cache_fd;
85 struct gotd_imsgev iev;
86 struct event tmo;
87 uid_t euid;
88 gid_t egid;
89 char *username;
90 char *packfile_path;
91 char *packidx_path;
92 int nref_updates;
93 int accept_flush_pkt;
94 int flush_disconnect;
95 } gotd_session_client;
97 static void session_read_shutdown(void);
99 static void
100 disconnect(struct gotd_session_client *client)
102 log_debug("uid %d: disconnecting", client->euid);
104 if (gotd_imsg_compose_event(&gotd_session.parent_iev,
105 GOTD_IMSG_DISCONNECT, PROC_SESSION_READ, -1, NULL, 0) == -1)
106 log_warn("imsg compose DISCONNECT");
108 imsg_clear(&gotd_session.repo_child_iev.ibuf);
109 event_del(&gotd_session.repo_child_iev.ev);
110 evtimer_del(&client->tmo);
111 close(client->fd);
112 if (client->delta_cache_fd != -1)
113 close(client->delta_cache_fd);
114 if (client->packfile_path) {
115 if (unlink(client->packfile_path) == -1 && errno != ENOENT)
116 log_warn("unlink %s: ", client->packfile_path);
117 free(client->packfile_path);
119 if (client->packidx_path) {
120 if (unlink(client->packidx_path) == -1 && errno != ENOENT)
121 log_warn("unlink %s: ", client->packidx_path);
122 free(client->packidx_path);
124 free(client->capabilities);
126 session_read_shutdown();
129 static void
130 disconnect_on_error(struct gotd_session_client *client,
131 const struct got_error *err)
133 struct imsgbuf ibuf;
135 if (err->code != GOT_ERR_EOF) {
136 log_warnx("uid %d: %s", client->euid, err->msg);
137 imsg_init(&ibuf, client->fd);
138 gotd_imsg_send_error(&ibuf, 0, PROC_SESSION_READ, err);
139 imsg_clear(&ibuf);
142 disconnect(client);
145 static void
146 gotd_request_timeout(int fd, short events, void *arg)
148 struct gotd_session_client *client = arg;
150 log_warnx("disconnecting uid %d due to timeout", client->euid);
151 disconnect(client);
154 static void
155 session_read_sighdlr(int sig, short event, void *arg)
157 /*
158 * Normal signal handler rules don't apply because libevent
159 * decouples for us.
160 */
162 switch (sig) {
163 case SIGHUP:
164 log_info("%s: ignoring SIGHUP", __func__);
165 break;
166 case SIGUSR1:
167 log_info("%s: ignoring SIGUSR1", __func__);
168 break;
169 case SIGTERM:
170 case SIGINT:
171 session_read_shutdown();
172 /* NOTREACHED */
173 break;
174 default:
175 fatalx("unexpected signal");
179 static const struct got_error *
180 recv_packfile_done(struct imsg *imsg)
182 size_t datalen;
184 log_debug("packfile-done received");
186 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
187 if (datalen != 0)
188 return got_error(GOT_ERR_PRIVSEP_LEN);
190 return NULL;
193 static void
194 session_dispatch_repo_child(int fd, short event, void *arg)
196 struct gotd_imsgev *iev = arg;
197 struct imsgbuf *ibuf = &iev->ibuf;
198 struct gotd_session_client *client = &gotd_session_client;
199 ssize_t n;
200 int shut = 0;
201 struct imsg imsg;
203 if (event & EV_READ) {
204 if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
205 fatal("imsg_read error");
206 if (n == 0) {
207 /* Connection closed. */
208 shut = 1;
209 goto done;
213 if (event & EV_WRITE) {
214 n = msgbuf_write(&ibuf->w);
215 if (n == -1 && errno != EAGAIN)
216 fatal("msgbuf_write");
217 if (n == 0) {
218 /* Connection closed. */
219 shut = 1;
220 goto done;
224 for (;;) {
225 const struct got_error *err = NULL;
226 uint32_t client_id = 0;
227 int do_disconnect = 0;
229 if ((n = imsg_get(ibuf, &imsg)) == -1)
230 fatal("%s: imsg_get error", __func__);
231 if (n == 0) /* No more messages. */
232 break;
234 switch (imsg.hdr.type) {
235 case GOTD_IMSG_ERROR:
236 do_disconnect = 1;
237 err = gotd_imsg_recv_error(&client_id, &imsg);
238 break;
239 case GOTD_IMSG_PACKFILE_DONE:
240 do_disconnect = 1;
241 err = recv_packfile_done(&imsg);
242 break;
243 default:
244 log_debug("unexpected imsg %d", imsg.hdr.type);
245 break;
248 if (do_disconnect) {
249 if (err)
250 disconnect_on_error(client, err);
251 else
252 disconnect(client);
253 } else {
254 if (err)
255 log_warnx("uid %d: %s", client->euid, err->msg);
257 imsg_free(&imsg);
259 done:
260 if (!shut) {
261 gotd_imsg_event_add(iev);
262 } else {
263 /* This pipe is dead. Remove its event handler */
264 event_del(&iev->ev);
265 event_loopexit(NULL);
269 static const struct got_error *
270 recv_capabilities(struct gotd_session_client *client, struct imsg *imsg)
272 struct gotd_imsg_capabilities icapas;
273 size_t datalen;
275 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
276 if (datalen != sizeof(icapas))
277 return got_error(GOT_ERR_PRIVSEP_LEN);
278 memcpy(&icapas, imsg->data, sizeof(icapas));
280 client->ncapa_alloc = icapas.ncapabilities;
281 client->capabilities = calloc(client->ncapa_alloc,
282 sizeof(*client->capabilities));
283 if (client->capabilities == NULL) {
284 client->ncapa_alloc = 0;
285 return got_error_from_errno("calloc");
288 log_debug("expecting %zu capabilities from uid %d",
289 client->ncapa_alloc, client->euid);
290 return NULL;
293 static const struct got_error *
294 recv_capability(struct gotd_session_client *client, struct imsg *imsg)
296 struct gotd_imsg_capability icapa;
297 struct gotd_client_capability *capa;
298 size_t datalen;
299 char *key, *value = NULL;
301 if (client->capabilities == NULL ||
302 client->ncapabilities >= client->ncapa_alloc) {
303 return got_error_msg(GOT_ERR_BAD_REQUEST,
304 "unexpected capability received");
307 memset(&icapa, 0, sizeof(icapa));
309 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
310 if (datalen < sizeof(icapa))
311 return got_error(GOT_ERR_PRIVSEP_LEN);
312 memcpy(&icapa, imsg->data, sizeof(icapa));
314 if (datalen != sizeof(icapa) + icapa.key_len + icapa.value_len)
315 return got_error(GOT_ERR_PRIVSEP_LEN);
317 key = strndup(imsg->data + sizeof(icapa), icapa.key_len);
318 if (key == NULL)
319 return got_error_from_errno("strndup");
320 if (icapa.value_len > 0) {
321 value = strndup(imsg->data + sizeof(icapa) + icapa.key_len,
322 icapa.value_len);
323 if (value == NULL) {
324 free(key);
325 return got_error_from_errno("strndup");
329 capa = &client->capabilities[client->ncapabilities++];
330 capa->key = key;
331 capa->value = value;
333 if (value)
334 log_debug("uid %d: capability %s=%s", client->euid, key, value);
335 else
336 log_debug("uid %d: capability %s", client->euid, key);
338 return NULL;
341 static const struct got_error *
342 forward_want(struct gotd_session_client *client, struct imsg *imsg)
344 struct gotd_imsg_want ireq;
345 struct gotd_imsg_want iwant;
346 size_t datalen;
348 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
349 if (datalen != sizeof(ireq))
350 return got_error(GOT_ERR_PRIVSEP_LEN);
352 memcpy(&ireq, imsg->data, datalen);
354 memset(&iwant, 0, sizeof(iwant));
355 memcpy(iwant.object_id, ireq.object_id, SHA1_DIGEST_LENGTH);
357 if (gotd_imsg_compose_event(&gotd_session.repo_child_iev,
358 GOTD_IMSG_WANT, PROC_SESSION_READ, -1,
359 &iwant, sizeof(iwant)) == -1)
360 return got_error_from_errno("imsg compose WANT");
362 return NULL;
365 static const struct got_error *
366 forward_have(struct gotd_session_client *client, struct imsg *imsg)
368 struct gotd_imsg_have ireq;
369 struct gotd_imsg_have ihave;
370 size_t datalen;
372 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
373 if (datalen != sizeof(ireq))
374 return got_error(GOT_ERR_PRIVSEP_LEN);
376 memcpy(&ireq, imsg->data, datalen);
378 memset(&ihave, 0, sizeof(ihave));
379 memcpy(ihave.object_id, ireq.object_id, SHA1_DIGEST_LENGTH);
381 if (gotd_imsg_compose_event(&gotd_session.repo_child_iev,
382 GOTD_IMSG_HAVE, PROC_SESSION_READ, -1,
383 &ihave, sizeof(ihave)) == -1)
384 return got_error_from_errno("imsg compose HAVE");
386 return NULL;
389 static int
390 client_has_capability(struct gotd_session_client *client, const char *capastr)
392 struct gotd_client_capability *capa;
393 size_t i;
395 if (client->ncapabilities == 0)
396 return 0;
398 for (i = 0; i < client->ncapabilities; i++) {
399 capa = &client->capabilities[i];
400 if (strcmp(capa->key, capastr) == 0)
401 return 1;
404 return 0;
407 static const struct got_error *
408 send_packfile(struct gotd_session_client *client)
410 const struct got_error *err = NULL;
411 struct gotd_imsg_send_packfile ipack;
412 int pipe[2];
414 if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pipe) == -1)
415 return got_error_from_errno("socketpair");
417 memset(&ipack, 0, sizeof(ipack));
419 if (client_has_capability(client, GOT_CAPA_SIDE_BAND_64K))
420 ipack.report_progress = 1;
422 client->delta_cache_fd = got_opentempfd();
423 if (client->delta_cache_fd == -1)
424 return got_error_from_errno("got_opentempfd");
426 if (gotd_imsg_compose_event(&gotd_session.repo_child_iev,
427 GOTD_IMSG_SEND_PACKFILE, PROC_GOTD, client->delta_cache_fd,
428 &ipack, sizeof(ipack)) == -1) {
429 err = got_error_from_errno("imsg compose SEND_PACKFILE");
430 close(pipe[0]);
431 close(pipe[1]);
432 return err;
435 /* Send pack pipe end 0 to repo child process. */
436 if (gotd_imsg_compose_event(&gotd_session.repo_child_iev,
437 GOTD_IMSG_PACKFILE_PIPE, PROC_GOTD, pipe[0], NULL, 0) == -1) {
438 err = got_error_from_errno("imsg compose PACKFILE_PIPE");
439 close(pipe[1]);
440 return err;
443 /* Send pack pipe end 1 to gotsh(1) (expects just an fd, no data). */
444 if (gotd_imsg_compose_event(&client->iev,
445 GOTD_IMSG_PACKFILE_PIPE, PROC_GOTD, pipe[1], NULL, 0) == -1)
446 err = got_error_from_errno("imsg compose PACKFILE_PIPE");
448 return err;
451 static void
452 session_dispatch_client(int fd, short events, void *arg)
454 struct gotd_imsgev *iev = arg;
455 struct imsgbuf *ibuf = &iev->ibuf;
456 struct gotd_session_client *client = &gotd_session_client;
457 const struct got_error *err = NULL;
458 struct imsg imsg;
459 ssize_t n;
461 if (events & EV_WRITE) {
462 while (ibuf->w.queued) {
463 n = msgbuf_write(&ibuf->w);
464 if (n == -1 && errno != EAGAIN) {
465 err = got_error_from_errno("imsg_flush");
466 disconnect_on_error(client, err);
467 return;
469 if (n == 0) {
470 /* Connection closed. */
471 err = got_error(GOT_ERR_EOF);
472 disconnect_on_error(client, err);
473 return;
477 if (client->flush_disconnect) {
478 disconnect(client);
479 return;
483 if ((events & EV_READ) == 0)
484 return;
486 memset(&imsg, 0, sizeof(imsg));
488 while (err == NULL) {
489 err = gotd_imsg_recv(&imsg, ibuf, 0);
490 if (err) {
491 if (err->code == GOT_ERR_PRIVSEP_READ)
492 err = NULL;
493 else if (err->code == GOT_ERR_EOF &&
494 gotd_session.state ==
495 GOTD_STATE_EXPECT_CAPABILITIES) {
496 /*
497 * The client has closed its socket before
498 * sending its capability announcement.
499 * This can happen when Git clients have
500 * no ref-updates to send.
501 */
502 disconnect_on_error(client, err);
503 return;
505 break;
508 evtimer_del(&client->tmo);
510 switch (imsg.hdr.type) {
511 case GOTD_IMSG_CAPABILITIES:
512 if (gotd_session.state !=
513 GOTD_STATE_EXPECT_CAPABILITIES) {
514 err = got_error_msg(GOT_ERR_BAD_REQUEST,
515 "unexpected capabilities received");
516 break;
518 log_debug("receiving capabilities from uid %d",
519 client->euid);
520 err = recv_capabilities(client, &imsg);
521 break;
522 case GOTD_IMSG_CAPABILITY:
523 if (gotd_session.state != GOTD_STATE_EXPECT_CAPABILITIES) {
524 err = got_error_msg(GOT_ERR_BAD_REQUEST,
525 "unexpected capability received");
526 break;
528 err = recv_capability(client, &imsg);
529 if (err || client->ncapabilities < client->ncapa_alloc)
530 break;
531 gotd_session.state = GOTD_STATE_EXPECT_WANT;
532 client->accept_flush_pkt = 1;
533 log_debug("uid %d: expecting want-lines", client->euid);
534 break;
535 case GOTD_IMSG_WANT:
536 if (gotd_session.state != GOTD_STATE_EXPECT_WANT) {
537 err = got_error_msg(GOT_ERR_BAD_REQUEST,
538 "unexpected want-line received");
539 break;
541 log_debug("received want-line from uid %d",
542 client->euid);
543 client->accept_flush_pkt = 1;
544 err = forward_want(client, &imsg);
545 break;
546 case GOTD_IMSG_HAVE:
547 if (gotd_session.state !=
548 GOTD_STATE_EXPECT_HAVE_OR_DONE) {
549 err = got_error_msg(GOT_ERR_BAD_REQUEST,
550 "unexpected have-line received");
551 break;
553 log_debug("received have-line from uid %d",
554 client->euid);
555 err = forward_have(client, &imsg);
556 if (err)
557 break;
558 client->accept_flush_pkt = 1;
559 break;
560 case GOTD_IMSG_FLUSH:
561 if (gotd_session.state != GOTD_STATE_EXPECT_WANT &&
562 gotd_session.state !=
563 GOTD_STATE_EXPECT_HAVE_OR_DONE) {
564 err = got_error_msg(GOT_ERR_BAD_REQUEST,
565 "unexpected flush-pkt received");
566 break;
568 if (!client->accept_flush_pkt) {
569 err = got_error_msg(GOT_ERR_BAD_REQUEST,
570 "unexpected flush-pkt received");
571 break;
574 /*
575 * Accept just one flush packet at a time.
576 * Future client state transitions will set this flag
577 * again if another flush packet is expected.
578 */
579 client->accept_flush_pkt = 0;
581 log_debug("received flush-pkt from uid %d",
582 client->euid);
583 if (gotd_session.state == GOTD_STATE_EXPECT_WANT) {
584 gotd_session.state =
585 GOTD_STATE_EXPECT_HAVE_OR_DONE;
586 log_debug("uid %d: expecting have-lines "
587 "or 'done'", client->euid);
588 } else if (gotd_session.state ==
589 GOTD_STATE_EXPECT_HAVE_OR_DONE) {
590 client->accept_flush_pkt = 1;
591 log_debug("uid %d: expecting more have-lines "
592 "or 'done'", client->euid);
593 } else if (gotd_session.state !=
594 GOTD_STATE_EXPECT_HAVE_OR_DONE) {
595 /* should not happen, see above */
596 err = got_error_msg(GOT_ERR_BAD_REQUEST,
597 "unexpected client state");
598 break;
600 break;
601 case GOTD_IMSG_DONE:
602 if (gotd_session.state !=
603 GOTD_STATE_EXPECT_HAVE_OR_DONE) {
604 err = got_error_msg(GOT_ERR_BAD_REQUEST,
605 "unexpected flush-pkt received");
606 break;
608 log_debug("received 'done' from uid %d", client->euid);
609 gotd_session.state = GOTD_STATE_DONE;
610 client->accept_flush_pkt = 1;
611 err = send_packfile(client);
612 break;
613 default:
614 log_debug("unexpected imsg %d", imsg.hdr.type);
615 err = got_error(GOT_ERR_PRIVSEP_MSG);
616 break;
619 imsg_free(&imsg);
622 if (err) {
623 if (err->code != GOT_ERR_EOF)
624 disconnect_on_error(client, err);
625 } else {
626 gotd_imsg_event_add(iev);
627 evtimer_add(&client->tmo, &gotd_session.request_timeout);
631 static const struct got_error *
632 list_refs_request(void)
634 static const struct got_error *err;
635 struct gotd_session_client *client = &gotd_session_client;
636 struct gotd_imsgev *iev = &gotd_session.repo_child_iev;
637 int fd;
639 if (gotd_session.state != GOTD_STATE_EXPECT_LIST_REFS)
640 return got_error(GOT_ERR_PRIVSEP_MSG);
642 fd = dup(client->fd);
643 if (fd == -1)
644 return got_error_from_errno("dup");
646 if (gotd_imsg_compose_event(iev, GOTD_IMSG_LIST_REFS_INTERNAL,
647 PROC_SESSION_READ, fd, NULL, 0) == -1) {
648 err = got_error_from_errno("imsg compose LIST_REFS_INTERNAL");
649 close(fd);
650 return err;
653 gotd_session.state = GOTD_STATE_EXPECT_CAPABILITIES;
654 log_debug("uid %d: expecting capabilities", client->euid);
655 return NULL;
658 static const struct got_error *
659 recv_connect(struct imsg *imsg)
661 struct gotd_session_client *client = &gotd_session_client;
662 struct gotd_imsg_connect iconnect;
663 size_t datalen;
665 if (gotd_session.state != GOTD_STATE_EXPECT_LIST_REFS)
666 return got_error(GOT_ERR_PRIVSEP_MSG);
668 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
669 if (datalen < sizeof(iconnect))
670 return got_error(GOT_ERR_PRIVSEP_LEN);
671 memcpy(&iconnect, imsg->data, sizeof(iconnect));
672 if (iconnect.username_len == 0 ||
673 datalen != sizeof(iconnect) + iconnect.username_len)
674 return got_error(GOT_ERR_PRIVSEP_LEN);
676 client->euid = iconnect.euid;
677 client->egid = iconnect.egid;
678 client->fd = imsg_get_fd(imsg);
679 if (client->fd == -1)
680 return got_error(GOT_ERR_PRIVSEP_NO_FD);
682 client->username = strndup(imsg->data + sizeof(iconnect),
683 iconnect.username_len);
684 if (client->username == NULL)
685 return got_error_from_errno("strndup");
687 imsg_init(&client->iev.ibuf, client->fd);
688 client->iev.handler = session_dispatch_client;
689 client->iev.events = EV_READ;
690 client->iev.handler_arg = NULL;
691 event_set(&client->iev.ev, client->iev.ibuf.fd, EV_READ,
692 session_dispatch_client, &client->iev);
693 gotd_imsg_event_add(&client->iev);
694 evtimer_set(&client->tmo, gotd_request_timeout, client);
695 evtimer_add(&client->tmo, &gotd_session.request_timeout);
697 return NULL;
700 static const struct got_error *
701 recv_repo_child(struct imsg *imsg)
703 struct gotd_imsg_connect_repo_child ichild;
704 struct gotd_session_client *client = &gotd_session_client;
705 size_t datalen;
706 int fd;
708 if (gotd_session.state != GOTD_STATE_EXPECT_LIST_REFS)
709 return got_error(GOT_ERR_PRIVSEP_MSG);
711 /* We should already have received a pipe to the listener. */
712 if (client->fd == -1)
713 return got_error(GOT_ERR_PRIVSEP_MSG);
715 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
716 if (datalen != sizeof(ichild))
717 return got_error(GOT_ERR_PRIVSEP_LEN);
719 memcpy(&ichild, imsg->data, sizeof(ichild));
721 if (ichild.proc_id != PROC_REPO_READ)
722 return got_error_msg(GOT_ERR_PRIVSEP_MSG,
723 "bad child process type");
725 fd = imsg_get_fd(imsg);
726 if (fd == -1)
727 return got_error(GOT_ERR_PRIVSEP_NO_FD);
729 imsg_init(&gotd_session.repo_child_iev.ibuf, fd);
730 gotd_session.repo_child_iev.handler = session_dispatch_repo_child;
731 gotd_session.repo_child_iev.events = EV_READ;
732 gotd_session.repo_child_iev.handler_arg = NULL;
733 event_set(&gotd_session.repo_child_iev.ev,
734 gotd_session.repo_child_iev.ibuf.fd, EV_READ,
735 session_dispatch_repo_child, &gotd_session.repo_child_iev);
736 gotd_imsg_event_add(&gotd_session.repo_child_iev);
738 /* The "recvfd" pledge promise is no longer needed. */
739 if (pledge("stdio rpath wpath cpath sendfd fattr flock", NULL) == -1)
740 fatal("pledge");
742 return NULL;
745 static void
746 session_dispatch(int fd, short event, void *arg)
748 struct gotd_imsgev *iev = arg;
749 struct imsgbuf *ibuf = &iev->ibuf;
750 struct gotd_session_client *client = &gotd_session_client;
751 ssize_t n;
752 int shut = 0;
753 struct imsg imsg;
755 if (event & EV_READ) {
756 if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
757 fatal("imsg_read error");
758 if (n == 0) {
759 /* Connection closed. */
760 shut = 1;
761 goto done;
765 if (event & EV_WRITE) {
766 n = msgbuf_write(&ibuf->w);
767 if (n == -1 && errno != EAGAIN)
768 fatal("msgbuf_write");
769 if (n == 0) {
770 /* Connection closed. */
771 shut = 1;
772 goto done;
776 for (;;) {
777 const struct got_error *err = NULL;
778 uint32_t client_id = 0;
779 int do_disconnect = 0, do_list_refs = 0;
781 if ((n = imsg_get(ibuf, &imsg)) == -1)
782 fatal("%s: imsg_get error", __func__);
783 if (n == 0) /* No more messages. */
784 break;
786 switch (imsg.hdr.type) {
787 case GOTD_IMSG_ERROR:
788 do_disconnect = 1;
789 err = gotd_imsg_recv_error(&client_id, &imsg);
790 break;
791 case GOTD_IMSG_CONNECT:
792 err = recv_connect(&imsg);
793 break;
794 case GOTD_IMSG_DISCONNECT:
795 do_disconnect = 1;
796 break;
797 case GOTD_IMSG_CONNECT_REPO_CHILD:
798 err = recv_repo_child(&imsg);
799 if (err)
800 break;
801 do_list_refs = 1;
802 break;
803 default:
804 log_debug("unexpected imsg %d", imsg.hdr.type);
805 break;
807 imsg_free(&imsg);
809 if (do_disconnect) {
810 if (err)
811 disconnect_on_error(client, err);
812 else
813 disconnect(client);
814 } else if (do_list_refs)
815 err = list_refs_request();
817 if (err)
818 log_warnx("uid %d: %s", client->euid, err->msg);
820 done:
821 if (!shut) {
822 gotd_imsg_event_add(iev);
823 } else {
824 /* This pipe is dead. Remove its event handler */
825 event_del(&iev->ev);
826 event_loopexit(NULL);
830 void
831 session_read_main(const char *title, const char *repo_path,
832 int *pack_fds, int *temp_fds, struct timeval *request_timeout,
833 struct gotd_repo *repo_cfg)
835 const struct got_error *err = NULL;
836 struct event evsigint, evsigterm, evsighup, evsigusr1;
838 gotd_session.title = title;
839 gotd_session.pid = getpid();
840 gotd_session.pack_fds = pack_fds;
841 gotd_session.temp_fds = temp_fds;
842 memcpy(&gotd_session.request_timeout, request_timeout,
843 sizeof(gotd_session.request_timeout));
844 gotd_session.repo_cfg = repo_cfg;
846 imsg_init(&gotd_session.notifier_iev.ibuf, -1);
848 err = got_repo_open(&gotd_session.repo, repo_path, NULL, pack_fds);
849 if (err)
850 goto done;
851 if (!got_repo_is_bare(gotd_session.repo)) {
852 err = got_error_msg(GOT_ERR_NOT_GIT_REPO,
853 "bare git repository required");
854 goto done;
856 if (got_repo_get_object_format(gotd_session.repo) != GOT_HASH_SHA1) {
857 err = got_error_msg(GOT_ERR_NOT_IMPL,
858 "sha256 object IDs unsupported in network protocol");
859 goto done;
862 got_repo_temp_fds_set(gotd_session.repo, temp_fds);
864 signal_set(&evsigint, SIGINT, session_read_sighdlr, NULL);
865 signal_set(&evsigterm, SIGTERM, session_read_sighdlr, NULL);
866 signal_set(&evsighup, SIGHUP, session_read_sighdlr, NULL);
867 signal_set(&evsigusr1, SIGUSR1, session_read_sighdlr, NULL);
868 signal(SIGPIPE, SIG_IGN);
870 signal_add(&evsigint, NULL);
871 signal_add(&evsigterm, NULL);
872 signal_add(&evsighup, NULL);
873 signal_add(&evsigusr1, NULL);
875 gotd_session.state = GOTD_STATE_EXPECT_LIST_REFS;
877 gotd_session_client.fd = -1;
878 gotd_session_client.nref_updates = -1;
879 gotd_session_client.delta_cache_fd = -1;
880 gotd_session_client.accept_flush_pkt = 1;
882 imsg_init(&gotd_session.parent_iev.ibuf, GOTD_FILENO_MSG_PIPE);
883 gotd_session.parent_iev.handler = session_dispatch;
884 gotd_session.parent_iev.events = EV_READ;
885 gotd_session.parent_iev.handler_arg = NULL;
886 event_set(&gotd_session.parent_iev.ev, gotd_session.parent_iev.ibuf.fd,
887 EV_READ, session_dispatch, &gotd_session.parent_iev);
888 if (gotd_imsg_compose_event(&gotd_session.parent_iev,
889 GOTD_IMSG_CLIENT_SESSION_READY, PROC_SESSION_READ,
890 -1, NULL, 0) == -1) {
891 err = got_error_from_errno("imsg compose CLIENT_SESSION_READY");
892 goto done;
895 event_dispatch();
896 done:
897 if (err)
898 log_warnx("%s: %s", title, err->msg);
899 session_read_shutdown();
902 static void
903 session_read_shutdown(void)
905 log_debug("%s: shutting down", gotd_session.title);
907 if (gotd_session.repo)
908 got_repo_close(gotd_session.repo);
909 got_repo_pack_fds_close(gotd_session.pack_fds);
910 got_repo_temp_fds_close(gotd_session.temp_fds);
911 free(gotd_session_client.username);
912 exit(0);