make got-read-gitconfig clear its imsgbuf before exit in an error case
[got-portable.git] / gotd / session_read.c
blob6f748c4d6c02f54e8f2e13dc13e92789711bbb4e
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 imsgbuf_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 if (imsgbuf_init(&ibuf, client->fd) == -1) {
137 log_warn("imsgbuf_init");
138 } else {
139 gotd_imsg_send_error(&ibuf, 0, PROC_SESSION_READ, err);
140 imsgbuf_clear(&ibuf);
144 disconnect(client);
147 static void
148 gotd_request_timeout(int fd, short events, void *arg)
150 struct gotd_session_client *client = arg;
152 log_warnx("disconnecting uid %d due to timeout", client->euid);
153 disconnect(client);
156 static void
157 session_read_sighdlr(int sig, short event, void *arg)
160 * Normal signal handler rules don't apply because libevent
161 * decouples for us.
164 switch (sig) {
165 case SIGHUP:
166 log_info("%s: ignoring SIGHUP", __func__);
167 break;
168 case SIGUSR1:
169 log_info("%s: ignoring SIGUSR1", __func__);
170 break;
171 case SIGTERM:
172 case SIGINT:
173 session_read_shutdown();
174 /* NOTREACHED */
175 break;
176 default:
177 fatalx("unexpected signal");
181 static const struct got_error *
182 recv_packfile_done(struct imsg *imsg)
184 size_t datalen;
186 log_debug("packfile-done received");
188 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
189 if (datalen != 0)
190 return got_error(GOT_ERR_PRIVSEP_LEN);
192 return NULL;
195 static void
196 session_dispatch_repo_child(int fd, short event, void *arg)
198 const struct got_error *err = NULL;
199 struct gotd_imsgev *iev = arg;
200 struct imsgbuf *ibuf = &iev->ibuf;
201 struct gotd_session_client *client = &gotd_session_client;
202 ssize_t n;
203 int shut = 0;
204 struct imsg imsg;
206 if (event & EV_READ) {
207 if ((n = imsgbuf_read(ibuf)) == -1)
208 fatal("imsgbuf_read error");
209 if (n == 0) {
210 /* Connection closed. */
211 shut = 1;
212 goto done;
216 if (event & EV_WRITE) {
217 err = gotd_imsg_flush(ibuf);
218 if (err)
219 fatalx("%s", err->msg);
222 for (;;) {
223 const struct got_error *err = NULL;
224 uint32_t client_id = 0;
225 int do_disconnect = 0;
227 if ((n = imsg_get(ibuf, &imsg)) == -1)
228 fatal("%s: imsg_get error", __func__);
229 if (n == 0) /* No more messages. */
230 break;
232 switch (imsg.hdr.type) {
233 case GOTD_IMSG_ERROR:
234 do_disconnect = 1;
235 err = gotd_imsg_recv_error(&client_id, &imsg);
236 break;
237 case GOTD_IMSG_PACKFILE_DONE:
238 do_disconnect = 1;
239 err = recv_packfile_done(&imsg);
240 break;
241 default:
242 log_debug("unexpected imsg %d", imsg.hdr.type);
243 break;
246 if (do_disconnect) {
247 if (err)
248 disconnect_on_error(client, err);
249 else
250 disconnect(client);
251 } else {
252 if (err)
253 log_warnx("uid %d: %s", client->euid, err->msg);
255 imsg_free(&imsg);
257 done:
258 if (!shut) {
259 gotd_imsg_event_add(iev);
260 } else {
261 /* This pipe is dead. Remove its event handler */
262 event_del(&iev->ev);
263 event_loopexit(NULL);
267 static const struct got_error *
268 recv_capabilities(struct gotd_session_client *client, struct imsg *imsg)
270 struct gotd_imsg_capabilities icapas;
271 size_t datalen;
273 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
274 if (datalen != sizeof(icapas))
275 return got_error(GOT_ERR_PRIVSEP_LEN);
276 memcpy(&icapas, imsg->data, sizeof(icapas));
278 client->ncapa_alloc = icapas.ncapabilities;
279 client->capabilities = calloc(client->ncapa_alloc,
280 sizeof(*client->capabilities));
281 if (client->capabilities == NULL) {
282 client->ncapa_alloc = 0;
283 return got_error_from_errno("calloc");
286 log_debug("expecting %zu capabilities from uid %d",
287 client->ncapa_alloc, client->euid);
288 return NULL;
291 static const struct got_error *
292 recv_capability(struct gotd_session_client *client, struct imsg *imsg)
294 struct gotd_imsg_capability icapa;
295 struct gotd_client_capability *capa;
296 size_t datalen;
297 char *key, *value = NULL;
299 if (client->capabilities == NULL ||
300 client->ncapabilities >= client->ncapa_alloc) {
301 return got_error_msg(GOT_ERR_BAD_REQUEST,
302 "unexpected capability received");
305 memset(&icapa, 0, sizeof(icapa));
307 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
308 if (datalen < sizeof(icapa))
309 return got_error(GOT_ERR_PRIVSEP_LEN);
310 memcpy(&icapa, imsg->data, sizeof(icapa));
312 if (datalen != sizeof(icapa) + icapa.key_len + icapa.value_len)
313 return got_error(GOT_ERR_PRIVSEP_LEN);
315 key = strndup(imsg->data + sizeof(icapa), icapa.key_len);
316 if (key == NULL)
317 return got_error_from_errno("strndup");
318 if (icapa.value_len > 0) {
319 value = strndup(imsg->data + sizeof(icapa) + icapa.key_len,
320 icapa.value_len);
321 if (value == NULL) {
322 free(key);
323 return got_error_from_errno("strndup");
327 capa = &client->capabilities[client->ncapabilities++];
328 capa->key = key;
329 capa->value = value;
331 if (value)
332 log_debug("uid %d: capability %s=%s", client->euid, key, value);
333 else
334 log_debug("uid %d: capability %s", client->euid, key);
336 return NULL;
339 static const struct got_error *
340 forward_want(struct gotd_session_client *client, struct imsg *imsg)
342 struct gotd_imsg_want ireq;
343 struct gotd_imsg_want iwant;
344 size_t datalen;
346 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
347 if (datalen != sizeof(ireq))
348 return got_error(GOT_ERR_PRIVSEP_LEN);
350 memcpy(&ireq, imsg->data, datalen);
352 memset(&iwant, 0, sizeof(iwant));
353 memcpy(iwant.object_id, ireq.object_id, SHA1_DIGEST_LENGTH);
355 if (gotd_imsg_compose_event(&gotd_session.repo_child_iev,
356 GOTD_IMSG_WANT, PROC_SESSION_READ, -1,
357 &iwant, sizeof(iwant)) == -1)
358 return got_error_from_errno("imsg compose WANT");
360 return NULL;
363 static const struct got_error *
364 forward_have(struct gotd_session_client *client, struct imsg *imsg)
366 struct gotd_imsg_have ireq;
367 struct gotd_imsg_have ihave;
368 size_t datalen;
370 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
371 if (datalen != sizeof(ireq))
372 return got_error(GOT_ERR_PRIVSEP_LEN);
374 memcpy(&ireq, imsg->data, datalen);
376 memset(&ihave, 0, sizeof(ihave));
377 memcpy(ihave.object_id, ireq.object_id, SHA1_DIGEST_LENGTH);
379 if (gotd_imsg_compose_event(&gotd_session.repo_child_iev,
380 GOTD_IMSG_HAVE, PROC_SESSION_READ, -1,
381 &ihave, sizeof(ihave)) == -1)
382 return got_error_from_errno("imsg compose HAVE");
384 return NULL;
387 static int
388 client_has_capability(struct gotd_session_client *client, const char *capastr)
390 struct gotd_client_capability *capa;
391 size_t i;
393 if (client->ncapabilities == 0)
394 return 0;
396 for (i = 0; i < client->ncapabilities; i++) {
397 capa = &client->capabilities[i];
398 if (strcmp(capa->key, capastr) == 0)
399 return 1;
402 return 0;
405 static const struct got_error *
406 send_packfile(struct gotd_session_client *client)
408 const struct got_error *err = NULL;
409 struct gotd_imsg_send_packfile ipack;
410 int pipe[2];
412 if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pipe) == -1)
413 return got_error_from_errno("socketpair");
415 memset(&ipack, 0, sizeof(ipack));
417 if (client_has_capability(client, GOT_CAPA_SIDE_BAND_64K))
418 ipack.report_progress = 1;
420 client->delta_cache_fd = got_opentempfd();
421 if (client->delta_cache_fd == -1)
422 return got_error_from_errno("got_opentempfd");
424 if (gotd_imsg_compose_event(&gotd_session.repo_child_iev,
425 GOTD_IMSG_SEND_PACKFILE, PROC_GOTD, client->delta_cache_fd,
426 &ipack, sizeof(ipack)) == -1) {
427 err = got_error_from_errno("imsg compose SEND_PACKFILE");
428 close(pipe[0]);
429 close(pipe[1]);
430 return err;
433 /* Send pack pipe end 0 to repo child process. */
434 if (gotd_imsg_compose_event(&gotd_session.repo_child_iev,
435 GOTD_IMSG_PACKFILE_PIPE, PROC_GOTD, pipe[0], NULL, 0) == -1) {
436 err = got_error_from_errno("imsg compose PACKFILE_PIPE");
437 close(pipe[1]);
438 return err;
441 /* Send pack pipe end 1 to gotsh(1) (expects just an fd, no data). */
442 if (gotd_imsg_compose_event(&client->iev,
443 GOTD_IMSG_PACKFILE_PIPE, PROC_GOTD, pipe[1], NULL, 0) == -1)
444 err = got_error_from_errno("imsg compose PACKFILE_PIPE");
446 return err;
449 static void
450 session_dispatch_client(int fd, short events, void *arg)
452 struct gotd_imsgev *iev = arg;
453 struct imsgbuf *ibuf = &iev->ibuf;
454 struct gotd_session_client *client = &gotd_session_client;
455 const struct got_error *err = NULL;
456 struct imsg imsg;
457 ssize_t n;
459 if (events & EV_WRITE) {
460 err = gotd_imsg_flush(ibuf);
461 if (err) {
462 disconnect_on_error(client, err);
463 return;
466 if (client->flush_disconnect) {
467 disconnect(client);
468 return;
472 if (events & EV_READ) {
473 n = imsgbuf_read(ibuf);
474 if (n == -1) {
475 err = got_error_from_errno("imsgbuf_read");
476 disconnect_on_error(client, err);
477 return;
479 if (n == 0) {
480 err = got_error(GOT_ERR_EOF);
481 disconnect_on_error(client, err);
482 return;
486 while (err == NULL) {
487 n = imsg_get(ibuf, &imsg);
488 if (n == -1) {
489 err = got_error_from_errno("imsg_get");
490 break;
492 if (n == 0)
493 break;
495 evtimer_del(&client->tmo);
497 switch (imsg.hdr.type) {
498 case GOTD_IMSG_CAPABILITIES:
499 if (gotd_session.state !=
500 GOTD_STATE_EXPECT_CAPABILITIES) {
501 err = got_error_msg(GOT_ERR_BAD_REQUEST,
502 "unexpected capabilities received");
503 break;
505 log_debug("receiving capabilities from uid %d",
506 client->euid);
507 err = recv_capabilities(client, &imsg);
508 break;
509 case GOTD_IMSG_CAPABILITY:
510 if (gotd_session.state != GOTD_STATE_EXPECT_CAPABILITIES) {
511 err = got_error_msg(GOT_ERR_BAD_REQUEST,
512 "unexpected capability received");
513 break;
515 err = recv_capability(client, &imsg);
516 if (err || client->ncapabilities < client->ncapa_alloc)
517 break;
518 gotd_session.state = GOTD_STATE_EXPECT_WANT;
519 client->accept_flush_pkt = 1;
520 log_debug("uid %d: expecting want-lines", client->euid);
521 break;
522 case GOTD_IMSG_WANT:
523 if (gotd_session.state != GOTD_STATE_EXPECT_WANT) {
524 err = got_error_msg(GOT_ERR_BAD_REQUEST,
525 "unexpected want-line received");
526 break;
528 log_debug("received want-line from uid %d",
529 client->euid);
530 client->accept_flush_pkt = 1;
531 err = forward_want(client, &imsg);
532 break;
533 case GOTD_IMSG_HAVE:
534 if (gotd_session.state !=
535 GOTD_STATE_EXPECT_HAVE_OR_DONE) {
536 err = got_error_msg(GOT_ERR_BAD_REQUEST,
537 "unexpected have-line received");
538 break;
540 log_debug("received have-line from uid %d",
541 client->euid);
542 err = forward_have(client, &imsg);
543 if (err)
544 break;
545 client->accept_flush_pkt = 1;
546 break;
547 case GOTD_IMSG_FLUSH:
548 if (gotd_session.state != GOTD_STATE_EXPECT_WANT &&
549 gotd_session.state !=
550 GOTD_STATE_EXPECT_HAVE_OR_DONE) {
551 err = got_error_msg(GOT_ERR_BAD_REQUEST,
552 "unexpected flush-pkt received");
553 break;
555 if (!client->accept_flush_pkt) {
556 err = got_error_msg(GOT_ERR_BAD_REQUEST,
557 "unexpected flush-pkt received");
558 break;
562 * Accept just one flush packet at a time.
563 * Future client state transitions will set this flag
564 * again if another flush packet is expected.
566 client->accept_flush_pkt = 0;
568 log_debug("received flush-pkt from uid %d",
569 client->euid);
570 if (gotd_session.state == GOTD_STATE_EXPECT_WANT) {
571 gotd_session.state =
572 GOTD_STATE_EXPECT_HAVE_OR_DONE;
573 log_debug("uid %d: expecting have-lines "
574 "or 'done'", client->euid);
575 } else if (gotd_session.state ==
576 GOTD_STATE_EXPECT_HAVE_OR_DONE) {
577 client->accept_flush_pkt = 1;
578 log_debug("uid %d: expecting more have-lines "
579 "or 'done'", client->euid);
580 } else if (gotd_session.state !=
581 GOTD_STATE_EXPECT_HAVE_OR_DONE) {
582 /* should not happen, see above */
583 err = got_error_msg(GOT_ERR_BAD_REQUEST,
584 "unexpected client state");
585 break;
587 break;
588 case GOTD_IMSG_DONE:
589 if (gotd_session.state !=
590 GOTD_STATE_EXPECT_HAVE_OR_DONE) {
591 err = got_error_msg(GOT_ERR_BAD_REQUEST,
592 "unexpected flush-pkt received");
593 break;
595 log_debug("received 'done' from uid %d", client->euid);
596 gotd_session.state = GOTD_STATE_DONE;
597 client->accept_flush_pkt = 1;
598 err = send_packfile(client);
599 break;
600 default:
601 log_debug("unexpected imsg %d", imsg.hdr.type);
602 err = got_error(GOT_ERR_PRIVSEP_MSG);
603 break;
606 imsg_free(&imsg);
609 if (err) {
610 if (err->code != GOT_ERR_EOF)
611 disconnect_on_error(client, err);
612 } else {
613 gotd_imsg_event_add(iev);
614 evtimer_add(&client->tmo, &gotd_session.request_timeout);
618 static const struct got_error *
619 list_refs_request(void)
621 static const struct got_error *err;
622 struct gotd_session_client *client = &gotd_session_client;
623 struct gotd_imsgev *iev = &gotd_session.repo_child_iev;
624 int fd;
626 if (gotd_session.state != GOTD_STATE_EXPECT_LIST_REFS)
627 return got_error(GOT_ERR_PRIVSEP_MSG);
629 fd = dup(client->fd);
630 if (fd == -1)
631 return got_error_from_errno("dup");
633 if (gotd_imsg_compose_event(iev, GOTD_IMSG_LIST_REFS_INTERNAL,
634 PROC_SESSION_READ, fd, NULL, 0) == -1) {
635 err = got_error_from_errno("imsg compose LIST_REFS_INTERNAL");
636 close(fd);
637 return err;
640 gotd_session.state = GOTD_STATE_EXPECT_CAPABILITIES;
641 log_debug("uid %d: expecting capabilities", client->euid);
642 return NULL;
645 static const struct got_error *
646 recv_connect(struct imsg *imsg)
648 struct gotd_session_client *client = &gotd_session_client;
649 struct gotd_imsg_connect iconnect;
650 size_t datalen;
652 if (gotd_session.state != GOTD_STATE_EXPECT_LIST_REFS)
653 return got_error(GOT_ERR_PRIVSEP_MSG);
655 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
656 if (datalen < sizeof(iconnect))
657 return got_error(GOT_ERR_PRIVSEP_LEN);
658 memcpy(&iconnect, imsg->data, sizeof(iconnect));
659 if (iconnect.username_len == 0 ||
660 datalen != sizeof(iconnect) + iconnect.username_len)
661 return got_error(GOT_ERR_PRIVSEP_LEN);
663 client->euid = iconnect.euid;
664 client->egid = iconnect.egid;
665 client->fd = imsg_get_fd(imsg);
666 if (client->fd == -1)
667 return got_error(GOT_ERR_PRIVSEP_NO_FD);
669 client->username = strndup(imsg->data + sizeof(iconnect),
670 iconnect.username_len);
671 if (client->username == NULL)
672 return got_error_from_errno("strndup");
674 if (imsgbuf_init(&client->iev.ibuf, client->fd) == -1)
675 return got_error_from_errno("imsgbuf_init");
676 imsgbuf_allow_fdpass(&client->iev.ibuf);
677 client->iev.handler = session_dispatch_client;
678 client->iev.events = EV_READ;
679 client->iev.handler_arg = NULL;
680 event_set(&client->iev.ev, client->iev.ibuf.fd, EV_READ,
681 session_dispatch_client, &client->iev);
682 gotd_imsg_event_add(&client->iev);
683 evtimer_set(&client->tmo, gotd_request_timeout, client);
684 evtimer_add(&client->tmo, &gotd_session.request_timeout);
686 return NULL;
689 static const struct got_error *
690 recv_repo_child(struct imsg *imsg)
692 struct gotd_imsg_connect_repo_child ichild;
693 struct gotd_session_client *client = &gotd_session_client;
694 size_t datalen;
695 int fd;
697 if (gotd_session.state != GOTD_STATE_EXPECT_LIST_REFS)
698 return got_error(GOT_ERR_PRIVSEP_MSG);
700 /* We should already have received a pipe to the listener. */
701 if (client->fd == -1)
702 return got_error(GOT_ERR_PRIVSEP_MSG);
704 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
705 if (datalen != sizeof(ichild))
706 return got_error(GOT_ERR_PRIVSEP_LEN);
708 memcpy(&ichild, imsg->data, sizeof(ichild));
710 if (ichild.proc_id != PROC_REPO_READ)
711 return got_error_msg(GOT_ERR_PRIVSEP_MSG,
712 "bad child process type");
714 fd = imsg_get_fd(imsg);
715 if (fd == -1)
716 return got_error(GOT_ERR_PRIVSEP_NO_FD);
718 if (imsgbuf_init(&gotd_session.repo_child_iev.ibuf, fd)) {
719 close(fd);
720 return got_error_from_errno("imsgbuf_init");
722 imsgbuf_allow_fdpass(&gotd_session.repo_child_iev.ibuf);
723 gotd_session.repo_child_iev.handler = session_dispatch_repo_child;
724 gotd_session.repo_child_iev.events = EV_READ;
725 gotd_session.repo_child_iev.handler_arg = NULL;
726 event_set(&gotd_session.repo_child_iev.ev,
727 gotd_session.repo_child_iev.ibuf.fd, EV_READ,
728 session_dispatch_repo_child, &gotd_session.repo_child_iev);
729 gotd_imsg_event_add(&gotd_session.repo_child_iev);
731 /* The "recvfd" pledge promise is no longer needed. */
732 if (pledge("stdio rpath wpath cpath sendfd fattr flock", NULL) == -1)
733 fatal("pledge");
735 return NULL;
738 static void
739 session_dispatch(int fd, short event, void *arg)
741 const struct got_error *err = NULL;
742 struct gotd_imsgev *iev = arg;
743 struct imsgbuf *ibuf = &iev->ibuf;
744 struct gotd_session_client *client = &gotd_session_client;
745 ssize_t n;
746 int shut = 0;
747 struct imsg imsg;
749 if (event & EV_READ) {
750 if ((n = imsgbuf_read(ibuf)) == -1)
751 fatal("imsgbuf_read error");
752 if (n == 0) {
753 /* Connection closed. */
754 shut = 1;
755 goto done;
759 if (event & EV_WRITE) {
760 err = gotd_imsg_flush(ibuf);
761 if (err)
762 fatalx("%s", err->msg);
765 for (;;) {
766 const struct got_error *err = NULL;
767 uint32_t client_id = 0;
768 int do_disconnect = 0, do_list_refs = 0;
770 if ((n = imsg_get(ibuf, &imsg)) == -1)
771 fatal("%s: imsg_get error", __func__);
772 if (n == 0) /* No more messages. */
773 break;
775 switch (imsg.hdr.type) {
776 case GOTD_IMSG_ERROR:
777 do_disconnect = 1;
778 err = gotd_imsg_recv_error(&client_id, &imsg);
779 break;
780 case GOTD_IMSG_CONNECT:
781 err = recv_connect(&imsg);
782 break;
783 case GOTD_IMSG_DISCONNECT:
784 do_disconnect = 1;
785 break;
786 case GOTD_IMSG_CONNECT_REPO_CHILD:
787 err = recv_repo_child(&imsg);
788 if (err)
789 break;
790 do_list_refs = 1;
791 break;
792 default:
793 log_debug("unexpected imsg %d", imsg.hdr.type);
794 break;
796 imsg_free(&imsg);
798 if (do_disconnect) {
799 if (err)
800 disconnect_on_error(client, err);
801 else
802 disconnect(client);
803 } else if (do_list_refs)
804 err = list_refs_request();
806 if (err)
807 log_warnx("uid %d: %s", client->euid, err->msg);
809 done:
810 if (!shut) {
811 gotd_imsg_event_add(iev);
812 } else {
813 /* This pipe is dead. Remove its event handler */
814 event_del(&iev->ev);
815 event_loopexit(NULL);
819 void
820 session_read_main(const char *title, const char *repo_path,
821 int *pack_fds, int *temp_fds, struct timeval *request_timeout,
822 struct gotd_repo *repo_cfg)
824 const struct got_error *err = NULL;
825 struct event evsigint, evsigterm, evsighup, evsigusr1;
827 gotd_session.title = title;
828 gotd_session.pid = getpid();
829 gotd_session.pack_fds = pack_fds;
830 gotd_session.temp_fds = temp_fds;
831 memcpy(&gotd_session.request_timeout, request_timeout,
832 sizeof(gotd_session.request_timeout));
833 gotd_session.repo_cfg = repo_cfg;
835 if (imsgbuf_init(&gotd_session.notifier_iev.ibuf, -1) == -1) {
836 err = got_error_from_errno("imsgbuf_init");
837 goto done;
839 imsgbuf_allow_fdpass(&gotd_session.notifier_iev.ibuf);
841 err = got_repo_open(&gotd_session.repo, repo_path, NULL, pack_fds);
842 if (err)
843 goto done;
844 if (!got_repo_is_bare(gotd_session.repo)) {
845 err = got_error_msg(GOT_ERR_NOT_GIT_REPO,
846 "bare git repository required");
847 goto done;
849 if (got_repo_get_object_format(gotd_session.repo) != GOT_HASH_SHA1) {
850 err = got_error_msg(GOT_ERR_NOT_IMPL,
851 "sha256 object IDs unsupported in network protocol");
852 goto done;
855 got_repo_temp_fds_set(gotd_session.repo, temp_fds);
857 signal_set(&evsigint, SIGINT, session_read_sighdlr, NULL);
858 signal_set(&evsigterm, SIGTERM, session_read_sighdlr, NULL);
859 signal_set(&evsighup, SIGHUP, session_read_sighdlr, NULL);
860 signal_set(&evsigusr1, SIGUSR1, session_read_sighdlr, NULL);
861 signal(SIGPIPE, SIG_IGN);
863 signal_add(&evsigint, NULL);
864 signal_add(&evsigterm, NULL);
865 signal_add(&evsighup, NULL);
866 signal_add(&evsigusr1, NULL);
868 gotd_session.state = GOTD_STATE_EXPECT_LIST_REFS;
870 gotd_session_client.fd = -1;
871 gotd_session_client.nref_updates = -1;
872 gotd_session_client.delta_cache_fd = -1;
873 gotd_session_client.accept_flush_pkt = 1;
875 if (imsgbuf_init(&gotd_session.parent_iev.ibuf, GOTD_FILENO_MSG_PIPE)
876 == -1) {
877 err = got_error_from_errno("imsgbuf_init");
878 goto done;
880 imsgbuf_allow_fdpass(&gotd_session.parent_iev.ibuf);
881 gotd_session.parent_iev.handler = session_dispatch;
882 gotd_session.parent_iev.events = EV_READ;
883 gotd_session.parent_iev.handler_arg = NULL;
884 event_set(&gotd_session.parent_iev.ev, gotd_session.parent_iev.ibuf.fd,
885 EV_READ, session_dispatch, &gotd_session.parent_iev);
886 if (gotd_imsg_compose_event(&gotd_session.parent_iev,
887 GOTD_IMSG_CLIENT_SESSION_READY, PROC_SESSION_READ,
888 -1, NULL, 0) == -1) {
889 err = got_error_from_errno("imsg compose CLIENT_SESSION_READY");
890 goto done;
893 event_dispatch();
894 done:
895 if (err)
896 log_warnx("%s: %s", title, err->msg);
897 session_read_shutdown();
900 static void
901 session_read_shutdown(void)
903 log_debug("%s: shutting down", gotd_session.title);
905 if (gotd_session.repo)
906 got_repo_close(gotd_session.repo);
907 got_repo_pack_fds_close(gotd_session.pack_fds);
908 got_repo_temp_fds_close(gotd_session.temp_fds);
909 free(gotd_session_client.username);
910 exit(0);