commit - 97a1ea05aadab5340a13a0a9760a7887f5779b9c
commit + e70fd95218f6bb77ff45fc0c94b8daeb6709ffa7
blob - 72ddf39763d42262c40de7298eb23144cf2d1703
blob + 40093462c835f76308db6ab0dc60b0735412ab92
--- gotd/Makefile
+++ gotd/Makefile
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
#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"
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
};
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
-/*
- * Copyright (c) 2022, 2023 Stefan Sperling <stsp@openbsd.org>
- *
- * 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 <sys/types.h>
-#include <sys/queue.h>
-#include <sys/tree.h>
-#include <sys/socket.h>
-#include <sys/stat.h>
-#include <sys/uio.h>
-
-#include <errno.h>
-#include <event.h>
-#include <limits.h>
-#include <sha1.h>
-#include <sha2.h>
-#include <signal.h>
-#include <stdint.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-#include <imsg.h>
-#include <unistd.h>
-
-#include "got_error.h"
-#include "got_repository.h"
-#include "got_object.h"
-#include "got_path.h"
-#include "got_reference.h"
-#include "got_opentemp.h"
-
-#include "got_lib_hash.h"
-#include "got_lib_delta.h"
-#include "got_lib_object.h"
-#include "got_lib_object_cache.h"
-#include "got_lib_pack.h"
-#include "got_lib_repository.h"
-#include "got_lib_gitproto.h"
-
-#include "gotd.h"
-#include "log.h"
-#include "session.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
+/*
+ * Copyright (c) 2022, 2023 Stefan Sperling <stsp@openbsd.org>
+ *
+ * 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 <sys/types.h>
+#include <sys/queue.h>
+#include <sys/tree.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/uio.h>
+
+#include <errno.h>
+#include <event.h>
+#include <limits.h>
+#include <sha1.h>
+#include <sha2.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <imsg.h>
+#include <unistd.h>
+
+#include "got_error.h"
+#include "got_repository.h"
+#include "got_object.h"
+#include "got_path.h"
+#include "got_reference.h"
+#include "got_opentemp.h"
+
+#include "got_lib_hash.h"
+#include "got_lib_delta.h"
+#include "got_lib_object.h"
+#include "got_lib_object_cache.h"
+#include "got_lib_pack.h"
+#include "got_lib_repository.h"
+#include "got_lib_gitproto.h"
+
+#include "gotd.h"
+#include "log.h"
+#include "session_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
-/*
- * Copyright (c) 2022, 2023 Stefan Sperling <stsp@openbsd.org>
- *
- * 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
+/*
+ * Copyright (c) 2022, 2023 Stefan Sperling <stsp@openbsd.org>
+ *
+ * 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
+/*
+ * Copyright (c) 2022, 2023 Stefan Sperling <stsp@openbsd.org>
+ *
+ * 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 <sys/types.h>
+#include <sys/queue.h>
+#include <sys/tree.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/uio.h>
+
+#include <errno.h>
+#include <event.h>
+#include <limits.h>
+#include <sha1.h>
+#include <sha2.h>
+#include <signal.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <imsg.h>
+#include <unistd.h>
+
+#include "got_error.h"
+#include "got_repository.h"
+#include "got_object.h"
+#include "got_path.h"
+#include "got_reference.h"
+#include "got_opentemp.h"
+
+#include "got_lib_hash.h"
+#include "got_lib_delta.h"
+#include "got_lib_object.h"
+#include "got_lib_object_cache.h"
+#include "got_lib_pack.h"
+#include "got_lib_repository.h"
+#include "got_lib_gitproto.h"
+
+#include "gotd.h"
+#include "log.h"
+#include "session_write.h"
+
+struct gotd_session_notif {
+ STAILQ_ENTRY(gotd_session_notif) entry;
+ int fd;
+ enum gotd_notification_action action;
+ char *refname;
+ struct got_object_id old_id;
+ struct got_object_id new_id;
+};
+STAILQ_HEAD(gotd_session_notifications, gotd_session_notif) notifications;
+
+enum gotd_session_write_state {
+ GOTD_STATE_EXPECT_LIST_REFS,
+ GOTD_STATE_EXPECT_CAPABILITIES,
+ GOTD_STATE_EXPECT_REF_UPDATE,
+ GOTD_STATE_EXPECT_MORE_REF_UPDATES,
+ GOTD_STATE_EXPECT_PACKFILE,
+ GOTD_STATE_NOTIFY,
+};
+
+static struct gotd_session_write {
+ pid_t pid;
+ const char *title;
+ struct got_repository *repo;
+ struct gotd_repo *repo_cfg;
+ int *pack_fds;
+ int *temp_fds;
+ struct gotd_imsgev parent_iev;
+ struct gotd_imsgev notifier_iev;
+ struct timeval request_timeout;
+ enum gotd_session_write_state state;
+ struct gotd_imsgev repo_child_iev;
+} gotd_session;
+
+static struct gotd_session_client {
+ struct gotd_client_capability *capabilities;
+ size_t ncapa_alloc;
+ size_t ncapabilities;
+ uint32_t id;
+ int fd;
+ int delta_cache_fd;
+ struct gotd_imsgev iev;
+ struct event tmo;
+ uid_t euid;
+ gid_t egid;
+ char *username;
+ char *packfile_path;
+ char *packidx_path;
+ int nref_updates;
+ int accept_flush_pkt;
+ int flush_disconnect;
+} gotd_session_client;
+
+static void session_write_shutdown(void);
+
+static void
+disconnect(struct gotd_session_client *client)
+{
+ log_debug("uid %d: disconnecting", client->euid);
+
+ if (gotd_imsg_compose_event(&gotd_session.parent_iev,
+ GOTD_IMSG_DISCONNECT, PROC_SESSION_WRITE, -1, NULL, 0) == -1)
+ log_warn("imsg compose DISCONNECT");
+
+ imsg_clear(&gotd_session.repo_child_iev.ibuf);
+ event_del(&gotd_session.repo_child_iev.ev);
+ evtimer_del(&client->tmo);
+ close(client->fd);
+ if (client->delta_cache_fd != -1)
+ close(client->delta_cache_fd);
+ if (client->packfile_path) {
+ if (unlink(client->packfile_path) == -1 && errno != ENOENT)
+ log_warn("unlink %s: ", client->packfile_path);
+ free(client->packfile_path);
+ }
+ if (client->packidx_path) {
+ if (unlink(client->packidx_path) == -1 && errno != ENOENT)
+ log_warn("unlink %s: ", client->packidx_path);
+ free(client->packidx_path);
+ }
+ free(client->capabilities);
+
+ session_write_shutdown();
+}
+
+static void
+disconnect_on_error(struct gotd_session_client *client,
+ const struct got_error *err)
+{
+ struct imsgbuf ibuf;
+
+ if (err->code != GOT_ERR_EOF) {
+ log_warnx("uid %d: %s", client->euid, err->msg);
+ imsg_init(&ibuf, client->fd);
+ gotd_imsg_send_error(&ibuf, 0, PROC_SESSION_WRITE, err);
+ imsg_clear(&ibuf);
+ }
+
+ disconnect(client);
+}
+
+static void
+gotd_request_timeout(int fd, short events, void *arg)
+{
+ struct gotd_session_client *client = arg;
+
+ log_debug("disconnecting uid %d due to timeout", client->euid);
+ disconnect(client);
+}
+
+static void
+session_write_sighdlr(int sig, short event, void *arg)
+{
+ /*
+ * Normal signal handler rules don't apply because libevent
+ * decouples for us.
+ */
+
+ switch (sig) {
+ case SIGHUP:
+ log_info("%s: ignoring SIGHUP", __func__);
+ break;
+ case SIGUSR1:
+ log_info("%s: ignoring SIGUSR1", __func__);
+ break;
+ case SIGTERM:
+ case SIGINT:
+ session_write_shutdown();
+ /* NOTREACHED */
+ break;
+ default:
+ fatalx("unexpected signal");
+ }
+}
+
+static const struct got_error *
+recv_packfile_install(struct imsg *imsg)
+{
+ struct gotd_imsg_packfile_install inst;
+ size_t datalen;
+
+ log_debug("packfile-install received");
+
+ datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
+ if (datalen != sizeof(inst))
+ return got_error(GOT_ERR_PRIVSEP_LEN);
+ memcpy(&inst, imsg->data, sizeof(inst));
+
+ return NULL;
+}
+
+static const struct got_error *
+recv_ref_updates_start(struct imsg *imsg)
+{
+ struct gotd_imsg_ref_updates_start istart;
+ size_t datalen;
+
+ log_debug("ref-updates-start received");
+
+ datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
+ if (datalen != sizeof(istart))
+ return got_error(GOT_ERR_PRIVSEP_LEN);
+ memcpy(&istart, imsg->data, sizeof(istart));
+
+ return NULL;
+}
+
+static const struct got_error *
+recv_ref_update(struct imsg *imsg)
+{
+ struct gotd_imsg_ref_update iref;
+ size_t datalen;
+
+ log_debug("ref-update received");
+
+ datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
+ if (datalen < sizeof(iref))
+ return got_error(GOT_ERR_PRIVSEP_LEN);
+ memcpy(&iref, imsg->data, sizeof(iref));
+
+ return NULL;
+}
+
+static const struct got_error *
+send_ref_update_ok(struct gotd_session_client *client,
+ struct gotd_imsg_ref_update *iref, const char *refname)
+{
+ struct gotd_imsg_ref_update_ok iok;
+ struct gotd_imsgev *iev = &client->iev;
+ struct ibuf *wbuf;
+ size_t len;
+
+ memset(&iok, 0, sizeof(iok));
+ memcpy(iok.old_id, iref->old_id, SHA1_DIGEST_LENGTH);
+ memcpy(iok.new_id, iref->new_id, SHA1_DIGEST_LENGTH);
+ iok.name_len = strlen(refname);
+
+ len = sizeof(iok) + iok.name_len;
+ wbuf = imsg_create(&iev->ibuf, GOTD_IMSG_REF_UPDATE_OK,
+ PROC_SESSION_WRITE, gotd_session.pid, len);
+ if (wbuf == NULL)
+ return got_error_from_errno("imsg_create REF_UPDATE_OK");
+
+ if (imsg_add(wbuf, &iok, sizeof(iok)) == -1)
+ return got_error_from_errno("imsg_add REF_UPDATE_OK");
+ if (imsg_add(wbuf, refname, iok.name_len) == -1)
+ return got_error_from_errno("imsg_add REF_UPDATE_OK");
+
+ imsg_close(&iev->ibuf, wbuf);
+ gotd_imsg_event_add(iev);
+ return NULL;
+}
+
+static void
+send_refs_updated(struct gotd_session_client *client)
+{
+ if (gotd_imsg_compose_event(&client->iev, GOTD_IMSG_REFS_UPDATED,
+ PROC_SESSION_WRITE, -1, NULL, 0) == -1)
+ log_warn("imsg compose REFS_UPDATED");
+}
+
+static const struct got_error *
+send_ref_update_ng(struct gotd_session_client *client,
+ struct gotd_imsg_ref_update *iref, const char *refname,
+ const char *reason)
+{
+ const struct got_error *ng_err;
+ struct gotd_imsg_ref_update_ng ing;
+ struct gotd_imsgev *iev = &client->iev;
+ struct ibuf *wbuf;
+ size_t len;
+
+ memset(&ing, 0, sizeof(ing));
+ memcpy(ing.old_id, iref->old_id, SHA1_DIGEST_LENGTH);
+ memcpy(ing.new_id, iref->new_id, SHA1_DIGEST_LENGTH);
+ ing.name_len = strlen(refname);
+
+ ng_err = got_error_fmt(GOT_ERR_REF_BUSY, "%s", reason);
+ ing.reason_len = strlen(ng_err->msg);
+
+ len = sizeof(ing) + ing.name_len + ing.reason_len;
+ wbuf = imsg_create(&iev->ibuf, GOTD_IMSG_REF_UPDATE_NG,
+ PROC_SESSION_WRITE, gotd_session.pid, len);
+ if (wbuf == NULL)
+ return got_error_from_errno("imsg_create REF_UPDATE_NG");
+
+ if (imsg_add(wbuf, &ing, sizeof(ing)) == -1)
+ return got_error_from_errno("imsg_add REF_UPDATE_NG");
+ if (imsg_add(wbuf, refname, ing.name_len) == -1)
+ return got_error_from_errno("imsg_add REF_UPDATE_NG");
+ if (imsg_add(wbuf, ng_err->msg, ing.reason_len) == -1)
+ return got_error_from_errno("imsg_add REF_UPDATE_NG");
+
+ imsg_close(&iev->ibuf, wbuf);
+ gotd_imsg_event_add(iev);
+ return NULL;
+}
+
+static const struct got_error *
+install_pack(struct gotd_session_client *client, const char *repo_path,
+ struct imsg *imsg)
+{
+ const struct got_error *err = NULL;
+ struct gotd_imsg_packfile_install inst;
+ char hex[SHA1_DIGEST_STRING_LENGTH];
+ size_t datalen;
+ char *packfile_path = NULL, *packidx_path = NULL;
+
+ datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
+ if (datalen != sizeof(inst))
+ return got_error(GOT_ERR_PRIVSEP_LEN);
+ memcpy(&inst, imsg->data, sizeof(inst));
+
+ if (client->packfile_path == NULL)
+ return got_error_msg(GOT_ERR_BAD_REQUEST,
+ "client has no pack file");
+ if (client->packidx_path == NULL)
+ return got_error_msg(GOT_ERR_BAD_REQUEST,
+ "client has no pack file index");
+
+ if (got_sha1_digest_to_str(inst.pack_sha1, hex, sizeof(hex)) == NULL)
+ return got_error_msg(GOT_ERR_NO_SPACE,
+ "could not convert pack file SHA1 to hex");
+
+ if (asprintf(&packfile_path, "/%s/%s/pack-%s.pack",
+ repo_path, GOT_OBJECTS_PACK_DIR, hex) == -1) {
+ err = got_error_from_errno("asprintf");
+ goto done;
+ }
+
+ if (asprintf(&packidx_path, "/%s/%s/pack-%s.idx",
+ repo_path, GOT_OBJECTS_PACK_DIR, hex) == -1) {
+ err = got_error_from_errno("asprintf");
+ goto done;
+ }
+
+ if (rename(client->packfile_path, packfile_path) == -1) {
+ err = got_error_from_errno3("rename", client->packfile_path,
+ packfile_path);
+ goto done;
+ }
+
+ free(client->packfile_path);
+ client->packfile_path = NULL;
+
+ if (rename(client->packidx_path, packidx_path) == -1) {
+ err = got_error_from_errno3("rename", client->packidx_path,
+ packidx_path);
+ goto done;
+ }
+
+ /* Ensure we re-read the pack index list upon next access. */
+ gotd_session.repo->pack_path_mtime.tv_sec = 0;
+ gotd_session.repo->pack_path_mtime.tv_nsec = 0;
+
+ free(client->packidx_path);
+ client->packidx_path = NULL;
+done:
+ free(packfile_path);
+ free(packidx_path);
+ return err;
+}
+
+static const struct got_error *
+begin_ref_updates(struct gotd_session_client *client, struct imsg *imsg)
+{
+ struct gotd_imsg_ref_updates_start istart;
+ size_t datalen;
+
+ if (client->nref_updates != -1)
+ return got_error(GOT_ERR_PRIVSEP_MSG);
+
+ datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
+ if (datalen != sizeof(istart))
+ return got_error(GOT_ERR_PRIVSEP_LEN);
+ memcpy(&istart, imsg->data, sizeof(istart));
+
+ if (istart.nref_updates <= 0)
+ return got_error(GOT_ERR_PRIVSEP_MSG);
+
+ client->nref_updates = istart.nref_updates;
+ return NULL;
+}
+
+static const struct got_error *
+validate_namespace(const char *namespace)
+{
+ size_t len = strlen(namespace);
+
+ if (len < 5 || strncmp("refs/", namespace, 5) != 0 ||
+ namespace[len - 1] != '/') {
+ return got_error_fmt(GOT_ERR_BAD_REF_NAME,
+ "reference namespace '%s'", namespace);
+ }
+
+ return NULL;
+}
+
+static const struct got_error *
+queue_notification(struct got_object_id *old_id, struct got_object_id *new_id,
+ struct got_repository *repo, struct got_reference *ref)
+{
+ const struct got_error *err = NULL;
+ struct gotd_repo *repo_cfg = gotd_session.repo_cfg;
+ struct gotd_imsgev *iev = &gotd_session.repo_child_iev;
+ struct got_pathlist_entry *pe;
+ struct gotd_session_notif *notif;
+
+ if (iev->ibuf.fd == -1 ||
+ STAILQ_EMPTY(&repo_cfg->notification_targets))
+ return NULL; /* notifications unused */
+
+ TAILQ_FOREACH(pe, &repo_cfg->notification_refs, entry) {
+ const char *refname = pe->path;
+ if (strcmp(got_ref_get_name(ref), refname) == 0)
+ break;
+ }
+ if (pe == NULL) {
+ TAILQ_FOREACH(pe, &repo_cfg->notification_ref_namespaces,
+ entry) {
+ const char *namespace = pe->path;
+
+ err = validate_namespace(namespace);
+ if (err)
+ return err;
+ if (strncmp(namespace, got_ref_get_name(ref),
+ strlen(namespace)) == 0)
+ break;
+ }
+ }
+
+ /*
+ * If a branch or a reference namespace was specified in the
+ * configuration file then only send notifications if a match
+ * was found.
+ */
+ if (pe == NULL && (!TAILQ_EMPTY(&repo_cfg->notification_refs) ||
+ !TAILQ_EMPTY(&repo_cfg->notification_ref_namespaces)))
+ return NULL;
+
+ notif = calloc(1, sizeof(*notif));
+ if (notif == NULL)
+ return got_error_from_errno("calloc");
+
+ notif->fd = -1;
+
+ if (old_id == NULL)
+ notif->action = GOTD_NOTIF_ACTION_CREATED;
+ else if (new_id == NULL)
+ notif->action = GOTD_NOTIF_ACTION_REMOVED;
+ else
+ notif->action = GOTD_NOTIF_ACTION_CHANGED;
+
+ if (old_id != NULL)
+ memcpy(¬if->old_id, old_id, sizeof(notif->old_id));
+ if (new_id != NULL)
+ memcpy(¬if->new_id, new_id, sizeof(notif->new_id));
+
+ notif->refname = strdup(got_ref_get_name(ref));
+ if (notif->refname == NULL) {
+ err = got_error_from_errno("strdup");
+ goto done;
+ }
+
+ STAILQ_INSERT_TAIL(¬ifications, notif, entry);
+done:
+ if (err && notif) {
+ free(notif->refname);
+ free(notif);
+ }
+ return err;
+}
+
+/* Forward notification content to the NOTIFY process. */
+static const struct got_error *
+forward_notification(struct gotd_session_client *client, struct imsg *imsg)
+{
+ const struct got_error *err = NULL;
+ struct gotd_imsgev *iev = &gotd_session.notifier_iev;
+ struct gotd_session_notif *notif;
+ struct gotd_imsg_notification_content icontent;
+ char *refname = NULL;
+ size_t datalen;
+ struct gotd_imsg_notify inotify;
+ const char *action;
+
+ memset(&inotify, 0, sizeof(inotify));
+
+ datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
+ if (datalen < sizeof(icontent))
+ return got_error(GOT_ERR_PRIVSEP_LEN);
+ memcpy(&icontent, imsg->data, sizeof(icontent));
+ if (datalen != sizeof(icontent) + icontent.refname_len)
+ return got_error(GOT_ERR_PRIVSEP_LEN);
+ refname = strndup(imsg->data + sizeof(icontent), icontent.refname_len);
+ if (refname == NULL)
+ return got_error_from_errno("strndup");
+
+ notif = STAILQ_FIRST(¬ifications);
+ if (notif == NULL)
+ return got_error(GOT_ERR_PRIVSEP_MSG);
+
+ STAILQ_REMOVE(¬ifications, notif, gotd_session_notif, entry);
+
+ if (notif->action != icontent.action || notif->fd == -1 ||
+ strcmp(notif->refname, refname) != 0) {
+ err = got_error(GOT_ERR_PRIVSEP_MSG);
+ goto done;
+ }
+ if (notif->action == GOTD_NOTIF_ACTION_CREATED) {
+ if (memcmp(notif->new_id.sha1, icontent.new_id,
+ SHA1_DIGEST_LENGTH) != 0) {
+ err = got_error_msg(GOT_ERR_PRIVSEP_MSG,
+ "received notification content for unknown event");
+ goto done;
+ }
+ } else if (notif->action == GOTD_NOTIF_ACTION_REMOVED) {
+ if (memcmp(notif->old_id.sha1, icontent.old_id,
+ SHA1_DIGEST_LENGTH) != 0) {
+ err = got_error_msg(GOT_ERR_PRIVSEP_MSG,
+ "received notification content for unknown event");
+ goto done;
+ }
+ } else if (memcmp(notif->old_id.sha1, icontent.old_id,
+ SHA1_DIGEST_LENGTH) != 0 ||
+ memcmp(notif->new_id.sha1, icontent.new_id,
+ SHA1_DIGEST_LENGTH) != 0) {
+ err = got_error_msg(GOT_ERR_PRIVSEP_MSG,
+ "received notification content for unknown event");
+ goto done;
+ }
+
+ switch (notif->action) {
+ case GOTD_NOTIF_ACTION_CREATED:
+ action = "created";
+ break;
+ case GOTD_NOTIF_ACTION_REMOVED:
+ action = "removed";
+ break;
+ case GOTD_NOTIF_ACTION_CHANGED:
+ action = "changed";
+ break;
+ default:
+ err = got_error(GOT_ERR_PRIVSEP_MSG);
+ goto done;
+ }
+
+ strlcpy(inotify.repo_name, gotd_session.repo_cfg->name,
+ sizeof(inotify.repo_name));
+
+ snprintf(inotify.subject_line, sizeof(inotify.subject_line),
+ "%s: %s %s %s", gotd_session.repo_cfg->name,
+ client->username, action, notif->refname);
+
+ if (gotd_imsg_compose_event(iev, GOTD_IMSG_NOTIFY,
+ PROC_SESSION_WRITE, notif->fd, &inotify, sizeof(inotify))
+ == -1) {
+ err = got_error_from_errno("imsg compose NOTIFY");
+ goto done;
+ }
+ notif->fd = -1;
+done:
+ if (notif->fd != -1)
+ close(notif->fd);
+ free(notif);
+ free(refname);
+ return err;
+}
+
+/* Request notification content from REPO_WRITE process. */
+static const struct got_error *
+request_notification(struct gotd_session_notif *notif)
+{
+ const struct got_error *err = NULL;
+ struct gotd_imsgev *iev = &gotd_session.repo_child_iev;
+ struct gotd_imsg_notification_content icontent;
+ struct ibuf *wbuf;
+ size_t len;
+ int fd;
+
+ fd = got_opentempfd();
+ if (fd == -1)
+ return got_error_from_errno("got_opentemp");
+
+ memset(&icontent, 0, sizeof(icontent));
+
+ icontent.action = notif->action;
+ memcpy(&icontent.old_id, ¬if->old_id, sizeof(notif->old_id));
+ memcpy(&icontent.new_id, ¬if->new_id, sizeof(notif->new_id));
+ icontent.refname_len = strlen(notif->refname);
+
+ len = sizeof(icontent) + icontent.refname_len;
+ wbuf = imsg_create(&iev->ibuf, GOTD_IMSG_NOTIFY,
+ PROC_SESSION_WRITE, gotd_session.pid, len);
+ if (wbuf == NULL) {
+ err = got_error_from_errno("imsg_create NOTIFY");
+ goto done;
+ }
+ if (imsg_add(wbuf, &icontent, sizeof(icontent)) == -1) {
+ err = got_error_from_errno("imsg_add NOTIFY");
+ goto done;
+ }
+ if (imsg_add(wbuf, notif->refname, icontent.refname_len) == -1) {
+ err = got_error_from_errno("imsg_add NOTIFY");
+ goto done;
+ }
+
+ notif->fd = dup(fd);
+ if (notif->fd == -1) {
+ err = got_error_from_errno("dup");
+ goto done;
+ }
+
+ ibuf_fd_set(wbuf, fd);
+ fd = -1;
+
+ imsg_close(&iev->ibuf, wbuf);
+ gotd_imsg_event_add(iev);
+done:
+ if (err && fd != -1)
+ close(fd);
+ return err;
+}
+
+static const struct got_error *
+update_ref(int *shut, struct gotd_session_client *client,
+ const char *repo_path, struct imsg *imsg)
+{
+ const struct got_error *err = NULL;
+ struct got_repository *repo = gotd_session.repo;
+ struct got_reference *ref = NULL;
+ struct gotd_imsg_ref_update iref;
+ struct got_object_id old_id, new_id;
+ struct gotd_session_notif *notif;
+ struct got_object_id *id = NULL;
+ char *refname = NULL;
+ size_t datalen;
+ int locked = 0;
+ char hex1[SHA1_DIGEST_STRING_LENGTH];
+ char hex2[SHA1_DIGEST_STRING_LENGTH];
+
+ log_debug("update-ref from uid %d", client->euid);
+
+ if (client->nref_updates <= 0)
+ return got_error(GOT_ERR_PRIVSEP_MSG);
+
+ datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
+ if (datalen < sizeof(iref))
+ return got_error(GOT_ERR_PRIVSEP_LEN);
+ memcpy(&iref, imsg->data, sizeof(iref));
+ if (datalen != sizeof(iref) + iref.name_len)
+ return got_error(GOT_ERR_PRIVSEP_LEN);
+ refname = strndup(imsg->data + sizeof(iref), iref.name_len);
+ if (refname == NULL)
+ return got_error_from_errno("strndup");
+
+ log_debug("updating ref %s for uid %d", refname, client->euid);
+
+ memcpy(old_id.sha1, iref.old_id, SHA1_DIGEST_LENGTH);
+ memcpy(new_id.sha1, iref.new_id, SHA1_DIGEST_LENGTH);
+ err = got_repo_find_object_id(iref.delete_ref ? &old_id : &new_id,
+ repo);
+ if (err)
+ goto done;
+
+ if (iref.ref_is_new) {
+ err = got_ref_open(&ref, repo, refname, 0);
+ if (err) {
+ if (err->code != GOT_ERR_NOT_REF)
+ goto done;
+ err = got_ref_alloc(&ref, refname, &new_id);
+ if (err)
+ goto done;
+ err = got_ref_write(ref, repo); /* will lock/unlock */
+ if (err)
+ goto done;
+ err = queue_notification(NULL, &new_id, repo, ref);
+ if (err)
+ goto done;
+ } else {
+ err = got_ref_resolve(&id, repo, ref);
+ if (err)
+ goto done;
+ got_object_id_hex(&new_id, hex1, sizeof(hex1));
+ got_object_id_hex(id, hex2, sizeof(hex2));
+ err = got_error_fmt(GOT_ERR_REF_BUSY,
+ "Addition %s: %s failed; %s: %s has been "
+ "created by someone else while transaction "
+ "was in progress",
+ got_ref_get_name(ref), hex1,
+ got_ref_get_name(ref), hex2);
+ goto done;
+ }
+ } else if (iref.delete_ref) {
+ err = got_ref_open(&ref, repo, refname, 1 /* lock */);
+ if (err)
+ goto done;
+ locked = 1;
+
+ err = got_ref_resolve(&id, repo, ref);
+ if (err)
+ goto done;
+
+ if (got_object_id_cmp(id, &old_id) != 0) {
+ got_object_id_hex(&old_id, hex1, sizeof(hex1));
+ got_object_id_hex(id, hex2, sizeof(hex2));
+ err = got_error_fmt(GOT_ERR_REF_BUSY,
+ "Deletion %s: %s failed; %s: %s has been "
+ "created by someone else while transaction "
+ "was in progress",
+ got_ref_get_name(ref), hex1,
+ got_ref_get_name(ref), hex2);
+ goto done;
+ }
+
+ err = got_ref_delete(ref, repo);
+ if (err)
+ goto done;
+ err = queue_notification(&old_id, NULL, repo, ref);
+ if (err)
+ goto done;
+ free(id);
+ id = NULL;
+ } else {
+ err = got_ref_open(&ref, repo, refname, 1 /* lock */);
+ if (err)
+ goto done;
+ locked = 1;
+
+ err = got_ref_resolve(&id, repo, ref);
+ if (err)
+ goto done;
+
+ if (got_object_id_cmp(id, &old_id) != 0) {
+ got_object_id_hex(&old_id, hex1, sizeof(hex1));
+ got_object_id_hex(id, hex2, sizeof(hex2));
+ err = got_error_fmt(GOT_ERR_REF_BUSY,
+ "Update %s: %s failed; %s: %s has been "
+ "created by someone else while transaction "
+ "was in progress",
+ got_ref_get_name(ref), hex1,
+ got_ref_get_name(ref), hex2);
+ goto done;
+ }
+
+ if (got_object_id_cmp(&new_id, &old_id) != 0) {
+ err = got_ref_change_ref(ref, &new_id);
+ if (err)
+ goto done;
+ err = got_ref_write(ref, repo);
+ if (err)
+ goto done;
+ err = queue_notification(&old_id, &new_id, repo, ref);
+ if (err)
+ goto done;
+ }
+
+ free(id);
+ id = NULL;
+ }
+done:
+ if (err) {
+ if (err->code == GOT_ERR_LOCKFILE_TIMEOUT) {
+ err = got_error_fmt(GOT_ERR_LOCKFILE_TIMEOUT,
+ "could not acquire exclusive file lock for %s",
+ refname);
+ }
+ send_ref_update_ng(client, &iref, refname, err->msg);
+ } else
+ send_ref_update_ok(client, &iref, refname);
+
+ if (client->nref_updates > 0) {
+ client->nref_updates--;
+ if (client->nref_updates == 0) {
+ send_refs_updated(client);
+ notif = STAILQ_FIRST(¬ifications);
+ if (notif) {
+ gotd_session.state = GOTD_STATE_NOTIFY;
+ err = request_notification(notif);
+ if (err) {
+ log_warn("could not send notification: "
+ "%s", err->msg);
+ client->flush_disconnect = 1;
+ }
+ } else
+ client->flush_disconnect = 1;
+ }
+
+ }
+ if (locked) {
+ const struct got_error *unlock_err;
+ unlock_err = got_ref_unlock(ref);
+ if (unlock_err && err == NULL)
+ err = unlock_err;
+ }
+ if (ref)
+ got_ref_close(ref);
+ free(refname);
+ free(id);
+ return err;
+}
+
+static const struct got_error *
+recv_notification_content(struct imsg *imsg)
+{
+ struct gotd_imsg_notification_content inotif;
+ size_t datalen;
+
+ datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
+ if (datalen < sizeof(inotif))
+ return got_error(GOT_ERR_PRIVSEP_LEN);
+ memcpy(&inotif, imsg->data, sizeof(inotif));
+
+ return NULL;
+}
+
+static void
+session_dispatch_repo_child(int fd, short event, void *arg)
+{
+ struct gotd_imsgev *iev = arg;
+ struct imsgbuf *ibuf = &iev->ibuf;
+ struct gotd_session_client *client = &gotd_session_client;
+ ssize_t n;
+ int shut = 0;
+ struct imsg imsg;
+
+ if (event & EV_READ) {
+ if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
+ fatal("imsg_read error");
+ if (n == 0) {
+ /* Connection closed. */
+ shut = 1;
+ goto done;
+ }
+ }
+
+ if (event & EV_WRITE) {
+ n = msgbuf_write(&ibuf->w);
+ if (n == -1 && errno != EAGAIN)
+ fatal("msgbuf_write");
+ if (n == 0) {
+ /* Connection closed. */
+ shut = 1;
+ goto done;
+ }
+ }
+
+ for (;;) {
+ const struct got_error *err = NULL;
+ uint32_t client_id = 0;
+ int do_disconnect = 0;
+ int do_ref_updates = 0, do_ref_update = 0;
+ int do_packfile_install = 0, do_notify = 0;
+
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("%s: imsg_get error", __func__);
+ if (n == 0) /* No more messages. */
+ break;
+
+ switch (imsg.hdr.type) {
+ case GOTD_IMSG_ERROR:
+ do_disconnect = 1;
+ err = gotd_imsg_recv_error(&client_id, &imsg);
+ break;
+ case GOTD_IMSG_PACKFILE_INSTALL:
+ err = recv_packfile_install(&imsg);
+ if (err == NULL)
+ do_packfile_install = 1;
+ break;
+ case GOTD_IMSG_REF_UPDATES_START:
+ err = recv_ref_updates_start(&imsg);
+ if (err == NULL)
+ do_ref_updates = 1;
+ break;
+ case GOTD_IMSG_REF_UPDATE:
+ err = recv_ref_update(&imsg);
+ if (err == NULL)
+ do_ref_update = 1;
+ break;
+ case GOTD_IMSG_NOTIFY:
+ err = recv_notification_content(&imsg);
+ if (err == NULL)
+ do_notify = 1;
+ break;
+ default:
+ log_debug("unexpected imsg %d", imsg.hdr.type);
+ break;
+ }
+
+ if (do_disconnect) {
+ 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
+/*
+ * Copyright (c) 2022, 2023 Stefan Sperling <stsp@openbsd.org>
+ *
+ * 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 *);