fix failure of gotwebd action_patch test on single-digit days of the month
[got-portable.git] / gotd / session_write.c
blobdabd4c0255c4808148ecb26b5e6ca93cfeeca242
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/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 <signal.h>
28 #include <stdint.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <imsg.h>
33 #include <unistd.h>
35 #include "got_compat.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_write.h"
56 struct gotd_session_notif {
57 STAILQ_ENTRY(gotd_session_notif) entry;
58 int fd;
59 enum gotd_notification_action action;
60 char *refname;
61 struct got_object_id old_id;
62 struct got_object_id new_id;
64 STAILQ_HEAD(gotd_session_notifications, gotd_session_notif) notifications;
66 enum gotd_session_write_state {
67 GOTD_STATE_EXPECT_LIST_REFS,
68 GOTD_STATE_EXPECT_CAPABILITIES,
69 GOTD_STATE_EXPECT_REF_UPDATE,
70 GOTD_STATE_EXPECT_MORE_REF_UPDATES,
71 GOTD_STATE_EXPECT_PACKFILE,
72 GOTD_STATE_NOTIFY,
75 static struct gotd_session_write {
76 pid_t pid;
77 const char *title;
78 struct got_repository *repo;
79 struct gotd_repo *repo_cfg;
80 int *pack_fds;
81 int *temp_fds;
82 struct gotd_imsgev parent_iev;
83 struct gotd_imsgev notifier_iev;
84 struct timeval request_timeout;
85 enum gotd_session_write_state state;
86 struct gotd_imsgev repo_child_iev;
87 } gotd_session;
89 static struct gotd_session_client {
90 struct gotd_client_capability *capabilities;
91 size_t ncapa_alloc;
92 size_t ncapabilities;
93 uint32_t id;
94 int fd;
95 int delta_cache_fd;
96 struct gotd_imsgev iev;
97 struct event tmo;
98 uid_t euid;
99 gid_t egid;
100 char *username;
101 char *packfile_path;
102 char *packidx_path;
103 int nref_updates;
104 int accept_flush_pkt;
105 int flush_disconnect;
106 } gotd_session_client;
108 static void session_write_shutdown(void);
110 static void
111 disconnect(struct gotd_session_client *client)
113 log_debug("uid %d: disconnecting", client->euid);
115 if (gotd_imsg_compose_event(&gotd_session.parent_iev,
116 GOTD_IMSG_DISCONNECT, PROC_SESSION_WRITE, -1, NULL, 0) == -1)
117 log_warn("imsg compose DISCONNECT");
119 imsgbuf_clear(&gotd_session.repo_child_iev.ibuf);
120 event_del(&gotd_session.repo_child_iev.ev);
121 evtimer_del(&client->tmo);
122 close(client->fd);
123 if (client->delta_cache_fd != -1)
124 close(client->delta_cache_fd);
125 if (client->packfile_path) {
126 if (unlink(client->packfile_path) == -1 && errno != ENOENT)
127 log_warn("unlink %s: ", client->packfile_path);
128 free(client->packfile_path);
130 if (client->packidx_path) {
131 if (unlink(client->packidx_path) == -1 && errno != ENOENT)
132 log_warn("unlink %s: ", client->packidx_path);
133 free(client->packidx_path);
135 free(client->capabilities);
137 session_write_shutdown();
140 static void
141 disconnect_on_error(struct gotd_session_client *client,
142 const struct got_error *err)
144 struct imsgbuf ibuf;
146 if (err->code != GOT_ERR_EOF) {
147 log_warnx("uid %d: %s", client->euid, err->msg);
148 if (imsgbuf_init(&ibuf, client->fd) == -1) {
149 log_warn("imsgbuf_init");
150 } else {
151 gotd_imsg_send_error(&ibuf, 0, PROC_SESSION_WRITE,
152 err);
153 imsgbuf_clear(&ibuf);
157 disconnect(client);
160 static void
161 gotd_request_timeout(int fd, short events, void *arg)
163 struct gotd_session_client *client = arg;
165 log_warnx("disconnecting uid %d due to timeout", client->euid);
166 disconnect(client);
169 static void
170 session_write_sighdlr(int sig, short event, void *arg)
173 * Normal signal handler rules don't apply because libevent
174 * decouples for us.
177 switch (sig) {
178 case SIGHUP:
179 log_info("%s: ignoring SIGHUP", __func__);
180 break;
181 case SIGUSR1:
182 log_info("%s: ignoring SIGUSR1", __func__);
183 break;
184 case SIGTERM:
185 case SIGINT:
186 session_write_shutdown();
187 /* NOTREACHED */
188 break;
189 default:
190 fatalx("unexpected signal");
194 static const struct got_error *
195 recv_packfile_install(struct imsg *imsg)
197 struct gotd_imsg_packfile_install inst;
198 size_t datalen;
200 log_debug("packfile-install received");
202 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
203 if (datalen != sizeof(inst))
204 return got_error(GOT_ERR_PRIVSEP_LEN);
205 memcpy(&inst, imsg->data, sizeof(inst));
207 return NULL;
210 static const struct got_error *
211 recv_ref_updates_start(struct imsg *imsg)
213 struct gotd_imsg_ref_updates_start istart;
214 size_t datalen;
216 log_debug("ref-updates-start received");
218 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
219 if (datalen != sizeof(istart))
220 return got_error(GOT_ERR_PRIVSEP_LEN);
221 memcpy(&istart, imsg->data, sizeof(istart));
223 return NULL;
226 static const struct got_error *
227 recv_ref_update(struct imsg *imsg)
229 struct gotd_imsg_ref_update iref;
230 size_t datalen;
232 log_debug("ref-update received");
234 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
235 if (datalen < sizeof(iref))
236 return got_error(GOT_ERR_PRIVSEP_LEN);
237 memcpy(&iref, imsg->data, sizeof(iref));
239 return NULL;
242 static const struct got_error *
243 send_ref_update_ok(struct gotd_session_client *client,
244 struct gotd_imsg_ref_update *iref, const char *refname)
246 struct gotd_imsg_ref_update_ok iok;
247 struct gotd_imsgev *iev = &client->iev;
248 struct ibuf *wbuf;
249 size_t len;
251 memset(&iok, 0, sizeof(iok));
252 memcpy(iok.old_id, iref->old_id, SHA1_DIGEST_LENGTH);
253 memcpy(iok.new_id, iref->new_id, SHA1_DIGEST_LENGTH);
254 iok.name_len = strlen(refname);
256 len = sizeof(iok) + iok.name_len;
257 wbuf = imsg_create(&iev->ibuf, GOTD_IMSG_REF_UPDATE_OK,
258 PROC_SESSION_WRITE, gotd_session.pid, len);
259 if (wbuf == NULL)
260 return got_error_from_errno("imsg_create REF_UPDATE_OK");
262 if (imsg_add(wbuf, &iok, sizeof(iok)) == -1)
263 return got_error_from_errno("imsg_add REF_UPDATE_OK");
264 if (imsg_add(wbuf, refname, iok.name_len) == -1)
265 return got_error_from_errno("imsg_add REF_UPDATE_OK");
267 imsg_close(&iev->ibuf, wbuf);
268 gotd_imsg_event_add(iev);
269 return NULL;
272 static void
273 send_refs_updated(struct gotd_session_client *client)
275 if (gotd_imsg_compose_event(&client->iev, GOTD_IMSG_REFS_UPDATED,
276 PROC_SESSION_WRITE, -1, NULL, 0) == -1)
277 log_warn("imsg compose REFS_UPDATED");
280 static const struct got_error *
281 send_ref_update_ng(struct gotd_session_client *client,
282 struct gotd_imsg_ref_update *iref, const char *refname,
283 const char *reason)
285 const struct got_error *ng_err;
286 struct gotd_imsg_ref_update_ng ing;
287 struct gotd_imsgev *iev = &client->iev;
288 struct ibuf *wbuf;
289 size_t len;
291 memset(&ing, 0, sizeof(ing));
292 memcpy(ing.old_id, iref->old_id, SHA1_DIGEST_LENGTH);
293 memcpy(ing.new_id, iref->new_id, SHA1_DIGEST_LENGTH);
294 ing.name_len = strlen(refname);
296 ng_err = got_error_fmt(GOT_ERR_REF_BUSY, "%s", reason);
297 ing.reason_len = strlen(ng_err->msg);
299 len = sizeof(ing) + ing.name_len + ing.reason_len;
300 wbuf = imsg_create(&iev->ibuf, GOTD_IMSG_REF_UPDATE_NG,
301 PROC_SESSION_WRITE, gotd_session.pid, len);
302 if (wbuf == NULL)
303 return got_error_from_errno("imsg_create REF_UPDATE_NG");
305 if (imsg_add(wbuf, &ing, sizeof(ing)) == -1)
306 return got_error_from_errno("imsg_add REF_UPDATE_NG");
307 if (imsg_add(wbuf, refname, ing.name_len) == -1)
308 return got_error_from_errno("imsg_add REF_UPDATE_NG");
309 if (imsg_add(wbuf, ng_err->msg, ing.reason_len) == -1)
310 return got_error_from_errno("imsg_add REF_UPDATE_NG");
312 imsg_close(&iev->ibuf, wbuf);
313 gotd_imsg_event_add(iev);
314 return NULL;
317 static const struct got_error *
318 install_pack(struct gotd_session_client *client, const char *repo_path,
319 struct imsg *imsg)
321 const struct got_error *err = NULL;
322 struct gotd_imsg_packfile_install inst;
323 char hex[SHA1_DIGEST_STRING_LENGTH];
324 size_t datalen;
325 char *packfile_path = NULL, *packidx_path = NULL;
327 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
328 if (datalen != sizeof(inst))
329 return got_error(GOT_ERR_PRIVSEP_LEN);
330 memcpy(&inst, imsg->data, sizeof(inst));
332 if (client->packfile_path == NULL)
333 return got_error_msg(GOT_ERR_BAD_REQUEST,
334 "client has no pack file");
335 if (client->packidx_path == NULL)
336 return got_error_msg(GOT_ERR_BAD_REQUEST,
337 "client has no pack file index");
339 if (got_sha1_digest_to_str(inst.pack_sha1, hex, sizeof(hex)) == NULL)
340 return got_error_msg(GOT_ERR_NO_SPACE,
341 "could not convert pack file SHA1 to hex");
343 if (asprintf(&packfile_path, "/%s/%s/pack-%s.pack",
344 repo_path, GOT_OBJECTS_PACK_DIR, hex) == -1) {
345 err = got_error_from_errno("asprintf");
346 goto done;
349 if (asprintf(&packidx_path, "/%s/%s/pack-%s.idx",
350 repo_path, GOT_OBJECTS_PACK_DIR, hex) == -1) {
351 err = got_error_from_errno("asprintf");
352 goto done;
355 if (rename(client->packfile_path, packfile_path) == -1) {
356 err = got_error_from_errno3("rename", client->packfile_path,
357 packfile_path);
358 goto done;
361 free(client->packfile_path);
362 client->packfile_path = NULL;
364 if (rename(client->packidx_path, packidx_path) == -1) {
365 err = got_error_from_errno3("rename", client->packidx_path,
366 packidx_path);
367 goto done;
370 /* Ensure we re-read the pack index list upon next access. */
371 gotd_session.repo->pack_path_mtime.tv_sec = 0;
372 gotd_session.repo->pack_path_mtime.tv_nsec = 0;
374 free(client->packidx_path);
375 client->packidx_path = NULL;
376 done:
377 free(packfile_path);
378 free(packidx_path);
379 return err;
382 static const struct got_error *
383 begin_ref_updates(struct gotd_session_client *client, struct imsg *imsg)
385 struct gotd_imsg_ref_updates_start istart;
386 size_t datalen;
388 if (client->nref_updates != -1)
389 return got_error(GOT_ERR_PRIVSEP_MSG);
391 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
392 if (datalen != sizeof(istart))
393 return got_error(GOT_ERR_PRIVSEP_LEN);
394 memcpy(&istart, imsg->data, sizeof(istart));
396 if (istart.nref_updates <= 0)
397 return got_error(GOT_ERR_PRIVSEP_MSG);
399 client->nref_updates = istart.nref_updates;
400 return NULL;
403 static const struct got_error *
404 validate_namespace(const char *namespace)
406 size_t len = strlen(namespace);
408 if (len < 5 || strncmp("refs/", namespace, 5) != 0 ||
409 namespace[len - 1] != '/') {
410 return got_error_fmt(GOT_ERR_BAD_REF_NAME,
411 "reference namespace '%s'", namespace);
414 return NULL;
417 static const struct got_error *
418 queue_notification(struct got_object_id *old_id, struct got_object_id *new_id,
419 struct got_repository *repo, struct got_reference *ref)
421 const struct got_error *err = NULL;
422 struct gotd_repo *repo_cfg = gotd_session.repo_cfg;
423 struct gotd_imsgev *iev = &gotd_session.repo_child_iev;
424 struct got_pathlist_entry *pe;
425 struct gotd_session_notif *notif;
427 if (iev->ibuf.fd == -1 ||
428 STAILQ_EMPTY(&repo_cfg->notification_targets))
429 return NULL; /* notifications unused */
431 TAILQ_FOREACH(pe, &repo_cfg->notification_refs, entry) {
432 const char *refname = pe->path;
433 if (strcmp(got_ref_get_name(ref), refname) == 0)
434 break;
436 if (pe == NULL) {
437 TAILQ_FOREACH(pe, &repo_cfg->notification_ref_namespaces,
438 entry) {
439 const char *namespace = pe->path;
441 err = validate_namespace(namespace);
442 if (err)
443 return err;
444 if (strncmp(namespace, got_ref_get_name(ref),
445 strlen(namespace)) == 0)
446 break;
451 * If a branch or a reference namespace was specified in the
452 * configuration file then only send notifications if a match
453 * was found.
455 if (pe == NULL && (!TAILQ_EMPTY(&repo_cfg->notification_refs) ||
456 !TAILQ_EMPTY(&repo_cfg->notification_ref_namespaces)))
457 return NULL;
459 notif = calloc(1, sizeof(*notif));
460 if (notif == NULL)
461 return got_error_from_errno("calloc");
463 notif->fd = -1;
465 if (old_id == NULL)
466 notif->action = GOTD_NOTIF_ACTION_CREATED;
467 else if (new_id == NULL)
468 notif->action = GOTD_NOTIF_ACTION_REMOVED;
469 else
470 notif->action = GOTD_NOTIF_ACTION_CHANGED;
472 if (old_id != NULL)
473 memcpy(&notif->old_id, old_id, sizeof(notif->old_id));
474 if (new_id != NULL)
475 memcpy(&notif->new_id, new_id, sizeof(notif->new_id));
477 notif->refname = strdup(got_ref_get_name(ref));
478 if (notif->refname == NULL) {
479 err = got_error_from_errno("strdup");
480 goto done;
483 STAILQ_INSERT_TAIL(&notifications, notif, entry);
484 done:
485 if (err && notif) {
486 free(notif->refname);
487 free(notif);
489 return err;
492 /* Forward notification content to the NOTIFY process. */
493 static const struct got_error *
494 forward_notification(struct gotd_session_client *client, struct imsg *imsg)
496 const struct got_error *err = NULL;
497 struct gotd_imsgev *iev = &gotd_session.notifier_iev;
498 struct gotd_session_notif *notif;
499 struct gotd_imsg_notification_content icontent;
500 char *refname = NULL, *id_str = NULL;
501 size_t datalen;
502 struct gotd_imsg_notify inotify;
503 const char *action;
504 struct ibuf *wbuf;
506 memset(&inotify, 0, sizeof(inotify));
508 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
509 if (datalen < sizeof(icontent))
510 return got_error(GOT_ERR_PRIVSEP_LEN);
511 memcpy(&icontent, imsg->data, sizeof(icontent));
512 if (datalen != sizeof(icontent) + icontent.refname_len)
513 return got_error(GOT_ERR_PRIVSEP_LEN);
514 refname = strndup(imsg->data + sizeof(icontent), icontent.refname_len);
515 if (refname == NULL)
516 return got_error_from_errno("strndup");
518 notif = STAILQ_FIRST(&notifications);
519 if (notif == NULL)
520 return got_error(GOT_ERR_PRIVSEP_MSG);
522 STAILQ_REMOVE(&notifications, notif, gotd_session_notif, entry);
524 if (notif->action != icontent.action || notif->fd == -1 ||
525 strcmp(notif->refname, refname) != 0) {
526 err = got_error(GOT_ERR_PRIVSEP_MSG);
527 goto done;
529 if (notif->action == GOTD_NOTIF_ACTION_CREATED) {
530 if (memcmp(&notif->new_id, &icontent.new_id,
531 sizeof(notif->new_id)) != 0) {
532 err = got_error_msg(GOT_ERR_PRIVSEP_MSG,
533 "received notification content for unknown event");
534 goto done;
536 } else if (notif->action == GOTD_NOTIF_ACTION_REMOVED) {
537 if (memcmp(&notif->old_id, &icontent.old_id,
538 sizeof(notif->old_id)) != 0) {
539 err = got_error_msg(GOT_ERR_PRIVSEP_MSG,
540 "received notification content for unknown event");
541 goto done;
543 } else if (memcmp(&notif->old_id, &icontent.old_id,
544 sizeof(notif->old_id)) != 0 ||
545 memcmp(&notif->new_id, &icontent.new_id,
546 sizeof(notif->old_id)) != 0) {
547 err = got_error_msg(GOT_ERR_PRIVSEP_MSG,
548 "received notification content for unknown event");
549 goto done;
552 switch (notif->action) {
553 case GOTD_NOTIF_ACTION_CREATED:
554 action = "created";
555 err = got_object_id_str(&id_str, &notif->new_id);
556 if (err)
557 goto done;
558 break;
559 case GOTD_NOTIF_ACTION_REMOVED:
560 action = "removed";
561 err = got_object_id_str(&id_str, &notif->old_id);
562 if (err)
563 goto done;
564 break;
565 case GOTD_NOTIF_ACTION_CHANGED:
566 action = "changed";
567 err = got_object_id_str(&id_str, &notif->new_id);
568 if (err)
569 goto done;
570 break;
571 default:
572 err = got_error(GOT_ERR_PRIVSEP_MSG);
573 goto done;
576 strlcpy(inotify.repo_name, gotd_session.repo_cfg->name,
577 sizeof(inotify.repo_name));
579 snprintf(inotify.subject_line, sizeof(inotify.subject_line),
580 "%s: %s %s %s: %.12s", gotd_session.repo_cfg->name,
581 client->username, action, notif->refname, id_str);
583 inotify.username_len = strlen(client->username);
584 wbuf = imsg_create(&iev->ibuf, GOTD_IMSG_NOTIFY,
585 PROC_SESSION_WRITE, gotd_session.pid,
586 sizeof(inotify) + inotify.username_len);
587 if (wbuf == NULL) {
588 err = got_error_from_errno("imsg_create NOTIFY");
589 goto done;
591 if (imsg_add(wbuf, &inotify, sizeof(inotify)) == -1) {
592 err = got_error_from_errno("imsg_add NOTIFY");
593 goto done;
595 if (imsg_add(wbuf, client->username, inotify.username_len) == -1) {
596 err = got_error_from_errno("imsg_add NOTIFY");
597 goto done;
600 ibuf_fd_set(wbuf, notif->fd);
601 notif->fd = -1;
603 imsg_close(&iev->ibuf, wbuf);
604 gotd_imsg_event_add(iev);
605 done:
606 if (notif->fd != -1)
607 close(notif->fd);
608 free(notif);
609 free(refname);
610 free(id_str);
611 return err;
614 /* Request notification content from REPO_WRITE process. */
615 static const struct got_error *
616 request_notification(struct gotd_session_notif *notif)
618 const struct got_error *err = NULL;
619 struct gotd_imsgev *iev = &gotd_session.repo_child_iev;
620 struct gotd_imsg_notification_content icontent;
621 struct ibuf *wbuf;
622 size_t len;
623 int fd;
625 fd = got_opentempfd();
626 if (fd == -1)
627 return got_error_from_errno("got_opentemp");
629 memset(&icontent, 0, sizeof(icontent));
631 icontent.action = notif->action;
632 memcpy(&icontent.old_id, &notif->old_id, sizeof(notif->old_id));
633 memcpy(&icontent.new_id, &notif->new_id, sizeof(notif->new_id));
634 icontent.refname_len = strlen(notif->refname);
636 len = sizeof(icontent) + icontent.refname_len;
637 wbuf = imsg_create(&iev->ibuf, GOTD_IMSG_NOTIFY,
638 PROC_SESSION_WRITE, gotd_session.pid, len);
639 if (wbuf == NULL) {
640 err = got_error_from_errno("imsg_create NOTIFY");
641 goto done;
643 if (imsg_add(wbuf, &icontent, sizeof(icontent)) == -1) {
644 err = got_error_from_errno("imsg_add NOTIFY");
645 goto done;
647 if (imsg_add(wbuf, notif->refname, icontent.refname_len) == -1) {
648 err = got_error_from_errno("imsg_add NOTIFY");
649 goto done;
652 notif->fd = dup(fd);
653 if (notif->fd == -1) {
654 err = got_error_from_errno("dup");
655 goto done;
658 ibuf_fd_set(wbuf, fd);
659 fd = -1;
661 imsg_close(&iev->ibuf, wbuf);
662 gotd_imsg_event_add(iev);
663 done:
664 if (err && fd != -1)
665 close(fd);
666 return err;
669 static const struct got_error *
670 update_ref(int *shut, struct gotd_session_client *client,
671 const char *repo_path, struct imsg *imsg)
673 const struct got_error *err = NULL;
674 struct got_repository *repo = gotd_session.repo;
675 struct got_reference *ref = NULL;
676 struct gotd_imsg_ref_update iref;
677 struct got_object_id old_id, new_id;
678 struct gotd_session_notif *notif;
679 struct got_object_id *id = NULL;
680 char *refname = NULL;
681 size_t datalen;
682 int locked = 0;
683 char hex1[SHA1_DIGEST_STRING_LENGTH];
684 char hex2[SHA1_DIGEST_STRING_LENGTH];
686 log_debug("update-ref from uid %d", client->euid);
688 if (client->nref_updates <= 0)
689 return got_error(GOT_ERR_PRIVSEP_MSG);
691 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
692 if (datalen < sizeof(iref))
693 return got_error(GOT_ERR_PRIVSEP_LEN);
694 memcpy(&iref, imsg->data, sizeof(iref));
695 if (datalen != sizeof(iref) + iref.name_len)
696 return got_error(GOT_ERR_PRIVSEP_LEN);
697 refname = strndup(imsg->data + sizeof(iref), iref.name_len);
698 if (refname == NULL)
699 return got_error_from_errno("strndup");
701 log_debug("updating ref %s for uid %d", refname, client->euid);
703 memset(&old_id, 0, sizeof(old_id));
704 memcpy(old_id.hash, iref.old_id, SHA1_DIGEST_LENGTH);
705 memset(&new_id, 0, sizeof(new_id));
706 memcpy(new_id.hash, iref.new_id, SHA1_DIGEST_LENGTH);
707 err = got_repo_find_object_id(iref.delete_ref ? &old_id : &new_id,
708 repo);
709 if (err)
710 goto done;
712 if (iref.ref_is_new) {
713 err = got_ref_open(&ref, repo, refname, 0);
714 if (err) {
715 if (err->code != GOT_ERR_NOT_REF)
716 goto done;
717 err = got_ref_alloc(&ref, refname, &new_id);
718 if (err)
719 goto done;
720 err = got_ref_write(ref, repo); /* will lock/unlock */
721 if (err)
722 goto done;
723 err = queue_notification(NULL, &new_id, repo, ref);
724 if (err)
725 goto done;
726 } else {
727 err = got_ref_resolve(&id, repo, ref);
728 if (err)
729 goto done;
730 got_object_id_hex(&new_id, hex1, sizeof(hex1));
731 got_object_id_hex(id, hex2, sizeof(hex2));
732 err = got_error_fmt(GOT_ERR_REF_BUSY,
733 "Addition %s: %s failed; %s: %s has been "
734 "created by someone else while transaction "
735 "was in progress",
736 got_ref_get_name(ref), hex1,
737 got_ref_get_name(ref), hex2);
738 goto done;
740 } else if (iref.delete_ref) {
741 err = got_ref_open(&ref, repo, refname, 1 /* lock */);
742 if (err)
743 goto done;
744 locked = 1;
746 err = got_ref_resolve(&id, repo, ref);
747 if (err)
748 goto done;
750 if (got_object_id_cmp(id, &old_id) != 0) {
751 got_object_id_hex(&old_id, hex1, sizeof(hex1));
752 got_object_id_hex(id, hex2, sizeof(hex2));
753 err = got_error_fmt(GOT_ERR_REF_BUSY,
754 "Deletion %s: %s failed; %s: %s has been "
755 "created by someone else while transaction "
756 "was in progress",
757 got_ref_get_name(ref), hex1,
758 got_ref_get_name(ref), hex2);
759 goto done;
762 err = got_ref_delete(ref, repo);
763 if (err)
764 goto done;
765 err = queue_notification(&old_id, NULL, repo, ref);
766 if (err)
767 goto done;
768 free(id);
769 id = NULL;
770 } else {
771 err = got_ref_open(&ref, repo, refname, 1 /* lock */);
772 if (err)
773 goto done;
774 locked = 1;
776 err = got_ref_resolve(&id, repo, ref);
777 if (err)
778 goto done;
780 if (got_object_id_cmp(id, &old_id) != 0) {
781 got_object_id_hex(&old_id, hex1, sizeof(hex1));
782 got_object_id_hex(id, hex2, sizeof(hex2));
783 err = got_error_fmt(GOT_ERR_REF_BUSY,
784 "Update %s: %s failed; %s: %s has been "
785 "created by someone else while transaction "
786 "was in progress",
787 got_ref_get_name(ref), hex1,
788 got_ref_get_name(ref), hex2);
789 goto done;
792 if (got_object_id_cmp(&new_id, &old_id) != 0) {
793 err = got_ref_change_ref(ref, &new_id);
794 if (err)
795 goto done;
796 err = got_ref_write(ref, repo);
797 if (err)
798 goto done;
799 err = queue_notification(&old_id, &new_id, repo, ref);
800 if (err)
801 goto done;
804 free(id);
805 id = NULL;
807 done:
808 if (err) {
809 if (err->code == GOT_ERR_LOCKFILE_TIMEOUT) {
810 err = got_error_fmt(GOT_ERR_LOCKFILE_TIMEOUT,
811 "could not acquire exclusive file lock for %s",
812 refname);
814 send_ref_update_ng(client, &iref, refname, err->msg);
815 } else
816 send_ref_update_ok(client, &iref, refname);
818 if (client->nref_updates > 0) {
819 client->nref_updates--;
820 if (client->nref_updates == 0) {
821 send_refs_updated(client);
822 notif = STAILQ_FIRST(&notifications);
823 if (notif) {
824 gotd_session.state = GOTD_STATE_NOTIFY;
825 err = request_notification(notif);
826 if (err) {
827 log_warn("could not send notification: "
828 "%s", err->msg);
829 client->flush_disconnect = 1;
831 } else
832 client->flush_disconnect = 1;
836 if (locked) {
837 const struct got_error *unlock_err;
838 unlock_err = got_ref_unlock(ref);
839 if (unlock_err && err == NULL)
840 err = unlock_err;
842 if (ref)
843 got_ref_close(ref);
844 free(refname);
845 free(id);
846 return err;
849 static const struct got_error *
850 recv_notification_content(struct imsg *imsg)
852 struct gotd_imsg_notification_content inotif;
853 size_t datalen;
855 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
856 if (datalen < sizeof(inotif))
857 return got_error(GOT_ERR_PRIVSEP_LEN);
858 memcpy(&inotif, imsg->data, sizeof(inotif));
860 return NULL;
863 static void
864 session_dispatch_repo_child(int fd, short event, void *arg)
866 const struct got_error *err = NULL;
867 struct gotd_imsgev *iev = arg;
868 struct imsgbuf *ibuf = &iev->ibuf;
869 struct gotd_session_client *client = &gotd_session_client;
870 ssize_t n;
871 int shut = 0;
872 struct imsg imsg;
874 if (event & EV_READ) {
875 if ((n = imsgbuf_read(ibuf)) == -1)
876 fatal("imsg_read error");
877 if (n == 0) {
878 /* Connection closed. */
879 shut = 1;
880 goto done;
884 if (event & EV_WRITE) {
885 err = gotd_imsg_flush(ibuf);
886 if (err)
887 fatalx("%s", err->msg);
890 for (;;) {
891 const struct got_error *err = NULL;
892 uint32_t client_id = 0;
893 int do_disconnect = 0;
894 int do_ref_updates = 0, do_ref_update = 0;
895 int do_packfile_install = 0, do_notify = 0;
897 if ((n = imsg_get(ibuf, &imsg)) == -1)
898 fatal("%s: imsg_get error", __func__);
899 if (n == 0) /* No more messages. */
900 break;
902 switch (imsg.hdr.type) {
903 case GOTD_IMSG_ERROR:
904 do_disconnect = 1;
905 err = gotd_imsg_recv_error(&client_id, &imsg);
906 break;
907 case GOTD_IMSG_PACKFILE_INSTALL:
908 err = recv_packfile_install(&imsg);
909 if (err == NULL)
910 do_packfile_install = 1;
911 break;
912 case GOTD_IMSG_REF_UPDATES_START:
913 err = recv_ref_updates_start(&imsg);
914 if (err == NULL)
915 do_ref_updates = 1;
916 break;
917 case GOTD_IMSG_REF_UPDATE:
918 err = recv_ref_update(&imsg);
919 if (err == NULL)
920 do_ref_update = 1;
921 break;
922 case GOTD_IMSG_NOTIFY:
923 err = recv_notification_content(&imsg);
924 if (err == NULL)
925 do_notify = 1;
926 break;
927 default:
928 log_debug("unexpected imsg %d", imsg.hdr.type);
929 break;
932 if (do_disconnect || err) {
933 if (err)
934 disconnect_on_error(client, err);
935 else
936 disconnect(client);
937 } else {
938 struct gotd_session_notif *notif;
940 if (do_packfile_install)
941 err = install_pack(client,
942 gotd_session.repo->path, &imsg);
943 else if (do_ref_updates)
944 err = begin_ref_updates(client, &imsg);
945 else if (do_ref_update)
946 err = update_ref(&shut, client,
947 gotd_session.repo->path, &imsg);
948 else if (do_notify)
949 err = forward_notification(client, &imsg);
950 if (err)
951 log_warnx("uid %d: %s", client->euid, err->msg);
953 notif = STAILQ_FIRST(&notifications);
954 if (notif && do_notify) {
955 /* Request content for next notification. */
956 err = request_notification(notif);
957 if (err) {
958 log_warn("could not send notification: "
959 "%s", err->msg);
960 shut = 1;
964 imsg_free(&imsg);
966 done:
967 if (!shut) {
968 gotd_imsg_event_add(iev);
969 } else {
970 /* This pipe is dead. Remove its event handler */
971 event_del(&iev->ev);
972 event_loopexit(NULL);
976 static const struct got_error *
977 recv_capabilities(struct gotd_session_client *client, struct imsg *imsg)
979 struct gotd_imsg_capabilities icapas;
980 size_t datalen;
982 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
983 if (datalen != sizeof(icapas))
984 return got_error(GOT_ERR_PRIVSEP_LEN);
985 memcpy(&icapas, imsg->data, sizeof(icapas));
987 client->ncapa_alloc = icapas.ncapabilities;
988 client->capabilities = calloc(client->ncapa_alloc,
989 sizeof(*client->capabilities));
990 if (client->capabilities == NULL) {
991 client->ncapa_alloc = 0;
992 return got_error_from_errno("calloc");
995 log_debug("expecting %zu capabilities from uid %d",
996 client->ncapa_alloc, client->euid);
997 return NULL;
1000 static const struct got_error *
1001 recv_capability(struct gotd_session_client *client, struct imsg *imsg)
1003 struct gotd_imsg_capability icapa;
1004 struct gotd_client_capability *capa;
1005 size_t datalen;
1006 char *key, *value = NULL;
1008 if (client->capabilities == NULL ||
1009 client->ncapabilities >= client->ncapa_alloc) {
1010 return got_error_msg(GOT_ERR_BAD_REQUEST,
1011 "unexpected capability received");
1014 memset(&icapa, 0, sizeof(icapa));
1016 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
1017 if (datalen < sizeof(icapa))
1018 return got_error(GOT_ERR_PRIVSEP_LEN);
1019 memcpy(&icapa, imsg->data, sizeof(icapa));
1021 if (datalen != sizeof(icapa) + icapa.key_len + icapa.value_len)
1022 return got_error(GOT_ERR_PRIVSEP_LEN);
1024 key = strndup(imsg->data + sizeof(icapa), icapa.key_len);
1025 if (key == NULL)
1026 return got_error_from_errno("strndup");
1027 if (icapa.value_len > 0) {
1028 value = strndup(imsg->data + sizeof(icapa) + icapa.key_len,
1029 icapa.value_len);
1030 if (value == NULL) {
1031 free(key);
1032 return got_error_from_errno("strndup");
1036 capa = &client->capabilities[client->ncapabilities++];
1037 capa->key = key;
1038 capa->value = value;
1040 if (value)
1041 log_debug("uid %d: capability %s=%s", client->euid, key, value);
1042 else
1043 log_debug("uid %d: capability %s", client->euid, key);
1045 return NULL;
1048 static const struct got_error *
1049 forward_ref_update(struct gotd_session_client *client, struct imsg *imsg)
1051 const struct got_error *err = NULL;
1052 struct gotd_imsg_ref_update ireq;
1053 struct gotd_imsg_ref_update *iref = NULL;
1054 size_t datalen;
1056 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
1057 if (datalen < sizeof(ireq))
1058 return got_error(GOT_ERR_PRIVSEP_LEN);
1059 memcpy(&ireq, imsg->data, sizeof(ireq));
1060 if (datalen != sizeof(ireq) + ireq.name_len)
1061 return got_error(GOT_ERR_PRIVSEP_LEN);
1063 iref = malloc(datalen);
1064 if (iref == NULL)
1065 return got_error_from_errno("malloc");
1066 memcpy(iref, imsg->data, datalen);
1068 if (gotd_imsg_compose_event(&gotd_session.repo_child_iev,
1069 GOTD_IMSG_REF_UPDATE, PROC_SESSION_WRITE, -1,
1070 iref, datalen) == -1)
1071 err = got_error_from_errno("imsg compose REF_UPDATE");
1072 free(iref);
1073 return err;
1076 static int
1077 client_has_capability(struct gotd_session_client *client, const char *capastr)
1079 struct gotd_client_capability *capa;
1080 size_t i;
1082 if (client->ncapabilities == 0)
1083 return 0;
1085 for (i = 0; i < client->ncapabilities; i++) {
1086 capa = &client->capabilities[i];
1087 if (strcmp(capa->key, capastr) == 0)
1088 return 1;
1091 return 0;
1094 static const struct got_error *
1095 recv_packfile(struct gotd_session_client *client)
1097 const struct got_error *err = NULL;
1098 struct gotd_imsg_recv_packfile ipack;
1099 char *basepath = NULL, *pack_path = NULL, *idx_path = NULL;
1100 int packfd = -1, idxfd = -1;
1101 int pipe[2] = { -1, -1 };
1103 if (client->packfile_path) {
1104 return got_error_fmt(GOT_ERR_PRIVSEP_MSG,
1105 "uid %d already has a pack file", client->euid);
1108 if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pipe) == -1)
1109 return got_error_from_errno("socketpair");
1111 /* Send pack pipe end 0 to repo child process. */
1112 if (gotd_imsg_compose_event(&gotd_session.repo_child_iev,
1113 GOTD_IMSG_PACKFILE_PIPE, PROC_SESSION_WRITE, pipe[0],
1114 NULL, 0) == -1) {
1115 err = got_error_from_errno("imsg compose PACKFILE_PIPE");
1116 pipe[0] = -1;
1117 goto done;
1119 pipe[0] = -1;
1121 /* Send pack pipe end 1 to gotsh(1) (expects just an fd, no data). */
1122 if (gotd_imsg_compose_event(&client->iev,
1123 GOTD_IMSG_PACKFILE_PIPE, PROC_SESSION_WRITE, pipe[1],
1124 NULL, 0) == -1)
1125 err = got_error_from_errno("imsg compose PACKFILE_PIPE");
1126 pipe[1] = -1;
1128 if (asprintf(&basepath, "%s/%s/receiving-from-uid-%d.pack",
1129 got_repo_get_path(gotd_session.repo), GOT_OBJECTS_PACK_DIR,
1130 client->euid) == -1) {
1131 err = got_error_from_errno("asprintf");
1132 goto done;
1135 err = got_opentemp_named_fd(&pack_path, &packfd, basepath, "");
1136 if (err)
1137 goto done;
1138 if (fchmod(packfd, GOT_DEFAULT_PACK_MODE) == -1) {
1139 err = got_error_from_errno2("fchmod", pack_path);
1140 goto done;
1143 free(basepath);
1144 if (asprintf(&basepath, "%s/%s/receiving-from-uid-%d.idx",
1145 got_repo_get_path(gotd_session.repo), GOT_OBJECTS_PACK_DIR,
1146 client->euid) == -1) {
1147 err = got_error_from_errno("asprintf");
1148 basepath = NULL;
1149 goto done;
1151 err = got_opentemp_named_fd(&idx_path, &idxfd, basepath, "");
1152 if (err)
1153 goto done;
1154 if (fchmod(idxfd, GOT_DEFAULT_PACK_MODE) == -1) {
1155 err = got_error_from_errno2("fchmod", idx_path);
1156 goto done;
1159 if (gotd_imsg_compose_event(&gotd_session.repo_child_iev,
1160 GOTD_IMSG_PACKIDX_FILE, PROC_SESSION_WRITE,
1161 idxfd, NULL, 0) == -1) {
1162 err = got_error_from_errno("imsg compose PACKIDX_FILE");
1163 idxfd = -1;
1164 goto done;
1166 idxfd = -1;
1168 memset(&ipack, 0, sizeof(ipack));
1169 if (client_has_capability(client, GOT_CAPA_REPORT_STATUS))
1170 ipack.report_status = 1;
1172 if (gotd_imsg_compose_event(&gotd_session.repo_child_iev,
1173 GOTD_IMSG_RECV_PACKFILE, PROC_SESSION_WRITE, packfd,
1174 &ipack, sizeof(ipack)) == -1) {
1175 err = got_error_from_errno("imsg compose RECV_PACKFILE");
1176 packfd = -1;
1177 goto done;
1179 packfd = -1;
1181 done:
1182 free(basepath);
1183 if (pipe[0] != -1 && close(pipe[0]) == -1 && err == NULL)
1184 err = got_error_from_errno("close");
1185 if (pipe[1] != -1 && close(pipe[1]) == -1 && err == NULL)
1186 err = got_error_from_errno("close");
1187 if (packfd != -1 && close(packfd) == -1 && err == NULL)
1188 err = got_error_from_errno("close");
1189 if (idxfd != -1 && close(idxfd) == -1 && err == NULL)
1190 err = got_error_from_errno("close");
1191 if (err) {
1192 free(pack_path);
1193 free(idx_path);
1194 } else {
1195 client->packfile_path = pack_path;
1196 client->packidx_path = idx_path;
1198 return err;
1201 static void
1202 session_dispatch_client(int fd, short events, void *arg)
1204 struct gotd_imsgev *iev = arg;
1205 struct imsgbuf *ibuf = &iev->ibuf;
1206 struct gotd_session_client *client = &gotd_session_client;
1207 const struct got_error *err = NULL;
1208 struct imsg imsg;
1209 ssize_t n;
1211 if (events & EV_WRITE) {
1212 err = gotd_imsg_flush(ibuf);
1213 if (err) {
1215 * The client has closed its socket. This can
1216 * happen when Git clients are done sending
1217 * pack file data.
1218 * Pending notifications should still be sent.
1220 if (STAILQ_FIRST(&notifications) != NULL)
1221 return;
1222 if (err->code == GOT_ERR_ERRNO && errno == EPIPE) {
1223 disconnect(client);
1224 return;
1226 disconnect_on_error(client, err);
1227 return;
1230 if (client->flush_disconnect) {
1231 disconnect(client);
1232 return;
1236 if (events & EV_READ) {
1237 n = imsgbuf_read(ibuf);
1238 if (n == -1) {
1239 err = got_error_from_errno("imsgbuf_read");
1240 disconnect_on_error(client, err);
1241 return;
1243 if (n == 0) {
1245 * The client has closed its socket. This can
1246 * happen when Git clients are done sending
1247 * pack file data.
1248 * Pending notifications should still be sent.
1250 if (STAILQ_FIRST(&notifications) != NULL)
1251 return;
1252 err = got_error(GOT_ERR_EOF);
1253 disconnect_on_error(client, err);
1254 return;
1258 while (err == NULL) {
1259 n = imsg_get(ibuf, &imsg);
1260 if (n == -1) {
1261 err = got_error_from_errno("imsg_get");
1262 break;
1264 if (n == 0)
1265 break;
1267 evtimer_del(&client->tmo);
1269 switch (imsg.hdr.type) {
1270 case GOTD_IMSG_CAPABILITIES:
1271 if (gotd_session.state !=
1272 GOTD_STATE_EXPECT_CAPABILITIES) {
1273 err = got_error_msg(GOT_ERR_BAD_REQUEST,
1274 "unexpected capabilities received");
1275 break;
1277 log_debug("receiving capabilities from uid %d",
1278 client->euid);
1279 err = recv_capabilities(client, &imsg);
1280 break;
1281 case GOTD_IMSG_CAPABILITY:
1282 if (gotd_session.state != GOTD_STATE_EXPECT_CAPABILITIES) {
1283 err = got_error_msg(GOT_ERR_BAD_REQUEST,
1284 "unexpected capability received");
1285 break;
1287 err = recv_capability(client, &imsg);
1288 if (err || client->ncapabilities < client->ncapa_alloc)
1289 break;
1290 gotd_session.state = GOTD_STATE_EXPECT_REF_UPDATE;
1291 client->accept_flush_pkt = 1;
1292 log_debug("uid %d: expecting ref-update-lines",
1293 client->euid);
1294 break;
1295 case GOTD_IMSG_REF_UPDATE:
1296 if (gotd_session.state != GOTD_STATE_EXPECT_REF_UPDATE &&
1297 gotd_session.state !=
1298 GOTD_STATE_EXPECT_MORE_REF_UPDATES) {
1299 err = got_error_msg(GOT_ERR_BAD_REQUEST,
1300 "unexpected ref-update-line received");
1301 break;
1303 log_debug("received ref-update-line from uid %d",
1304 client->euid);
1305 err = forward_ref_update(client, &imsg);
1306 if (err)
1307 break;
1308 gotd_session.state = GOTD_STATE_EXPECT_MORE_REF_UPDATES;
1309 client->accept_flush_pkt = 1;
1310 break;
1311 case GOTD_IMSG_FLUSH:
1312 if (gotd_session.state !=
1313 GOTD_STATE_EXPECT_MORE_REF_UPDATES) {
1314 err = got_error_msg(GOT_ERR_BAD_REQUEST,
1315 "unexpected flush-pkt received");
1316 break;
1318 if (!client->accept_flush_pkt) {
1319 err = got_error_msg(GOT_ERR_BAD_REQUEST,
1320 "unexpected flush-pkt received");
1321 break;
1325 * Accept just one flush packet at a time.
1326 * Future client state transitions will set this flag
1327 * again if another flush packet is expected.
1329 client->accept_flush_pkt = 0;
1331 log_debug("received flush-pkt from uid %d",
1332 client->euid);
1333 if (gotd_session.state ==
1334 GOTD_STATE_EXPECT_MORE_REF_UPDATES) {
1335 gotd_session.state = GOTD_STATE_EXPECT_PACKFILE;
1336 log_debug("uid %d: expecting packfile",
1337 client->euid);
1338 err = recv_packfile(client);
1339 } else {
1340 /* should not happen, see above */
1341 err = got_error_msg(GOT_ERR_BAD_REQUEST,
1342 "unexpected client state");
1343 break;
1345 break;
1346 default:
1347 log_debug("unexpected imsg %d", imsg.hdr.type);
1348 err = got_error(GOT_ERR_PRIVSEP_MSG);
1349 break;
1352 imsg_free(&imsg);
1355 if (err) {
1356 if (err->code != GOT_ERR_EOF ||
1357 (gotd_session.state != GOTD_STATE_EXPECT_PACKFILE &&
1358 gotd_session.state != GOTD_STATE_NOTIFY))
1359 disconnect_on_error(client, err);
1360 } else {
1361 gotd_imsg_event_add(iev);
1362 evtimer_add(&client->tmo, &gotd_session.request_timeout);
1366 static const struct got_error *
1367 list_refs_request(void)
1369 static const struct got_error *err;
1370 struct gotd_session_client *client = &gotd_session_client;
1371 struct gotd_imsgev *iev = &gotd_session.repo_child_iev;
1372 int fd;
1374 if (gotd_session.state != GOTD_STATE_EXPECT_LIST_REFS)
1375 return got_error(GOT_ERR_PRIVSEP_MSG);
1377 fd = dup(client->fd);
1378 if (fd == -1)
1379 return got_error_from_errno("dup");
1381 if (gotd_imsg_compose_event(iev, GOTD_IMSG_LIST_REFS_INTERNAL,
1382 PROC_SESSION_WRITE, fd, NULL, 0) == -1) {
1383 err = got_error_from_errno("imsg compose LIST_REFS_INTERNAL");
1384 close(fd);
1385 return err;
1388 gotd_session.state = GOTD_STATE_EXPECT_CAPABILITIES;
1389 log_debug("uid %d: expecting capabilities", client->euid);
1390 return NULL;
1393 static const struct got_error *
1394 recv_connect(struct imsg *imsg)
1396 struct gotd_session_client *client = &gotd_session_client;
1397 struct gotd_imsg_connect iconnect;
1398 size_t datalen;
1400 if (gotd_session.state != GOTD_STATE_EXPECT_LIST_REFS)
1401 return got_error(GOT_ERR_PRIVSEP_MSG);
1403 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
1404 if (datalen < sizeof(iconnect))
1405 return got_error(GOT_ERR_PRIVSEP_LEN);
1406 memcpy(&iconnect, imsg->data, sizeof(iconnect));
1407 if (iconnect.username_len == 0 ||
1408 datalen != sizeof(iconnect) + iconnect.username_len)
1409 return got_error(GOT_ERR_PRIVSEP_LEN);
1411 client->euid = iconnect.euid;
1412 client->egid = iconnect.egid;
1413 client->fd = imsg_get_fd(imsg);
1414 if (client->fd == -1)
1415 return got_error(GOT_ERR_PRIVSEP_NO_FD);
1417 client->username = strndup(imsg->data + sizeof(iconnect),
1418 iconnect.username_len);
1419 if (client->username == NULL)
1420 return got_error_from_errno("strndup");
1422 if (imsgbuf_init(&client->iev.ibuf, client->fd) == -1)
1423 return got_error_from_errno("imsgbuf_init");
1424 imsgbuf_allow_fdpass(&client->iev.ibuf);
1425 client->iev.handler = session_dispatch_client;
1426 client->iev.events = EV_READ;
1427 client->iev.handler_arg = NULL;
1428 event_set(&client->iev.ev, client->iev.ibuf.fd, EV_READ,
1429 session_dispatch_client, &client->iev);
1430 gotd_imsg_event_add(&client->iev);
1431 evtimer_set(&client->tmo, gotd_request_timeout, client);
1432 evtimer_add(&client->tmo, &gotd_session.request_timeout);
1434 return NULL;
1437 static void
1438 session_dispatch_notifier(int fd, short event, void *arg)
1440 const struct got_error *err;
1441 struct gotd_session_client *client = &gotd_session_client;
1442 struct gotd_imsgev *iev = arg;
1443 struct imsgbuf *ibuf = &iev->ibuf;
1444 ssize_t n;
1445 int shut = 0;
1446 struct imsg imsg;
1447 struct gotd_session_notif *notif;
1449 if (event & EV_READ) {
1450 if ((n = imsgbuf_read(ibuf)) == -1)
1451 fatal("imsg_read error");
1452 if (n == 0) {
1453 /* Connection closed. */
1454 shut = 1;
1455 goto done;
1459 if (event & EV_WRITE) {
1460 err = gotd_imsg_flush(ibuf);
1461 if (err)
1462 fatalx("%s", err->msg);
1465 for (;;) {
1466 if ((n = imsg_get(ibuf, &imsg)) == -1)
1467 fatal("%s: imsg_get error", __func__);
1468 if (n == 0) /* No more messages. */
1469 break;
1471 switch (imsg.hdr.type) {
1472 case GOTD_IMSG_NOTIFICATION_SENT:
1473 if (gotd_session.state != GOTD_STATE_NOTIFY) {
1474 log_warn("unexpected imsg %d", imsg.hdr.type);
1475 break;
1477 notif = STAILQ_FIRST(&notifications);
1478 if (notif == NULL) {
1479 disconnect(client);
1480 break; /* NOTREACHED */
1482 /* Request content for the next notification. */
1483 err = request_notification(notif);
1484 if (err) {
1485 log_warn("could not send notification: %s",
1486 err->msg);
1487 disconnect(client);
1489 break;
1490 default:
1491 log_debug("unexpected imsg %d", imsg.hdr.type);
1492 break;
1495 imsg_free(&imsg);
1497 done:
1498 if (!shut) {
1499 gotd_imsg_event_add(iev);
1500 } else {
1501 /* This pipe is dead. Remove its event handler */
1502 event_del(&iev->ev);
1503 imsgbuf_clear(&iev->ibuf);
1507 static const struct got_error *
1508 recv_notifier(struct imsg *imsg)
1510 struct gotd_imsgev *iev = &gotd_session.notifier_iev;
1511 struct gotd_session_client *client = &gotd_session_client;
1512 size_t datalen;
1513 int fd;
1515 if (gotd_session.state != GOTD_STATE_EXPECT_LIST_REFS)
1516 return got_error(GOT_ERR_PRIVSEP_MSG);
1518 /* We should already have received a pipe to the listener. */
1519 if (client->fd == -1)
1520 return got_error(GOT_ERR_PRIVSEP_MSG);
1522 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
1523 if (datalen != 0)
1524 return got_error(GOT_ERR_PRIVSEP_LEN);
1526 fd = imsg_get_fd(imsg);
1527 if (fd == -1)
1528 return NULL; /* notifications unused */
1530 if (imsgbuf_init(&iev->ibuf, fd) == -1) {
1531 close(fd);
1532 return got_error_from_errno("imsgbuf_init");
1534 imsgbuf_allow_fdpass(&iev->ibuf);
1535 iev->handler = session_dispatch_notifier;
1536 iev->events = EV_READ;
1537 iev->handler_arg = NULL;
1538 event_set(&iev->ev, iev->ibuf.fd, EV_READ,
1539 session_dispatch_notifier, iev);
1540 gotd_imsg_event_add(iev);
1542 return NULL;
1545 static const struct got_error *
1546 recv_repo_child(struct imsg *imsg)
1548 struct gotd_imsg_connect_repo_child ichild;
1549 struct gotd_session_client *client = &gotd_session_client;
1550 size_t datalen;
1551 int fd;
1553 if (gotd_session.state != GOTD_STATE_EXPECT_LIST_REFS)
1554 return got_error(GOT_ERR_PRIVSEP_MSG);
1556 /* We should already have received a pipe to the listener. */
1557 if (client->fd == -1)
1558 return got_error(GOT_ERR_PRIVSEP_MSG);
1560 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
1561 if (datalen != sizeof(ichild))
1562 return got_error(GOT_ERR_PRIVSEP_LEN);
1564 memcpy(&ichild, imsg->data, sizeof(ichild));
1566 if (ichild.proc_id != PROC_REPO_WRITE)
1567 return got_error_msg(GOT_ERR_PRIVSEP_MSG,
1568 "bad child process type");
1570 fd = imsg_get_fd(imsg);
1571 if (fd == -1)
1572 return got_error(GOT_ERR_PRIVSEP_NO_FD);
1574 if (imsgbuf_init(&gotd_session.repo_child_iev.ibuf, fd) == -1) {
1575 close(fd);
1576 return got_error_from_errno("imsgbuf_init");
1578 imsgbuf_allow_fdpass(&gotd_session.repo_child_iev.ibuf);
1579 gotd_session.repo_child_iev.handler = session_dispatch_repo_child;
1580 gotd_session.repo_child_iev.events = EV_READ;
1581 gotd_session.repo_child_iev.handler_arg = NULL;
1582 event_set(&gotd_session.repo_child_iev.ev,
1583 gotd_session.repo_child_iev.ibuf.fd, EV_READ,
1584 session_dispatch_repo_child, &gotd_session.repo_child_iev);
1585 gotd_imsg_event_add(&gotd_session.repo_child_iev);
1587 /* The "recvfd" pledge promise is no longer needed. */
1588 if (pledge("stdio rpath wpath cpath sendfd fattr flock", NULL) == -1)
1589 fatal("pledge");
1591 return NULL;
1594 static void
1595 session_dispatch(int fd, short event, void *arg)
1597 const struct got_error *err = NULL;
1598 struct gotd_imsgev *iev = arg;
1599 struct imsgbuf *ibuf = &iev->ibuf;
1600 struct gotd_session_client *client = &gotd_session_client;
1601 ssize_t n;
1602 int shut = 0;
1603 struct imsg imsg;
1605 if (event & EV_READ) {
1606 if ((n = imsgbuf_read(ibuf)) == -1)
1607 fatal("imsg_read error");
1608 if (n == 0) {
1609 /* Connection closed. */
1610 shut = 1;
1611 goto done;
1615 if (event & EV_WRITE) {
1616 err = gotd_imsg_flush(ibuf);
1617 if (err)
1618 fatalx("%s", err->msg);
1621 for (;;) {
1622 const struct got_error *err = NULL;
1623 uint32_t client_id = 0;
1624 int do_disconnect = 0, do_list_refs = 0;
1626 if ((n = imsg_get(ibuf, &imsg)) == -1)
1627 fatal("%s: imsg_get error", __func__);
1628 if (n == 0) /* No more messages. */
1629 break;
1631 switch (imsg.hdr.type) {
1632 case GOTD_IMSG_ERROR:
1633 do_disconnect = 1;
1634 err = gotd_imsg_recv_error(&client_id, &imsg);
1635 break;
1636 case GOTD_IMSG_CONNECT:
1637 err = recv_connect(&imsg);
1638 break;
1639 case GOTD_IMSG_DISCONNECT:
1640 do_disconnect = 1;
1641 break;
1642 case GOTD_IMSG_CONNECT_NOTIFIER:
1643 err = recv_notifier(&imsg);
1644 break;
1645 case GOTD_IMSG_CONNECT_REPO_CHILD:
1646 err = recv_repo_child(&imsg);
1647 if (err)
1648 break;
1649 do_list_refs = 1;
1650 break;
1651 default:
1652 log_debug("unexpected imsg %d", imsg.hdr.type);
1653 break;
1655 imsg_free(&imsg);
1657 if (do_disconnect) {
1658 if (err)
1659 disconnect_on_error(client, err);
1660 else
1661 disconnect(client);
1662 } else if (do_list_refs)
1663 err = list_refs_request();
1665 if (err)
1666 log_warnx("uid %d: %s", client->euid, err->msg);
1668 done:
1669 if (!shut) {
1670 gotd_imsg_event_add(iev);
1671 } else {
1672 /* This pipe is dead. Remove its event handler */
1673 event_del(&iev->ev);
1674 event_loopexit(NULL);
1678 void
1679 session_write_main(const char *title, const char *repo_path,
1680 int *pack_fds, int *temp_fds, struct timeval *request_timeout,
1681 struct gotd_repo *repo_cfg)
1683 const struct got_error *err = NULL;
1684 struct event evsigint, evsigterm, evsighup, evsigusr1;
1686 STAILQ_INIT(&notifications);
1688 gotd_session.title = title;
1689 gotd_session.pid = getpid();
1690 gotd_session.pack_fds = pack_fds;
1691 gotd_session.temp_fds = temp_fds;
1692 memcpy(&gotd_session.request_timeout, request_timeout,
1693 sizeof(gotd_session.request_timeout));
1694 gotd_session.repo_cfg = repo_cfg;
1696 if (imsgbuf_init(&gotd_session.notifier_iev.ibuf, -1) == -1) {
1697 err = got_error_from_errno("imsgbuf_init");
1698 goto done;
1700 imsgbuf_allow_fdpass(&gotd_session.notifier_iev.ibuf);
1702 err = got_repo_open(&gotd_session.repo, repo_path, NULL, pack_fds);
1703 if (err)
1704 goto done;
1705 if (!got_repo_is_bare(gotd_session.repo)) {
1706 err = got_error_msg(GOT_ERR_NOT_GIT_REPO,
1707 "bare git repository required");
1708 goto done;
1710 if (got_repo_get_object_format(gotd_session.repo) != GOT_HASH_SHA1) {
1711 err = got_error_msg(GOT_ERR_NOT_IMPL,
1712 "sha256 object IDs unsupported in network protocol");
1713 goto done;
1716 got_repo_temp_fds_set(gotd_session.repo, temp_fds);
1718 signal_set(&evsigint, SIGINT, session_write_sighdlr, NULL);
1719 signal_set(&evsigterm, SIGTERM, session_write_sighdlr, NULL);
1720 signal_set(&evsighup, SIGHUP, session_write_sighdlr, NULL);
1721 signal_set(&evsigusr1, SIGUSR1, session_write_sighdlr, NULL);
1722 signal(SIGPIPE, SIG_IGN);
1724 signal_add(&evsigint, NULL);
1725 signal_add(&evsigterm, NULL);
1726 signal_add(&evsighup, NULL);
1727 signal_add(&evsigusr1, NULL);
1729 gotd_session.state = GOTD_STATE_EXPECT_LIST_REFS;
1731 gotd_session_client.fd = -1;
1732 gotd_session_client.nref_updates = -1;
1733 gotd_session_client.delta_cache_fd = -1;
1734 gotd_session_client.accept_flush_pkt = 1;
1736 if (imsgbuf_init(&gotd_session.parent_iev.ibuf, GOTD_FILENO_MSG_PIPE)
1737 == -1) {
1738 err = got_error_from_errno("imsgbuf_init");
1739 goto done;
1741 imsgbuf_allow_fdpass(&gotd_session.parent_iev.ibuf);
1742 gotd_session.parent_iev.handler = session_dispatch;
1743 gotd_session.parent_iev.events = EV_READ;
1744 gotd_session.parent_iev.handler_arg = NULL;
1745 event_set(&gotd_session.parent_iev.ev, gotd_session.parent_iev.ibuf.fd,
1746 EV_READ, session_dispatch, &gotd_session.parent_iev);
1747 if (gotd_imsg_compose_event(&gotd_session.parent_iev,
1748 GOTD_IMSG_CLIENT_SESSION_READY, PROC_SESSION_WRITE,
1749 -1, NULL, 0) == -1) {
1750 err = got_error_from_errno("imsg compose CLIENT_SESSION_READY");
1751 goto done;
1754 event_dispatch();
1755 done:
1756 if (err)
1757 log_warnx("%s: %s", title, err->msg);
1758 session_write_shutdown();
1761 static void
1762 session_write_shutdown(void)
1764 struct gotd_session_notif *notif;
1766 log_debug("%s: shutting down", gotd_session.title);
1768 while (!STAILQ_EMPTY(&notifications)) {
1769 notif = STAILQ_FIRST(&notifications);
1770 STAILQ_REMOVE_HEAD(&notifications, entry);
1771 if (notif->fd != -1)
1772 close(notif->fd);
1773 free(notif->refname);
1774 free(notif);
1777 if (gotd_session.repo)
1778 got_repo_close(gotd_session.repo);
1779 got_repo_pack_fds_close(gotd_session.pack_fds);
1780 got_repo_temp_fds_close(gotd_session.temp_fds);
1781 free(gotd_session_client.username);
1782 exit(0);