portable: include sha{1,2} portably
[got-portable.git] / gotd / session_read.c
blob95210cf05dd311697cdc1b9d746ab30fd318fb49
1 /*
2 * Copyright (c) 2022, 2023 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/types.h>
20 #include <sys/queue.h>
21 #include <sys/socket.h>
22 #include <sys/stat.h>
23 #include <sys/uio.h>
25 #include <errno.h>
26 #include <event.h>
27 #include <limits.h>
28 #include <signal.h>
29 #include <stdint.h>
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <imsg.h>
34 #include <unistd.h>
36 #include "got_error.h"
37 #include "got_repository.h"
38 #include "got_object.h"
39 #include "got_path.h"
40 #include "got_reference.h"
41 #include "got_opentemp.h"
43 #include "got_lib_hash.h"
44 #include "got_lib_delta.h"
45 #include "got_lib_object.h"
46 #include "got_lib_object_cache.h"
47 #include "got_lib_pack.h"
48 #include "got_lib_repository.h"
49 #include "got_lib_gitproto.h"
51 #include "gotd.h"
52 #include "log.h"
53 #include "session_read.h"
55 enum gotd_session_read_state {
56 GOTD_STATE_EXPECT_LIST_REFS,
57 GOTD_STATE_EXPECT_CAPABILITIES,
58 GOTD_STATE_EXPECT_WANT,
59 GOTD_STATE_EXPECT_HAVE_OR_DONE,
60 GOTD_STATE_DONE,
63 static struct gotd_session_read {
64 pid_t pid;
65 const char *title;
66 struct got_repository *repo;
67 struct gotd_repo *repo_cfg;
68 int *pack_fds;
69 int *temp_fds;
70 struct gotd_imsgev parent_iev;
71 struct gotd_imsgev notifier_iev;
72 struct timeval request_timeout;
73 enum gotd_session_read_state state;
74 struct gotd_imsgev repo_child_iev;
75 } gotd_session;
77 static struct gotd_session_client {
78 struct gotd_client_capability *capabilities;
79 size_t ncapa_alloc;
80 size_t ncapabilities;
81 uint32_t id;
82 int fd;
83 int delta_cache_fd;
84 struct gotd_imsgev iev;
85 struct event tmo;
86 uid_t euid;
87 gid_t egid;
88 char *username;
89 char *packfile_path;
90 char *packidx_path;
91 int nref_updates;
92 int accept_flush_pkt;
93 int flush_disconnect;
94 } gotd_session_client;
96 static void session_read_shutdown(void);
98 static void
99 disconnect(struct gotd_session_client *client)
101 log_debug("uid %d: disconnecting", client->euid);
103 if (gotd_imsg_compose_event(&gotd_session.parent_iev,
104 GOTD_IMSG_DISCONNECT, PROC_SESSION_READ, -1, NULL, 0) == -1)
105 log_warn("imsg compose DISCONNECT");
107 imsg_clear(&gotd_session.repo_child_iev.ibuf);
108 event_del(&gotd_session.repo_child_iev.ev);
109 evtimer_del(&client->tmo);
110 close(client->fd);
111 if (client->delta_cache_fd != -1)
112 close(client->delta_cache_fd);
113 if (client->packfile_path) {
114 if (unlink(client->packfile_path) == -1 && errno != ENOENT)
115 log_warn("unlink %s: ", client->packfile_path);
116 free(client->packfile_path);
118 if (client->packidx_path) {
119 if (unlink(client->packidx_path) == -1 && errno != ENOENT)
120 log_warn("unlink %s: ", client->packidx_path);
121 free(client->packidx_path);
123 free(client->capabilities);
125 session_read_shutdown();
128 static void
129 disconnect_on_error(struct gotd_session_client *client,
130 const struct got_error *err)
132 struct imsgbuf ibuf;
134 if (err->code != GOT_ERR_EOF) {
135 log_warnx("uid %d: %s", client->euid, err->msg);
136 imsg_init(&ibuf, client->fd);
137 gotd_imsg_send_error(&ibuf, 0, PROC_SESSION_READ, err);
138 imsg_clear(&ibuf);
141 disconnect(client);
144 static void
145 gotd_request_timeout(int fd, short events, void *arg)
147 struct gotd_session_client *client = arg;
149 log_warnx("disconnecting uid %d due to timeout", client->euid);
150 disconnect(client);
153 static void
154 session_read_sighdlr(int sig, short event, void *arg)
157 * Normal signal handler rules don't apply because libevent
158 * decouples for us.
161 switch (sig) {
162 case SIGHUP:
163 log_info("%s: ignoring SIGHUP", __func__);
164 break;
165 case SIGUSR1:
166 log_info("%s: ignoring SIGUSR1", __func__);
167 break;
168 case SIGTERM:
169 case SIGINT:
170 session_read_shutdown();
171 /* NOTREACHED */
172 break;
173 default:
174 fatalx("unexpected signal");
178 static const struct got_error *
179 recv_packfile_done(struct imsg *imsg)
181 size_t datalen;
183 log_debug("packfile-done received");
185 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
186 if (datalen != 0)
187 return got_error(GOT_ERR_PRIVSEP_LEN);
189 return NULL;
192 static void
193 session_dispatch_repo_child(int fd, short event, void *arg)
195 struct gotd_imsgev *iev = arg;
196 struct imsgbuf *ibuf = &iev->ibuf;
197 struct gotd_session_client *client = &gotd_session_client;
198 ssize_t n;
199 int shut = 0;
200 struct imsg imsg;
202 if (event & EV_READ) {
203 if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
204 fatal("imsg_read error");
205 if (n == 0) {
206 /* Connection closed. */
207 shut = 1;
208 goto done;
212 if (event & EV_WRITE) {
213 n = msgbuf_write(&ibuf->w);
214 if (n == -1 && errno != EAGAIN)
215 fatal("msgbuf_write");
216 if (n == 0) {
217 /* Connection closed. */
218 shut = 1;
219 goto done;
223 for (;;) {
224 const struct got_error *err = NULL;
225 uint32_t client_id = 0;
226 int do_disconnect = 0;
228 if ((n = imsg_get(ibuf, &imsg)) == -1)
229 fatal("%s: imsg_get error", __func__);
230 if (n == 0) /* No more messages. */
231 break;
233 switch (imsg.hdr.type) {
234 case GOTD_IMSG_ERROR:
235 do_disconnect = 1;
236 err = gotd_imsg_recv_error(&client_id, &imsg);
237 break;
238 case GOTD_IMSG_PACKFILE_DONE:
239 do_disconnect = 1;
240 err = recv_packfile_done(&imsg);
241 break;
242 default:
243 log_debug("unexpected imsg %d", imsg.hdr.type);
244 break;
247 if (do_disconnect) {
248 if (err)
249 disconnect_on_error(client, err);
250 else
251 disconnect(client);
252 } else {
253 if (err)
254 log_warnx("uid %d: %s", client->euid, err->msg);
256 imsg_free(&imsg);
258 done:
259 if (!shut) {
260 gotd_imsg_event_add(iev);
261 } else {
262 /* This pipe is dead. Remove its event handler */
263 event_del(&iev->ev);
264 event_loopexit(NULL);
268 static const struct got_error *
269 recv_capabilities(struct gotd_session_client *client, struct imsg *imsg)
271 struct gotd_imsg_capabilities icapas;
272 size_t datalen;
274 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
275 if (datalen != sizeof(icapas))
276 return got_error(GOT_ERR_PRIVSEP_LEN);
277 memcpy(&icapas, imsg->data, sizeof(icapas));
279 client->ncapa_alloc = icapas.ncapabilities;
280 client->capabilities = calloc(client->ncapa_alloc,
281 sizeof(*client->capabilities));
282 if (client->capabilities == NULL) {
283 client->ncapa_alloc = 0;
284 return got_error_from_errno("calloc");
287 log_debug("expecting %zu capabilities from uid %d",
288 client->ncapa_alloc, client->euid);
289 return NULL;
292 static const struct got_error *
293 recv_capability(struct gotd_session_client *client, struct imsg *imsg)
295 struct gotd_imsg_capability icapa;
296 struct gotd_client_capability *capa;
297 size_t datalen;
298 char *key, *value = NULL;
300 if (client->capabilities == NULL ||
301 client->ncapabilities >= client->ncapa_alloc) {
302 return got_error_msg(GOT_ERR_BAD_REQUEST,
303 "unexpected capability received");
306 memset(&icapa, 0, sizeof(icapa));
308 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
309 if (datalen < sizeof(icapa))
310 return got_error(GOT_ERR_PRIVSEP_LEN);
311 memcpy(&icapa, imsg->data, sizeof(icapa));
313 if (datalen != sizeof(icapa) + icapa.key_len + icapa.value_len)
314 return got_error(GOT_ERR_PRIVSEP_LEN);
316 key = strndup(imsg->data + sizeof(icapa), icapa.key_len);
317 if (key == NULL)
318 return got_error_from_errno("strndup");
319 if (icapa.value_len > 0) {
320 value = strndup(imsg->data + sizeof(icapa) + icapa.key_len,
321 icapa.value_len);
322 if (value == NULL) {
323 free(key);
324 return got_error_from_errno("strndup");
328 capa = &client->capabilities[client->ncapabilities++];
329 capa->key = key;
330 capa->value = value;
332 if (value)
333 log_debug("uid %d: capability %s=%s", client->euid, key, value);
334 else
335 log_debug("uid %d: capability %s", client->euid, key);
337 return NULL;
340 static const struct got_error *
341 forward_want(struct gotd_session_client *client, struct imsg *imsg)
343 struct gotd_imsg_want ireq;
344 struct gotd_imsg_want iwant;
345 size_t datalen;
347 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
348 if (datalen != sizeof(ireq))
349 return got_error(GOT_ERR_PRIVSEP_LEN);
351 memcpy(&ireq, imsg->data, datalen);
353 memset(&iwant, 0, sizeof(iwant));
354 memcpy(iwant.object_id, ireq.object_id, SHA1_DIGEST_LENGTH);
356 if (gotd_imsg_compose_event(&gotd_session.repo_child_iev,
357 GOTD_IMSG_WANT, PROC_SESSION_READ, -1,
358 &iwant, sizeof(iwant)) == -1)
359 return got_error_from_errno("imsg compose WANT");
361 return NULL;
364 static const struct got_error *
365 forward_have(struct gotd_session_client *client, struct imsg *imsg)
367 struct gotd_imsg_have ireq;
368 struct gotd_imsg_have ihave;
369 size_t datalen;
371 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
372 if (datalen != sizeof(ireq))
373 return got_error(GOT_ERR_PRIVSEP_LEN);
375 memcpy(&ireq, imsg->data, datalen);
377 memset(&ihave, 0, sizeof(ihave));
378 memcpy(ihave.object_id, ireq.object_id, SHA1_DIGEST_LENGTH);
380 if (gotd_imsg_compose_event(&gotd_session.repo_child_iev,
381 GOTD_IMSG_HAVE, PROC_SESSION_READ, -1,
382 &ihave, sizeof(ihave)) == -1)
383 return got_error_from_errno("imsg compose HAVE");
385 return NULL;
388 static int
389 client_has_capability(struct gotd_session_client *client, const char *capastr)
391 struct gotd_client_capability *capa;
392 size_t i;
394 if (client->ncapabilities == 0)
395 return 0;
397 for (i = 0; i < client->ncapabilities; i++) {
398 capa = &client->capabilities[i];
399 if (strcmp(capa->key, capastr) == 0)
400 return 1;
403 return 0;
406 static const struct got_error *
407 send_packfile(struct gotd_session_client *client)
409 const struct got_error *err = NULL;
410 struct gotd_imsg_send_packfile ipack;
411 int pipe[2];
413 if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pipe) == -1)
414 return got_error_from_errno("socketpair");
416 memset(&ipack, 0, sizeof(ipack));
418 if (client_has_capability(client, GOT_CAPA_SIDE_BAND_64K))
419 ipack.report_progress = 1;
421 client->delta_cache_fd = got_opentempfd();
422 if (client->delta_cache_fd == -1)
423 return got_error_from_errno("got_opentempfd");
425 if (gotd_imsg_compose_event(&gotd_session.repo_child_iev,
426 GOTD_IMSG_SEND_PACKFILE, PROC_GOTD, client->delta_cache_fd,
427 &ipack, sizeof(ipack)) == -1) {
428 err = got_error_from_errno("imsg compose SEND_PACKFILE");
429 close(pipe[0]);
430 close(pipe[1]);
431 return err;
434 /* Send pack pipe end 0 to repo child process. */
435 if (gotd_imsg_compose_event(&gotd_session.repo_child_iev,
436 GOTD_IMSG_PACKFILE_PIPE, PROC_GOTD, pipe[0], NULL, 0) == -1) {
437 err = got_error_from_errno("imsg compose PACKFILE_PIPE");
438 close(pipe[1]);
439 return err;
442 /* Send pack pipe end 1 to gotsh(1) (expects just an fd, no data). */
443 if (gotd_imsg_compose_event(&client->iev,
444 GOTD_IMSG_PACKFILE_PIPE, PROC_GOTD, pipe[1], NULL, 0) == -1)
445 err = got_error_from_errno("imsg compose PACKFILE_PIPE");
447 return err;
450 static void
451 session_dispatch_client(int fd, short events, void *arg)
453 struct gotd_imsgev *iev = arg;
454 struct imsgbuf *ibuf = &iev->ibuf;
455 struct gotd_session_client *client = &gotd_session_client;
456 const struct got_error *err = NULL;
457 struct imsg imsg;
458 ssize_t n;
460 if (events & EV_WRITE) {
461 while (ibuf->w.queued) {
462 n = msgbuf_write(&ibuf->w);
463 if (n == -1 && errno != EAGAIN) {
464 err = got_error_from_errno("imsg_flush");
465 disconnect_on_error(client, err);
466 return;
468 if (n == 0) {
469 /* Connection closed. */
470 err = got_error(GOT_ERR_EOF);
471 disconnect_on_error(client, err);
472 return;
476 if (client->flush_disconnect) {
477 disconnect(client);
478 return;
482 if ((events & EV_READ) == 0)
483 return;
485 memset(&imsg, 0, sizeof(imsg));
487 while (err == NULL) {
488 err = gotd_imsg_recv(&imsg, ibuf, 0);
489 if (err) {
490 if (err->code == GOT_ERR_PRIVSEP_READ)
491 err = NULL;
492 else if (err->code == GOT_ERR_EOF &&
493 gotd_session.state ==
494 GOTD_STATE_EXPECT_CAPABILITIES) {
496 * The client has closed its socket before
497 * sending its capability announcement.
498 * This can happen when Git clients have
499 * no ref-updates to send.
501 disconnect_on_error(client, err);
502 return;
504 break;
507 evtimer_del(&client->tmo);
509 switch (imsg.hdr.type) {
510 case GOTD_IMSG_CAPABILITIES:
511 if (gotd_session.state !=
512 GOTD_STATE_EXPECT_CAPABILITIES) {
513 err = got_error_msg(GOT_ERR_BAD_REQUEST,
514 "unexpected capabilities received");
515 break;
517 log_debug("receiving capabilities from uid %d",
518 client->euid);
519 err = recv_capabilities(client, &imsg);
520 break;
521 case GOTD_IMSG_CAPABILITY:
522 if (gotd_session.state != GOTD_STATE_EXPECT_CAPABILITIES) {
523 err = got_error_msg(GOT_ERR_BAD_REQUEST,
524 "unexpected capability received");
525 break;
527 err = recv_capability(client, &imsg);
528 if (err || client->ncapabilities < client->ncapa_alloc)
529 break;
530 gotd_session.state = GOTD_STATE_EXPECT_WANT;
531 client->accept_flush_pkt = 1;
532 log_debug("uid %d: expecting want-lines", client->euid);
533 break;
534 case GOTD_IMSG_WANT:
535 if (gotd_session.state != GOTD_STATE_EXPECT_WANT) {
536 err = got_error_msg(GOT_ERR_BAD_REQUEST,
537 "unexpected want-line received");
538 break;
540 log_debug("received want-line from uid %d",
541 client->euid);
542 client->accept_flush_pkt = 1;
543 err = forward_want(client, &imsg);
544 break;
545 case GOTD_IMSG_HAVE:
546 if (gotd_session.state !=
547 GOTD_STATE_EXPECT_HAVE_OR_DONE) {
548 err = got_error_msg(GOT_ERR_BAD_REQUEST,
549 "unexpected have-line received");
550 break;
552 log_debug("received have-line from uid %d",
553 client->euid);
554 err = forward_have(client, &imsg);
555 if (err)
556 break;
557 client->accept_flush_pkt = 1;
558 break;
559 case GOTD_IMSG_FLUSH:
560 if (gotd_session.state != GOTD_STATE_EXPECT_WANT &&
561 gotd_session.state !=
562 GOTD_STATE_EXPECT_HAVE_OR_DONE) {
563 err = got_error_msg(GOT_ERR_BAD_REQUEST,
564 "unexpected flush-pkt received");
565 break;
567 if (!client->accept_flush_pkt) {
568 err = got_error_msg(GOT_ERR_BAD_REQUEST,
569 "unexpected flush-pkt received");
570 break;
574 * Accept just one flush packet at a time.
575 * Future client state transitions will set this flag
576 * again if another flush packet is expected.
578 client->accept_flush_pkt = 0;
580 log_debug("received flush-pkt from uid %d",
581 client->euid);
582 if (gotd_session.state == GOTD_STATE_EXPECT_WANT) {
583 gotd_session.state =
584 GOTD_STATE_EXPECT_HAVE_OR_DONE;
585 log_debug("uid %d: expecting have-lines "
586 "or 'done'", client->euid);
587 } else if (gotd_session.state ==
588 GOTD_STATE_EXPECT_HAVE_OR_DONE) {
589 client->accept_flush_pkt = 1;
590 log_debug("uid %d: expecting more have-lines "
591 "or 'done'", client->euid);
592 } else if (gotd_session.state !=
593 GOTD_STATE_EXPECT_HAVE_OR_DONE) {
594 /* should not happen, see above */
595 err = got_error_msg(GOT_ERR_BAD_REQUEST,
596 "unexpected client state");
597 break;
599 break;
600 case GOTD_IMSG_DONE:
601 if (gotd_session.state !=
602 GOTD_STATE_EXPECT_HAVE_OR_DONE) {
603 err = got_error_msg(GOT_ERR_BAD_REQUEST,
604 "unexpected flush-pkt received");
605 break;
607 log_debug("received 'done' from uid %d", client->euid);
608 gotd_session.state = GOTD_STATE_DONE;
609 client->accept_flush_pkt = 1;
610 err = send_packfile(client);
611 break;
612 default:
613 log_debug("unexpected imsg %d", imsg.hdr.type);
614 err = got_error(GOT_ERR_PRIVSEP_MSG);
615 break;
618 imsg_free(&imsg);
621 if (err) {
622 if (err->code != GOT_ERR_EOF)
623 disconnect_on_error(client, err);
624 } else {
625 gotd_imsg_event_add(iev);
626 evtimer_add(&client->tmo, &gotd_session.request_timeout);
630 static const struct got_error *
631 list_refs_request(void)
633 static const struct got_error *err;
634 struct gotd_session_client *client = &gotd_session_client;
635 struct gotd_imsgev *iev = &gotd_session.repo_child_iev;
636 int fd;
638 if (gotd_session.state != GOTD_STATE_EXPECT_LIST_REFS)
639 return got_error(GOT_ERR_PRIVSEP_MSG);
641 fd = dup(client->fd);
642 if (fd == -1)
643 return got_error_from_errno("dup");
645 if (gotd_imsg_compose_event(iev, GOTD_IMSG_LIST_REFS_INTERNAL,
646 PROC_SESSION_READ, fd, NULL, 0) == -1) {
647 err = got_error_from_errno("imsg compose LIST_REFS_INTERNAL");
648 close(fd);
649 return err;
652 gotd_session.state = GOTD_STATE_EXPECT_CAPABILITIES;
653 log_debug("uid %d: expecting capabilities", client->euid);
654 return NULL;
657 static const struct got_error *
658 recv_connect(struct imsg *imsg)
660 struct gotd_session_client *client = &gotd_session_client;
661 struct gotd_imsg_connect iconnect;
662 size_t datalen;
664 if (gotd_session.state != GOTD_STATE_EXPECT_LIST_REFS)
665 return got_error(GOT_ERR_PRIVSEP_MSG);
667 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
668 if (datalen < sizeof(iconnect))
669 return got_error(GOT_ERR_PRIVSEP_LEN);
670 memcpy(&iconnect, imsg->data, sizeof(iconnect));
671 if (iconnect.username_len == 0 ||
672 datalen != sizeof(iconnect) + iconnect.username_len)
673 return got_error(GOT_ERR_PRIVSEP_LEN);
675 client->euid = iconnect.euid;
676 client->egid = iconnect.egid;
677 client->fd = imsg_get_fd(imsg);
678 if (client->fd == -1)
679 return got_error(GOT_ERR_PRIVSEP_NO_FD);
681 client->username = strndup(imsg->data + sizeof(iconnect),
682 iconnect.username_len);
683 if (client->username == NULL)
684 return got_error_from_errno("strndup");
686 imsg_init(&client->iev.ibuf, client->fd);
687 client->iev.handler = session_dispatch_client;
688 client->iev.events = EV_READ;
689 client->iev.handler_arg = NULL;
690 event_set(&client->iev.ev, client->iev.ibuf.fd, EV_READ,
691 session_dispatch_client, &client->iev);
692 gotd_imsg_event_add(&client->iev);
693 evtimer_set(&client->tmo, gotd_request_timeout, client);
694 evtimer_add(&client->tmo, &gotd_session.request_timeout);
696 return NULL;
699 static const struct got_error *
700 recv_repo_child(struct imsg *imsg)
702 struct gotd_imsg_connect_repo_child ichild;
703 struct gotd_session_client *client = &gotd_session_client;
704 size_t datalen;
705 int fd;
707 if (gotd_session.state != GOTD_STATE_EXPECT_LIST_REFS)
708 return got_error(GOT_ERR_PRIVSEP_MSG);
710 /* We should already have received a pipe to the listener. */
711 if (client->fd == -1)
712 return got_error(GOT_ERR_PRIVSEP_MSG);
714 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
715 if (datalen != sizeof(ichild))
716 return got_error(GOT_ERR_PRIVSEP_LEN);
718 memcpy(&ichild, imsg->data, sizeof(ichild));
720 if (ichild.proc_id != PROC_REPO_READ)
721 return got_error_msg(GOT_ERR_PRIVSEP_MSG,
722 "bad child process type");
724 fd = imsg_get_fd(imsg);
725 if (fd == -1)
726 return got_error(GOT_ERR_PRIVSEP_NO_FD);
728 imsg_init(&gotd_session.repo_child_iev.ibuf, fd);
729 gotd_session.repo_child_iev.handler = session_dispatch_repo_child;
730 gotd_session.repo_child_iev.events = EV_READ;
731 gotd_session.repo_child_iev.handler_arg = NULL;
732 event_set(&gotd_session.repo_child_iev.ev,
733 gotd_session.repo_child_iev.ibuf.fd, EV_READ,
734 session_dispatch_repo_child, &gotd_session.repo_child_iev);
735 gotd_imsg_event_add(&gotd_session.repo_child_iev);
737 /* The "recvfd" pledge promise is no longer needed. */
738 if (pledge("stdio rpath wpath cpath sendfd fattr flock", NULL) == -1)
739 fatal("pledge");
741 return NULL;
744 static void
745 session_dispatch(int fd, short event, void *arg)
747 struct gotd_imsgev *iev = arg;
748 struct imsgbuf *ibuf = &iev->ibuf;
749 struct gotd_session_client *client = &gotd_session_client;
750 ssize_t n;
751 int shut = 0;
752 struct imsg imsg;
754 if (event & EV_READ) {
755 if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
756 fatal("imsg_read error");
757 if (n == 0) {
758 /* Connection closed. */
759 shut = 1;
760 goto done;
764 if (event & EV_WRITE) {
765 n = msgbuf_write(&ibuf->w);
766 if (n == -1 && errno != EAGAIN)
767 fatal("msgbuf_write");
768 if (n == 0) {
769 /* Connection closed. */
770 shut = 1;
771 goto done;
775 for (;;) {
776 const struct got_error *err = NULL;
777 uint32_t client_id = 0;
778 int do_disconnect = 0, do_list_refs = 0;
780 if ((n = imsg_get(ibuf, &imsg)) == -1)
781 fatal("%s: imsg_get error", __func__);
782 if (n == 0) /* No more messages. */
783 break;
785 switch (imsg.hdr.type) {
786 case GOTD_IMSG_ERROR:
787 do_disconnect = 1;
788 err = gotd_imsg_recv_error(&client_id, &imsg);
789 break;
790 case GOTD_IMSG_CONNECT:
791 err = recv_connect(&imsg);
792 break;
793 case GOTD_IMSG_DISCONNECT:
794 do_disconnect = 1;
795 break;
796 case GOTD_IMSG_CONNECT_REPO_CHILD:
797 err = recv_repo_child(&imsg);
798 if (err)
799 break;
800 do_list_refs = 1;
801 break;
802 default:
803 log_debug("unexpected imsg %d", imsg.hdr.type);
804 break;
806 imsg_free(&imsg);
808 if (do_disconnect) {
809 if (err)
810 disconnect_on_error(client, err);
811 else
812 disconnect(client);
813 } else if (do_list_refs)
814 err = list_refs_request();
816 if (err)
817 log_warnx("uid %d: %s", client->euid, err->msg);
819 done:
820 if (!shut) {
821 gotd_imsg_event_add(iev);
822 } else {
823 /* This pipe is dead. Remove its event handler */
824 event_del(&iev->ev);
825 event_loopexit(NULL);
829 void
830 session_read_main(const char *title, const char *repo_path,
831 int *pack_fds, int *temp_fds, struct timeval *request_timeout,
832 struct gotd_repo *repo_cfg)
834 const struct got_error *err = NULL;
835 struct event evsigint, evsigterm, evsighup, evsigusr1;
837 gotd_session.title = title;
838 gotd_session.pid = getpid();
839 gotd_session.pack_fds = pack_fds;
840 gotd_session.temp_fds = temp_fds;
841 memcpy(&gotd_session.request_timeout, request_timeout,
842 sizeof(gotd_session.request_timeout));
843 gotd_session.repo_cfg = repo_cfg;
845 imsg_init(&gotd_session.notifier_iev.ibuf, -1);
847 err = got_repo_open(&gotd_session.repo, repo_path, NULL, pack_fds);
848 if (err)
849 goto done;
850 if (!got_repo_is_bare(gotd_session.repo)) {
851 err = got_error_msg(GOT_ERR_NOT_GIT_REPO,
852 "bare git repository required");
853 goto done;
856 got_repo_temp_fds_set(gotd_session.repo, temp_fds);
858 signal_set(&evsigint, SIGINT, session_read_sighdlr, NULL);
859 signal_set(&evsigterm, SIGTERM, session_read_sighdlr, NULL);
860 signal_set(&evsighup, SIGHUP, session_read_sighdlr, NULL);
861 signal_set(&evsigusr1, SIGUSR1, session_read_sighdlr, NULL);
862 signal(SIGPIPE, SIG_IGN);
864 signal_add(&evsigint, NULL);
865 signal_add(&evsigterm, NULL);
866 signal_add(&evsighup, NULL);
867 signal_add(&evsigusr1, NULL);
869 gotd_session.state = GOTD_STATE_EXPECT_LIST_REFS;
871 gotd_session_client.fd = -1;
872 gotd_session_client.nref_updates = -1;
873 gotd_session_client.delta_cache_fd = -1;
874 gotd_session_client.accept_flush_pkt = 1;
876 imsg_init(&gotd_session.parent_iev.ibuf, GOTD_FILENO_MSG_PIPE);
877 gotd_session.parent_iev.handler = session_dispatch;
878 gotd_session.parent_iev.events = EV_READ;
879 gotd_session.parent_iev.handler_arg = NULL;
880 event_set(&gotd_session.parent_iev.ev, gotd_session.parent_iev.ibuf.fd,
881 EV_READ, session_dispatch, &gotd_session.parent_iev);
882 if (gotd_imsg_compose_event(&gotd_session.parent_iev,
883 GOTD_IMSG_CLIENT_SESSION_READY, PROC_SESSION_READ,
884 -1, NULL, 0) == -1) {
885 err = got_error_from_errno("imsg compose CLIENT_SESSION_READY");
886 goto done;
889 event_dispatch();
890 done:
891 if (err)
892 log_warnx("%s: %s", title, err->msg);
893 session_read_shutdown();
896 static void
897 session_read_shutdown(void)
899 log_debug("%s: shutting down", gotd_session.title);
901 if (gotd_session.repo)
902 got_repo_close(gotd_session.repo);
903 got_repo_pack_fds_close(gotd_session.pack_fds);
904 got_repo_temp_fds_close(gotd_session.temp_fds);
905 free(gotd_session_client.username);
906 exit(0);