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,
61 GOTD_STATE_EXPECT_DONE,
62 GOTD_STATE_DONE,
63 };
65 static struct gotd_session_read {
66 pid_t pid;
67 const char *title;
68 struct got_repository *repo;
69 struct gotd_repo *repo_cfg;
70 int *pack_fds;
71 int *temp_fds;
72 struct gotd_imsgev parent_iev;
73 struct gotd_imsgev notifier_iev;
74 struct timeval request_timeout;
75 enum gotd_session_read_state state;
76 struct gotd_imsgev repo_child_iev;
77 } gotd_session;
79 static struct gotd_session_client {
80 struct gotd_client_capability *capabilities;
81 size_t ncapa_alloc;
82 size_t ncapabilities;
83 uint32_t id;
84 int fd;
85 int delta_cache_fd;
86 struct gotd_imsgev iev;
87 struct event tmo;
88 uid_t euid;
89 gid_t egid;
90 char *username;
91 char *packfile_path;
92 char *packidx_path;
93 int nref_updates;
94 int accept_flush_pkt;
95 int flush_disconnect;
96 } gotd_session_client;
98 static void session_read_shutdown(void);
100 static void
101 disconnect(struct gotd_session_client *client)
103 log_debug("uid %d: disconnecting", client->euid);
105 if (gotd_imsg_compose_event(&gotd_session.parent_iev,
106 GOTD_IMSG_DISCONNECT, PROC_SESSION_READ, -1, NULL, 0) == -1)
107 log_warn("imsg compose DISCONNECT");
109 imsg_clear(&gotd_session.repo_child_iev.ibuf);
110 event_del(&gotd_session.repo_child_iev.ev);
111 evtimer_del(&client->tmo);
112 close(client->fd);
113 if (client->delta_cache_fd != -1)
114 close(client->delta_cache_fd);
115 if (client->packfile_path) {
116 if (unlink(client->packfile_path) == -1 && errno != ENOENT)
117 log_warn("unlink %s: ", client->packfile_path);
118 free(client->packfile_path);
120 if (client->packidx_path) {
121 if (unlink(client->packidx_path) == -1 && errno != ENOENT)
122 log_warn("unlink %s: ", client->packidx_path);
123 free(client->packidx_path);
125 free(client->capabilities);
127 session_read_shutdown();
130 static void
131 disconnect_on_error(struct gotd_session_client *client,
132 const struct got_error *err)
134 struct imsgbuf ibuf;
136 if (err->code != GOT_ERR_EOF) {
137 log_warnx("uid %d: %s", client->euid, err->msg);
138 imsg_init(&ibuf, client->fd);
139 gotd_imsg_send_error(&ibuf, 0, PROC_SESSION_READ, err);
140 imsg_clear(&ibuf);
143 disconnect(client);
146 static void
147 gotd_request_timeout(int fd, short events, void *arg)
149 struct gotd_session_client *client = arg;
151 log_debug("disconnecting uid %d due to timeout", client->euid);
152 disconnect(client);
155 static void
156 session_read_sighdlr(int sig, short event, void *arg)
158 /*
159 * Normal signal handler rules don't apply because libevent
160 * decouples for us.
161 */
163 switch (sig) {
164 case SIGHUP:
165 log_info("%s: ignoring SIGHUP", __func__);
166 break;
167 case SIGUSR1:
168 log_info("%s: ignoring SIGUSR1", __func__);
169 break;
170 case SIGTERM:
171 case SIGINT:
172 session_read_shutdown();
173 /* NOTREACHED */
174 break;
175 default:
176 fatalx("unexpected signal");
180 static const struct got_error *
181 recv_packfile_done(struct imsg *imsg)
183 size_t datalen;
185 log_debug("packfile-done received");
187 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
188 if (datalen != 0)
189 return got_error(GOT_ERR_PRIVSEP_LEN);
191 return NULL;
194 static void
195 session_dispatch_repo_child(int fd, short event, void *arg)
197 struct gotd_imsgev *iev = arg;
198 struct imsgbuf *ibuf = &iev->ibuf;
199 struct gotd_session_client *client = &gotd_session_client;
200 ssize_t n;
201 int shut = 0;
202 struct imsg imsg;
204 if (event & EV_READ) {
205 if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
206 fatal("imsg_read error");
207 if (n == 0) {
208 /* Connection closed. */
209 shut = 1;
210 goto done;
214 if (event & EV_WRITE) {
215 n = msgbuf_write(&ibuf->w);
216 if (n == -1 && errno != EAGAIN)
217 fatal("msgbuf_write");
218 if (n == 0) {
219 /* Connection closed. */
220 shut = 1;
221 goto done;
225 for (;;) {
226 const struct got_error *err = NULL;
227 uint32_t client_id = 0;
228 int do_disconnect = 0;
230 if ((n = imsg_get(ibuf, &imsg)) == -1)
231 fatal("%s: imsg_get error", __func__);
232 if (n == 0) /* No more messages. */
233 break;
235 switch (imsg.hdr.type) {
236 case GOTD_IMSG_ERROR:
237 do_disconnect = 1;
238 err = gotd_imsg_recv_error(&client_id, &imsg);
239 break;
240 case GOTD_IMSG_PACKFILE_DONE:
241 do_disconnect = 1;
242 err = recv_packfile_done(&imsg);
243 break;
244 default:
245 log_debug("unexpected imsg %d", imsg.hdr.type);
246 break;
249 if (do_disconnect) {
250 if (err)
251 disconnect_on_error(client, err);
252 else
253 disconnect(client);
254 } else {
255 if (err)
256 log_warnx("uid %d: %s", client->euid, err->msg);
258 imsg_free(&imsg);
260 done:
261 if (!shut) {
262 gotd_imsg_event_add(iev);
263 } else {
264 /* This pipe is dead. Remove its event handler */
265 event_del(&iev->ev);
266 event_loopexit(NULL);
270 static const struct got_error *
271 recv_capabilities(struct gotd_session_client *client, struct imsg *imsg)
273 struct gotd_imsg_capabilities icapas;
274 size_t datalen;
276 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
277 if (datalen != sizeof(icapas))
278 return got_error(GOT_ERR_PRIVSEP_LEN);
279 memcpy(&icapas, imsg->data, sizeof(icapas));
281 client->ncapa_alloc = icapas.ncapabilities;
282 client->capabilities = calloc(client->ncapa_alloc,
283 sizeof(*client->capabilities));
284 if (client->capabilities == NULL) {
285 client->ncapa_alloc = 0;
286 return got_error_from_errno("calloc");
289 log_debug("expecting %zu capabilities from uid %d",
290 client->ncapa_alloc, client->euid);
291 return NULL;
294 static const struct got_error *
295 recv_capability(struct gotd_session_client *client, struct imsg *imsg)
297 struct gotd_imsg_capability icapa;
298 struct gotd_client_capability *capa;
299 size_t datalen;
300 char *key, *value = NULL;
302 if (client->capabilities == NULL ||
303 client->ncapabilities >= client->ncapa_alloc) {
304 return got_error_msg(GOT_ERR_BAD_REQUEST,
305 "unexpected capability received");
308 memset(&icapa, 0, sizeof(icapa));
310 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
311 if (datalen < sizeof(icapa))
312 return got_error(GOT_ERR_PRIVSEP_LEN);
313 memcpy(&icapa, imsg->data, sizeof(icapa));
315 if (datalen != sizeof(icapa) + icapa.key_len + icapa.value_len)
316 return got_error(GOT_ERR_PRIVSEP_LEN);
318 key = strndup(imsg->data + sizeof(icapa), icapa.key_len);
319 if (key == NULL)
320 return got_error_from_errno("strndup");
321 if (icapa.value_len > 0) {
322 value = strndup(imsg->data + sizeof(icapa) + icapa.key_len,
323 icapa.value_len);
324 if (value == NULL) {
325 free(key);
326 return got_error_from_errno("strndup");
330 capa = &client->capabilities[client->ncapabilities++];
331 capa->key = key;
332 capa->value = value;
334 if (value)
335 log_debug("uid %d: capability %s=%s", client->euid, key, value);
336 else
337 log_debug("uid %d: capability %s", client->euid, key);
339 return NULL;
342 static const struct got_error *
343 forward_want(struct gotd_session_client *client, struct imsg *imsg)
345 struct gotd_imsg_want ireq;
346 struct gotd_imsg_want iwant;
347 size_t datalen;
349 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
350 if (datalen != sizeof(ireq))
351 return got_error(GOT_ERR_PRIVSEP_LEN);
353 memcpy(&ireq, imsg->data, datalen);
355 memset(&iwant, 0, sizeof(iwant));
356 memcpy(iwant.object_id, ireq.object_id, SHA1_DIGEST_LENGTH);
358 if (gotd_imsg_compose_event(&gotd_session.repo_child_iev,
359 GOTD_IMSG_WANT, PROC_SESSION_READ, -1,
360 &iwant, sizeof(iwant)) == -1)
361 return got_error_from_errno("imsg compose WANT");
363 return NULL;
366 static const struct got_error *
367 forward_have(struct gotd_session_client *client, struct imsg *imsg)
369 struct gotd_imsg_have ireq;
370 struct gotd_imsg_have ihave;
371 size_t datalen;
373 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
374 if (datalen != sizeof(ireq))
375 return got_error(GOT_ERR_PRIVSEP_LEN);
377 memcpy(&ireq, imsg->data, datalen);
379 memset(&ihave, 0, sizeof(ihave));
380 memcpy(ihave.object_id, ireq.object_id, SHA1_DIGEST_LENGTH);
382 if (gotd_imsg_compose_event(&gotd_session.repo_child_iev,
383 GOTD_IMSG_HAVE, PROC_SESSION_READ, -1,
384 &ihave, sizeof(ihave)) == -1)
385 return got_error_from_errno("imsg compose HAVE");
387 return NULL;
390 static int
391 client_has_capability(struct gotd_session_client *client, const char *capastr)
393 struct gotd_client_capability *capa;
394 size_t i;
396 if (client->ncapabilities == 0)
397 return 0;
399 for (i = 0; i < client->ncapabilities; i++) {
400 capa = &client->capabilities[i];
401 if (strcmp(capa->key, capastr) == 0)
402 return 1;
405 return 0;
408 static const struct got_error *
409 send_packfile(struct gotd_session_client *client)
411 const struct got_error *err = NULL;
412 struct gotd_imsg_send_packfile ipack;
413 int pipe[2];
415 if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pipe) == -1)
416 return got_error_from_errno("socketpair");
418 memset(&ipack, 0, sizeof(ipack));
420 if (client_has_capability(client, GOT_CAPA_SIDE_BAND_64K))
421 ipack.report_progress = 1;
423 client->delta_cache_fd = got_opentempfd();
424 if (client->delta_cache_fd == -1)
425 return got_error_from_errno("got_opentempfd");
427 if (gotd_imsg_compose_event(&gotd_session.repo_child_iev,
428 GOTD_IMSG_SEND_PACKFILE, PROC_GOTD, client->delta_cache_fd,
429 &ipack, sizeof(ipack)) == -1) {
430 err = got_error_from_errno("imsg compose SEND_PACKFILE");
431 close(pipe[0]);
432 close(pipe[1]);
433 return err;
436 /* Send pack pipe end 0 to repo child process. */
437 if (gotd_imsg_compose_event(&gotd_session.repo_child_iev,
438 GOTD_IMSG_PACKFILE_PIPE, PROC_GOTD, pipe[0], NULL, 0) == -1) {
439 err = got_error_from_errno("imsg compose PACKFILE_PIPE");
440 close(pipe[1]);
441 return err;
444 /* Send pack pipe end 1 to gotsh(1) (expects just an fd, no data). */
445 if (gotd_imsg_compose_event(&client->iev,
446 GOTD_IMSG_PACKFILE_PIPE, PROC_GOTD, pipe[1], NULL, 0) == -1)
447 err = got_error_from_errno("imsg compose PACKFILE_PIPE");
449 return err;
452 static void
453 session_dispatch_client(int fd, short events, void *arg)
455 struct gotd_imsgev *iev = arg;
456 struct imsgbuf *ibuf = &iev->ibuf;
457 struct gotd_session_client *client = &gotd_session_client;
458 const struct got_error *err = NULL;
459 struct imsg imsg;
460 ssize_t n;
462 if (events & EV_WRITE) {
463 while (ibuf->w.queued) {
464 n = msgbuf_write(&ibuf->w);
465 if (n == -1 && errno == EPIPE) {
466 /*
467 * The client has closed its socket.
468 * This can happen when Git clients are
469 * done sending pack file data.
470 */
471 msgbuf_clear(&ibuf->w);
472 continue;
473 } else if (n == -1 && errno != EAGAIN) {
474 err = got_error_from_errno("imsg_flush");
475 disconnect_on_error(client, err);
476 return;
478 if (n == 0) {
479 /* Connection closed. */
480 err = got_error(GOT_ERR_EOF);
481 disconnect_on_error(client, err);
482 return;
486 if (client->flush_disconnect) {
487 disconnect(client);
488 return;
492 if ((events & EV_READ) == 0)
493 return;
495 memset(&imsg, 0, sizeof(imsg));
497 while (err == NULL) {
498 err = gotd_imsg_recv(&imsg, ibuf, 0);
499 if (err) {
500 if (err->code == GOT_ERR_PRIVSEP_READ)
501 err = NULL;
502 else if (err->code == GOT_ERR_EOF &&
503 gotd_session.state ==
504 GOTD_STATE_EXPECT_CAPABILITIES) {
505 /*
506 * The client has closed its socket before
507 * sending its capability announcement.
508 * This can happen when Git clients have
509 * no ref-updates to send.
510 */
511 disconnect_on_error(client, err);
512 return;
514 break;
517 evtimer_del(&client->tmo);
519 switch (imsg.hdr.type) {
520 case GOTD_IMSG_CAPABILITIES:
521 if (gotd_session.state !=
522 GOTD_STATE_EXPECT_CAPABILITIES) {
523 err = got_error_msg(GOT_ERR_BAD_REQUEST,
524 "unexpected capabilities received");
525 break;
527 log_debug("receiving capabilities from uid %d",
528 client->euid);
529 err = recv_capabilities(client, &imsg);
530 break;
531 case GOTD_IMSG_CAPABILITY:
532 if (gotd_session.state != GOTD_STATE_EXPECT_CAPABILITIES) {
533 err = got_error_msg(GOT_ERR_BAD_REQUEST,
534 "unexpected capability received");
535 break;
537 err = recv_capability(client, &imsg);
538 if (err || client->ncapabilities < client->ncapa_alloc)
539 break;
540 gotd_session.state = GOTD_STATE_EXPECT_WANT;
541 client->accept_flush_pkt = 1;
542 log_debug("uid %d: expecting want-lines", client->euid);
543 break;
544 case GOTD_IMSG_WANT:
545 if (gotd_session.state != GOTD_STATE_EXPECT_WANT) {
546 err = got_error_msg(GOT_ERR_BAD_REQUEST,
547 "unexpected want-line received");
548 break;
550 log_debug("received want-line from uid %d",
551 client->euid);
552 client->accept_flush_pkt = 1;
553 err = forward_want(client, &imsg);
554 break;
555 case GOTD_IMSG_HAVE:
556 if (gotd_session.state != GOTD_STATE_EXPECT_HAVE) {
557 err = got_error_msg(GOT_ERR_BAD_REQUEST,
558 "unexpected have-line received");
559 break;
561 log_debug("received have-line from uid %d",
562 client->euid);
563 err = forward_have(client, &imsg);
564 if (err)
565 break;
566 client->accept_flush_pkt = 1;
567 break;
568 case GOTD_IMSG_FLUSH:
569 if (gotd_session.state != GOTD_STATE_EXPECT_WANT &&
570 gotd_session.state != GOTD_STATE_EXPECT_HAVE &&
571 gotd_session.state != GOTD_STATE_EXPECT_DONE) {
572 err = got_error_msg(GOT_ERR_BAD_REQUEST,
573 "unexpected flush-pkt received");
574 break;
576 if (!client->accept_flush_pkt) {
577 err = got_error_msg(GOT_ERR_BAD_REQUEST,
578 "unexpected flush-pkt received");
579 break;
582 /*
583 * Accept just one flush packet at a time.
584 * Future client state transitions will set this flag
585 * again if another flush packet is expected.
586 */
587 client->accept_flush_pkt = 0;
589 log_debug("received flush-pkt from uid %d",
590 client->euid);
591 if (gotd_session.state == GOTD_STATE_EXPECT_WANT) {
592 gotd_session.state = GOTD_STATE_EXPECT_HAVE;
593 log_debug("uid %d: expecting have-lines",
594 client->euid);
595 } else if (gotd_session.state == GOTD_STATE_EXPECT_HAVE) {
596 gotd_session.state = GOTD_STATE_EXPECT_DONE;
597 client->accept_flush_pkt = 1;
598 log_debug("uid %d: expecting 'done'",
599 client->euid);
600 } else if (gotd_session.state != GOTD_STATE_EXPECT_DONE) {
601 /* should not happen, see above */
602 err = got_error_msg(GOT_ERR_BAD_REQUEST,
603 "unexpected client state");
604 break;
606 break;
607 case GOTD_IMSG_DONE:
608 if (gotd_session.state != GOTD_STATE_EXPECT_HAVE &&
609 gotd_session.state != GOTD_STATE_EXPECT_DONE) {
610 err = got_error_msg(GOT_ERR_BAD_REQUEST,
611 "unexpected flush-pkt received");
612 break;
614 log_debug("received 'done' from uid %d", client->euid);
615 gotd_session.state = GOTD_STATE_DONE;
616 client->accept_flush_pkt = 1;
617 err = send_packfile(client);
618 break;
619 default:
620 log_debug("unexpected imsg %d", imsg.hdr.type);
621 err = got_error(GOT_ERR_PRIVSEP_MSG);
622 break;
625 imsg_free(&imsg);
628 if (err) {
629 if (err->code != GOT_ERR_EOF)
630 disconnect_on_error(client, err);
631 } else {
632 gotd_imsg_event_add(iev);
633 evtimer_add(&client->tmo, &gotd_session.request_timeout);
637 static const struct got_error *
638 list_refs_request(void)
640 static const struct got_error *err;
641 struct gotd_session_client *client = &gotd_session_client;
642 struct gotd_imsgev *iev = &gotd_session.repo_child_iev;
643 int fd;
645 if (gotd_session.state != GOTD_STATE_EXPECT_LIST_REFS)
646 return got_error(GOT_ERR_PRIVSEP_MSG);
648 fd = dup(client->fd);
649 if (fd == -1)
650 return got_error_from_errno("dup");
652 if (gotd_imsg_compose_event(iev, GOTD_IMSG_LIST_REFS_INTERNAL,
653 PROC_SESSION_READ, fd, NULL, 0) == -1) {
654 err = got_error_from_errno("imsg compose LIST_REFS_INTERNAL");
655 close(fd);
656 return err;
659 gotd_session.state = GOTD_STATE_EXPECT_CAPABILITIES;
660 log_debug("uid %d: expecting capabilities", client->euid);
661 return NULL;
664 static const struct got_error *
665 recv_connect(struct imsg *imsg)
667 struct gotd_session_client *client = &gotd_session_client;
668 struct gotd_imsg_connect iconnect;
669 size_t datalen;
671 if (gotd_session.state != GOTD_STATE_EXPECT_LIST_REFS)
672 return got_error(GOT_ERR_PRIVSEP_MSG);
674 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
675 if (datalen < sizeof(iconnect))
676 return got_error(GOT_ERR_PRIVSEP_LEN);
677 memcpy(&iconnect, imsg->data, sizeof(iconnect));
678 if (iconnect.username_len == 0 ||
679 datalen != sizeof(iconnect) + iconnect.username_len)
680 return got_error(GOT_ERR_PRIVSEP_LEN);
682 client->euid = iconnect.euid;
683 client->egid = iconnect.egid;
684 client->fd = imsg_get_fd(imsg);
685 if (client->fd == -1)
686 return got_error(GOT_ERR_PRIVSEP_NO_FD);
688 client->username = strndup(imsg->data + sizeof(iconnect),
689 iconnect.username_len);
690 if (client->username == NULL)
691 return got_error_from_errno("strndup");
693 imsg_init(&client->iev.ibuf, client->fd);
694 client->iev.handler = session_dispatch_client;
695 client->iev.events = EV_READ;
696 client->iev.handler_arg = NULL;
697 event_set(&client->iev.ev, client->iev.ibuf.fd, EV_READ,
698 session_dispatch_client, &client->iev);
699 gotd_imsg_event_add(&client->iev);
700 evtimer_set(&client->tmo, gotd_request_timeout, client);
702 return NULL;
705 static const struct got_error *
706 recv_repo_child(struct imsg *imsg)
708 struct gotd_imsg_connect_repo_child ichild;
709 struct gotd_session_client *client = &gotd_session_client;
710 size_t datalen;
711 int fd;
713 if (gotd_session.state != GOTD_STATE_EXPECT_LIST_REFS)
714 return got_error(GOT_ERR_PRIVSEP_MSG);
716 /* We should already have received a pipe to the listener. */
717 if (client->fd == -1)
718 return got_error(GOT_ERR_PRIVSEP_MSG);
720 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
721 if (datalen != sizeof(ichild))
722 return got_error(GOT_ERR_PRIVSEP_LEN);
724 memcpy(&ichild, imsg->data, sizeof(ichild));
726 if (ichild.proc_id != PROC_REPO_READ)
727 return got_error_msg(GOT_ERR_PRIVSEP_MSG,
728 "bad child process type");
730 fd = imsg_get_fd(imsg);
731 if (fd == -1)
732 return got_error(GOT_ERR_PRIVSEP_NO_FD);
734 imsg_init(&gotd_session.repo_child_iev.ibuf, fd);
735 gotd_session.repo_child_iev.handler = session_dispatch_repo_child;
736 gotd_session.repo_child_iev.events = EV_READ;
737 gotd_session.repo_child_iev.handler_arg = NULL;
738 event_set(&gotd_session.repo_child_iev.ev,
739 gotd_session.repo_child_iev.ibuf.fd, EV_READ,
740 session_dispatch_repo_child, &gotd_session.repo_child_iev);
741 gotd_imsg_event_add(&gotd_session.repo_child_iev);
743 /* The "recvfd" pledge promise is no longer needed. */
744 if (pledge("stdio rpath wpath cpath sendfd fattr flock", NULL) == -1)
745 fatal("pledge");
747 return NULL;
750 static void
751 session_dispatch(int fd, short event, void *arg)
753 struct gotd_imsgev *iev = arg;
754 struct imsgbuf *ibuf = &iev->ibuf;
755 struct gotd_session_client *client = &gotd_session_client;
756 ssize_t n;
757 int shut = 0;
758 struct imsg imsg;
760 if (event & EV_READ) {
761 if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
762 fatal("imsg_read error");
763 if (n == 0) {
764 /* Connection closed. */
765 shut = 1;
766 goto done;
770 if (event & EV_WRITE) {
771 n = msgbuf_write(&ibuf->w);
772 if (n == -1 && errno != EAGAIN)
773 fatal("msgbuf_write");
774 if (n == 0) {
775 /* Connection closed. */
776 shut = 1;
777 goto done;
781 for (;;) {
782 const struct got_error *err = NULL;
783 uint32_t client_id = 0;
784 int do_disconnect = 0, do_list_refs = 0;
786 if ((n = imsg_get(ibuf, &imsg)) == -1)
787 fatal("%s: imsg_get error", __func__);
788 if (n == 0) /* No more messages. */
789 break;
791 switch (imsg.hdr.type) {
792 case GOTD_IMSG_ERROR:
793 do_disconnect = 1;
794 err = gotd_imsg_recv_error(&client_id, &imsg);
795 break;
796 case GOTD_IMSG_CONNECT:
797 err = recv_connect(&imsg);
798 break;
799 case GOTD_IMSG_DISCONNECT:
800 do_disconnect = 1;
801 break;
802 case GOTD_IMSG_CONNECT_REPO_CHILD:
803 err = recv_repo_child(&imsg);
804 if (err)
805 break;
806 do_list_refs = 1;
807 break;
808 default:
809 log_debug("unexpected imsg %d", imsg.hdr.type);
810 break;
812 imsg_free(&imsg);
814 if (do_disconnect) {
815 if (err)
816 disconnect_on_error(client, err);
817 else
818 disconnect(client);
819 } else if (do_list_refs)
820 err = list_refs_request();
822 if (err)
823 log_warnx("uid %d: %s", client->euid, err->msg);
825 done:
826 if (!shut) {
827 gotd_imsg_event_add(iev);
828 } else {
829 /* This pipe is dead. Remove its event handler */
830 event_del(&iev->ev);
831 event_loopexit(NULL);
835 void
836 session_read_main(const char *title, const char *repo_path,
837 int *pack_fds, int *temp_fds, struct timeval *request_timeout,
838 struct gotd_repo *repo_cfg)
840 const struct got_error *err = NULL;
841 struct event evsigint, evsigterm, evsighup, evsigusr1;
843 gotd_session.title = title;
844 gotd_session.pid = getpid();
845 gotd_session.pack_fds = pack_fds;
846 gotd_session.temp_fds = temp_fds;
847 memcpy(&gotd_session.request_timeout, request_timeout,
848 sizeof(gotd_session.request_timeout));
849 gotd_session.repo_cfg = repo_cfg;
851 imsg_init(&gotd_session.notifier_iev.ibuf, -1);
853 err = got_repo_open(&gotd_session.repo, repo_path, NULL, pack_fds);
854 if (err)
855 goto done;
856 if (!got_repo_is_bare(gotd_session.repo)) {
857 err = got_error_msg(GOT_ERR_NOT_GIT_REPO,
858 "bare git repository required");
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);