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 "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_compat.h"
38 #include "got_error.h"
39 #include "got_repository.h"
40 #include "got_object.h"
41 #include "got_path.h"
42 #include "got_reference.h"
43 #include "got_opentemp.h"
45 #include "got_lib_hash.h"
46 #include "got_lib_delta.h"
47 #include "got_lib_object.h"
48 #include "got_lib_object_cache.h"
49 #include "got_lib_pack.h"
50 #include "got_lib_repository.h"
51 #include "got_lib_gitproto.h"
53 #include "gotd.h"
54 #include "log.h"
55 #include "session.h"
57 struct gotd_session_notif {
58 STAILQ_ENTRY(gotd_session_notif) entry;
59 int fd;
60 enum gotd_notification_action action;
61 char *refname;
62 struct got_object_id old_id;
63 struct got_object_id new_id;
64 };
65 STAILQ_HEAD(gotd_session_notifications, gotd_session_notif) notifications;
67 static struct gotd_session {
68 pid_t pid;
69 const char *title;
70 struct got_repository *repo;
71 struct gotd_repo *repo_cfg;
72 int *pack_fds;
73 int *temp_fds;
74 struct gotd_imsgev parent_iev;
75 struct gotd_imsgev notifier_iev;
76 struct timeval request_timeout;
77 enum gotd_procid proc_id;
78 } gotd_session;
80 static struct gotd_session_client {
81 enum gotd_session_state state;
82 int is_writing;
83 struct gotd_client_capability *capabilities;
84 size_t ncapa_alloc;
85 size_t ncapabilities;
86 uint32_t id;
87 int fd;
88 int delta_cache_fd;
89 struct gotd_imsgev iev;
90 struct gotd_imsgev repo_child_iev;
91 struct event tmo;
92 uid_t euid;
93 gid_t egid;
94 char *username;
95 char *packfile_path;
96 char *packidx_path;
97 int nref_updates;
98 int accept_flush_pkt;
99 int flush_disconnect;
100 } gotd_session_client;
102 void gotd_session_sighdlr(int sig, short event, void *arg);
103 static void gotd_session_shutdown(void);
105 static void
106 disconnect(struct gotd_session_client *client)
108 log_debug("uid %d: disconnecting", client->euid);
110 if (gotd_imsg_compose_event(&gotd_session.parent_iev,
111 GOTD_IMSG_DISCONNECT, gotd_session.proc_id, -1, NULL, 0) == -1)
112 log_warn("imsg compose DISCONNECT");
114 imsg_clear(&client->repo_child_iev.ibuf);
115 event_del(&client->repo_child_iev.ev);
116 evtimer_del(&client->tmo);
117 close(client->fd);
118 if (client->delta_cache_fd != -1)
119 close(client->delta_cache_fd);
120 if (client->packfile_path) {
121 if (unlink(client->packfile_path) == -1 && errno != ENOENT)
122 log_warn("unlink %s: ", client->packfile_path);
123 free(client->packfile_path);
125 if (client->packidx_path) {
126 if (unlink(client->packidx_path) == -1 && errno != ENOENT)
127 log_warn("unlink %s: ", client->packidx_path);
128 free(client->packidx_path);
130 free(client->capabilities);
132 gotd_session_shutdown();
135 static void
136 disconnect_on_error(struct gotd_session_client *client,
137 const struct got_error *err)
139 struct imsgbuf ibuf;
141 if (err->code != GOT_ERR_EOF) {
142 log_warnx("uid %d: %s", client->euid, err->msg);
143 imsg_init(&ibuf, client->fd);
144 gotd_imsg_send_error(&ibuf, 0, gotd_session.proc_id, err);
145 imsg_clear(&ibuf);
148 disconnect(client);
151 static void
152 gotd_request_timeout(int fd, short events, void *arg)
154 struct gotd_session_client *client = arg;
156 log_debug("disconnecting uid %d due to timeout", client->euid);
157 disconnect(client);
160 void
161 gotd_session_sighdlr(int sig, short event, void *arg)
163 /*
164 * Normal signal handler rules don't apply because libevent
165 * decouples for us.
166 */
168 switch (sig) {
169 case SIGHUP:
170 log_info("%s: ignoring SIGHUP", __func__);
171 break;
172 case SIGUSR1:
173 log_info("%s: ignoring SIGUSR1", __func__);
174 break;
175 case SIGTERM:
176 case SIGINT:
177 gotd_session_shutdown();
178 /* NOTREACHED */
179 break;
180 default:
181 fatalx("unexpected signal");
185 static const struct got_error *
186 recv_packfile_done(uint32_t *client_id, struct imsg *imsg)
188 struct gotd_imsg_packfile_done idone;
189 size_t datalen;
191 log_debug("packfile-done received");
193 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
194 if (datalen != sizeof(idone))
195 return got_error(GOT_ERR_PRIVSEP_LEN);
196 memcpy(&idone, imsg->data, sizeof(idone));
198 *client_id = idone.client_id;
199 return NULL;
202 static const struct got_error *
203 recv_packfile_install(uint32_t *client_id, struct imsg *imsg)
205 struct gotd_imsg_packfile_install inst;
206 size_t datalen;
208 log_debug("packfile-install received");
210 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
211 if (datalen != sizeof(inst))
212 return got_error(GOT_ERR_PRIVSEP_LEN);
213 memcpy(&inst, imsg->data, sizeof(inst));
215 *client_id = inst.client_id;
216 return NULL;
219 static const struct got_error *
220 recv_ref_updates_start(uint32_t *client_id, struct imsg *imsg)
222 struct gotd_imsg_ref_updates_start istart;
223 size_t datalen;
225 log_debug("ref-updates-start received");
227 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
228 if (datalen != sizeof(istart))
229 return got_error(GOT_ERR_PRIVSEP_LEN);
230 memcpy(&istart, imsg->data, sizeof(istart));
232 *client_id = istart.client_id;
233 return NULL;
236 static const struct got_error *
237 recv_ref_update(uint32_t *client_id, struct imsg *imsg)
239 struct gotd_imsg_ref_update iref;
240 size_t datalen;
242 log_debug("ref-update received");
244 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
245 if (datalen < sizeof(iref))
246 return got_error(GOT_ERR_PRIVSEP_LEN);
247 memcpy(&iref, imsg->data, sizeof(iref));
249 *client_id = iref.client_id;
250 return NULL;
253 static const struct got_error *
254 send_ref_update_ok(struct gotd_session_client *client,
255 struct gotd_imsg_ref_update *iref, const char *refname)
257 struct gotd_imsg_ref_update_ok iok;
258 struct gotd_imsgev *iev = &client->iev;
259 struct ibuf *wbuf;
260 size_t len;
262 memset(&iok, 0, sizeof(iok));
263 iok.client_id = client->id;
264 memcpy(iok.old_id, iref->old_id, SHA1_DIGEST_LENGTH);
265 memcpy(iok.new_id, iref->new_id, SHA1_DIGEST_LENGTH);
266 iok.name_len = strlen(refname);
268 len = sizeof(iok) + iok.name_len;
269 wbuf = imsg_create(&iev->ibuf, GOTD_IMSG_REF_UPDATE_OK,
270 gotd_session.proc_id, gotd_session.pid, len);
271 if (wbuf == NULL)
272 return got_error_from_errno("imsg_create REF_UPDATE_OK");
274 if (imsg_add(wbuf, &iok, sizeof(iok)) == -1)
275 return got_error_from_errno("imsg_add REF_UPDATE_OK");
276 if (imsg_add(wbuf, refname, iok.name_len) == -1)
277 return got_error_from_errno("imsg_add REF_UPDATE_OK");
279 imsg_close(&iev->ibuf, wbuf);
280 gotd_imsg_event_add(iev);
281 return NULL;
284 static void
285 send_refs_updated(struct gotd_session_client *client)
287 if (gotd_imsg_compose_event(&client->iev, GOTD_IMSG_REFS_UPDATED,
288 gotd_session.proc_id, -1, NULL, 0) == -1)
289 log_warn("imsg compose REFS_UPDATED");
292 static const struct got_error *
293 send_ref_update_ng(struct gotd_session_client *client,
294 struct gotd_imsg_ref_update *iref, const char *refname,
295 const char *reason)
297 const struct got_error *ng_err;
298 struct gotd_imsg_ref_update_ng ing;
299 struct gotd_imsgev *iev = &client->iev;
300 struct ibuf *wbuf;
301 size_t len;
303 memset(&ing, 0, sizeof(ing));
304 ing.client_id = client->id;
305 memcpy(ing.old_id, iref->old_id, SHA1_DIGEST_LENGTH);
306 memcpy(ing.new_id, iref->new_id, SHA1_DIGEST_LENGTH);
307 ing.name_len = strlen(refname);
309 ng_err = got_error_fmt(GOT_ERR_REF_BUSY, "%s", reason);
310 ing.reason_len = strlen(ng_err->msg);
312 len = sizeof(ing) + ing.name_len + ing.reason_len;
313 wbuf = imsg_create(&iev->ibuf, GOTD_IMSG_REF_UPDATE_NG,
314 gotd_session.proc_id, gotd_session.pid, len);
315 if (wbuf == NULL)
316 return got_error_from_errno("imsg_create REF_UPDATE_NG");
318 if (imsg_add(wbuf, &ing, sizeof(ing)) == -1)
319 return got_error_from_errno("imsg_add REF_UPDATE_NG");
320 if (imsg_add(wbuf, refname, ing.name_len) == -1)
321 return got_error_from_errno("imsg_add REF_UPDATE_NG");
322 if (imsg_add(wbuf, ng_err->msg, ing.reason_len) == -1)
323 return got_error_from_errno("imsg_add REF_UPDATE_NG");
325 imsg_close(&iev->ibuf, wbuf);
326 gotd_imsg_event_add(iev);
327 return NULL;
330 static const struct got_error *
331 install_pack(struct gotd_session_client *client, const char *repo_path,
332 struct imsg *imsg)
334 const struct got_error *err = NULL;
335 struct gotd_imsg_packfile_install inst;
336 char hex[SHA1_DIGEST_STRING_LENGTH];
337 size_t datalen;
338 char *packfile_path = NULL, *packidx_path = NULL;
340 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
341 if (datalen != sizeof(inst))
342 return got_error(GOT_ERR_PRIVSEP_LEN);
343 memcpy(&inst, imsg->data, sizeof(inst));
345 if (client->packfile_path == NULL)
346 return got_error_msg(GOT_ERR_BAD_REQUEST,
347 "client has no pack file");
348 if (client->packidx_path == NULL)
349 return got_error_msg(GOT_ERR_BAD_REQUEST,
350 "client has no pack file index");
352 if (got_sha1_digest_to_str(inst.pack_sha1, hex, sizeof(hex)) == NULL)
353 return got_error_msg(GOT_ERR_NO_SPACE,
354 "could not convert pack file SHA1 to hex");
356 if (asprintf(&packfile_path, "/%s/%s/pack-%s.pack",
357 repo_path, GOT_OBJECTS_PACK_DIR, hex) == -1) {
358 err = got_error_from_errno("asprintf");
359 goto done;
362 if (asprintf(&packidx_path, "/%s/%s/pack-%s.idx",
363 repo_path, GOT_OBJECTS_PACK_DIR, hex) == -1) {
364 err = got_error_from_errno("asprintf");
365 goto done;
368 if (rename(client->packfile_path, packfile_path) == -1) {
369 err = got_error_from_errno3("rename", client->packfile_path,
370 packfile_path);
371 goto done;
374 free(client->packfile_path);
375 client->packfile_path = NULL;
377 if (rename(client->packidx_path, packidx_path) == -1) {
378 err = got_error_from_errno3("rename", client->packidx_path,
379 packidx_path);
380 goto done;
383 /* Ensure we re-read the pack index list upon next access. */
384 gotd_session.repo->pack_path_mtime.tv_sec = 0;
385 gotd_session.repo->pack_path_mtime.tv_nsec = 0;
387 free(client->packidx_path);
388 client->packidx_path = NULL;
389 done:
390 free(packfile_path);
391 free(packidx_path);
392 return err;
395 static const struct got_error *
396 begin_ref_updates(struct gotd_session_client *client, struct imsg *imsg)
398 struct gotd_imsg_ref_updates_start istart;
399 size_t datalen;
401 if (client->nref_updates != -1)
402 return got_error(GOT_ERR_PRIVSEP_MSG);
404 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
405 if (datalen != sizeof(istart))
406 return got_error(GOT_ERR_PRIVSEP_LEN);
407 memcpy(&istart, imsg->data, sizeof(istart));
409 if (istart.nref_updates <= 0)
410 return got_error(GOT_ERR_PRIVSEP_MSG);
412 client->nref_updates = istart.nref_updates;
413 return NULL;
416 static const struct got_error *
417 validate_namespace(const char *namespace)
419 size_t len = strlen(namespace);
421 if (len < 5 || strncmp("refs/", namespace, 5) != 0 ||
422 namespace[len - 1] != '/') {
423 return got_error_fmt(GOT_ERR_BAD_REF_NAME,
424 "reference namespace '%s'", namespace);
427 return NULL;
430 static const struct got_error *
431 queue_notification(struct got_object_id *old_id, struct got_object_id *new_id,
432 struct got_repository *repo, struct got_reference *ref)
434 const struct got_error *err = NULL;
435 struct gotd_session_client *client = &gotd_session_client;
436 struct gotd_repo *repo_cfg = gotd_session.repo_cfg;
437 struct gotd_imsgev *iev = &client->repo_child_iev;
438 struct got_pathlist_entry *pe;
439 struct gotd_session_notif *notif;
441 if (iev->ibuf.fd == -1 ||
442 STAILQ_EMPTY(&repo_cfg->notification_targets))
443 return NULL; /* notifications unused */
445 TAILQ_FOREACH(pe, &repo_cfg->notification_refs, entry) {
446 const char *refname = pe->path;
447 if (strcmp(got_ref_get_name(ref), refname) == 0)
448 break;
450 if (pe == NULL) {
451 TAILQ_FOREACH(pe, &repo_cfg->notification_ref_namespaces,
452 entry) {
453 const char *namespace = pe->path;
455 err = validate_namespace(namespace);
456 if (err)
457 return err;
458 if (strncmp(namespace, got_ref_get_name(ref),
459 strlen(namespace)) == 0)
460 break;
464 /*
465 * If a branch or a reference namespace was specified in the
466 * configuration file then only send notifications if a match
467 * was found.
468 */
469 if (pe == NULL && (!TAILQ_EMPTY(&repo_cfg->notification_refs) ||
470 !TAILQ_EMPTY(&repo_cfg->notification_ref_namespaces)))
471 return NULL;
473 notif = calloc(1, sizeof(*notif));
474 if (notif == NULL)
475 return got_error_from_errno("calloc");
477 notif->fd = -1;
479 if (old_id == NULL)
480 notif->action = GOTD_NOTIF_ACTION_CREATED;
481 else if (new_id == NULL)
482 notif->action = GOTD_NOTIF_ACTION_REMOVED;
483 else
484 notif->action = GOTD_NOTIF_ACTION_CHANGED;
486 if (old_id != NULL)
487 memcpy(&notif->old_id, old_id, sizeof(notif->old_id));
488 if (new_id != NULL)
489 memcpy(&notif->new_id, new_id, sizeof(notif->new_id));
491 notif->refname = strdup(got_ref_get_name(ref));
492 if (notif->refname == NULL) {
493 err = got_error_from_errno("strdup");
494 goto done;
497 STAILQ_INSERT_TAIL(&notifications, notif, entry);
498 done:
499 if (err && notif) {
500 free(notif->refname);
501 free(notif);
503 return err;
506 /* Forward notification content to the NOTIFY process. */
507 static const struct got_error *
508 forward_notification(struct gotd_session_client *client, struct imsg *imsg)
510 const struct got_error *err = NULL;
511 struct gotd_imsgev *iev = &gotd_session.notifier_iev;
512 struct gotd_session_notif *notif;
513 struct gotd_imsg_notification_content icontent;
514 char *refname = NULL;
515 size_t datalen;
516 struct gotd_imsg_notify inotify;
517 const char *action;
519 memset(&inotify, 0, sizeof(inotify));
521 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
522 if (datalen < sizeof(icontent))
523 return got_error(GOT_ERR_PRIVSEP_LEN);
524 memcpy(&icontent, imsg->data, sizeof(icontent));
525 if (datalen != sizeof(icontent) + icontent.refname_len)
526 return got_error(GOT_ERR_PRIVSEP_LEN);
527 refname = strndup(imsg->data + sizeof(icontent), icontent.refname_len);
528 if (refname == NULL)
529 return got_error_from_errno("strndup");
531 notif = STAILQ_FIRST(&notifications);
532 if (notif == NULL)
533 return got_error(GOT_ERR_PRIVSEP_MSG);
535 STAILQ_REMOVE(&notifications, notif, gotd_session_notif, entry);
537 if (notif->action != icontent.action || notif->fd == -1 ||
538 strcmp(notif->refname, refname) != 0) {
539 err = got_error(GOT_ERR_PRIVSEP_MSG);
540 goto done;
542 if (notif->action == GOTD_NOTIF_ACTION_CREATED) {
543 if (memcmp(notif->new_id.sha1, icontent.new_id,
544 SHA1_DIGEST_LENGTH) != 0) {
545 err = got_error_msg(GOT_ERR_PRIVSEP_MSG,
546 "received notification content for unknown event");
547 goto done;
549 } else if (notif->action == GOTD_NOTIF_ACTION_REMOVED) {
550 if (memcmp(notif->old_id.sha1, icontent.old_id,
551 SHA1_DIGEST_LENGTH) != 0) {
552 err = got_error_msg(GOT_ERR_PRIVSEP_MSG,
553 "received notification content for unknown event");
554 goto done;
556 } else if (memcmp(notif->old_id.sha1, icontent.old_id,
557 SHA1_DIGEST_LENGTH) != 0 ||
558 memcmp(notif->new_id.sha1, icontent.new_id,
559 SHA1_DIGEST_LENGTH) != 0) {
560 err = got_error_msg(GOT_ERR_PRIVSEP_MSG,
561 "received notification content for unknown event");
562 goto done;
565 switch (notif->action) {
566 case GOTD_NOTIF_ACTION_CREATED:
567 action = "created";
568 break;
569 case GOTD_NOTIF_ACTION_REMOVED:
570 action = "removed";
571 break;
572 case GOTD_NOTIF_ACTION_CHANGED:
573 action = "changed";
574 break;
575 default:
576 err = got_error(GOT_ERR_PRIVSEP_MSG);
577 goto done;
580 strlcpy(inotify.repo_name, gotd_session.repo_cfg->name,
581 sizeof(inotify.repo_name));
583 snprintf(inotify.subject_line, sizeof(inotify.subject_line),
584 "%s: %s %s %s", gotd_session.repo_cfg->name,
585 client->username, action, notif->refname);
587 if (gotd_imsg_compose_event(iev, GOTD_IMSG_NOTIFY,
588 PROC_SESSION_WRITE, notif->fd, &inotify, sizeof(inotify))
589 == -1) {
590 err = got_error_from_errno("imsg compose NOTIFY");
591 goto done;
593 notif->fd = -1;
594 done:
595 if (notif->fd != -1)
596 close(notif->fd);
597 free(notif);
598 free(refname);
599 return err;
602 /* Request notification content from REPO_WRITE process. */
603 static const struct got_error *
604 request_notification(struct gotd_session_notif *notif)
606 const struct got_error *err = NULL;
607 struct gotd_session_client *client = &gotd_session_client;
608 struct gotd_imsgev *iev = &client->repo_child_iev;
609 struct gotd_imsg_notification_content icontent;
610 struct ibuf *wbuf;
611 size_t len;
612 int fd;
614 fd = got_opentempfd();
615 if (fd == -1)
616 return got_error_from_errno("got_opentemp");
618 memset(&icontent, 0, sizeof(icontent));
619 icontent.client_id = client->id;
621 icontent.action = notif->action;
622 memcpy(&icontent.old_id, &notif->old_id, sizeof(notif->old_id));
623 memcpy(&icontent.new_id, &notif->new_id, sizeof(notif->new_id));
624 icontent.refname_len = strlen(notif->refname);
626 len = sizeof(icontent) + icontent.refname_len;
627 wbuf = imsg_create(&iev->ibuf, GOTD_IMSG_NOTIFY,
628 gotd_session.proc_id, gotd_session.pid, len);
629 if (wbuf == NULL) {
630 err = got_error_from_errno("imsg_create NOTIFY");
631 goto done;
633 if (imsg_add(wbuf, &icontent, sizeof(icontent)) == -1) {
634 err = got_error_from_errno("imsg_add NOTIFY");
635 goto done;
637 if (imsg_add(wbuf, notif->refname, icontent.refname_len) == -1) {
638 err = got_error_from_errno("imsg_add NOTIFY");
639 goto done;
642 notif->fd = dup(fd);
643 if (notif->fd == -1) {
644 err = got_error_from_errno("dup");
645 goto done;
648 ibuf_fd_set(wbuf, fd);
649 fd = -1;
651 imsg_close(&iev->ibuf, wbuf);
652 gotd_imsg_event_add(iev);
653 done:
654 if (err && fd != -1)
655 close(fd);
656 return err;
659 static const struct got_error *
660 update_ref(int *shut, struct gotd_session_client *client,
661 const char *repo_path, struct imsg *imsg)
663 const struct got_error *err = NULL;
664 struct got_repository *repo = gotd_session.repo;
665 struct got_reference *ref = NULL;
666 struct gotd_imsg_ref_update iref;
667 struct got_object_id old_id, new_id;
668 struct gotd_session_notif *notif;
669 struct got_object_id *id = NULL;
670 char *refname = NULL;
671 size_t datalen;
672 int locked = 0;
673 char hex1[SHA1_DIGEST_STRING_LENGTH];
674 char hex2[SHA1_DIGEST_STRING_LENGTH];
676 log_debug("update-ref from uid %d", client->euid);
678 if (client->nref_updates <= 0)
679 return got_error(GOT_ERR_PRIVSEP_MSG);
681 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
682 if (datalen < sizeof(iref))
683 return got_error(GOT_ERR_PRIVSEP_LEN);
684 memcpy(&iref, imsg->data, sizeof(iref));
685 if (datalen != sizeof(iref) + iref.name_len)
686 return got_error(GOT_ERR_PRIVSEP_LEN);
687 refname = strndup(imsg->data + sizeof(iref), iref.name_len);
688 if (refname == NULL)
689 return got_error_from_errno("strndup");
691 log_debug("updating ref %s for uid %d", refname, client->euid);
693 memcpy(old_id.sha1, iref.old_id, SHA1_DIGEST_LENGTH);
694 memcpy(new_id.sha1, iref.new_id, SHA1_DIGEST_LENGTH);
695 err = got_repo_find_object_id(iref.delete_ref ? &old_id : &new_id,
696 repo);
697 if (err)
698 goto done;
700 if (iref.ref_is_new) {
701 err = got_ref_open(&ref, repo, refname, 0);
702 if (err) {
703 if (err->code != GOT_ERR_NOT_REF)
704 goto done;
705 err = got_ref_alloc(&ref, refname, &new_id);
706 if (err)
707 goto done;
708 err = got_ref_write(ref, repo); /* will lock/unlock */
709 if (err)
710 goto done;
711 err = queue_notification(NULL, &new_id, repo, ref);
712 if (err)
713 goto done;
714 } else {
715 err = got_ref_resolve(&id, repo, ref);
716 if (err)
717 goto done;
718 got_object_id_hex(&new_id, hex1, sizeof(hex1));
719 got_object_id_hex(id, hex2, sizeof(hex2));
720 err = got_error_fmt(GOT_ERR_REF_BUSY,
721 "Addition %s: %s failed; %s: %s has been "
722 "created by someone else while transaction "
723 "was in progress",
724 got_ref_get_name(ref), hex1,
725 got_ref_get_name(ref), hex2);
726 goto done;
728 } else if (iref.delete_ref) {
729 err = got_ref_open(&ref, repo, refname, 1 /* lock */);
730 if (err)
731 goto done;
732 locked = 1;
734 err = got_ref_resolve(&id, repo, ref);
735 if (err)
736 goto done;
738 if (got_object_id_cmp(id, &old_id) != 0) {
739 got_object_id_hex(&old_id, hex1, sizeof(hex1));
740 got_object_id_hex(id, hex2, sizeof(hex2));
741 err = got_error_fmt(GOT_ERR_REF_BUSY,
742 "Deletion %s: %s failed; %s: %s has been "
743 "created by someone else while transaction "
744 "was in progress",
745 got_ref_get_name(ref), hex1,
746 got_ref_get_name(ref), hex2);
747 goto done;
750 err = got_ref_delete(ref, repo);
751 if (err)
752 goto done;
753 err = queue_notification(&old_id, NULL, repo, ref);
754 if (err)
755 goto done;
756 free(id);
757 id = NULL;
758 } else {
759 err = got_ref_open(&ref, repo, refname, 1 /* lock */);
760 if (err)
761 goto done;
762 locked = 1;
764 err = got_ref_resolve(&id, repo, ref);
765 if (err)
766 goto done;
768 if (got_object_id_cmp(id, &old_id) != 0) {
769 got_object_id_hex(&old_id, hex1, sizeof(hex1));
770 got_object_id_hex(id, hex2, sizeof(hex2));
771 err = got_error_fmt(GOT_ERR_REF_BUSY,
772 "Update %s: %s failed; %s: %s has been "
773 "created by someone else while transaction "
774 "was in progress",
775 got_ref_get_name(ref), hex1,
776 got_ref_get_name(ref), hex2);
777 goto done;
780 if (got_object_id_cmp(&new_id, &old_id) != 0) {
781 err = got_ref_change_ref(ref, &new_id);
782 if (err)
783 goto done;
784 err = got_ref_write(ref, repo);
785 if (err)
786 goto done;
787 err = queue_notification(&old_id, &new_id, repo, ref);
788 if (err)
789 goto done;
792 free(id);
793 id = NULL;
795 done:
796 if (err) {
797 if (err->code == GOT_ERR_LOCKFILE_TIMEOUT) {
798 err = got_error_fmt(GOT_ERR_LOCKFILE_TIMEOUT,
799 "could not acquire exclusive file lock for %s",
800 refname);
802 send_ref_update_ng(client, &iref, refname, err->msg);
803 } else
804 send_ref_update_ok(client, &iref, refname);
806 if (client->nref_updates > 0) {
807 client->nref_updates--;
808 if (client->nref_updates == 0) {
809 send_refs_updated(client);
810 notif = STAILQ_FIRST(&notifications);
811 if (notif) {
812 client->state = GOTD_STATE_NOTIFY;
813 err = request_notification(notif);
814 if (err) {
815 log_warn("could not send notification: "
816 "%s", err->msg);
817 client->flush_disconnect = 1;
819 } else
820 client->flush_disconnect = 1;
824 if (locked) {
825 const struct got_error *unlock_err;
826 unlock_err = got_ref_unlock(ref);
827 if (unlock_err && err == NULL)
828 err = unlock_err;
830 if (ref)
831 got_ref_close(ref);
832 free(refname);
833 free(id);
834 return err;
837 static const struct got_error *
838 recv_notification_content(uint32_t *client_id, struct imsg *imsg)
840 struct gotd_imsg_notification_content inotif;
841 size_t datalen;
843 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
844 if (datalen < sizeof(inotif))
845 return got_error(GOT_ERR_PRIVSEP_LEN);
846 memcpy(&inotif, imsg->data, sizeof(inotif));
848 *client_id = inotif.client_id;
849 return NULL;
852 static void
853 session_dispatch_repo_child(int fd, short event, void *arg)
855 struct gotd_imsgev *iev = arg;
856 struct imsgbuf *ibuf = &iev->ibuf;
857 struct gotd_session_client *client = &gotd_session_client;
858 ssize_t n;
859 int shut = 0;
860 struct imsg imsg;
862 if (event & EV_READ) {
863 if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
864 fatal("imsg_read error");
865 if (n == 0) {
866 /* Connection closed. */
867 shut = 1;
868 goto done;
872 if (event & EV_WRITE) {
873 n = msgbuf_write(&ibuf->w);
874 if (n == -1 && errno != EAGAIN)
875 fatal("msgbuf_write");
876 if (n == 0) {
877 /* Connection closed. */
878 shut = 1;
879 goto done;
883 for (;;) {
884 const struct got_error *err = NULL;
885 uint32_t client_id = 0;
886 int do_disconnect = 0;
887 int do_ref_updates = 0, do_ref_update = 0;
888 int do_packfile_install = 0, do_notify = 0;
890 if ((n = imsg_get(ibuf, &imsg)) == -1)
891 fatal("%s: imsg_get error", __func__);
892 if (n == 0) /* No more messages. */
893 break;
895 switch (imsg.hdr.type) {
896 case GOTD_IMSG_ERROR:
897 do_disconnect = 1;
898 err = gotd_imsg_recv_error(&client_id, &imsg);
899 break;
900 case GOTD_IMSG_PACKFILE_DONE:
901 do_disconnect = 1;
902 err = recv_packfile_done(&client_id, &imsg);
903 break;
904 case GOTD_IMSG_PACKFILE_INSTALL:
905 err = recv_packfile_install(&client_id, &imsg);
906 if (err == NULL)
907 do_packfile_install = 1;
908 break;
909 case GOTD_IMSG_REF_UPDATES_START:
910 err = recv_ref_updates_start(&client_id, &imsg);
911 if (err == NULL)
912 do_ref_updates = 1;
913 break;
914 case GOTD_IMSG_REF_UPDATE:
915 err = recv_ref_update(&client_id, &imsg);
916 if (err == NULL)
917 do_ref_update = 1;
918 break;
919 case GOTD_IMSG_NOTIFY:
920 err = recv_notification_content(&client_id, &imsg);
921 if (err == NULL)
922 do_notify = 1;
923 break;
924 default:
925 log_debug("unexpected imsg %d", imsg.hdr.type);
926 break;
929 if (do_disconnect) {
930 if (err)
931 disconnect_on_error(client, err);
932 else
933 disconnect(client);
934 } else {
935 struct gotd_session_notif *notif;
937 if (do_packfile_install)
938 err = install_pack(client,
939 gotd_session.repo->path, &imsg);
940 else if (do_ref_updates)
941 err = begin_ref_updates(client, &imsg);
942 else if (do_ref_update)
943 err = update_ref(&shut, client,
944 gotd_session.repo->path, &imsg);
945 else if (do_notify)
946 err = forward_notification(client, &imsg);
947 if (err)
948 log_warnx("uid %d: %s", client->euid, err->msg);
950 notif = STAILQ_FIRST(&notifications);
951 if (notif && do_notify) {
952 /* Request content for next notification. */
953 err = request_notification(notif);
954 if (err) {
955 log_warn("could not send notification: "
956 "%s", err->msg);
957 shut = 1;
961 imsg_free(&imsg);
963 done:
964 if (!shut) {
965 gotd_imsg_event_add(iev);
966 } else {
967 /* This pipe is dead. Remove its event handler */
968 event_del(&iev->ev);
969 event_loopexit(NULL);
973 static const struct got_error *
974 recv_capabilities(struct gotd_session_client *client, struct imsg *imsg)
976 struct gotd_imsg_capabilities icapas;
977 size_t datalen;
979 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
980 if (datalen != sizeof(icapas))
981 return got_error(GOT_ERR_PRIVSEP_LEN);
982 memcpy(&icapas, imsg->data, sizeof(icapas));
984 client->ncapa_alloc = icapas.ncapabilities;
985 client->capabilities = calloc(client->ncapa_alloc,
986 sizeof(*client->capabilities));
987 if (client->capabilities == NULL) {
988 client->ncapa_alloc = 0;
989 return got_error_from_errno("calloc");
992 log_debug("expecting %zu capabilities from uid %d",
993 client->ncapa_alloc, client->euid);
994 return NULL;
997 static const struct got_error *
998 recv_capability(struct gotd_session_client *client, struct imsg *imsg)
1000 struct gotd_imsg_capability icapa;
1001 struct gotd_client_capability *capa;
1002 size_t datalen;
1003 char *key, *value = NULL;
1005 if (client->capabilities == NULL ||
1006 client->ncapabilities >= client->ncapa_alloc) {
1007 return got_error_msg(GOT_ERR_BAD_REQUEST,
1008 "unexpected capability received");
1011 memset(&icapa, 0, sizeof(icapa));
1013 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
1014 if (datalen < sizeof(icapa))
1015 return got_error(GOT_ERR_PRIVSEP_LEN);
1016 memcpy(&icapa, imsg->data, sizeof(icapa));
1018 if (datalen != sizeof(icapa) + icapa.key_len + icapa.value_len)
1019 return got_error(GOT_ERR_PRIVSEP_LEN);
1021 key = strndup(imsg->data + sizeof(icapa), icapa.key_len);
1022 if (key == NULL)
1023 return got_error_from_errno("strndup");
1024 if (icapa.value_len > 0) {
1025 value = strndup(imsg->data + sizeof(icapa) + icapa.key_len,
1026 icapa.value_len);
1027 if (value == NULL) {
1028 free(key);
1029 return got_error_from_errno("strndup");
1033 capa = &client->capabilities[client->ncapabilities++];
1034 capa->key = key;
1035 capa->value = value;
1037 if (value)
1038 log_debug("uid %d: capability %s=%s", client->euid, key, value);
1039 else
1040 log_debug("uid %d: capability %s", client->euid, key);
1042 return NULL;
1045 static const struct got_error *
1046 ensure_client_is_reading(struct gotd_session_client *client)
1048 if (client->is_writing) {
1049 return got_error_fmt(GOT_ERR_BAD_PACKET,
1050 "uid %d made a read-request but is not reading from "
1051 "a repository", client->euid);
1054 return NULL;
1057 static const struct got_error *
1058 ensure_client_is_writing(struct gotd_session_client *client)
1060 if (!client->is_writing) {
1061 return got_error_fmt(GOT_ERR_BAD_PACKET,
1062 "uid %d made a write-request but is not writing to "
1063 "a repository", client->euid);
1066 return NULL;
1069 static const struct got_error *
1070 forward_want(struct gotd_session_client *client, struct imsg *imsg)
1072 struct gotd_imsg_want ireq;
1073 struct gotd_imsg_want iwant;
1074 size_t datalen;
1076 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
1077 if (datalen != sizeof(ireq))
1078 return got_error(GOT_ERR_PRIVSEP_LEN);
1080 memcpy(&ireq, imsg->data, datalen);
1082 memset(&iwant, 0, sizeof(iwant));
1083 memcpy(iwant.object_id, ireq.object_id, SHA1_DIGEST_LENGTH);
1084 iwant.client_id = client->id;
1086 if (gotd_imsg_compose_event(&client->repo_child_iev, GOTD_IMSG_WANT,
1087 gotd_session.proc_id, -1, &iwant, sizeof(iwant)) == -1)
1088 return got_error_from_errno("imsg compose WANT");
1090 return NULL;
1093 static const struct got_error *
1094 forward_ref_update(struct gotd_session_client *client, struct imsg *imsg)
1096 const struct got_error *err = NULL;
1097 struct gotd_imsg_ref_update ireq;
1098 struct gotd_imsg_ref_update *iref = NULL;
1099 size_t datalen;
1101 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
1102 if (datalen < sizeof(ireq))
1103 return got_error(GOT_ERR_PRIVSEP_LEN);
1104 memcpy(&ireq, imsg->data, sizeof(ireq));
1105 if (datalen != sizeof(ireq) + ireq.name_len)
1106 return got_error(GOT_ERR_PRIVSEP_LEN);
1108 iref = malloc(datalen);
1109 if (iref == NULL)
1110 return got_error_from_errno("malloc");
1111 memcpy(iref, imsg->data, datalen);
1113 iref->client_id = client->id;
1114 if (gotd_imsg_compose_event(&client->repo_child_iev,
1115 GOTD_IMSG_REF_UPDATE, gotd_session.proc_id, -1,
1116 iref, datalen) == -1)
1117 err = got_error_from_errno("imsg compose REF_UPDATE");
1118 free(iref);
1119 return err;
1122 static const struct got_error *
1123 forward_have(struct gotd_session_client *client, struct imsg *imsg)
1125 struct gotd_imsg_have ireq;
1126 struct gotd_imsg_have ihave;
1127 size_t datalen;
1129 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
1130 if (datalen != sizeof(ireq))
1131 return got_error(GOT_ERR_PRIVSEP_LEN);
1133 memcpy(&ireq, imsg->data, datalen);
1135 memset(&ihave, 0, sizeof(ihave));
1136 memcpy(ihave.object_id, ireq.object_id, SHA1_DIGEST_LENGTH);
1137 ihave.client_id = client->id;
1139 if (gotd_imsg_compose_event(&client->repo_child_iev, GOTD_IMSG_HAVE,
1140 gotd_session.proc_id, -1, &ihave, sizeof(ihave)) == -1)
1141 return got_error_from_errno("imsg compose HAVE");
1143 return NULL;
1146 static int
1147 client_has_capability(struct gotd_session_client *client, const char *capastr)
1149 struct gotd_client_capability *capa;
1150 size_t i;
1152 if (client->ncapabilities == 0)
1153 return 0;
1155 for (i = 0; i < client->ncapabilities; i++) {
1156 capa = &client->capabilities[i];
1157 if (strcmp(capa->key, capastr) == 0)
1158 return 1;
1161 return 0;
1164 static const struct got_error *
1165 recv_packfile(struct gotd_session_client *client)
1167 const struct got_error *err = NULL;
1168 struct gotd_imsg_recv_packfile ipack;
1169 struct gotd_imsg_packfile_pipe ipipe;
1170 struct gotd_imsg_packidx_file ifile;
1171 char *basepath = NULL, *pack_path = NULL, *idx_path = NULL;
1172 int packfd = -1, idxfd = -1;
1173 int pipe[2] = { -1, -1 };
1175 if (client->packfile_path) {
1176 return got_error_fmt(GOT_ERR_PRIVSEP_MSG,
1177 "uid %d already has a pack file", client->euid);
1180 if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pipe) == -1)
1181 return got_error_from_errno("socketpair");
1183 memset(&ipipe, 0, sizeof(ipipe));
1184 ipipe.client_id = client->id;
1186 /* Send pack pipe end 0 to repo child process. */
1187 if (gotd_imsg_compose_event(&client->repo_child_iev,
1188 GOTD_IMSG_PACKFILE_PIPE, gotd_session.proc_id, pipe[0],
1189 &ipipe, sizeof(ipipe)) == -1) {
1190 err = got_error_from_errno("imsg compose PACKFILE_PIPE");
1191 pipe[0] = -1;
1192 goto done;
1194 pipe[0] = -1;
1196 /* Send pack pipe end 1 to gotsh(1) (expects just an fd, no data). */
1197 if (gotd_imsg_compose_event(&client->iev,
1198 GOTD_IMSG_PACKFILE_PIPE, gotd_session.proc_id, pipe[1],
1199 NULL, 0) == -1)
1200 err = got_error_from_errno("imsg compose PACKFILE_PIPE");
1201 pipe[1] = -1;
1203 if (asprintf(&basepath, "%s/%s/receiving-from-uid-%d.pack",
1204 got_repo_get_path(gotd_session.repo), GOT_OBJECTS_PACK_DIR,
1205 client->euid) == -1) {
1206 err = got_error_from_errno("asprintf");
1207 goto done;
1210 err = got_opentemp_named_fd(&pack_path, &packfd, basepath, "");
1211 if (err)
1212 goto done;
1213 if (fchmod(packfd, GOT_DEFAULT_PACK_MODE) == -1) {
1214 err = got_error_from_errno2("fchmod", pack_path);
1215 goto done;
1218 free(basepath);
1219 if (asprintf(&basepath, "%s/%s/receiving-from-uid-%d.idx",
1220 got_repo_get_path(gotd_session.repo), GOT_OBJECTS_PACK_DIR,
1221 client->euid) == -1) {
1222 err = got_error_from_errno("asprintf");
1223 basepath = NULL;
1224 goto done;
1226 err = got_opentemp_named_fd(&idx_path, &idxfd, basepath, "");
1227 if (err)
1228 goto done;
1229 if (fchmod(idxfd, GOT_DEFAULT_PACK_MODE) == -1) {
1230 err = got_error_from_errno2("fchmod", idx_path);
1231 goto done;
1234 memset(&ifile, 0, sizeof(ifile));
1235 ifile.client_id = client->id;
1236 if (gotd_imsg_compose_event(&client->repo_child_iev,
1237 GOTD_IMSG_PACKIDX_FILE, gotd_session.proc_id,
1238 idxfd, &ifile, sizeof(ifile)) == -1) {
1239 err = got_error_from_errno("imsg compose PACKIDX_FILE");
1240 idxfd = -1;
1241 goto done;
1243 idxfd = -1;
1245 memset(&ipack, 0, sizeof(ipack));
1246 ipack.client_id = client->id;
1247 if (client_has_capability(client, GOT_CAPA_REPORT_STATUS))
1248 ipack.report_status = 1;
1250 if (gotd_imsg_compose_event(&client->repo_child_iev,
1251 GOTD_IMSG_RECV_PACKFILE, gotd_session.proc_id, packfd,
1252 &ipack, sizeof(ipack)) == -1) {
1253 err = got_error_from_errno("imsg compose RECV_PACKFILE");
1254 packfd = -1;
1255 goto done;
1257 packfd = -1;
1259 done:
1260 free(basepath);
1261 if (pipe[0] != -1 && close(pipe[0]) == -1 && err == NULL)
1262 err = got_error_from_errno("close");
1263 if (pipe[1] != -1 && close(pipe[1]) == -1 && err == NULL)
1264 err = got_error_from_errno("close");
1265 if (packfd != -1 && close(packfd) == -1 && err == NULL)
1266 err = got_error_from_errno("close");
1267 if (idxfd != -1 && close(idxfd) == -1 && err == NULL)
1268 err = got_error_from_errno("close");
1269 if (err) {
1270 free(pack_path);
1271 free(idx_path);
1272 } else {
1273 client->packfile_path = pack_path;
1274 client->packidx_path = idx_path;
1276 return err;
1279 static const struct got_error *
1280 send_packfile(struct gotd_session_client *client)
1282 const struct got_error *err = NULL;
1283 struct gotd_imsg_send_packfile ipack;
1284 struct gotd_imsg_packfile_pipe ipipe;
1285 int pipe[2];
1287 if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pipe) == -1)
1288 return got_error_from_errno("socketpair");
1290 memset(&ipack, 0, sizeof(ipack));
1291 memset(&ipipe, 0, sizeof(ipipe));
1293 ipack.client_id = client->id;
1294 if (client_has_capability(client, GOT_CAPA_SIDE_BAND_64K))
1295 ipack.report_progress = 1;
1297 client->delta_cache_fd = got_opentempfd();
1298 if (client->delta_cache_fd == -1)
1299 return got_error_from_errno("got_opentempfd");
1301 if (gotd_imsg_compose_event(&client->repo_child_iev,
1302 GOTD_IMSG_SEND_PACKFILE, PROC_GOTD, client->delta_cache_fd,
1303 &ipack, sizeof(ipack)) == -1) {
1304 err = got_error_from_errno("imsg compose SEND_PACKFILE");
1305 close(pipe[0]);
1306 close(pipe[1]);
1307 return err;
1310 ipipe.client_id = client->id;
1312 /* Send pack pipe end 0 to repo child process. */
1313 if (gotd_imsg_compose_event(&client->repo_child_iev,
1314 GOTD_IMSG_PACKFILE_PIPE, PROC_GOTD,
1315 pipe[0], &ipipe, sizeof(ipipe)) == -1) {
1316 err = got_error_from_errno("imsg compose PACKFILE_PIPE");
1317 close(pipe[1]);
1318 return err;
1321 /* Send pack pipe end 1 to gotsh(1) (expects just an fd, no data). */
1322 if (gotd_imsg_compose_event(&client->iev,
1323 GOTD_IMSG_PACKFILE_PIPE, PROC_GOTD, pipe[1], NULL, 0) == -1)
1324 err = got_error_from_errno("imsg compose PACKFILE_PIPE");
1326 return err;
1329 static void
1330 session_dispatch_client(int fd, short events, void *arg)
1332 struct gotd_imsgev *iev = arg;
1333 struct imsgbuf *ibuf = &iev->ibuf;
1334 struct gotd_session_client *client = &gotd_session_client;
1335 const struct got_error *err = NULL;
1336 struct imsg imsg;
1337 ssize_t n;
1339 if (events & EV_WRITE) {
1340 while (ibuf->w.queued) {
1341 n = msgbuf_write(&ibuf->w);
1342 if (n == -1 && errno == EPIPE) {
1344 * The client has closed its socket.
1345 * This can happen when Git clients are
1346 * done sending pack file data.
1348 msgbuf_clear(&ibuf->w);
1349 continue;
1350 } else if (n == -1 && errno != EAGAIN) {
1351 err = got_error_from_errno("imsg_flush");
1352 disconnect_on_error(client, err);
1353 return;
1355 if (n == 0) {
1356 /* Connection closed. */
1357 err = got_error(GOT_ERR_EOF);
1358 disconnect_on_error(client, err);
1359 return;
1363 if (client->flush_disconnect) {
1364 disconnect(client);
1365 return;
1369 if ((events & EV_READ) == 0)
1370 return;
1372 memset(&imsg, 0, sizeof(imsg));
1374 while (err == NULL) {
1375 err = gotd_imsg_recv(&imsg, ibuf, 0);
1376 if (err) {
1377 if (err->code == GOT_ERR_PRIVSEP_READ)
1378 err = NULL;
1379 else if (err->code == GOT_ERR_EOF &&
1380 client->state == GOTD_STATE_EXPECT_CAPABILITIES) {
1382 * The client has closed its socket before
1383 * sending its capability announcement.
1384 * This can happen when Git clients have
1385 * no ref-updates to send.
1387 disconnect_on_error(client, err);
1388 return;
1390 break;
1393 evtimer_del(&client->tmo);
1395 switch (imsg.hdr.type) {
1396 case GOTD_IMSG_CAPABILITIES:
1397 if (client->state != GOTD_STATE_EXPECT_CAPABILITIES) {
1398 err = got_error_msg(GOT_ERR_BAD_REQUEST,
1399 "unexpected capabilities received");
1400 break;
1402 log_debug("receiving capabilities from uid %d",
1403 client->euid);
1404 err = recv_capabilities(client, &imsg);
1405 break;
1406 case GOTD_IMSG_CAPABILITY:
1407 if (client->state != GOTD_STATE_EXPECT_CAPABILITIES) {
1408 err = got_error_msg(GOT_ERR_BAD_REQUEST,
1409 "unexpected capability received");
1410 break;
1412 err = recv_capability(client, &imsg);
1413 if (err || client->ncapabilities < client->ncapa_alloc)
1414 break;
1415 if (!client->is_writing) {
1416 client->state = GOTD_STATE_EXPECT_WANT;
1417 client->accept_flush_pkt = 1;
1418 log_debug("uid %d: expecting want-lines",
1419 client->euid);
1420 } else if (client->is_writing) {
1421 client->state = GOTD_STATE_EXPECT_REF_UPDATE;
1422 client->accept_flush_pkt = 1;
1423 log_debug("uid %d: expecting ref-update-lines",
1424 client->euid);
1425 } else
1426 fatalx("client %d is both reading and writing",
1427 client->euid);
1428 break;
1429 case GOTD_IMSG_WANT:
1430 if (client->state != GOTD_STATE_EXPECT_WANT) {
1431 err = got_error_msg(GOT_ERR_BAD_REQUEST,
1432 "unexpected want-line received");
1433 break;
1435 log_debug("received want-line from uid %d",
1436 client->euid);
1437 err = ensure_client_is_reading(client);
1438 if (err)
1439 break;
1440 client->accept_flush_pkt = 1;
1441 err = forward_want(client, &imsg);
1442 break;
1443 case GOTD_IMSG_REF_UPDATE:
1444 if (client->state != GOTD_STATE_EXPECT_REF_UPDATE &&
1445 client->state !=
1446 GOTD_STATE_EXPECT_MORE_REF_UPDATES) {
1447 err = got_error_msg(GOT_ERR_BAD_REQUEST,
1448 "unexpected ref-update-line received");
1449 break;
1451 log_debug("received ref-update-line from uid %d",
1452 client->euid);
1453 err = ensure_client_is_writing(client);
1454 if (err)
1455 break;
1456 err = forward_ref_update(client, &imsg);
1457 if (err)
1458 break;
1459 client->state = GOTD_STATE_EXPECT_MORE_REF_UPDATES;
1460 client->accept_flush_pkt = 1;
1461 break;
1462 case GOTD_IMSG_HAVE:
1463 if (client->state != GOTD_STATE_EXPECT_HAVE) {
1464 err = got_error_msg(GOT_ERR_BAD_REQUEST,
1465 "unexpected have-line received");
1466 break;
1468 log_debug("received have-line from uid %d",
1469 client->euid);
1470 err = ensure_client_is_reading(client);
1471 if (err)
1472 break;
1473 err = forward_have(client, &imsg);
1474 if (err)
1475 break;
1476 client->accept_flush_pkt = 1;
1477 break;
1478 case GOTD_IMSG_FLUSH:
1479 if (client->state == GOTD_STATE_EXPECT_WANT ||
1480 client->state == GOTD_STATE_EXPECT_HAVE) {
1481 err = ensure_client_is_reading(client);
1482 if (err)
1483 break;
1484 } else if (client->state ==
1485 GOTD_STATE_EXPECT_MORE_REF_UPDATES) {
1486 err = ensure_client_is_writing(client);
1487 if (err)
1488 break;
1489 } else if (client->state != GOTD_STATE_EXPECT_DONE) {
1490 err = got_error_msg(GOT_ERR_BAD_REQUEST,
1491 "unexpected flush-pkt received");
1492 break;
1494 if (!client->accept_flush_pkt) {
1495 err = got_error_msg(GOT_ERR_BAD_REQUEST,
1496 "unexpected flush-pkt received");
1497 break;
1501 * Accept just one flush packet at a time.
1502 * Future client state transitions will set this flag
1503 * again if another flush packet is expected.
1505 client->accept_flush_pkt = 0;
1507 log_debug("received flush-pkt from uid %d",
1508 client->euid);
1509 if (client->state == GOTD_STATE_EXPECT_WANT) {
1510 client->state = GOTD_STATE_EXPECT_HAVE;
1511 log_debug("uid %d: expecting have-lines",
1512 client->euid);
1513 } else if (client->state == GOTD_STATE_EXPECT_HAVE) {
1514 client->state = GOTD_STATE_EXPECT_DONE;
1515 client->accept_flush_pkt = 1;
1516 log_debug("uid %d: expecting 'done'",
1517 client->euid);
1518 } else if (client->state ==
1519 GOTD_STATE_EXPECT_MORE_REF_UPDATES) {
1520 client->state = GOTD_STATE_EXPECT_PACKFILE;
1521 log_debug("uid %d: expecting packfile",
1522 client->euid);
1523 err = recv_packfile(client);
1524 } else if (client->state != GOTD_STATE_EXPECT_DONE) {
1525 /* should not happen, see above */
1526 err = got_error_msg(GOT_ERR_BAD_REQUEST,
1527 "unexpected client state");
1528 break;
1530 break;
1531 case GOTD_IMSG_DONE:
1532 if (client->state != GOTD_STATE_EXPECT_HAVE &&
1533 client->state != GOTD_STATE_EXPECT_DONE) {
1534 err = got_error_msg(GOT_ERR_BAD_REQUEST,
1535 "unexpected flush-pkt received");
1536 break;
1538 log_debug("received 'done' from uid %d", client->euid);
1539 err = ensure_client_is_reading(client);
1540 if (err)
1541 break;
1542 client->state = GOTD_STATE_DONE;
1543 client->accept_flush_pkt = 1;
1544 err = send_packfile(client);
1545 break;
1546 default:
1547 log_debug("unexpected imsg %d", imsg.hdr.type);
1548 err = got_error(GOT_ERR_PRIVSEP_MSG);
1549 break;
1552 imsg_free(&imsg);
1555 if (err) {
1556 if (err->code != GOT_ERR_EOF ||
1557 client->state != GOTD_STATE_EXPECT_PACKFILE)
1558 disconnect_on_error(client, err);
1559 } else {
1560 gotd_imsg_event_add(iev);
1561 evtimer_add(&client->tmo, &gotd_session.request_timeout);
1565 static const struct got_error *
1566 list_refs_request(void)
1568 static const struct got_error *err;
1569 struct gotd_session_client *client = &gotd_session_client;
1570 struct gotd_imsgev *iev = &client->repo_child_iev;
1571 struct gotd_imsg_list_refs_internal ilref;
1572 int fd;
1574 if (client->state != GOTD_STATE_EXPECT_LIST_REFS)
1575 return got_error(GOT_ERR_PRIVSEP_MSG);
1577 memset(&ilref, 0, sizeof(ilref));
1578 ilref.client_id = client->id;
1580 fd = dup(client->fd);
1581 if (fd == -1)
1582 return got_error_from_errno("dup");
1584 if (gotd_imsg_compose_event(iev, GOTD_IMSG_LIST_REFS_INTERNAL,
1585 gotd_session.proc_id, fd, &ilref, sizeof(ilref)) == -1) {
1586 err = got_error_from_errno("imsg compose LIST_REFS_INTERNAL");
1587 close(fd);
1588 return err;
1591 client->state = GOTD_STATE_EXPECT_CAPABILITIES;
1592 log_debug("uid %d: expecting capabilities", client->euid);
1593 return NULL;
1596 static const struct got_error *
1597 recv_connect(struct imsg *imsg)
1599 struct gotd_session_client *client = &gotd_session_client;
1600 struct gotd_imsg_connect iconnect;
1601 size_t datalen;
1603 if (client->state != GOTD_STATE_EXPECT_LIST_REFS)
1604 return got_error(GOT_ERR_PRIVSEP_MSG);
1606 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
1607 if (datalen < sizeof(iconnect))
1608 return got_error(GOT_ERR_PRIVSEP_LEN);
1609 memcpy(&iconnect, imsg->data, sizeof(iconnect));
1610 if (iconnect.username_len == 0 ||
1611 datalen != sizeof(iconnect) + iconnect.username_len)
1612 return got_error(GOT_ERR_PRIVSEP_LEN);
1614 client->euid = iconnect.euid;
1615 client->egid = iconnect.egid;
1616 client->fd = imsg_get_fd(imsg);
1617 if (client->fd == -1)
1618 return got_error(GOT_ERR_PRIVSEP_NO_FD);
1620 client->username = strndup(imsg->data + sizeof(iconnect),
1621 iconnect.username_len);
1622 if (client->username == NULL)
1623 return got_error_from_errno("strndup");
1625 imsg_init(&client->iev.ibuf, client->fd);
1626 client->iev.handler = session_dispatch_client;
1627 client->iev.events = EV_READ;
1628 client->iev.handler_arg = NULL;
1629 event_set(&client->iev.ev, client->iev.ibuf.fd, EV_READ,
1630 session_dispatch_client, &client->iev);
1631 gotd_imsg_event_add(&client->iev);
1632 evtimer_set(&client->tmo, gotd_request_timeout, client);
1634 return NULL;
1637 static void
1638 session_dispatch_notifier(int fd, short event, void *arg)
1640 const struct got_error *err;
1641 struct gotd_session_client *client = &gotd_session_client;
1642 struct gotd_imsgev *iev = arg;
1643 struct imsgbuf *ibuf = &iev->ibuf;
1644 ssize_t n;
1645 int shut = 0;
1646 struct imsg imsg;
1647 struct gotd_session_notif *notif;
1649 if (event & EV_READ) {
1650 if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
1651 fatal("imsg_read error");
1652 if (n == 0) {
1653 /* Connection closed. */
1654 shut = 1;
1655 goto done;
1659 if (event & EV_WRITE) {
1660 n = msgbuf_write(&ibuf->w);
1661 if (n == -1 && errno != EAGAIN)
1662 fatal("msgbuf_write");
1663 if (n == 0) {
1664 /* Connection closed. */
1665 shut = 1;
1666 goto done;
1670 for (;;) {
1671 if ((n = imsg_get(ibuf, &imsg)) == -1)
1672 fatal("%s: imsg_get error", __func__);
1673 if (n == 0) /* No more messages. */
1674 break;
1676 switch (imsg.hdr.type) {
1677 case GOTD_IMSG_NOTIFICATION_SENT:
1678 if (client->state != GOTD_STATE_NOTIFY) {
1679 log_warn("unexpected imsg %d", imsg.hdr.type);
1680 break;
1682 notif = STAILQ_FIRST(&notifications);
1683 if (notif == NULL) {
1684 disconnect(client);
1685 break; /* NOTREACHED */
1687 /* Request content for the next notification. */
1688 err = request_notification(notif);
1689 if (err) {
1690 log_warn("could not send notification: %s",
1691 err->msg);
1692 disconnect(client);
1694 break;
1695 default:
1696 log_debug("unexpected imsg %d", imsg.hdr.type);
1697 break;
1700 imsg_free(&imsg);
1702 done:
1703 if (!shut) {
1704 gotd_imsg_event_add(iev);
1705 } else {
1706 /* This pipe is dead. Remove its event handler */
1707 event_del(&iev->ev);
1708 imsg_clear(&iev->ibuf);
1709 imsg_init(&iev->ibuf, -1);
1713 static const struct got_error *
1714 recv_notifier(struct imsg *imsg)
1716 struct gotd_imsgev *iev = &gotd_session.notifier_iev;
1717 struct gotd_session_client *client = &gotd_session_client;
1718 size_t datalen;
1719 int fd;
1721 if (client->state != GOTD_STATE_EXPECT_LIST_REFS)
1722 return got_error(GOT_ERR_PRIVSEP_MSG);
1724 /* We should already have received a pipe to the listener. */
1725 if (client->fd == -1)
1726 return got_error(GOT_ERR_PRIVSEP_MSG);
1728 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
1729 if (datalen != 0)
1730 return got_error(GOT_ERR_PRIVSEP_LEN);
1732 fd = imsg_get_fd(imsg);
1733 if (fd == -1)
1734 return NULL; /* notifications unused */
1736 imsg_init(&iev->ibuf, fd);
1737 iev->handler = session_dispatch_notifier;
1738 iev->events = EV_READ;
1739 iev->handler_arg = NULL;
1740 event_set(&iev->ev, iev->ibuf.fd, EV_READ,
1741 session_dispatch_notifier, iev);
1742 gotd_imsg_event_add(iev);
1744 return NULL;
1747 static const struct got_error *
1748 recv_repo_child(struct imsg *imsg)
1750 struct gotd_imsg_connect_repo_child ichild;
1751 struct gotd_session_client *client = &gotd_session_client;
1752 size_t datalen;
1753 int fd;
1755 if (client->state != GOTD_STATE_EXPECT_LIST_REFS)
1756 return got_error(GOT_ERR_PRIVSEP_MSG);
1758 /* We should already have received a pipe to the listener. */
1759 if (client->fd == -1)
1760 return got_error(GOT_ERR_PRIVSEP_MSG);
1762 datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
1763 if (datalen != sizeof(ichild))
1764 return got_error(GOT_ERR_PRIVSEP_LEN);
1766 memcpy(&ichild, imsg->data, sizeof(ichild));
1768 client->id = ichild.client_id;
1769 if (ichild.proc_id == PROC_REPO_WRITE)
1770 client->is_writing = 1;
1771 else if (ichild.proc_id == PROC_REPO_READ)
1772 client->is_writing = 0;
1773 else
1774 return got_error_msg(GOT_ERR_PRIVSEP_MSG,
1775 "bad child process type");
1777 fd = imsg_get_fd(imsg);
1778 if (fd == -1)
1779 return got_error(GOT_ERR_PRIVSEP_NO_FD);
1781 imsg_init(&client->repo_child_iev.ibuf, fd);
1782 client->repo_child_iev.handler = session_dispatch_repo_child;
1783 client->repo_child_iev.events = EV_READ;
1784 client->repo_child_iev.handler_arg = NULL;
1785 event_set(&client->repo_child_iev.ev, client->repo_child_iev.ibuf.fd,
1786 EV_READ, session_dispatch_repo_child, &client->repo_child_iev);
1787 gotd_imsg_event_add(&client->repo_child_iev);
1789 /* The "recvfd" pledge promise is no longer needed. */
1790 if (pledge("stdio rpath wpath cpath sendfd fattr flock", NULL) == -1)
1791 fatal("pledge");
1793 return NULL;
1796 static void
1797 session_dispatch(int fd, short event, void *arg)
1799 struct gotd_imsgev *iev = arg;
1800 struct imsgbuf *ibuf = &iev->ibuf;
1801 struct gotd_session_client *client = &gotd_session_client;
1802 ssize_t n;
1803 int shut = 0;
1804 struct imsg imsg;
1806 if (event & EV_READ) {
1807 if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
1808 fatal("imsg_read error");
1809 if (n == 0) {
1810 /* Connection closed. */
1811 shut = 1;
1812 goto done;
1816 if (event & EV_WRITE) {
1817 n = msgbuf_write(&ibuf->w);
1818 if (n == -1 && errno != EAGAIN)
1819 fatal("msgbuf_write");
1820 if (n == 0) {
1821 /* Connection closed. */
1822 shut = 1;
1823 goto done;
1827 for (;;) {
1828 const struct got_error *err = NULL;
1829 uint32_t client_id = 0;
1830 int do_disconnect = 0, do_list_refs = 0;
1832 if ((n = imsg_get(ibuf, &imsg)) == -1)
1833 fatal("%s: imsg_get error", __func__);
1834 if (n == 0) /* No more messages. */
1835 break;
1837 switch (imsg.hdr.type) {
1838 case GOTD_IMSG_ERROR:
1839 do_disconnect = 1;
1840 err = gotd_imsg_recv_error(&client_id, &imsg);
1841 break;
1842 case GOTD_IMSG_CONNECT:
1843 err = recv_connect(&imsg);
1844 break;
1845 case GOTD_IMSG_DISCONNECT:
1846 do_disconnect = 1;
1847 break;
1848 case GOTD_IMSG_CONNECT_NOTIFIER:
1849 err = recv_notifier(&imsg);
1850 break;
1851 case GOTD_IMSG_CONNECT_REPO_CHILD:
1852 err = recv_repo_child(&imsg);
1853 if (err)
1854 break;
1855 do_list_refs = 1;
1856 break;
1857 default:
1858 log_debug("unexpected imsg %d", imsg.hdr.type);
1859 break;
1861 imsg_free(&imsg);
1863 if (do_disconnect) {
1864 if (err)
1865 disconnect_on_error(client, err);
1866 else
1867 disconnect(client);
1868 } else if (do_list_refs)
1869 err = list_refs_request();
1871 if (err)
1872 log_warnx("uid %d: %s", client->euid, err->msg);
1874 done:
1875 if (!shut) {
1876 gotd_imsg_event_add(iev);
1877 } else {
1878 /* This pipe is dead. Remove its event handler */
1879 event_del(&iev->ev);
1880 event_loopexit(NULL);
1884 void
1885 session_main(const char *title, const char *repo_path,
1886 int *pack_fds, int *temp_fds, struct timeval *request_timeout,
1887 struct gotd_repo *repo_cfg, enum gotd_procid proc_id)
1889 const struct got_error *err = NULL;
1890 struct event evsigint, evsigterm, evsighup, evsigusr1;
1892 STAILQ_INIT(&notifications);
1894 gotd_session.title = title;
1895 gotd_session.pid = getpid();
1896 gotd_session.pack_fds = pack_fds;
1897 gotd_session.temp_fds = temp_fds;
1898 memcpy(&gotd_session.request_timeout, request_timeout,
1899 sizeof(gotd_session.request_timeout));
1900 gotd_session.repo_cfg = repo_cfg;
1901 gotd_session.proc_id = proc_id;
1903 imsg_init(&gotd_session.notifier_iev.ibuf, -1);
1905 err = got_repo_open(&gotd_session.repo, repo_path, NULL, pack_fds);
1906 if (err)
1907 goto done;
1908 if (!got_repo_is_bare(gotd_session.repo)) {
1909 err = got_error_msg(GOT_ERR_NOT_GIT_REPO,
1910 "bare git repository required");
1911 goto done;
1914 got_repo_temp_fds_set(gotd_session.repo, temp_fds);
1916 signal_set(&evsigint, SIGINT, gotd_session_sighdlr, NULL);
1917 signal_set(&evsigterm, SIGTERM, gotd_session_sighdlr, NULL);
1918 signal_set(&evsighup, SIGHUP, gotd_session_sighdlr, NULL);
1919 signal_set(&evsigusr1, SIGUSR1, gotd_session_sighdlr, NULL);
1920 signal(SIGPIPE, SIG_IGN);
1922 signal_add(&evsigint, NULL);
1923 signal_add(&evsigterm, NULL);
1924 signal_add(&evsighup, NULL);
1925 signal_add(&evsigusr1, NULL);
1927 gotd_session_client.state = GOTD_STATE_EXPECT_LIST_REFS;
1928 gotd_session_client.fd = -1;
1929 gotd_session_client.nref_updates = -1;
1930 gotd_session_client.delta_cache_fd = -1;
1931 gotd_session_client.accept_flush_pkt = 1;
1933 imsg_init(&gotd_session.parent_iev.ibuf, GOTD_FILENO_MSG_PIPE);
1934 gotd_session.parent_iev.handler = session_dispatch;
1935 gotd_session.parent_iev.events = EV_READ;
1936 gotd_session.parent_iev.handler_arg = NULL;
1937 event_set(&gotd_session.parent_iev.ev, gotd_session.parent_iev.ibuf.fd,
1938 EV_READ, session_dispatch, &gotd_session.parent_iev);
1939 if (gotd_imsg_compose_event(&gotd_session.parent_iev,
1940 GOTD_IMSG_CLIENT_SESSION_READY, gotd_session.proc_id,
1941 -1, NULL, 0) == -1) {
1942 err = got_error_from_errno("imsg compose CLIENT_SESSION_READY");
1943 goto done;
1946 event_dispatch();
1947 done:
1948 if (err)
1949 log_warnx("%s: %s", title, err->msg);
1950 gotd_session_shutdown();
1953 void
1954 gotd_session_shutdown(void)
1956 struct gotd_session_notif *notif;
1958 log_debug("shutting down");
1960 while (!STAILQ_EMPTY(&notifications)) {
1961 notif = STAILQ_FIRST(&notifications);
1962 STAILQ_REMOVE_HEAD(&notifications, entry);
1963 if (notif->fd != -1)
1964 close(notif->fd);
1965 free(notif->refname);
1966 free(notif);
1969 if (gotd_session.repo)
1970 got_repo_close(gotd_session.repo);
1971 got_repo_pack_fds_close(gotd_session.pack_fds);
1972 got_repo_temp_fds_close(gotd_session.temp_fds);
1973 free(gotd_session_client.username);
1974 exit(0);