/* * Copyright (c) 2022, 2023 Stefan Sperling * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include "got_compat.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "got_compat.h" #include "got_error.h" #include "got_repository.h" #include "got_object.h" #include "got_path.h" #include "got_reference.h" #include "got_opentemp.h" #include "got_lib_hash.h" #include "got_lib_delta.h" #include "got_lib_object.h" #include "got_lib_object_cache.h" #include "got_lib_pack.h" #include "got_lib_repository.h" #include "got_lib_gitproto.h" #include "gotd.h" #include "log.h" #include "session_write.h" struct gotd_session_notif { STAILQ_ENTRY(gotd_session_notif) entry; int fd; enum gotd_notification_action action; char *refname; struct got_object_id old_id; struct got_object_id new_id; }; STAILQ_HEAD(gotd_session_notifications, gotd_session_notif) notifications; enum gotd_session_write_state { GOTD_STATE_EXPECT_LIST_REFS, GOTD_STATE_EXPECT_CAPABILITIES, GOTD_STATE_EXPECT_REF_UPDATE, GOTD_STATE_EXPECT_MORE_REF_UPDATES, GOTD_STATE_EXPECT_PACKFILE, GOTD_STATE_NOTIFY, }; static struct gotd_session_write { pid_t pid; const char *title; struct got_repository *repo; struct gotd_repo *repo_cfg; int *pack_fds; int *temp_fds; struct gotd_imsgev parent_iev; struct gotd_imsgev notifier_iev; struct timeval request_timeout; enum gotd_session_write_state state; struct gotd_imsgev repo_child_iev; } gotd_session; static struct gotd_session_client { struct gotd_client_capability *capabilities; size_t ncapa_alloc; size_t ncapabilities; uint32_t id; int fd; int delta_cache_fd; struct gotd_imsgev iev; struct event tmo; uid_t euid; gid_t egid; char *username; char *packfile_path; char *packidx_path; int nref_updates; int accept_flush_pkt; int flush_disconnect; } gotd_session_client; static void session_write_shutdown(void); static void disconnect(struct gotd_session_client *client) { log_debug("uid %d: disconnecting", client->euid); if (gotd_imsg_compose_event(&gotd_session.parent_iev, GOTD_IMSG_DISCONNECT, PROC_SESSION_WRITE, -1, NULL, 0) == -1) log_warn("imsg compose DISCONNECT"); imsg_clear(&gotd_session.repo_child_iev.ibuf); event_del(&gotd_session.repo_child_iev.ev); evtimer_del(&client->tmo); close(client->fd); if (client->delta_cache_fd != -1) close(client->delta_cache_fd); if (client->packfile_path) { if (unlink(client->packfile_path) == -1 && errno != ENOENT) log_warn("unlink %s: ", client->packfile_path); free(client->packfile_path); } if (client->packidx_path) { if (unlink(client->packidx_path) == -1 && errno != ENOENT) log_warn("unlink %s: ", client->packidx_path); free(client->packidx_path); } free(client->capabilities); session_write_shutdown(); } static void disconnect_on_error(struct gotd_session_client *client, const struct got_error *err) { struct imsgbuf ibuf; if (err->code != GOT_ERR_EOF) { log_warnx("uid %d: %s", client->euid, err->msg); imsg_init(&ibuf, client->fd); gotd_imsg_send_error(&ibuf, 0, PROC_SESSION_WRITE, err); imsg_clear(&ibuf); } disconnect(client); } static void gotd_request_timeout(int fd, short events, void *arg) { struct gotd_session_client *client = arg; log_debug("disconnecting uid %d due to timeout", client->euid); disconnect(client); } static void session_write_sighdlr(int sig, short event, void *arg) { /* * Normal signal handler rules don't apply because libevent * decouples for us. */ switch (sig) { case SIGHUP: log_info("%s: ignoring SIGHUP", __func__); break; case SIGUSR1: log_info("%s: ignoring SIGUSR1", __func__); break; case SIGTERM: case SIGINT: session_write_shutdown(); /* NOTREACHED */ break; default: fatalx("unexpected signal"); } } static const struct got_error * recv_packfile_install(struct imsg *imsg) { struct gotd_imsg_packfile_install inst; size_t datalen; log_debug("packfile-install received"); datalen = imsg->hdr.len - IMSG_HEADER_SIZE; if (datalen != sizeof(inst)) return got_error(GOT_ERR_PRIVSEP_LEN); memcpy(&inst, imsg->data, sizeof(inst)); return NULL; } static const struct got_error * recv_ref_updates_start(struct imsg *imsg) { struct gotd_imsg_ref_updates_start istart; size_t datalen; log_debug("ref-updates-start received"); datalen = imsg->hdr.len - IMSG_HEADER_SIZE; if (datalen != sizeof(istart)) return got_error(GOT_ERR_PRIVSEP_LEN); memcpy(&istart, imsg->data, sizeof(istart)); return NULL; } static const struct got_error * recv_ref_update(struct imsg *imsg) { struct gotd_imsg_ref_update iref; size_t datalen; log_debug("ref-update received"); datalen = imsg->hdr.len - IMSG_HEADER_SIZE; if (datalen < sizeof(iref)) return got_error(GOT_ERR_PRIVSEP_LEN); memcpy(&iref, imsg->data, sizeof(iref)); return NULL; } static const struct got_error * send_ref_update_ok(struct gotd_session_client *client, struct gotd_imsg_ref_update *iref, const char *refname) { struct gotd_imsg_ref_update_ok iok; struct gotd_imsgev *iev = &client->iev; struct ibuf *wbuf; size_t len; memset(&iok, 0, sizeof(iok)); memcpy(iok.old_id, iref->old_id, SHA1_DIGEST_LENGTH); memcpy(iok.new_id, iref->new_id, SHA1_DIGEST_LENGTH); iok.name_len = strlen(refname); len = sizeof(iok) + iok.name_len; wbuf = imsg_create(&iev->ibuf, GOTD_IMSG_REF_UPDATE_OK, PROC_SESSION_WRITE, gotd_session.pid, len); if (wbuf == NULL) return got_error_from_errno("imsg_create REF_UPDATE_OK"); if (imsg_add(wbuf, &iok, sizeof(iok)) == -1) return got_error_from_errno("imsg_add REF_UPDATE_OK"); if (imsg_add(wbuf, refname, iok.name_len) == -1) return got_error_from_errno("imsg_add REF_UPDATE_OK"); imsg_close(&iev->ibuf, wbuf); gotd_imsg_event_add(iev); return NULL; } static void send_refs_updated(struct gotd_session_client *client) { if (gotd_imsg_compose_event(&client->iev, GOTD_IMSG_REFS_UPDATED, PROC_SESSION_WRITE, -1, NULL, 0) == -1) log_warn("imsg compose REFS_UPDATED"); } static const struct got_error * send_ref_update_ng(struct gotd_session_client *client, struct gotd_imsg_ref_update *iref, const char *refname, const char *reason) { const struct got_error *ng_err; struct gotd_imsg_ref_update_ng ing; struct gotd_imsgev *iev = &client->iev; struct ibuf *wbuf; size_t len; memset(&ing, 0, sizeof(ing)); memcpy(ing.old_id, iref->old_id, SHA1_DIGEST_LENGTH); memcpy(ing.new_id, iref->new_id, SHA1_DIGEST_LENGTH); ing.name_len = strlen(refname); ng_err = got_error_fmt(GOT_ERR_REF_BUSY, "%s", reason); ing.reason_len = strlen(ng_err->msg); len = sizeof(ing) + ing.name_len + ing.reason_len; wbuf = imsg_create(&iev->ibuf, GOTD_IMSG_REF_UPDATE_NG, PROC_SESSION_WRITE, gotd_session.pid, len); if (wbuf == NULL) return got_error_from_errno("imsg_create REF_UPDATE_NG"); if (imsg_add(wbuf, &ing, sizeof(ing)) == -1) return got_error_from_errno("imsg_add REF_UPDATE_NG"); if (imsg_add(wbuf, refname, ing.name_len) == -1) return got_error_from_errno("imsg_add REF_UPDATE_NG"); if (imsg_add(wbuf, ng_err->msg, ing.reason_len) == -1) return got_error_from_errno("imsg_add REF_UPDATE_NG"); imsg_close(&iev->ibuf, wbuf); gotd_imsg_event_add(iev); return NULL; } static const struct got_error * install_pack(struct gotd_session_client *client, const char *repo_path, struct imsg *imsg) { const struct got_error *err = NULL; struct gotd_imsg_packfile_install inst; char hex[SHA1_DIGEST_STRING_LENGTH]; size_t datalen; char *packfile_path = NULL, *packidx_path = NULL; datalen = imsg->hdr.len - IMSG_HEADER_SIZE; if (datalen != sizeof(inst)) return got_error(GOT_ERR_PRIVSEP_LEN); memcpy(&inst, imsg->data, sizeof(inst)); if (client->packfile_path == NULL) return got_error_msg(GOT_ERR_BAD_REQUEST, "client has no pack file"); if (client->packidx_path == NULL) return got_error_msg(GOT_ERR_BAD_REQUEST, "client has no pack file index"); if (got_sha1_digest_to_str(inst.pack_sha1, hex, sizeof(hex)) == NULL) return got_error_msg(GOT_ERR_NO_SPACE, "could not convert pack file SHA1 to hex"); if (asprintf(&packfile_path, "/%s/%s/pack-%s.pack", repo_path, GOT_OBJECTS_PACK_DIR, hex) == -1) { err = got_error_from_errno("asprintf"); goto done; } if (asprintf(&packidx_path, "/%s/%s/pack-%s.idx", repo_path, GOT_OBJECTS_PACK_DIR, hex) == -1) { err = got_error_from_errno("asprintf"); goto done; } if (rename(client->packfile_path, packfile_path) == -1) { err = got_error_from_errno3("rename", client->packfile_path, packfile_path); goto done; } free(client->packfile_path); client->packfile_path = NULL; if (rename(client->packidx_path, packidx_path) == -1) { err = got_error_from_errno3("rename", client->packidx_path, packidx_path); goto done; } /* Ensure we re-read the pack index list upon next access. */ gotd_session.repo->pack_path_mtime.tv_sec = 0; gotd_session.repo->pack_path_mtime.tv_nsec = 0; free(client->packidx_path); client->packidx_path = NULL; done: free(packfile_path); free(packidx_path); return err; } static const struct got_error * begin_ref_updates(struct gotd_session_client *client, struct imsg *imsg) { struct gotd_imsg_ref_updates_start istart; size_t datalen; if (client->nref_updates != -1) return got_error(GOT_ERR_PRIVSEP_MSG); datalen = imsg->hdr.len - IMSG_HEADER_SIZE; if (datalen != sizeof(istart)) return got_error(GOT_ERR_PRIVSEP_LEN); memcpy(&istart, imsg->data, sizeof(istart)); if (istart.nref_updates <= 0) return got_error(GOT_ERR_PRIVSEP_MSG); client->nref_updates = istart.nref_updates; return NULL; } static const struct got_error * validate_namespace(const char *namespace) { size_t len = strlen(namespace); if (len < 5 || strncmp("refs/", namespace, 5) != 0 || namespace[len - 1] != '/') { return got_error_fmt(GOT_ERR_BAD_REF_NAME, "reference namespace '%s'", namespace); } return NULL; } static const struct got_error * queue_notification(struct got_object_id *old_id, struct got_object_id *new_id, struct got_repository *repo, struct got_reference *ref) { const struct got_error *err = NULL; struct gotd_repo *repo_cfg = gotd_session.repo_cfg; struct gotd_imsgev *iev = &gotd_session.repo_child_iev; struct got_pathlist_entry *pe; struct gotd_session_notif *notif; if (iev->ibuf.fd == -1 || STAILQ_EMPTY(&repo_cfg->notification_targets)) return NULL; /* notifications unused */ TAILQ_FOREACH(pe, &repo_cfg->notification_refs, entry) { const char *refname = pe->path; if (strcmp(got_ref_get_name(ref), refname) == 0) break; } if (pe == NULL) { TAILQ_FOREACH(pe, &repo_cfg->notification_ref_namespaces, entry) { const char *namespace = pe->path; err = validate_namespace(namespace); if (err) return err; if (strncmp(namespace, got_ref_get_name(ref), strlen(namespace)) == 0) break; } } /* * If a branch or a reference namespace was specified in the * configuration file then only send notifications if a match * was found. */ if (pe == NULL && (!TAILQ_EMPTY(&repo_cfg->notification_refs) || !TAILQ_EMPTY(&repo_cfg->notification_ref_namespaces))) return NULL; notif = calloc(1, sizeof(*notif)); if (notif == NULL) return got_error_from_errno("calloc"); notif->fd = -1; if (old_id == NULL) notif->action = GOTD_NOTIF_ACTION_CREATED; else if (new_id == NULL) notif->action = GOTD_NOTIF_ACTION_REMOVED; else notif->action = GOTD_NOTIF_ACTION_CHANGED; if (old_id != NULL) memcpy(¬if->old_id, old_id, sizeof(notif->old_id)); if (new_id != NULL) memcpy(¬if->new_id, new_id, sizeof(notif->new_id)); notif->refname = strdup(got_ref_get_name(ref)); if (notif->refname == NULL) { err = got_error_from_errno("strdup"); goto done; } STAILQ_INSERT_TAIL(¬ifications, notif, entry); done: if (err && notif) { free(notif->refname); free(notif); } return err; } /* Forward notification content to the NOTIFY process. */ static const struct got_error * forward_notification(struct gotd_session_client *client, struct imsg *imsg) { const struct got_error *err = NULL; struct gotd_imsgev *iev = &gotd_session.notifier_iev; struct gotd_session_notif *notif; struct gotd_imsg_notification_content icontent; char *refname = NULL; size_t datalen; struct gotd_imsg_notify inotify; const char *action; memset(&inotify, 0, sizeof(inotify)); datalen = imsg->hdr.len - IMSG_HEADER_SIZE; if (datalen < sizeof(icontent)) return got_error(GOT_ERR_PRIVSEP_LEN); memcpy(&icontent, imsg->data, sizeof(icontent)); if (datalen != sizeof(icontent) + icontent.refname_len) return got_error(GOT_ERR_PRIVSEP_LEN); refname = strndup(imsg->data + sizeof(icontent), icontent.refname_len); if (refname == NULL) return got_error_from_errno("strndup"); notif = STAILQ_FIRST(¬ifications); if (notif == NULL) return got_error(GOT_ERR_PRIVSEP_MSG); STAILQ_REMOVE(¬ifications, notif, gotd_session_notif, entry); if (notif->action != icontent.action || notif->fd == -1 || strcmp(notif->refname, refname) != 0) { err = got_error(GOT_ERR_PRIVSEP_MSG); goto done; } if (notif->action == GOTD_NOTIF_ACTION_CREATED) { if (memcmp(notif->new_id.sha1, icontent.new_id, SHA1_DIGEST_LENGTH) != 0) { err = got_error_msg(GOT_ERR_PRIVSEP_MSG, "received notification content for unknown event"); goto done; } } else if (notif->action == GOTD_NOTIF_ACTION_REMOVED) { if (memcmp(notif->old_id.sha1, icontent.old_id, SHA1_DIGEST_LENGTH) != 0) { err = got_error_msg(GOT_ERR_PRIVSEP_MSG, "received notification content for unknown event"); goto done; } } else if (memcmp(notif->old_id.sha1, icontent.old_id, SHA1_DIGEST_LENGTH) != 0 || memcmp(notif->new_id.sha1, icontent.new_id, SHA1_DIGEST_LENGTH) != 0) { err = got_error_msg(GOT_ERR_PRIVSEP_MSG, "received notification content for unknown event"); goto done; } switch (notif->action) { case GOTD_NOTIF_ACTION_CREATED: action = "created"; break; case GOTD_NOTIF_ACTION_REMOVED: action = "removed"; break; case GOTD_NOTIF_ACTION_CHANGED: action = "changed"; break; default: err = got_error(GOT_ERR_PRIVSEP_MSG); goto done; } strlcpy(inotify.repo_name, gotd_session.repo_cfg->name, sizeof(inotify.repo_name)); snprintf(inotify.subject_line, sizeof(inotify.subject_line), "%s: %s %s %s", gotd_session.repo_cfg->name, client->username, action, notif->refname); if (gotd_imsg_compose_event(iev, GOTD_IMSG_NOTIFY, PROC_SESSION_WRITE, notif->fd, &inotify, sizeof(inotify)) == -1) { err = got_error_from_errno("imsg compose NOTIFY"); goto done; } notif->fd = -1; done: if (notif->fd != -1) close(notif->fd); free(notif); free(refname); return err; } /* Request notification content from REPO_WRITE process. */ static const struct got_error * request_notification(struct gotd_session_notif *notif) { const struct got_error *err = NULL; struct gotd_imsgev *iev = &gotd_session.repo_child_iev; struct gotd_imsg_notification_content icontent; struct ibuf *wbuf; size_t len; int fd; fd = got_opentempfd(); if (fd == -1) return got_error_from_errno("got_opentemp"); memset(&icontent, 0, sizeof(icontent)); icontent.action = notif->action; memcpy(&icontent.old_id, ¬if->old_id, sizeof(notif->old_id)); memcpy(&icontent.new_id, ¬if->new_id, sizeof(notif->new_id)); icontent.refname_len = strlen(notif->refname); len = sizeof(icontent) + icontent.refname_len; wbuf = imsg_create(&iev->ibuf, GOTD_IMSG_NOTIFY, PROC_SESSION_WRITE, gotd_session.pid, len); if (wbuf == NULL) { err = got_error_from_errno("imsg_create NOTIFY"); goto done; } if (imsg_add(wbuf, &icontent, sizeof(icontent)) == -1) { err = got_error_from_errno("imsg_add NOTIFY"); goto done; } if (imsg_add(wbuf, notif->refname, icontent.refname_len) == -1) { err = got_error_from_errno("imsg_add NOTIFY"); goto done; } notif->fd = dup(fd); if (notif->fd == -1) { err = got_error_from_errno("dup"); goto done; } ibuf_fd_set(wbuf, fd); fd = -1; imsg_close(&iev->ibuf, wbuf); gotd_imsg_event_add(iev); done: if (err && fd != -1) close(fd); return err; } static const struct got_error * update_ref(int *shut, struct gotd_session_client *client, const char *repo_path, struct imsg *imsg) { const struct got_error *err = NULL; struct got_repository *repo = gotd_session.repo; struct got_reference *ref = NULL; struct gotd_imsg_ref_update iref; struct got_object_id old_id, new_id; struct gotd_session_notif *notif; struct got_object_id *id = NULL; char *refname = NULL; size_t datalen; int locked = 0; char hex1[SHA1_DIGEST_STRING_LENGTH]; char hex2[SHA1_DIGEST_STRING_LENGTH]; log_debug("update-ref from uid %d", client->euid); if (client->nref_updates <= 0) return got_error(GOT_ERR_PRIVSEP_MSG); datalen = imsg->hdr.len - IMSG_HEADER_SIZE; if (datalen < sizeof(iref)) return got_error(GOT_ERR_PRIVSEP_LEN); memcpy(&iref, imsg->data, sizeof(iref)); if (datalen != sizeof(iref) + iref.name_len) return got_error(GOT_ERR_PRIVSEP_LEN); refname = strndup(imsg->data + sizeof(iref), iref.name_len); if (refname == NULL) return got_error_from_errno("strndup"); log_debug("updating ref %s for uid %d", refname, client->euid); memcpy(old_id.sha1, iref.old_id, SHA1_DIGEST_LENGTH); memcpy(new_id.sha1, iref.new_id, SHA1_DIGEST_LENGTH); err = got_repo_find_object_id(iref.delete_ref ? &old_id : &new_id, repo); if (err) goto done; if (iref.ref_is_new) { err = got_ref_open(&ref, repo, refname, 0); if (err) { if (err->code != GOT_ERR_NOT_REF) goto done; err = got_ref_alloc(&ref, refname, &new_id); if (err) goto done; err = got_ref_write(ref, repo); /* will lock/unlock */ if (err) goto done; err = queue_notification(NULL, &new_id, repo, ref); if (err) goto done; } else { err = got_ref_resolve(&id, repo, ref); if (err) goto done; got_object_id_hex(&new_id, hex1, sizeof(hex1)); got_object_id_hex(id, hex2, sizeof(hex2)); err = got_error_fmt(GOT_ERR_REF_BUSY, "Addition %s: %s failed; %s: %s has been " "created by someone else while transaction " "was in progress", got_ref_get_name(ref), hex1, got_ref_get_name(ref), hex2); goto done; } } else if (iref.delete_ref) { err = got_ref_open(&ref, repo, refname, 1 /* lock */); if (err) goto done; locked = 1; err = got_ref_resolve(&id, repo, ref); if (err) goto done; if (got_object_id_cmp(id, &old_id) != 0) { got_object_id_hex(&old_id, hex1, sizeof(hex1)); got_object_id_hex(id, hex2, sizeof(hex2)); err = got_error_fmt(GOT_ERR_REF_BUSY, "Deletion %s: %s failed; %s: %s has been " "created by someone else while transaction " "was in progress", got_ref_get_name(ref), hex1, got_ref_get_name(ref), hex2); goto done; } err = got_ref_delete(ref, repo); if (err) goto done; err = queue_notification(&old_id, NULL, repo, ref); if (err) goto done; free(id); id = NULL; } else { err = got_ref_open(&ref, repo, refname, 1 /* lock */); if (err) goto done; locked = 1; err = got_ref_resolve(&id, repo, ref); if (err) goto done; if (got_object_id_cmp(id, &old_id) != 0) { got_object_id_hex(&old_id, hex1, sizeof(hex1)); got_object_id_hex(id, hex2, sizeof(hex2)); err = got_error_fmt(GOT_ERR_REF_BUSY, "Update %s: %s failed; %s: %s has been " "created by someone else while transaction " "was in progress", got_ref_get_name(ref), hex1, got_ref_get_name(ref), hex2); goto done; } if (got_object_id_cmp(&new_id, &old_id) != 0) { err = got_ref_change_ref(ref, &new_id); if (err) goto done; err = got_ref_write(ref, repo); if (err) goto done; err = queue_notification(&old_id, &new_id, repo, ref); if (err) goto done; } free(id); id = NULL; } done: if (err) { if (err->code == GOT_ERR_LOCKFILE_TIMEOUT) { err = got_error_fmt(GOT_ERR_LOCKFILE_TIMEOUT, "could not acquire exclusive file lock for %s", refname); } send_ref_update_ng(client, &iref, refname, err->msg); } else send_ref_update_ok(client, &iref, refname); if (client->nref_updates > 0) { client->nref_updates--; if (client->nref_updates == 0) { send_refs_updated(client); notif = STAILQ_FIRST(¬ifications); if (notif) { gotd_session.state = GOTD_STATE_NOTIFY; err = request_notification(notif); if (err) { log_warn("could not send notification: " "%s", err->msg); client->flush_disconnect = 1; } } else client->flush_disconnect = 1; } } if (locked) { const struct got_error *unlock_err; unlock_err = got_ref_unlock(ref); if (unlock_err && err == NULL) err = unlock_err; } if (ref) got_ref_close(ref); free(refname); free(id); return err; } static const struct got_error * recv_notification_content(struct imsg *imsg) { struct gotd_imsg_notification_content inotif; size_t datalen; datalen = imsg->hdr.len - IMSG_HEADER_SIZE; if (datalen < sizeof(inotif)) return got_error(GOT_ERR_PRIVSEP_LEN); memcpy(&inotif, imsg->data, sizeof(inotif)); return NULL; } static void session_dispatch_repo_child(int fd, short event, void *arg) { struct gotd_imsgev *iev = arg; struct imsgbuf *ibuf = &iev->ibuf; struct gotd_session_client *client = &gotd_session_client; ssize_t n; int shut = 0; struct imsg imsg; if (event & EV_READ) { if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) fatal("imsg_read error"); if (n == 0) { /* Connection closed. */ shut = 1; goto done; } } if (event & EV_WRITE) { n = msgbuf_write(&ibuf->w); if (n == -1 && errno != EAGAIN) fatal("msgbuf_write"); if (n == 0) { /* Connection closed. */ shut = 1; goto done; } } for (;;) { const struct got_error *err = NULL; uint32_t client_id = 0; int do_disconnect = 0; int do_ref_updates = 0, do_ref_update = 0; int do_packfile_install = 0, do_notify = 0; if ((n = imsg_get(ibuf, &imsg)) == -1) fatal("%s: imsg_get error", __func__); if (n == 0) /* No more messages. */ break; switch (imsg.hdr.type) { case GOTD_IMSG_ERROR: do_disconnect = 1; err = gotd_imsg_recv_error(&client_id, &imsg); break; case GOTD_IMSG_PACKFILE_INSTALL: err = recv_packfile_install(&imsg); if (err == NULL) do_packfile_install = 1; break; case GOTD_IMSG_REF_UPDATES_START: err = recv_ref_updates_start(&imsg); if (err == NULL) do_ref_updates = 1; break; case GOTD_IMSG_REF_UPDATE: err = recv_ref_update(&imsg); if (err == NULL) do_ref_update = 1; break; case GOTD_IMSG_NOTIFY: err = recv_notification_content(&imsg); if (err == NULL) do_notify = 1; break; default: log_debug("unexpected imsg %d", imsg.hdr.type); break; } if (do_disconnect || err) { if (err) disconnect_on_error(client, err); else disconnect(client); } else { struct gotd_session_notif *notif; if (do_packfile_install) err = install_pack(client, gotd_session.repo->path, &imsg); else if (do_ref_updates) err = begin_ref_updates(client, &imsg); else if (do_ref_update) err = update_ref(&shut, client, gotd_session.repo->path, &imsg); else if (do_notify) err = forward_notification(client, &imsg); if (err) log_warnx("uid %d: %s", client->euid, err->msg); notif = STAILQ_FIRST(¬ifications); if (notif && do_notify) { /* Request content for next notification. */ err = request_notification(notif); if (err) { log_warn("could not send notification: " "%s", err->msg); shut = 1; } } } imsg_free(&imsg); } done: if (!shut) { gotd_imsg_event_add(iev); } else { /* This pipe is dead. Remove its event handler */ event_del(&iev->ev); event_loopexit(NULL); } } static const struct got_error * recv_capabilities(struct gotd_session_client *client, struct imsg *imsg) { struct gotd_imsg_capabilities icapas; size_t datalen; datalen = imsg->hdr.len - IMSG_HEADER_SIZE; if (datalen != sizeof(icapas)) return got_error(GOT_ERR_PRIVSEP_LEN); memcpy(&icapas, imsg->data, sizeof(icapas)); client->ncapa_alloc = icapas.ncapabilities; client->capabilities = calloc(client->ncapa_alloc, sizeof(*client->capabilities)); if (client->capabilities == NULL) { client->ncapa_alloc = 0; return got_error_from_errno("calloc"); } log_debug("expecting %zu capabilities from uid %d", client->ncapa_alloc, client->euid); return NULL; } static const struct got_error * recv_capability(struct gotd_session_client *client, struct imsg *imsg) { struct gotd_imsg_capability icapa; struct gotd_client_capability *capa; size_t datalen; char *key, *value = NULL; if (client->capabilities == NULL || client->ncapabilities >= client->ncapa_alloc) { return got_error_msg(GOT_ERR_BAD_REQUEST, "unexpected capability received"); } memset(&icapa, 0, sizeof(icapa)); datalen = imsg->hdr.len - IMSG_HEADER_SIZE; if (datalen < sizeof(icapa)) return got_error(GOT_ERR_PRIVSEP_LEN); memcpy(&icapa, imsg->data, sizeof(icapa)); if (datalen != sizeof(icapa) + icapa.key_len + icapa.value_len) return got_error(GOT_ERR_PRIVSEP_LEN); key = strndup(imsg->data + sizeof(icapa), icapa.key_len); if (key == NULL) return got_error_from_errno("strndup"); if (icapa.value_len > 0) { value = strndup(imsg->data + sizeof(icapa) + icapa.key_len, icapa.value_len); if (value == NULL) { free(key); return got_error_from_errno("strndup"); } } capa = &client->capabilities[client->ncapabilities++]; capa->key = key; capa->value = value; if (value) log_debug("uid %d: capability %s=%s", client->euid, key, value); else log_debug("uid %d: capability %s", client->euid, key); return NULL; } static const struct got_error * forward_ref_update(struct gotd_session_client *client, struct imsg *imsg) { const struct got_error *err = NULL; struct gotd_imsg_ref_update ireq; struct gotd_imsg_ref_update *iref = NULL; size_t datalen; datalen = imsg->hdr.len - IMSG_HEADER_SIZE; if (datalen < sizeof(ireq)) return got_error(GOT_ERR_PRIVSEP_LEN); memcpy(&ireq, imsg->data, sizeof(ireq)); if (datalen != sizeof(ireq) + ireq.name_len) return got_error(GOT_ERR_PRIVSEP_LEN); iref = malloc(datalen); if (iref == NULL) return got_error_from_errno("malloc"); memcpy(iref, imsg->data, datalen); if (gotd_imsg_compose_event(&gotd_session.repo_child_iev, GOTD_IMSG_REF_UPDATE, PROC_SESSION_WRITE, -1, iref, datalen) == -1) err = got_error_from_errno("imsg compose REF_UPDATE"); free(iref); return err; } static int client_has_capability(struct gotd_session_client *client, const char *capastr) { struct gotd_client_capability *capa; size_t i; if (client->ncapabilities == 0) return 0; for (i = 0; i < client->ncapabilities; i++) { capa = &client->capabilities[i]; if (strcmp(capa->key, capastr) == 0) return 1; } return 0; } static const struct got_error * recv_packfile(struct gotd_session_client *client) { const struct got_error *err = NULL; struct gotd_imsg_recv_packfile ipack; char *basepath = NULL, *pack_path = NULL, *idx_path = NULL; int packfd = -1, idxfd = -1; int pipe[2] = { -1, -1 }; if (client->packfile_path) { return got_error_fmt(GOT_ERR_PRIVSEP_MSG, "uid %d already has a pack file", client->euid); } if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pipe) == -1) return got_error_from_errno("socketpair"); /* Send pack pipe end 0 to repo child process. */ if (gotd_imsg_compose_event(&gotd_session.repo_child_iev, GOTD_IMSG_PACKFILE_PIPE, PROC_SESSION_WRITE, pipe[0], NULL, 0) == -1) { err = got_error_from_errno("imsg compose PACKFILE_PIPE"); pipe[0] = -1; goto done; } pipe[0] = -1; /* Send pack pipe end 1 to gotsh(1) (expects just an fd, no data). */ if (gotd_imsg_compose_event(&client->iev, GOTD_IMSG_PACKFILE_PIPE, PROC_SESSION_WRITE, pipe[1], NULL, 0) == -1) err = got_error_from_errno("imsg compose PACKFILE_PIPE"); pipe[1] = -1; if (asprintf(&basepath, "%s/%s/receiving-from-uid-%d.pack", got_repo_get_path(gotd_session.repo), GOT_OBJECTS_PACK_DIR, client->euid) == -1) { err = got_error_from_errno("asprintf"); goto done; } err = got_opentemp_named_fd(&pack_path, &packfd, basepath, ""); if (err) goto done; if (fchmod(packfd, GOT_DEFAULT_PACK_MODE) == -1) { err = got_error_from_errno2("fchmod", pack_path); goto done; } free(basepath); if (asprintf(&basepath, "%s/%s/receiving-from-uid-%d.idx", got_repo_get_path(gotd_session.repo), GOT_OBJECTS_PACK_DIR, client->euid) == -1) { err = got_error_from_errno("asprintf"); basepath = NULL; goto done; } err = got_opentemp_named_fd(&idx_path, &idxfd, basepath, ""); if (err) goto done; if (fchmod(idxfd, GOT_DEFAULT_PACK_MODE) == -1) { err = got_error_from_errno2("fchmod", idx_path); goto done; } if (gotd_imsg_compose_event(&gotd_session.repo_child_iev, GOTD_IMSG_PACKIDX_FILE, PROC_SESSION_WRITE, idxfd, NULL, 0) == -1) { err = got_error_from_errno("imsg compose PACKIDX_FILE"); idxfd = -1; goto done; } idxfd = -1; memset(&ipack, 0, sizeof(ipack)); if (client_has_capability(client, GOT_CAPA_REPORT_STATUS)) ipack.report_status = 1; if (gotd_imsg_compose_event(&gotd_session.repo_child_iev, GOTD_IMSG_RECV_PACKFILE, PROC_SESSION_WRITE, packfd, &ipack, sizeof(ipack)) == -1) { err = got_error_from_errno("imsg compose RECV_PACKFILE"); packfd = -1; goto done; } packfd = -1; done: free(basepath); if (pipe[0] != -1 && close(pipe[0]) == -1 && err == NULL) err = got_error_from_errno("close"); if (pipe[1] != -1 && close(pipe[1]) == -1 && err == NULL) err = got_error_from_errno("close"); if (packfd != -1 && close(packfd) == -1 && err == NULL) err = got_error_from_errno("close"); if (idxfd != -1 && close(idxfd) == -1 && err == NULL) err = got_error_from_errno("close"); if (err) { free(pack_path); free(idx_path); } else { client->packfile_path = pack_path; client->packidx_path = idx_path; } return err; } static void session_dispatch_client(int fd, short events, void *arg) { struct gotd_imsgev *iev = arg; struct imsgbuf *ibuf = &iev->ibuf; struct gotd_session_client *client = &gotd_session_client; const struct got_error *err = NULL; struct imsg imsg; ssize_t n; if (events & EV_WRITE) { while (ibuf->w.queued) { n = msgbuf_write(&ibuf->w); if (n == -1 && errno == EPIPE) { /* * The client has closed its socket. * This can happen when Git clients are * done sending pack file data. */ msgbuf_clear(&ibuf->w); continue; } else if (n == -1 && errno != EAGAIN) { err = got_error_from_errno("imsg_flush"); disconnect_on_error(client, err); return; } if (n == 0) { /* Connection closed. */ err = got_error(GOT_ERR_EOF); disconnect_on_error(client, err); return; } } if (client->flush_disconnect) { disconnect(client); return; } } if ((events & EV_READ) == 0) return; memset(&imsg, 0, sizeof(imsg)); while (err == NULL) { err = gotd_imsg_recv(&imsg, ibuf, 0); if (err) { if (err->code == GOT_ERR_PRIVSEP_READ) err = NULL; else if (err->code == GOT_ERR_EOF && gotd_session.state == GOTD_STATE_EXPECT_CAPABILITIES) { /* * The client has closed its socket before * sending its capability announcement. * This can happen when Git clients have * no ref-updates to send. */ disconnect_on_error(client, err); return; } break; } evtimer_del(&client->tmo); switch (imsg.hdr.type) { case GOTD_IMSG_CAPABILITIES: if (gotd_session.state != GOTD_STATE_EXPECT_CAPABILITIES) { err = got_error_msg(GOT_ERR_BAD_REQUEST, "unexpected capabilities received"); break; } log_debug("receiving capabilities from uid %d", client->euid); err = recv_capabilities(client, &imsg); break; case GOTD_IMSG_CAPABILITY: if (gotd_session.state != GOTD_STATE_EXPECT_CAPABILITIES) { err = got_error_msg(GOT_ERR_BAD_REQUEST, "unexpected capability received"); break; } err = recv_capability(client, &imsg); if (err || client->ncapabilities < client->ncapa_alloc) break; gotd_session.state = GOTD_STATE_EXPECT_REF_UPDATE; client->accept_flush_pkt = 1; log_debug("uid %d: expecting ref-update-lines", client->euid); break; case GOTD_IMSG_REF_UPDATE: if (gotd_session.state != GOTD_STATE_EXPECT_REF_UPDATE && gotd_session.state != GOTD_STATE_EXPECT_MORE_REF_UPDATES) { err = got_error_msg(GOT_ERR_BAD_REQUEST, "unexpected ref-update-line received"); break; } log_debug("received ref-update-line from uid %d", client->euid); err = forward_ref_update(client, &imsg); if (err) break; gotd_session.state = GOTD_STATE_EXPECT_MORE_REF_UPDATES; client->accept_flush_pkt = 1; break; case GOTD_IMSG_FLUSH: if (gotd_session.state != GOTD_STATE_EXPECT_MORE_REF_UPDATES) { err = got_error_msg(GOT_ERR_BAD_REQUEST, "unexpected flush-pkt received"); break; } if (!client->accept_flush_pkt) { err = got_error_msg(GOT_ERR_BAD_REQUEST, "unexpected flush-pkt received"); break; } /* * Accept just one flush packet at a time. * Future client state transitions will set this flag * again if another flush packet is expected. */ client->accept_flush_pkt = 0; log_debug("received flush-pkt from uid %d", client->euid); if (gotd_session.state == GOTD_STATE_EXPECT_MORE_REF_UPDATES) { gotd_session.state = GOTD_STATE_EXPECT_PACKFILE; log_debug("uid %d: expecting packfile", client->euid); err = recv_packfile(client); } else { /* should not happen, see above */ err = got_error_msg(GOT_ERR_BAD_REQUEST, "unexpected client state"); break; } break; default: log_debug("unexpected imsg %d", imsg.hdr.type); err = got_error(GOT_ERR_PRIVSEP_MSG); break; } imsg_free(&imsg); } if (err) { if (err->code != GOT_ERR_EOF || gotd_session.state != GOTD_STATE_EXPECT_PACKFILE) disconnect_on_error(client, err); } else { gotd_imsg_event_add(iev); evtimer_add(&client->tmo, &gotd_session.request_timeout); } } static const struct got_error * list_refs_request(void) { static const struct got_error *err; struct gotd_session_client *client = &gotd_session_client; struct gotd_imsgev *iev = &gotd_session.repo_child_iev; int fd; if (gotd_session.state != GOTD_STATE_EXPECT_LIST_REFS) return got_error(GOT_ERR_PRIVSEP_MSG); fd = dup(client->fd); if (fd == -1) return got_error_from_errno("dup"); if (gotd_imsg_compose_event(iev, GOTD_IMSG_LIST_REFS_INTERNAL, PROC_SESSION_WRITE, fd, NULL, 0) == -1) { err = got_error_from_errno("imsg compose LIST_REFS_INTERNAL"); close(fd); return err; } gotd_session.state = GOTD_STATE_EXPECT_CAPABILITIES; log_debug("uid %d: expecting capabilities", client->euid); return NULL; } static const struct got_error * recv_connect(struct imsg *imsg) { struct gotd_session_client *client = &gotd_session_client; struct gotd_imsg_connect iconnect; size_t datalen; if (gotd_session.state != GOTD_STATE_EXPECT_LIST_REFS) return got_error(GOT_ERR_PRIVSEP_MSG); datalen = imsg->hdr.len - IMSG_HEADER_SIZE; if (datalen < sizeof(iconnect)) return got_error(GOT_ERR_PRIVSEP_LEN); memcpy(&iconnect, imsg->data, sizeof(iconnect)); if (iconnect.username_len == 0 || datalen != sizeof(iconnect) + iconnect.username_len) return got_error(GOT_ERR_PRIVSEP_LEN); client->euid = iconnect.euid; client->egid = iconnect.egid; client->fd = imsg_get_fd(imsg); if (client->fd == -1) return got_error(GOT_ERR_PRIVSEP_NO_FD); client->username = strndup(imsg->data + sizeof(iconnect), iconnect.username_len); if (client->username == NULL) return got_error_from_errno("strndup"); imsg_init(&client->iev.ibuf, client->fd); client->iev.handler = session_dispatch_client; client->iev.events = EV_READ; client->iev.handler_arg = NULL; event_set(&client->iev.ev, client->iev.ibuf.fd, EV_READ, session_dispatch_client, &client->iev); gotd_imsg_event_add(&client->iev); evtimer_set(&client->tmo, gotd_request_timeout, client); return NULL; } static void session_dispatch_notifier(int fd, short event, void *arg) { const struct got_error *err; struct gotd_session_client *client = &gotd_session_client; struct gotd_imsgev *iev = arg; struct imsgbuf *ibuf = &iev->ibuf; ssize_t n; int shut = 0; struct imsg imsg; struct gotd_session_notif *notif; if (event & EV_READ) { if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) fatal("imsg_read error"); if (n == 0) { /* Connection closed. */ shut = 1; goto done; } } if (event & EV_WRITE) { n = msgbuf_write(&ibuf->w); if (n == -1 && errno != EAGAIN) fatal("msgbuf_write"); if (n == 0) { /* Connection closed. */ shut = 1; goto done; } } for (;;) { if ((n = imsg_get(ibuf, &imsg)) == -1) fatal("%s: imsg_get error", __func__); if (n == 0) /* No more messages. */ break; switch (imsg.hdr.type) { case GOTD_IMSG_NOTIFICATION_SENT: if (gotd_session.state != GOTD_STATE_NOTIFY) { log_warn("unexpected imsg %d", imsg.hdr.type); break; } notif = STAILQ_FIRST(¬ifications); if (notif == NULL) { disconnect(client); break; /* NOTREACHED */ } /* Request content for the next notification. */ err = request_notification(notif); if (err) { log_warn("could not send notification: %s", err->msg); disconnect(client); } break; default: log_debug("unexpected imsg %d", imsg.hdr.type); break; } imsg_free(&imsg); } done: if (!shut) { gotd_imsg_event_add(iev); } else { /* This pipe is dead. Remove its event handler */ event_del(&iev->ev); imsg_clear(&iev->ibuf); imsg_init(&iev->ibuf, -1); } } static const struct got_error * recv_notifier(struct imsg *imsg) { struct gotd_imsgev *iev = &gotd_session.notifier_iev; struct gotd_session_client *client = &gotd_session_client; size_t datalen; int fd; if (gotd_session.state != GOTD_STATE_EXPECT_LIST_REFS) return got_error(GOT_ERR_PRIVSEP_MSG); /* We should already have received a pipe to the listener. */ if (client->fd == -1) return got_error(GOT_ERR_PRIVSEP_MSG); datalen = imsg->hdr.len - IMSG_HEADER_SIZE; if (datalen != 0) return got_error(GOT_ERR_PRIVSEP_LEN); fd = imsg_get_fd(imsg); if (fd == -1) return NULL; /* notifications unused */ imsg_init(&iev->ibuf, fd); iev->handler = session_dispatch_notifier; iev->events = EV_READ; iev->handler_arg = NULL; event_set(&iev->ev, iev->ibuf.fd, EV_READ, session_dispatch_notifier, iev); gotd_imsg_event_add(iev); return NULL; } static const struct got_error * recv_repo_child(struct imsg *imsg) { struct gotd_imsg_connect_repo_child ichild; struct gotd_session_client *client = &gotd_session_client; size_t datalen; int fd; if (gotd_session.state != GOTD_STATE_EXPECT_LIST_REFS) return got_error(GOT_ERR_PRIVSEP_MSG); /* We should already have received a pipe to the listener. */ if (client->fd == -1) return got_error(GOT_ERR_PRIVSEP_MSG); datalen = imsg->hdr.len - IMSG_HEADER_SIZE; if (datalen != sizeof(ichild)) return got_error(GOT_ERR_PRIVSEP_LEN); memcpy(&ichild, imsg->data, sizeof(ichild)); if (ichild.proc_id != PROC_REPO_WRITE) return got_error_msg(GOT_ERR_PRIVSEP_MSG, "bad child process type"); fd = imsg_get_fd(imsg); if (fd == -1) return got_error(GOT_ERR_PRIVSEP_NO_FD); imsg_init(&gotd_session.repo_child_iev.ibuf, fd); gotd_session.repo_child_iev.handler = session_dispatch_repo_child; gotd_session.repo_child_iev.events = EV_READ; gotd_session.repo_child_iev.handler_arg = NULL; event_set(&gotd_session.repo_child_iev.ev, gotd_session.repo_child_iev.ibuf.fd, EV_READ, session_dispatch_repo_child, &gotd_session.repo_child_iev); gotd_imsg_event_add(&gotd_session.repo_child_iev); /* The "recvfd" pledge promise is no longer needed. */ if (pledge("stdio rpath wpath cpath sendfd fattr flock", NULL) == -1) fatal("pledge"); return NULL; } static void session_dispatch(int fd, short event, void *arg) { struct gotd_imsgev *iev = arg; struct imsgbuf *ibuf = &iev->ibuf; struct gotd_session_client *client = &gotd_session_client; ssize_t n; int shut = 0; struct imsg imsg; if (event & EV_READ) { if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN) fatal("imsg_read error"); if (n == 0) { /* Connection closed. */ shut = 1; goto done; } } if (event & EV_WRITE) { n = msgbuf_write(&ibuf->w); if (n == -1 && errno != EAGAIN) fatal("msgbuf_write"); if (n == 0) { /* Connection closed. */ shut = 1; goto done; } } for (;;) { const struct got_error *err = NULL; uint32_t client_id = 0; int do_disconnect = 0, do_list_refs = 0; if ((n = imsg_get(ibuf, &imsg)) == -1) fatal("%s: imsg_get error", __func__); if (n == 0) /* No more messages. */ break; switch (imsg.hdr.type) { case GOTD_IMSG_ERROR: do_disconnect = 1; err = gotd_imsg_recv_error(&client_id, &imsg); break; case GOTD_IMSG_CONNECT: err = recv_connect(&imsg); break; case GOTD_IMSG_DISCONNECT: do_disconnect = 1; break; case GOTD_IMSG_CONNECT_NOTIFIER: err = recv_notifier(&imsg); break; case GOTD_IMSG_CONNECT_REPO_CHILD: err = recv_repo_child(&imsg); if (err) break; do_list_refs = 1; break; default: log_debug("unexpected imsg %d", imsg.hdr.type); break; } imsg_free(&imsg); if (do_disconnect) { if (err) disconnect_on_error(client, err); else disconnect(client); } else if (do_list_refs) err = list_refs_request(); if (err) log_warnx("uid %d: %s", client->euid, err->msg); } done: if (!shut) { gotd_imsg_event_add(iev); } else { /* This pipe is dead. Remove its event handler */ event_del(&iev->ev); event_loopexit(NULL); } } void session_write_main(const char *title, const char *repo_path, int *pack_fds, int *temp_fds, struct timeval *request_timeout, struct gotd_repo *repo_cfg) { const struct got_error *err = NULL; struct event evsigint, evsigterm, evsighup, evsigusr1; STAILQ_INIT(¬ifications); gotd_session.title = title; gotd_session.pid = getpid(); gotd_session.pack_fds = pack_fds; gotd_session.temp_fds = temp_fds; memcpy(&gotd_session.request_timeout, request_timeout, sizeof(gotd_session.request_timeout)); gotd_session.repo_cfg = repo_cfg; imsg_init(&gotd_session.notifier_iev.ibuf, -1); err = got_repo_open(&gotd_session.repo, repo_path, NULL, pack_fds); if (err) goto done; if (!got_repo_is_bare(gotd_session.repo)) { err = got_error_msg(GOT_ERR_NOT_GIT_REPO, "bare git repository required"); goto done; } got_repo_temp_fds_set(gotd_session.repo, temp_fds); signal_set(&evsigint, SIGINT, session_write_sighdlr, NULL); signal_set(&evsigterm, SIGTERM, session_write_sighdlr, NULL); signal_set(&evsighup, SIGHUP, session_write_sighdlr, NULL); signal_set(&evsigusr1, SIGUSR1, session_write_sighdlr, NULL); signal(SIGPIPE, SIG_IGN); signal_add(&evsigint, NULL); signal_add(&evsigterm, NULL); signal_add(&evsighup, NULL); signal_add(&evsigusr1, NULL); gotd_session.state = GOTD_STATE_EXPECT_LIST_REFS; gotd_session_client.fd = -1; gotd_session_client.nref_updates = -1; gotd_session_client.delta_cache_fd = -1; gotd_session_client.accept_flush_pkt = 1; imsg_init(&gotd_session.parent_iev.ibuf, GOTD_FILENO_MSG_PIPE); gotd_session.parent_iev.handler = session_dispatch; gotd_session.parent_iev.events = EV_READ; gotd_session.parent_iev.handler_arg = NULL; event_set(&gotd_session.parent_iev.ev, gotd_session.parent_iev.ibuf.fd, EV_READ, session_dispatch, &gotd_session.parent_iev); if (gotd_imsg_compose_event(&gotd_session.parent_iev, GOTD_IMSG_CLIENT_SESSION_READY, PROC_SESSION_WRITE, -1, NULL, 0) == -1) { err = got_error_from_errno("imsg compose CLIENT_SESSION_READY"); goto done; } event_dispatch(); done: if (err) log_warnx("%s: %s", title, err->msg); session_write_shutdown(); } static void session_write_shutdown(void) { struct gotd_session_notif *notif; log_debug("%s: shutting down", gotd_session.title); while (!STAILQ_EMPTY(¬ifications)) { notif = STAILQ_FIRST(¬ifications); STAILQ_REMOVE_HEAD(¬ifications, entry); if (notif->fd != -1) close(notif->fd); free(notif->refname); free(notif); } if (gotd_session.repo) got_repo_close(gotd_session.repo); got_repo_pack_fds_close(gotd_session.pack_fds); got_repo_temp_fds_close(gotd_session.temp_fds); free(gotd_session_client.username); exit(0); }