commit e70fd95218f6bb77ff45fc0c94b8daeb6709ffa7 from: Stefan Sperling date: Fri Mar 22 09:26:57 2024 UTC split gotd/session.c into session_read.c and session_write.c This makes it easier to tweak the read/write code paths separately. commit - 97a1ea05aadab5340a13a0a9760a7887f5779b9c commit + e70fd95218f6bb77ff45fc0c94b8daeb6709ffa7 blob - 72ddf39763d42262c40de7298eb23144cf2d1703 blob + 40093462c835f76308db6ab0dc60b0735412ab92 --- gotd/Makefile +++ gotd/Makefile @@ -17,8 +17,8 @@ SRCS= gotd.c auth.c repo_read.c repo_write.c log.c pr object_open_io.c object_parse.c opentemp.c pack.c path.c \ read_gitconfig.c read_gotconfig.c reference.c repository.c \ hash.c sigs.c pack_create_io.c pollfd.c reference_parse.c \ - repo_imsg.c pack_index.c session.c object_qid.c notify.c \ - commit_graph.c diffreg.c diff.c \ + repo_imsg.c pack_index.c session_read.c session_write.c \ + object_qid.c notify.c commit_graph.c diffreg.c diff.c \ diff_main.c diff_atomize_text.c diff_myers.c diff_output.c \ diff_output_plain.c diff_output_unidiff.c \ diff_output_edscript.c diff_patience.c blob - 768dd71c9d5971dd3cc7801fc4692bbe4f2b56d6 blob + 56aa6c3d8a0cc75fe5a0804df81b9f06486921a1 --- gotd/gotd.c +++ gotd/gotd.c @@ -62,7 +62,8 @@ #include "log.h" #include "listen.h" #include "auth.h" -#include "session.h" +#include "session_read.h" +#include "session_write.h" #include "repo_read.h" #include "repo_write.h" #include "notify.h" @@ -2217,8 +2218,12 @@ main(int argc, char **argv) if (repo == NULL) fatalx("no repository for path %s", repo_path); } - session_main(title, repo_path, pack_fds, temp_fds, - &gotd.request_timeout, repo, proc_id); + if (proc_id == PROC_SESSION_READ) + session_read_main(title, repo_path, pack_fds, temp_fds, + &gotd.request_timeout, repo); + else + session_write_main(title, repo_path, pack_fds, temp_fds, + &gotd.request_timeout, repo); /* NOTREACHED */ break; case PROC_REPO_READ: blob - f79b125406b4bb07bdc361abda15856360b65719 blob + 0db514ba51566c92c7c7cf3d1df4c1b69e81c7fe --- gotd/gotd.h +++ gotd/gotd.h @@ -131,19 +131,6 @@ struct gotd_repo { }; TAILQ_HEAD(gotd_repolist, gotd_repo); -enum gotd_session_state { - GOTD_STATE_EXPECT_LIST_REFS, - GOTD_STATE_EXPECT_CAPABILITIES, - GOTD_STATE_EXPECT_WANT, - GOTD_STATE_EXPECT_REF_UPDATE, - GOTD_STATE_EXPECT_MORE_REF_UPDATES, - GOTD_STATE_EXPECT_HAVE, - GOTD_STATE_EXPECT_PACKFILE, - GOTD_STATE_EXPECT_DONE, - GOTD_STATE_DONE, - GOTD_STATE_NOTIFY, -}; - struct gotd_client_capability { char *key; char *value; blob - 2d25688e104f748188eb06b72e9caa52e4918aa0 (mode 644) blob + /dev/null --- gotd/session.c +++ /dev/null @@ -1,1946 +0,0 @@ -/* - * 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 -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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.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; - -static struct gotd_session { - 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_procid proc_id; - enum gotd_session_state state; - struct gotd_imsgev repo_child_iev; -} gotd_session; - -static struct gotd_session_client { - int is_writing; - 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; - -void gotd_session_sighdlr(int sig, short event, void *arg); -static void gotd_session_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, gotd_session.proc_id, -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); - - gotd_session_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, gotd_session.proc_id, 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); -} - -void -gotd_session_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: - gotd_session_shutdown(); - /* NOTREACHED */ - break; - default: - fatalx("unexpected signal"); - } -} - -static const struct got_error * -recv_packfile_done(struct imsg *imsg) -{ - size_t datalen; - - log_debug("packfile-done received"); - - datalen = imsg->hdr.len - IMSG_HEADER_SIZE; - if (datalen != 0) - return got_error(GOT_ERR_PRIVSEP_LEN); - - return NULL; -} - -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, - gotd_session.proc_id, 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, - gotd_session.proc_id, -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, - gotd_session.proc_id, 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, - gotd_session.proc_id, 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_DONE: - do_disconnect = 1; - err = recv_packfile_done(&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) { - 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 * -ensure_client_is_reading(struct gotd_session_client *client) -{ - if (client->is_writing) { - return got_error_fmt(GOT_ERR_BAD_PACKET, - "uid %d made a read-request but is not reading from " - "a repository", client->euid); - } - - return NULL; -} - -static const struct got_error * -ensure_client_is_writing(struct gotd_session_client *client) -{ - if (!client->is_writing) { - return got_error_fmt(GOT_ERR_BAD_PACKET, - "uid %d made a write-request but is not writing to " - "a repository", client->euid); - } - - return NULL; -} - -static const struct got_error * -forward_want(struct gotd_session_client *client, struct imsg *imsg) -{ - struct gotd_imsg_want ireq; - struct gotd_imsg_want iwant; - 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, datalen); - - memset(&iwant, 0, sizeof(iwant)); - memcpy(iwant.object_id, ireq.object_id, SHA1_DIGEST_LENGTH); - - if (gotd_imsg_compose_event(&gotd_session.repo_child_iev, - GOTD_IMSG_WANT, gotd_session.proc_id, -1, - &iwant, sizeof(iwant)) == -1) - return got_error_from_errno("imsg compose WANT"); - - 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, gotd_session.proc_id, -1, - iref, datalen) == -1) - err = got_error_from_errno("imsg compose REF_UPDATE"); - free(iref); - return err; -} - -static const struct got_error * -forward_have(struct gotd_session_client *client, struct imsg *imsg) -{ - struct gotd_imsg_have ireq; - struct gotd_imsg_have ihave; - 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, datalen); - - memset(&ihave, 0, sizeof(ihave)); - memcpy(ihave.object_id, ireq.object_id, SHA1_DIGEST_LENGTH); - - if (gotd_imsg_compose_event(&gotd_session.repo_child_iev, - GOTD_IMSG_HAVE, gotd_session.proc_id, -1, - &ihave, sizeof(ihave)) == -1) - return got_error_from_errno("imsg compose HAVE"); - - return NULL; -} - -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, gotd_session.proc_id, 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, gotd_session.proc_id, 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, gotd_session.proc_id, - 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, gotd_session.proc_id, 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 const struct got_error * -send_packfile(struct gotd_session_client *client) -{ - const struct got_error *err = NULL; - struct gotd_imsg_send_packfile ipack; - int pipe[2]; - - if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pipe) == -1) - return got_error_from_errno("socketpair"); - - memset(&ipack, 0, sizeof(ipack)); - - if (client_has_capability(client, GOT_CAPA_SIDE_BAND_64K)) - ipack.report_progress = 1; - - client->delta_cache_fd = got_opentempfd(); - if (client->delta_cache_fd == -1) - return got_error_from_errno("got_opentempfd"); - - if (gotd_imsg_compose_event(&gotd_session.repo_child_iev, - GOTD_IMSG_SEND_PACKFILE, PROC_GOTD, client->delta_cache_fd, - &ipack, sizeof(ipack)) == -1) { - err = got_error_from_errno("imsg compose SEND_PACKFILE"); - close(pipe[0]); - close(pipe[1]); - return err; - } - - /* Send pack pipe end 0 to repo child process. */ - if (gotd_imsg_compose_event(&gotd_session.repo_child_iev, - GOTD_IMSG_PACKFILE_PIPE, PROC_GOTD, pipe[0], NULL, 0) == -1) { - err = got_error_from_errno("imsg compose PACKFILE_PIPE"); - close(pipe[1]); - return err; - } - - /* 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_GOTD, pipe[1], NULL, 0) == -1) - err = got_error_from_errno("imsg compose PACKFILE_PIPE"); - - 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; - if (!client->is_writing) { - gotd_session.state = GOTD_STATE_EXPECT_WANT; - client->accept_flush_pkt = 1; - log_debug("uid %d: expecting want-lines", - client->euid); - } else if (client->is_writing) { - gotd_session.state = GOTD_STATE_EXPECT_REF_UPDATE; - client->accept_flush_pkt = 1; - log_debug("uid %d: expecting ref-update-lines", - client->euid); - } else - fatalx("client %d is both reading and writing", - client->euid); - break; - case GOTD_IMSG_WANT: - if (gotd_session.state != GOTD_STATE_EXPECT_WANT) { - err = got_error_msg(GOT_ERR_BAD_REQUEST, - "unexpected want-line received"); - break; - } - log_debug("received want-line from uid %d", - client->euid); - err = ensure_client_is_reading(client); - if (err) - break; - client->accept_flush_pkt = 1; - err = forward_want(client, &imsg); - 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 = ensure_client_is_writing(client); - if (err) - break; - 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_HAVE: - if (gotd_session.state != GOTD_STATE_EXPECT_HAVE) { - err = got_error_msg(GOT_ERR_BAD_REQUEST, - "unexpected have-line received"); - break; - } - log_debug("received have-line from uid %d", - client->euid); - err = ensure_client_is_reading(client); - if (err) - break; - err = forward_have(client, &imsg); - if (err) - break; - client->accept_flush_pkt = 1; - break; - case GOTD_IMSG_FLUSH: - if (gotd_session.state == GOTD_STATE_EXPECT_WANT || - gotd_session.state == GOTD_STATE_EXPECT_HAVE) { - err = ensure_client_is_reading(client); - if (err) - break; - } else if (gotd_session.state == - GOTD_STATE_EXPECT_MORE_REF_UPDATES) { - err = ensure_client_is_writing(client); - if (err) - break; - } else if (gotd_session.state != GOTD_STATE_EXPECT_DONE) { - 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_WANT) { - gotd_session.state = GOTD_STATE_EXPECT_HAVE; - log_debug("uid %d: expecting have-lines", - client->euid); - } else if (gotd_session.state == GOTD_STATE_EXPECT_HAVE) { - gotd_session.state = GOTD_STATE_EXPECT_DONE; - client->accept_flush_pkt = 1; - log_debug("uid %d: expecting 'done'", - client->euid); - } else 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 if (gotd_session.state != GOTD_STATE_EXPECT_DONE) { - /* should not happen, see above */ - err = got_error_msg(GOT_ERR_BAD_REQUEST, - "unexpected client state"); - break; - } - break; - case GOTD_IMSG_DONE: - if (gotd_session.state != GOTD_STATE_EXPECT_HAVE && - gotd_session.state != GOTD_STATE_EXPECT_DONE) { - err = got_error_msg(GOT_ERR_BAD_REQUEST, - "unexpected flush-pkt received"); - break; - } - log_debug("received 'done' from uid %d", client->euid); - err = ensure_client_is_reading(client); - if (err) - break; - gotd_session.state = GOTD_STATE_DONE; - client->accept_flush_pkt = 1; - err = send_packfile(client); - 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, - gotd_session.proc_id, 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) - client->is_writing = 1; - else if (ichild.proc_id == PROC_REPO_READ) - client->is_writing = 0; - else - 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_main(const char *title, const char *repo_path, - int *pack_fds, int *temp_fds, struct timeval *request_timeout, - struct gotd_repo *repo_cfg, enum gotd_procid proc_id) -{ - 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; - gotd_session.proc_id = proc_id; - - 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, gotd_session_sighdlr, NULL); - signal_set(&evsigterm, SIGTERM, gotd_session_sighdlr, NULL); - signal_set(&evsighup, SIGHUP, gotd_session_sighdlr, NULL); - signal_set(&evsigusr1, SIGUSR1, gotd_session_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, gotd_session.proc_id, - -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); - gotd_session_shutdown(); -} - -void -gotd_session_shutdown(void) -{ - struct gotd_session_notif *notif; - - log_debug("shutting down"); - - 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); -} blob - /dev/null blob + e241b030efa315c61800ea1614da9cb6e48e47e3 (mode 644) --- /dev/null +++ gotd/session_read.c @@ -0,0 +1,913 @@ +/* + * 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 +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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_read.h" + +enum gotd_session_read_state { + GOTD_STATE_EXPECT_LIST_REFS, + GOTD_STATE_EXPECT_CAPABILITIES, + GOTD_STATE_EXPECT_WANT, + GOTD_STATE_EXPECT_HAVE, + GOTD_STATE_EXPECT_DONE, + GOTD_STATE_DONE, +}; + +static struct gotd_session_read { + 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_read_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_read_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_READ, -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_read_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_READ, 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_read_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_read_shutdown(); + /* NOTREACHED */ + break; + default: + fatalx("unexpected signal"); + } +} + +static const struct got_error * +recv_packfile_done(struct imsg *imsg) +{ + size_t datalen; + + log_debug("packfile-done received"); + + datalen = imsg->hdr.len - IMSG_HEADER_SIZE; + if (datalen != 0) + return got_error(GOT_ERR_PRIVSEP_LEN); + + 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; + + 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_DONE: + do_disconnect = 1; + err = recv_packfile_done(&imsg); + break; + default: + log_debug("unexpected imsg %d", imsg.hdr.type); + break; + } + + if (do_disconnect) { + if (err) + disconnect_on_error(client, err); + else + disconnect(client); + } else { + if (err) + log_warnx("uid %d: %s", client->euid, err->msg); + } + 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_want(struct gotd_session_client *client, struct imsg *imsg) +{ + struct gotd_imsg_want ireq; + struct gotd_imsg_want iwant; + 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, datalen); + + memset(&iwant, 0, sizeof(iwant)); + memcpy(iwant.object_id, ireq.object_id, SHA1_DIGEST_LENGTH); + + if (gotd_imsg_compose_event(&gotd_session.repo_child_iev, + GOTD_IMSG_WANT, PROC_SESSION_READ, -1, + &iwant, sizeof(iwant)) == -1) + return got_error_from_errno("imsg compose WANT"); + + return NULL; +} + +static const struct got_error * +forward_have(struct gotd_session_client *client, struct imsg *imsg) +{ + struct gotd_imsg_have ireq; + struct gotd_imsg_have ihave; + 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, datalen); + + memset(&ihave, 0, sizeof(ihave)); + memcpy(ihave.object_id, ireq.object_id, SHA1_DIGEST_LENGTH); + + if (gotd_imsg_compose_event(&gotd_session.repo_child_iev, + GOTD_IMSG_HAVE, PROC_SESSION_READ, -1, + &ihave, sizeof(ihave)) == -1) + return got_error_from_errno("imsg compose HAVE"); + + return NULL; +} + +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 * +send_packfile(struct gotd_session_client *client) +{ + const struct got_error *err = NULL; + struct gotd_imsg_send_packfile ipack; + int pipe[2]; + + if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pipe) == -1) + return got_error_from_errno("socketpair"); + + memset(&ipack, 0, sizeof(ipack)); + + if (client_has_capability(client, GOT_CAPA_SIDE_BAND_64K)) + ipack.report_progress = 1; + + client->delta_cache_fd = got_opentempfd(); + if (client->delta_cache_fd == -1) + return got_error_from_errno("got_opentempfd"); + + if (gotd_imsg_compose_event(&gotd_session.repo_child_iev, + GOTD_IMSG_SEND_PACKFILE, PROC_GOTD, client->delta_cache_fd, + &ipack, sizeof(ipack)) == -1) { + err = got_error_from_errno("imsg compose SEND_PACKFILE"); + close(pipe[0]); + close(pipe[1]); + return err; + } + + /* Send pack pipe end 0 to repo child process. */ + if (gotd_imsg_compose_event(&gotd_session.repo_child_iev, + GOTD_IMSG_PACKFILE_PIPE, PROC_GOTD, pipe[0], NULL, 0) == -1) { + err = got_error_from_errno("imsg compose PACKFILE_PIPE"); + close(pipe[1]); + return err; + } + + /* 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_GOTD, pipe[1], NULL, 0) == -1) + err = got_error_from_errno("imsg compose PACKFILE_PIPE"); + + 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_WANT; + client->accept_flush_pkt = 1; + log_debug("uid %d: expecting want-lines", client->euid); + break; + case GOTD_IMSG_WANT: + if (gotd_session.state != GOTD_STATE_EXPECT_WANT) { + err = got_error_msg(GOT_ERR_BAD_REQUEST, + "unexpected want-line received"); + break; + } + log_debug("received want-line from uid %d", + client->euid); + client->accept_flush_pkt = 1; + err = forward_want(client, &imsg); + break; + case GOTD_IMSG_HAVE: + if (gotd_session.state != GOTD_STATE_EXPECT_HAVE) { + err = got_error_msg(GOT_ERR_BAD_REQUEST, + "unexpected have-line received"); + break; + } + log_debug("received have-line from uid %d", + client->euid); + err = forward_have(client, &imsg); + if (err) + break; + client->accept_flush_pkt = 1; + break; + case GOTD_IMSG_FLUSH: + if (gotd_session.state != GOTD_STATE_EXPECT_WANT && + gotd_session.state != GOTD_STATE_EXPECT_HAVE && + gotd_session.state != GOTD_STATE_EXPECT_DONE) { + 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_WANT) { + gotd_session.state = GOTD_STATE_EXPECT_HAVE; + log_debug("uid %d: expecting have-lines", + client->euid); + } else if (gotd_session.state == GOTD_STATE_EXPECT_HAVE) { + gotd_session.state = GOTD_STATE_EXPECT_DONE; + client->accept_flush_pkt = 1; + log_debug("uid %d: expecting 'done'", + client->euid); + } else if (gotd_session.state != GOTD_STATE_EXPECT_DONE) { + /* should not happen, see above */ + err = got_error_msg(GOT_ERR_BAD_REQUEST, + "unexpected client state"); + break; + } + break; + case GOTD_IMSG_DONE: + if (gotd_session.state != GOTD_STATE_EXPECT_HAVE && + gotd_session.state != GOTD_STATE_EXPECT_DONE) { + err = got_error_msg(GOT_ERR_BAD_REQUEST, + "unexpected flush-pkt received"); + break; + } + log_debug("received 'done' from uid %d", client->euid); + gotd_session.state = GOTD_STATE_DONE; + client->accept_flush_pkt = 1; + err = send_packfile(client); + 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) + 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_READ, 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 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_READ) + 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_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_read_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; + + 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_read_sighdlr, NULL); + signal_set(&evsigterm, SIGTERM, session_read_sighdlr, NULL); + signal_set(&evsighup, SIGHUP, session_read_sighdlr, NULL); + signal_set(&evsigusr1, SIGUSR1, session_read_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_READ, + -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_read_shutdown(); +} + +static void +session_read_shutdown(void) +{ + log_debug("shutting down"); + + 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); +} blob - 624f7bb81035da8f1d5293bcbcdf72cd6f064102 (mode 644) blob + /dev/null --- gotd/session.h +++ /dev/null @@ -1,18 +0,0 @@ -/* - * 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. - */ - -void session_main(const char *, const char *, int *, int *, struct timeval *, - struct gotd_repo *, enum gotd_procid); blob - /dev/null blob + b96fc5b549fe7004dd780800c4f799ca93e17e4a (mode 644) --- /dev/null +++ gotd/session_read.h @@ -0,0 +1,18 @@ +/* + * 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. + */ + +void session_read_main(const char *, const char *, int *, int *, + struct timeval *, struct gotd_repo *); blob - /dev/null blob + c09b25ea44848c1e44137ce3c8cb35f931c01fa6 (mode 644) --- /dev/null +++ gotd/session_write.c @@ -0,0 +1,1738 @@ +/* + * 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 +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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) { + 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("shutting down"); + + 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); +} blob - /dev/null blob + 7f1a2353b4f56a051d2f89f6a21abcb94f0c3885 (mode 644) --- /dev/null +++ gotd/session_write.h @@ -0,0 +1,18 @@ +/* + * 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. + */ + +void session_write_main(const char *, const char *, int *, int *, + struct timeval *, struct gotd_repo *);