commit - 75b17c2a7d14fc0476cba0375a5a031cf0c13a00
commit + ae7c1b785440d9b4e2289d935ca85725f45680f1
blob - 898aca45a6b8febe42a7d4946fbe6a9d5a51ad55
blob + 447c95850f26e58bc93149f6531707c99bf1691b
--- gotctl/gotctl.c
+++ gotctl/gotctl.c
printf("client UID %d, GID %d, protocol state '%s', ",
info.euid, info.egid, get_state_name(info.state));
+ if (info.session_child_pid)
+ printf("session PID %ld, ", (long)info.session_child_pid);
+ if (info.repo_child_pid)
+ printf("repo PID %ld, ", (long)info.repo_child_pid);
if (info.is_writing)
printf("writing to %s\n", info.repo_name);
else
blob - 150c4e0f6fded4edfa8704bb486d56939d38c114
blob + 4ba33eed625e9340f8987306ffe6ead0dcedd209
--- 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 \
sha1.c sigs.c pack_create_io.c pollfd.c reference_parse.c \
- repo_imsg.c pack_index.c
+ repo_imsg.c pack_index.c session.c
MAN = ${PROG}.conf.5 ${PROG}.8
blob - ef8285e2c0dea56c703592e647a3268f64478a82
blob + de1e260cf720e4827802724eb7d7dc9215167201
--- gotd/gotd.c
+++ gotd/gotd.c
#include "log.h"
#include "listen.h"
#include "auth.h"
+#include "session.h"
#include "repo_read.h"
#include "repo_write.h"
struct gotd_child_proc *repo_read;
struct gotd_child_proc *repo_write;
struct gotd_child_proc *auth;
+ struct gotd_child_proc *session;
int required_auth;
char *packfile_path;
char *packidx_path;
- int nref_updates;
};
STAILQ_HEAD(gotd_clients, gotd_client);
void gotd_sighdlr(int sig, short event, void *arg);
static void gotd_shutdown(void);
+static const struct got_error *start_session_child(struct gotd_client *,
+ struct gotd_repo *, char *, const char *, int, int);
static const struct got_error *start_repo_child(struct gotd_client *,
enum gotd_procid, struct gotd_repo *, char *, const char *, int, int);
static const struct got_error *start_auth_child(struct gotd_client *, int,
}
static struct gotd_child_proc *
-get_client_proc(struct gotd_client *client)
+get_client_repo_proc(struct gotd_client *client)
{
if (client->repo_read && client->repo_write) {
fatalx("uid %d is reading and writing in the same session",
struct gotd_client *c;
STAILQ_FOREACH(c, &gotd_clients[slot], entry) {
- struct gotd_child_proc *proc = get_client_proc(c);
+ struct gotd_child_proc *proc = get_client_repo_proc(c);
if (proc && proc->iev.ibuf.fd == fd)
return c;
if (c->auth && c->auth->iev.ibuf.fd == fd)
+ return c;
+ if (c->session && c->session->iev.ibuf.fd == fd)
return c;
}
}
}
static const struct got_error *
-ensure_client_is_reading(struct gotd_client *client)
-{
- if (!client_is_reading(client)) {
- 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_client *client)
-{
- if (!client_is_writing(client)) {
- 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 *
ensure_client_is_not_writing(struct gotd_client *client)
{
if (client_is_writing(client)) {
(long)pid, WTERMSIG(status));
}
} while (pid != -1 || (pid == -1 && errno == EINTR));
+}
+
+static void
+proc_done(struct gotd_child_proc *proc)
+{
+ event_del(&proc->iev.ev);
+ msgbuf_clear(&proc->iev.ibuf.w);
+ close(proc->iev.ibuf.fd);
+ kill_proc(proc, 0);
+ wait_for_child(proc->pid);
+ free(proc);
}
static void
proc = client->auth;
client->auth = NULL;
- event_del(&proc->iev.ev);
- msgbuf_clear(&proc->iev.ibuf.w);
- close(proc->iev.ibuf.fd);
- kill_proc(proc, 0);
- wait_for_child(proc->pid);
- free(proc);
+ proc_done(proc);
}
static void
+kill_session_proc(struct gotd_client *client)
+{
+ struct gotd_child_proc *proc;
+
+ if (client->session == NULL)
+ return;
+
+ proc = client->session;
+ client->session = NULL;
+
+ proc_done(proc);
+}
+
+static void
disconnect(struct gotd_client *client)
{
struct gotd_imsg_disconnect idisconnect;
- struct gotd_child_proc *proc = get_client_proc(client);
+ struct gotd_child_proc *proc = get_client_repo_proc(client);
struct gotd_child_proc *listen_proc = &gotd.listen_proc;
uint64_t slot;
log_debug("uid %d: disconnecting", client->euid);
kill_auth_proc(client);
+ kill_session_proc(client);
idisconnect.client_id = client->id;
if (proc) {
imsg_clear(&client->iev.ibuf);
event_del(&client->iev.ev);
evtimer_del(&client->tmo);
- close(client->fd);
+ if (client->fd != -1)
+ close(client->fd);
+ else if (client->iev.ibuf.fd != -1)
+ close(client->iev.ibuf.fd);
if (client->delta_cache_fd != -1)
close(client->delta_cache_fd);
if (client->packfile_path) {
struct imsgbuf ibuf;
log_warnx("uid %d: %s", client->euid, err->msg);
- if (err->code != GOT_ERR_EOF) {
+ if (err->code != GOT_ERR_EOF && client->fd != -1) {
imsg_init(&ibuf, client->fd);
gotd_imsg_send_error(&ibuf, 0, PROC_GOTD, err);
imsg_clear(&ibuf);
iclient.euid = client->euid;
iclient.egid = client->egid;
- proc = get_client_proc(client);
+ proc = get_client_repo_proc(client);
if (proc) {
if (strlcpy(iclient.repo_name, proc->repo_path,
sizeof(iclient.repo_name)) >= sizeof(iclient.repo_name)) {
}
if (client_is_writing(client))
iclient.is_writing = 1;
+
+ iclient.repo_child_pid = proc->pid;
}
iclient.state = client->state;
+ if (client->session)
+ iclient.session_child_pid = client->session->pid;
iclient.ncapabilities = client->ncapabilities;
if (gotd_imsg_compose_event(iev, GOTD_IMSG_INFO_CLIENT, PROC_GOTD, -1,
}
static const struct got_error *
-start_client_session(struct gotd_client *client, struct imsg *imsg)
+start_client_authentication(struct gotd_client *client, struct imsg *imsg)
{
const struct got_error *err;
struct gotd_imsg_list_refs ireq;
log_debug("list-refs request from uid %d", client->euid);
+ if (client->state != GOTD_STATE_EXPECT_LIST_REFS)
+ return got_error_msg(GOT_ERR_BAD_REQUEST,
+ "unexpected list-refs request received");
+
datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
if (datalen != sizeof(ireq))
return got_error(GOT_ERR_PRIVSEP_LEN);
return err;
}
- /* Flow continues upon authentication successs/failure or timeout. */
- return NULL;
-}
-
-static const struct got_error *
-forward_want(struct gotd_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);
- iwant.client_id = client->id;
-
- if (gotd_imsg_compose_event(&client->repo_read->iev, GOTD_IMSG_WANT,
- PROC_GOTD, -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_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);
-
- iref->client_id = client->id;
- if (gotd_imsg_compose_event(&client->repo_write->iev,
- GOTD_IMSG_REF_UPDATE, PROC_GOTD, -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_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);
- ihave.client_id = client->id;
-
- if (gotd_imsg_compose_event(&client->repo_read->iev, GOTD_IMSG_HAVE,
- PROC_GOTD, -1, &ihave, sizeof(ihave)) == -1)
- return got_error_from_errno("imsg compose HAVE");
+ evtimer_add(&client->tmo, &auth_timeout);
+ /* Flow continues upon authentication successs/failure or timeout. */
return NULL;
}
-static int
-client_has_capability(struct gotd_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_capabilities(struct gotd_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_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 = malloc(icapa.key_len + 1);
- if (key == NULL)
- return got_error_from_errno("malloc");
- if (icapa.value_len > 0) {
- value = malloc(icapa.value_len + 1);
- if (value == NULL) {
- free(key);
- return got_error_from_errno("malloc");
- }
- }
-
- memcpy(key, imsg->data + sizeof(icapa), icapa.key_len);
- key[icapa.key_len] = '\0';
- if (value) {
- memcpy(value, imsg->data + sizeof(icapa) + icapa.key_len,
- icapa.value_len);
- value[icapa.value_len] = '\0';
- }
-
- 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 *
-send_packfile(struct gotd_client *client)
-{
- const struct got_error *err = NULL;
- struct gotd_imsg_send_packfile ipack;
- struct gotd_imsg_packfile_pipe ipipe;
- int pipe[2];
-
- if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pipe) == -1)
- return got_error_from_errno("socketpair");
-
- memset(&ipack, 0, sizeof(ipack));
- memset(&ipipe, 0, sizeof(ipipe));
-
- ipack.client_id = client->id;
- 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(&client->repo_read->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;
- }
-
- ipipe.client_id = client->id;
-
- /* Send pack pipe end 0 to repo_read. */
- if (gotd_imsg_compose_event(&client->repo_read->iev,
- GOTD_IMSG_PACKFILE_PIPE, PROC_GOTD, pipe[0],
- &ipipe, sizeof(ipipe)) == -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 const struct got_error *
-recv_packfile(struct gotd_client *client)
-{
- const struct got_error *err = NULL;
- struct gotd_imsg_recv_packfile ipack;
- struct gotd_imsg_packfile_pipe ipipe;
- struct gotd_imsg_packidx_file ifile;
- 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");
-
- memset(&ipipe, 0, sizeof(ipipe));
- ipipe.client_id = client->id;
-
- /* Send pack pipe end 0 to repo_write. */
- if (gotd_imsg_compose_event(&client->repo_write->iev,
- GOTD_IMSG_PACKFILE_PIPE, PROC_GOTD, pipe[0],
- &ipipe, sizeof(ipipe)) == -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_GOTD, 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",
- client->repo_write->repo_path, 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;
-
- free(basepath);
- if (asprintf(&basepath, "%s/%s/receiving-from-uid-%d.idx",
- client->repo_write->repo_path, 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;
-
- memset(&ifile, 0, sizeof(ifile));
- ifile.client_id = client->id;
- if (gotd_imsg_compose_event(&client->repo_write->iev,
- GOTD_IMSG_PACKIDX_FILE, PROC_GOTD, idxfd,
- &ifile, sizeof(ifile)) == -1) {
- err = got_error_from_errno("imsg compose PACKIDX_FILE");
- idxfd = -1;
- goto done;
- }
- idxfd = -1;
-
- memset(&ipack, 0, sizeof(ipack));
- ipack.client_id = client->id;
- if (client_has_capability(client, GOT_CAPA_REPORT_STATUS))
- ipack.report_status = 1;
-
- if (gotd_imsg_compose_event(&client->repo_write->iev,
- GOTD_IMSG_RECV_PACKFILE, PROC_GOTD, 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
gotd_request(int fd, short events, void *arg)
{
err = stop_gotd(client);
break;
case GOTD_IMSG_LIST_REFS:
- if (client->state != GOTD_STATE_EXPECT_LIST_REFS) {
- err = got_error_msg(GOT_ERR_BAD_REQUEST,
- "unexpected list-refs request received");
- break;
- }
- err = start_client_session(client, &imsg);
- if (err)
- break;
- break;
- case GOTD_IMSG_CAPABILITIES:
- if (client->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 (client->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_reading(client)) {
- client->state = GOTD_STATE_EXPECT_WANT;
- log_debug("uid %d: expecting want-lines",
- client->euid);
- } else if (client_is_writing(client)) {
- client->state = GOTD_STATE_EXPECT_REF_UPDATE;
- 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 (client->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;
- err = forward_want(client, &imsg);
- break;
- case GOTD_IMSG_REF_UPDATE:
- if (client->state != GOTD_STATE_EXPECT_REF_UPDATE) {
- 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;
- client->state = GOTD_STATE_EXPECT_MORE_REF_UPDATES;
- break;
- case GOTD_IMSG_HAVE:
- if (client->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;
+ err = start_client_authentication(client, &imsg);
break;
- case GOTD_IMSG_FLUSH:
- if (client->state == GOTD_STATE_EXPECT_WANT ||
- client->state == GOTD_STATE_EXPECT_HAVE) {
- err = ensure_client_is_reading(client);
- if (err)
- break;
- } else if (client->state ==
- GOTD_STATE_EXPECT_MORE_REF_UPDATES) {
- err = ensure_client_is_writing(client);
- if (err)
- break;
- } else {
- err = got_error_msg(GOT_ERR_BAD_REQUEST,
- "unexpected flush-pkt received");
- break;
- }
- log_debug("received flush-pkt from uid %d",
- client->euid);
- if (client->state == GOTD_STATE_EXPECT_WANT) {
- client->state = GOTD_STATE_EXPECT_HAVE;
- log_debug("uid %d: expecting have-lines",
- client->euid);
- } else if (client->state == GOTD_STATE_EXPECT_HAVE) {
- client->state = GOTD_STATE_EXPECT_DONE;
- log_debug("uid %d: expecting 'done'",
- client->euid);
- } else if (client->state ==
- GOTD_STATE_EXPECT_MORE_REF_UPDATES) {
- client->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;
- case GOTD_IMSG_DONE:
- if (client->state != GOTD_STATE_EXPECT_HAVE &&
- client->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;
- client->state = GOTD_STATE_DONE;
- err = send_packfile(client);
- break;
default:
+ log_debug("unexpected imsg %d", imsg.hdr.type);
err = got_error(GOT_ERR_PRIVSEP_MSG);
break;
}
disconnect_on_error(client, err);
} else {
gotd_imsg_event_add(&client->iev);
- if (client->state == GOTD_STATE_EXPECT_LIST_REFS)
- evtimer_add(&client->tmo, &auth_timeout);
- else
- evtimer_add(&client->tmo, &gotd.request_timeout);
}
}
static void
-gotd_request_timeout(int fd, short events, void *arg)
+gotd_auth_timeout(int fd, short events, void *arg)
{
struct gotd_client *client = arg;
- log_debug("disconnecting uid %d due to timeout", client->euid);
+ log_debug("disconnecting uid %d due to authentication timeout",
+ client->euid);
disconnect(client);
}
/* The auth process will verify UID/GID for us. */
client->euid = iconnect.euid;
client->egid = iconnect.egid;
- client->nref_updates = -1;
imsg_init(&client->iev.ibuf, client->fd);
client->iev.handler = gotd_request;
&client->iev);
gotd_imsg_event_add(&client->iev);
- evtimer_set(&client->tmo, gotd_request_timeout, client);
+ evtimer_set(&client->tmo, gotd_auth_timeout, client);
add_client(client);
log_debug("%s: new client uid %d connected on fd %d", __func__,
"parent",
"listen",
"auth",
+ "session",
"repo_read",
"repo_write"
};
struct gotd_child_proc *proc;
uint64_t slot;
+ log_debug("shutting down");
for (slot = 0; slot < nitems(gotd_clients); slot++) {
struct gotd_client *c, *tmp;
int ret = 0;
if (proc->type == PROC_REPO_READ || proc->type == PROC_REPO_WRITE) {
- client_proc = get_client_proc(client);
+ client_proc = get_client_repo_proc(client);
if (client_proc == NULL)
fatalx("no process found for uid %d", client->euid);
if (proc->pid != client_proc->pid) {
log_warnx("received message from PID %d for uid %d, "
"while PID %d is the process serving this user",
proc->pid, client->euid, client_proc->pid);
+ return 0;
+ }
+ }
+ if (proc->type == PROC_SESSION) {
+ if (client->session == NULL) {
+ log_warnx("no session found for uid %d", client->euid);
return 0;
}
+ if (proc->pid != client->session->pid) {
+ kill_proc(proc, 1);
+ log_warnx("received message from PID %d for uid %d, "
+ "while PID %d is the process serving this user",
+ proc->pid, client->euid, client->session->pid);
+ return 0;
+ }
}
switch (imsg->hdr.type) {
} else
ret = 1;
break;
+ case GOTD_IMSG_CLIENT_SESSION_READY:
+ if (proc->type != PROC_SESSION) {
+ err = got_error_fmt(GOT_ERR_BAD_PACKET,
+ "unexpected \"ready\" signal from PID %d",
+ proc->pid);
+ } else
+ ret = 1;
+ break;
case GOTD_IMSG_REPO_CHILD_READY:
if (proc->type != PROC_REPO_READ &&
proc->type != PROC_REPO_WRITE) {
}
static const struct got_error *
-list_refs_request(struct gotd_client *client, struct gotd_imsgev *iev)
+connect_repo_child(struct gotd_client *client,
+ struct gotd_child_proc *repo_proc)
{
static const struct got_error *err;
- struct gotd_imsg_list_refs_internal ilref;
- int fd;
+ struct gotd_imsgev *session_iev = &client->session->iev;
+ struct gotd_imsg_connect_repo_child ireq;
+ int pipe[2];
- memset(&ilref, 0, sizeof(ilref));
- ilref.client_id = client->id;
+ if (client->state != GOTD_STATE_EXPECT_LIST_REFS)
+ return got_error_msg(GOT_ERR_BAD_REQUEST,
+ "unexpected repo child ready signal received");
- fd = dup(client->fd);
- if (fd == -1)
- return got_error_from_errno("dup");
+ if (socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK,
+ PF_UNSPEC, pipe) == -1)
+ fatal("socketpair");
- if (gotd_imsg_compose_event(iev, GOTD_IMSG_LIST_REFS_INTERNAL,
- PROC_GOTD, fd, &ilref, sizeof(ilref)) == -1) {
- err = got_error_from_errno("imsg compose WANT");
- close(fd);
- return err;
- }
-
- client->state = GOTD_STATE_EXPECT_CAPABILITIES;
- log_debug("uid %d: expecting capabilities", client->euid);
- return NULL;
-}
-
-static const struct got_error *
-recv_packfile_done(uint32_t *client_id, struct imsg *imsg)
-{
- struct gotd_imsg_packfile_done idone;
- size_t datalen;
-
- log_debug("packfile-done received");
-
- datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
- if (datalen != sizeof(idone))
- return got_error(GOT_ERR_PRIVSEP_LEN);
- memcpy(&idone, imsg->data, sizeof(idone));
-
- *client_id = idone.client_id;
- return NULL;
-}
-
-static const struct got_error *
-recv_packfile_install(uint32_t *client_id, struct imsg *imsg)
-{
- struct gotd_imsg_packfile_install inst;
- size_t datalen;
-
- log_debug("packfile-install received");
+ memset(&ireq, 0, sizeof(ireq));
+ ireq.client_id = client->id;
+ ireq.proc_id = repo_proc->type;
- datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
- if (datalen != sizeof(inst))
- return got_error(GOT_ERR_PRIVSEP_LEN);
- memcpy(&inst, imsg->data, sizeof(inst));
-
- *client_id = inst.client_id;
- return NULL;
-}
-
-static const struct got_error *
-recv_ref_updates_start(uint32_t *client_id, 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));
-
- *client_id = istart.client_id;
- return NULL;
-}
-
-static const struct got_error *
-recv_ref_update(uint32_t *client_id, 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));
-
- *client_id = iref.client_id;
- return NULL;
-}
-
-static const struct got_error *
-send_ref_update_ok(struct gotd_client *client,
- struct gotd_imsg_ref_update *iref, const char *refname)
-{
- struct gotd_imsg_ref_update_ok iok;
- struct ibuf *wbuf;
- size_t len;
-
- memset(&iok, 0, sizeof(iok));
- iok.client_id = client->id;
- 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(&client->iev.ibuf, GOTD_IMSG_REF_UPDATE_OK,
- PROC_GOTD, gotd.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");
-
- wbuf->fd = -1;
- imsg_close(&client->iev.ibuf, wbuf);
- gotd_imsg_event_add(&client->iev);
- return NULL;
-}
-
-static void
-send_refs_updated(struct gotd_client *client)
-{
- if (gotd_imsg_compose_event(&client->iev,
- GOTD_IMSG_REFS_UPDATED, PROC_GOTD, -1, NULL, 0) == -1)
- log_warn("imsg compose REFS_UPDATED");
-}
-
-static const struct got_error *
-send_ref_update_ng(struct gotd_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 ibuf *wbuf;
- size_t len;
-
- memset(&ing, 0, sizeof(ing));
- ing.client_id = client->id;
- 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(&client->iev.ibuf, GOTD_IMSG_REF_UPDATE_NG,
- PROC_GOTD, gotd.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");
-
- wbuf->fd = -1;
- imsg_close(&client->iev.ibuf, wbuf);
- gotd_imsg_event_add(&client->iev);
- return NULL;
-}
-
-static const struct got_error *
-install_pack(struct gotd_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;
+ /* Pass repo child pipe to session child process. */
+ if (gotd_imsg_compose_event(session_iev, GOTD_IMSG_CONNECT_REPO_CHILD,
+ PROC_GOTD, pipe[0], &ireq, sizeof(ireq)) == -1) {
+ err = got_error_from_errno("imsg compose CONNECT_REPO_CHILD");
+ close(pipe[0]);
+ close(pipe[1]);
+ return err;
}
- 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;
+ /* Pass session child pipe to repo child process. */
+ if (gotd_imsg_compose_event(&repo_proc->iev,
+ GOTD_IMSG_CONNECT_REPO_CHILD, PROC_GOTD, pipe[1], NULL, 0) == -1) {
+ err = got_error_from_errno("imsg compose CONNECT_REPO_CHILD");
+ close(pipe[1]);
+ return err;
}
- 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;
- }
-
- 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_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 *
-update_ref(struct gotd_client *client, const char *repo_path,
- struct imsg *imsg)
-{
- const struct got_error *err = NULL;
- struct got_repository *repo = NULL;
- struct got_reference *ref = NULL;
- struct gotd_imsg_ref_update iref;
- struct got_object_id old_id, new_id;
- struct got_object_id *id = NULL;
- struct got_object *obj = NULL;
- char *refname = NULL;
- size_t datalen;
- int locked = 0;
-
- 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 = malloc(iref.name_len + 1);
- if (refname == NULL)
- return got_error_from_errno("malloc");
- memcpy(refname, imsg->data + sizeof(iref), iref.name_len);
- refname[iref.name_len] = '\0';
-
- log_debug("updating ref %s for uid %d", refname, client->euid);
-
- err = got_repo_open(&repo, repo_path, NULL, NULL);
- if (err)
- goto done;
-
- memcpy(old_id.sha1, iref.old_id, SHA1_DIGEST_LENGTH);
- memcpy(new_id.sha1, iref.new_id, SHA1_DIGEST_LENGTH);
- err = got_object_open(&obj, repo, &new_id);
- 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;
- } else {
- err = got_error_fmt(GOT_ERR_REF_BUSY,
- "%s has been created by someone else "
- "while transaction was in progress",
- got_ref_get_name(ref));
- goto done;
- }
- } 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) {
- err = got_error_fmt(GOT_ERR_REF_BUSY,
- "%s has been modified by someone else "
- "while transaction was in progress",
- got_ref_get_name(ref));
- goto done;
- }
-
- err = got_ref_change_ref(ref, &new_id);
- if (err)
- goto done;
-
- err = got_ref_write(ref, repo);
- 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);
-
- }
- 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);
- if (obj)
- got_object_close(obj);
- if (repo)
- got_repo_close(repo);
- free(refname);
- free(id);
- return err;
-}
-
static void
gotd_dispatch_listener(int fd, short event, void *arg)
{
struct imsg imsg;
uint32_t client_id = 0;
int do_disconnect = 0;
- enum gotd_procid proc_type;
client = find_client_by_proc_fd(fd);
if (client == NULL)
log_info("authenticated uid %d for repository %s\n",
client->euid, repo->name);
- if (client->required_auth & GOTD_AUTH_WRITE)
- proc_type = PROC_REPO_WRITE;
- else
- proc_type = PROC_REPO_READ;
-
- err = start_repo_child(client, proc_type, repo, gotd.argv0,
+ err = start_session_child(client, repo, gotd.argv0,
gotd.confpath, gotd.daemonize, gotd.verbosity);
+ if (err)
+ goto done;
done:
if (err)
log_warnx("uid %d: %s", client->euid, err->msg);
}
}
+static const struct got_error *
+connect_session(struct gotd_client *client)
+{
+ const struct got_error *err = NULL;
+ struct gotd_imsg_connect iconnect;
+ int s;
+
+ memset(&iconnect, 0, sizeof(iconnect));
+
+ s = dup(client->fd);
+ if (s == -1)
+ return got_error_from_errno("dup");
+
+ iconnect.client_id = client->id;
+ iconnect.euid = client->euid;
+ iconnect.egid = client->egid;
+
+ if (gotd_imsg_compose_event(&client->session->iev, GOTD_IMSG_CONNECT,
+ PROC_GOTD, s, &iconnect, sizeof(iconnect)) == -1) {
+ err = got_error_from_errno("imsg compose CONNECT");
+ close(s);
+ return err;
+ }
+
+ /*
+ * We are no longer interested in messages from this client.
+ * Further client requests will be handled by the session process.
+ */
+ msgbuf_clear(&client->iev.ibuf.w);
+ imsg_clear(&client->iev.ibuf);
+ event_del(&client->iev.ev);
+ client->fd = -1; /* will be closed via copy in client->iev.ibuf.fd */
+
+ return NULL;
+}
+
static void
-gotd_dispatch_repo_child(int fd, short event, void *arg)
+gotd_dispatch_client_session(int fd, short event, void *arg)
{
struct gotd_imsgev *iev = arg;
struct imsgbuf *ibuf = &iev->ibuf;
int shut = 0;
struct imsg imsg;
+ client = find_client_by_proc_fd(fd);
+ if (client == NULL)
+ fatalx("cannot find client for fd %d", fd);
+
if (event & EV_READ) {
if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
fatal("imsg_read error");
/* Connection closed. */
shut = 1;
goto done;
+ }
+ }
+
+ proc = client->session;
+ if (proc == NULL)
+ fatalx("cannot find session child process for fd %d", fd);
+
+ for (;;) {
+ const struct got_error *err = NULL;
+ uint32_t client_id = 0;
+ int do_disconnect = 0, do_start_repo_child = 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_CLIENT_SESSION_READY:
+ if (client->state != GOTD_STATE_EXPECT_LIST_REFS) {
+ err = got_error(GOT_ERR_PRIVSEP_MSG);
+ break;
+ }
+ do_start_repo_child = 1;
+ break;
+ case GOTD_IMSG_DISCONNECT:
+ do_disconnect = 1;
+ break;
+ default:
+ log_debug("unexpected imsg %d", imsg.hdr.type);
+ break;
}
+
+ if (!verify_imsg_src(client, proc, &imsg)) {
+ log_debug("dropping imsg type %d from PID %d",
+ imsg.hdr.type, proc->pid);
+ imsg_free(&imsg);
+ continue;
+ }
+ if (err)
+ log_warnx("uid %d: %s", client->euid, err->msg);
+
+ if (do_start_repo_child) {
+ struct gotd_repo *repo;
+
+ repo = find_repo_by_name(client->session->repo_name);
+ if (repo != NULL) {
+ enum gotd_procid proc_type;
+
+ if (client->required_auth & GOTD_AUTH_WRITE)
+ proc_type = PROC_REPO_WRITE;
+ else
+ proc_type = PROC_REPO_READ;
+
+ err = start_repo_child(client, proc_type, repo,
+ gotd.argv0, gotd.confpath, gotd.daemonize,
+ gotd.verbosity);
+ } else
+ err = got_error(GOT_ERR_NOT_GIT_REPO);
+
+ if (err) {
+ log_warnx("uid %d: %s", client->euid, err->msg);
+ do_disconnect = 1;
+ }
+ }
+
+ if (do_disconnect) {
+ if (err)
+ disconnect_on_error(client, err);
+ else
+ disconnect(client);
+ }
+
+ imsg_free(&imsg);
}
+done:
+ if (!shut) {
+ gotd_imsg_event_add(iev);
+ } else {
+ /* This pipe is dead. Remove its event handler */
+ event_del(&iev->ev);
+ disconnect(client);
+ }
+}
+static void
+gotd_dispatch_repo_child(int fd, short event, void *arg)
+{
+ struct gotd_imsgev *iev = arg;
+ struct imsgbuf *ibuf = &iev->ibuf;
+ struct gotd_child_proc *proc = NULL;
+ struct gotd_client *client;
+ ssize_t n;
+ int shut = 0;
+ struct imsg imsg;
+
client = find_client_by_proc_fd(fd);
if (client == NULL)
fatalx("cannot find client for fd %d", fd);
- proc = get_client_proc(client);
+ 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;
+ }
+ }
+
+ proc = get_client_repo_proc(client);
if (proc == NULL)
fatalx("cannot find child process for fd %d", fd);
const struct got_error *err = NULL;
uint32_t client_id = 0;
int do_disconnect = 0;
- int do_list_refs = 0, do_ref_updates = 0, do_ref_update = 0;
- int do_packfile_install = 0;
if ((n = imsg_get(ibuf, &imsg)) == -1)
fatal("%s: imsg_get error", __func__);
err = gotd_imsg_recv_error(&client_id, &imsg);
break;
case GOTD_IMSG_REPO_CHILD_READY:
- do_list_refs = 1;
+ err = connect_session(client);
+ if (err)
+ break;
+ err = connect_repo_child(client, proc);
break;
- case GOTD_IMSG_PACKFILE_DONE:
- do_disconnect = 1;
- err = recv_packfile_done(&client_id, &imsg);
- break;
- case GOTD_IMSG_PACKFILE_INSTALL:
- err = recv_packfile_install(&client_id, &imsg);
- if (err == NULL)
- do_packfile_install = 1;
- break;
- case GOTD_IMSG_REF_UPDATES_START:
- err = recv_ref_updates_start(&client_id, &imsg);
- if (err == NULL)
- do_ref_updates = 1;
- break;
- case GOTD_IMSG_REF_UPDATE:
- err = recv_ref_update(&client_id, &imsg);
- if (err == NULL)
- do_ref_update = 1;
- break;
default:
log_debug("unexpected imsg %d", imsg.hdr.type);
break;
disconnect_on_error(client, err);
else
disconnect(client);
- } else {
- if (do_list_refs)
- err = list_refs_request(client, iev);
- else if (do_packfile_install)
- err = install_pack(client, proc->repo_path,
- &imsg);
- else if (do_ref_updates)
- err = begin_ref_updates(client, &imsg);
- else if (do_ref_update)
- err = update_ref(client, proc->repo_path,
- &imsg);
- if (err)
- log_warnx("uid %d: %s", client->euid, err->msg);
}
+
imsg_free(&imsg);
}
done:
} else {
/* This pipe is dead. Remove its event handler */
event_del(&iev->ev);
- event_loopexit(NULL);
+ disconnect(client);
}
}
case PROC_AUTH:
argv[argc++] = (char *)"-A";
break;
+ case PROC_SESSION:
+ argv[argc++] = (char *)"-S";
+ break;
case PROC_REPO_READ:
argv[argc++] = (char *)"-R";
break;
}
static const struct got_error *
+start_session_child(struct gotd_client *client, struct gotd_repo *repo,
+ char *argv0, const char *confpath, int daemonize, int verbosity)
+{
+ struct gotd_child_proc *proc;
+
+ proc = calloc(1, sizeof(*proc));
+ if (proc == NULL)
+ return got_error_from_errno("calloc");
+
+ proc->type = PROC_SESSION;
+ if (strlcpy(proc->repo_name, repo->name,
+ sizeof(proc->repo_name)) >= sizeof(proc->repo_name))
+ fatalx("repository name too long: %s", repo->name);
+ log_debug("starting client uid %d session for repository %s",
+ client->euid, repo->name);
+ if (strlcpy(proc->repo_path, repo->path, sizeof(proc->repo_path)) >=
+ sizeof(proc->repo_path))
+ fatalx("repository path too long: %s", repo->path);
+ if (socketpair(AF_UNIX, SOCK_STREAM|SOCK_CLOEXEC|SOCK_NONBLOCK,
+ PF_UNSPEC, proc->pipe) == -1)
+ fatal("socketpair");
+ proc->pid = start_child(proc->type, proc->repo_path, argv0,
+ confpath, proc->pipe[1], daemonize, verbosity);
+ imsg_init(&proc->iev.ibuf, proc->pipe[0]);
+ log_debug("proc %s %s is on fd %d",
+ gotd_proc_names[proc->type], proc->repo_path,
+ proc->pipe[0]);
+ proc->iev.handler = gotd_dispatch_client_session;
+ proc->iev.events = EV_READ;
+ proc->iev.handler_arg = NULL;
+ event_set(&proc->iev.ev, proc->iev.ibuf.fd, EV_READ,
+ gotd_dispatch_client_session, &proc->iev);
+ gotd_imsg_event_add(&proc->iev);
+
+ client->session = proc;
+ return NULL;
+}
+
+static const struct got_error *
start_repo_child(struct gotd_client *client, enum gotd_procid proc_type,
struct gotd_repo *repo, char *argv0, const char *confpath,
int daemonize, int verbosity)
}
static void
+apply_unveil_repo_readwrite(const char *repo_path)
+{
+ if (unveil(repo_path, "rwc") == -1)
+ fatal("unveil %s", repo_path);
+
+ if (unveil(GOT_TMPDIR_STR, "rwc") == -1)
+ fatal("unveil %s", GOT_TMPDIR_STR);
+
+ if (unveil(NULL, NULL) == -1)
+ fatal("unveil");
+}
+
+static void
apply_unveil_none(void)
{
if (unveil("/", "") == -1)
}
static void
-apply_unveil(void)
+apply_unveil_selfexec(void)
{
- struct gotd_repo *repo;
-
if (unveil(gotd.argv0, "x") == -1)
fatal("unveil %s", gotd.argv0);
- TAILQ_FOREACH(repo, &gotd.repos, entry) {
- if (unveil(repo->path, "rwc") == -1)
- fatal("unveil %s", repo->path);
- }
-
- if (unveil(GOT_TMPDIR_STR, "rwc") == -1)
- fatal("unveil %s", GOT_TMPDIR_STR);
-
if (unveil(NULL, NULL) == -1)
fatal("unveil");
}
log_init(1, LOG_DAEMON); /* Log to stderr until daemonized. */
- while ((ch = getopt(argc, argv, "Adf:LnP:RvW")) != -1) {
+ while ((ch = getopt(argc, argv, "Adf:LnP:RSvW")) != -1) {
switch (ch) {
case 'A':
proc_id = PROC_AUTH;
case 'R':
proc_id = PROC_REPO_READ;
break;
+ case 'S':
+ proc_id = PROC_SESSION;
+ break;
case 'v':
if (verbosity < 3)
verbosity++;
gotd_proc_names[proc_id], repo_path);
if (daemonize && daemon(0, 0) == -1)
fatal("daemon");
- } else if (proc_id == PROC_REPO_READ || proc_id == PROC_REPO_WRITE) {
+ } else if (proc_id == PROC_REPO_READ || proc_id == PROC_REPO_WRITE ||
+ proc_id == PROC_SESSION) {
error = got_repo_pack_fds_open(&pack_fds);
if (error != NULL)
fatalx("cannot open pack tempfiles: %s", error->msg);
switch (proc_id) {
case PROC_GOTD:
#ifndef PROFILE
- if (pledge("stdio rpath wpath cpath proc exec "
- "sendfd recvfd fattr flock unveil", NULL) == -1)
+ /* "exec" promise will be limited to argv[0] via unveil(2). */
+ if (pledge("stdio proc exec sendfd recvfd unveil", NULL) == -1)
err(1, "pledge");
#endif
break;
auth_main(title, &gotd.repos, repo_path);
/* NOTREACHED */
break;
+ case PROC_SESSION:
+#ifndef PROFILE
+ /*
+ * The "recvfd" promise is only needed during setup and
+ * will be removed in a later pledge(2) call.
+ */
+ if (pledge("stdio rpath wpath cpath recvfd sendfd fattr flock "
+ "unveil", NULL) == -1)
+ err(1, "pledge");
+#endif
+ apply_unveil_repo_readwrite(repo_path);
+ session_main(title, repo_path, pack_fds, temp_fds,
+ &gotd.request_timeout);
+ /* NOTREACHED */
+ break;
case PROC_REPO_READ:
#ifndef PROFILE
if (pledge("stdio rpath recvfd unveil", NULL) == -1)
if (proc_id != PROC_GOTD)
fatal("invalid process id %d", proc_id);
- apply_unveil();
+ apply_unveil_selfexec();
signal_set(&evsigint, SIGINT, gotd_sighdlr, NULL);
signal_set(&evsigterm, SIGTERM, gotd_sighdlr, NULL);
event_dispatch();
- if (pack_fds)
- got_repo_pack_fds_close(pack_fds);
free(repo_path);
+ gotd_shutdown();
+
return 0;
}
blob - 266fba3b38a61177a81510a5a8753084f64b1667
blob + 3f6ddfc81056dd0933a199322c90fe6b5e8296b0
--- gotd/gotd.h
+++ gotd/gotd.h
PROC_GOTD = 0,
PROC_LISTEN,
PROC_AUTH,
+ PROC_SESSION,
PROC_REPO_READ,
PROC_REPO_WRITE,
PROC_MAX,
GOTD_IMSG_CONNECT,
/* Child process management. */
+ GOTD_IMSG_CLIENT_SESSION_READY,
GOTD_IMSG_REPO_CHILD_READY,
+ GOTD_IMSG_CONNECT_REPO_CHILD,
/* Auth child process. */
GOTD_IMSG_AUTHENTICATE,
char repo_name[NAME_MAX];
int is_writing;
enum gotd_client_state state;
+ pid_t session_child_pid;
+ pid_t repo_child_pid;
size_t ncapabilities;
/* Followed by ncapabilities GOTD_IMSG_CAPABILITY. */
gid_t egid;
};
+/* Structure for GOTD_IMSG_CONNECT_REPO_CHILD. */
+struct gotd_imsg_connect_repo_child {
+ uint32_t client_id;
+ enum gotd_procid proc_id;
+
+ /* repo child imsg pipe is passed via imsg fd */
+};
+
/* Structure for GOTD_IMSG_AUTHENTICATE. */
struct gotd_imsg_auth {
uid_t euid;
blob - 70f72ff14ea0d93944e8b9e766316bd5cd5cb1bc
blob + d9723d2768de50fbc762a863432cae47b27511ce
--- gotd/repo_read.c
+++ gotd/repo_read.c
struct got_repository *repo;
int *pack_fds;
int *temp_fds;
+ int session_fd;
+ struct gotd_imsgev session_iev;
} repo_read;
static struct repo_read_client {
}
static void
-repo_read_dispatch(int fd, short event, void *arg)
+repo_read_dispatch_session(int fd, short event, void *arg)
{
const struct got_error *err = NULL;
struct gotd_imsgev *iev = arg;
if (err)
log_warnx("%s: disconnect: %s",
repo_read.title, err->msg);
+ shut = 1;
+ break;
+ default:
+ log_debug("%s: unexpected imsg %d", repo_read.title,
+ imsg.hdr.type);
+ break;
+ }
+
+ imsg_free(&imsg);
+ }
+
+ if (!shut && check_cancelled(NULL) == NULL) {
+ if (err &&
+ gotd_imsg_send_error_event(iev, PROC_REPO_READ,
+ client->id, err) == -1) {
+ log_warnx("could not send error to parent: %s",
+ err->msg);
+ }
+ 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_connect(struct imsg *imsg)
+{
+ struct gotd_imsgev *iev = &repo_read.session_iev;
+ size_t datalen;
+
+ datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
+ if (datalen != 0)
+ return got_error(GOT_ERR_PRIVSEP_LEN);
+ if (imsg->fd == -1)
+ return got_error(GOT_ERR_PRIVSEP_NO_FD);
+
+ if (repo_read.session_fd != -1)
+ return got_error(GOT_ERR_PRIVSEP_MSG);
+
+ repo_read.session_fd = imsg->fd;
+
+ imsg_init(&iev->ibuf, repo_read.session_fd);
+ iev->handler = repo_read_dispatch_session;
+ iev->events = EV_READ;
+ iev->handler_arg = NULL;
+ event_set(&iev->ev, iev->ibuf.fd, EV_READ,
+ repo_read_dispatch_session, iev);
+ gotd_imsg_event_add(iev);
+
+ return NULL;
+}
+
+static void
+repo_read_dispatch(int fd, short event, void *arg)
+{
+ const struct got_error *err = NULL;
+ struct gotd_imsgev *iev = arg;
+ struct imsgbuf *ibuf = &iev->ibuf;
+ struct imsg imsg;
+ ssize_t n;
+ int shut = 0;
+ struct repo_read_client *client = &repo_read_client;
+
+ if (event & EV_READ) {
+ if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
+ fatal("imsg_read error");
+ if (n == 0) /* Connection closed. */
shut = 1;
+ }
+
+ if (event & EV_WRITE) {
+ n = msgbuf_write(&ibuf->w);
+ if (n == -1 && errno != EAGAIN)
+ fatal("msgbuf_write");
+ if (n == 0) /* Connection closed. */
+ shut = 1;
+ }
+
+ while (err == NULL && check_cancelled(NULL) == NULL) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("%s: imsg_get", __func__);
+ if (n == 0) /* No more messages. */
break;
+
+ switch (imsg.hdr.type) {
+ case GOTD_IMSG_CONNECT_REPO_CHILD:
+ err = recv_connect(&imsg);
+ break;
default:
log_debug("%s: unexpected imsg %d", repo_read.title,
imsg.hdr.type);
repo_read.pid = getpid();
repo_read.pack_fds = pack_fds;
repo_read.temp_fds = temp_fds;
+ repo_read.session_fd = -1;
+ repo_read.session_iev.ibuf.fd = -1;
err = got_repo_open(&repo_read.repo, repo_path, NULL, pack_fds);
if (err)
got_repo_close(repo_read.repo);
got_repo_pack_fds_close(repo_read.pack_fds);
got_repo_temp_fds_close(repo_read.temp_fds);
+ if (repo_read.session_fd != -1)
+ close(repo_read.session_fd);
exit(0);
}
blob - 965572537276de7c92606cb33b97d87e09190553
blob + 92c95bf4a867f80378b7cb3e6f94566e95ae26b1
--- gotd/repo_write.c
+++ gotd/repo_write.c
struct got_repository *repo;
int *pack_fds;
int *temp_fds;
+ int session_fd;
+ struct gotd_imsgev session_iev;
} repo_write;
struct gotd_ref_update {
}
static void
-repo_write_dispatch(int fd, short event, void *arg)
+repo_write_dispatch_session(int fd, short event, void *arg)
{
const struct got_error *err = NULL;
struct gotd_imsgev *iev = arg;
if (err)
log_warnx("%s: disconnect: %s",
repo_write.title, err->msg);
+ shut = 1;
+ break;
+ default:
+ log_debug("%s: unexpected imsg %d", repo_write.title,
+ imsg.hdr.type);
+ break;
+ }
+
+ imsg_free(&imsg);
+ }
+
+ if (!shut && check_cancelled(NULL) == NULL) {
+ if (err &&
+ gotd_imsg_send_error_event(iev, PROC_REPO_WRITE,
+ client->id, err) == -1) {
+ log_warnx("could not send error to parent: %s",
+ err->msg);
+ }
+ 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_connect(struct imsg *imsg)
+{
+ struct gotd_imsgev *iev = &repo_write.session_iev;
+ size_t datalen;
+
+ datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
+ if (datalen != 0)
+ return got_error(GOT_ERR_PRIVSEP_LEN);
+ if (imsg->fd == -1)
+ return got_error(GOT_ERR_PRIVSEP_NO_FD);
+
+ if (repo_write.session_fd != -1)
+ return got_error(GOT_ERR_PRIVSEP_MSG);
+
+ repo_write.session_fd = imsg->fd;
+
+ imsg_init(&iev->ibuf, repo_write.session_fd);
+ iev->handler = repo_write_dispatch_session;
+ iev->events = EV_READ;
+ iev->handler_arg = NULL;
+ event_set(&iev->ev, iev->ibuf.fd, EV_READ,
+ repo_write_dispatch_session, iev);
+ gotd_imsg_event_add(iev);
+
+ return NULL;
+}
+
+static void
+repo_write_dispatch(int fd, short event, void *arg)
+{
+ const struct got_error *err = NULL;
+ struct gotd_imsgev *iev = arg;
+ struct imsgbuf *ibuf = &iev->ibuf;
+ struct imsg imsg;
+ ssize_t n;
+ int shut = 0;
+ struct repo_write_client *client = &repo_write_client;
+
+ if (event & EV_READ) {
+ if ((n = imsg_read(ibuf)) == -1 && errno != EAGAIN)
+ fatal("imsg_read error");
+ if (n == 0) /* Connection closed. */
shut = 1;
+ }
+
+ if (event & EV_WRITE) {
+ n = msgbuf_write(&ibuf->w);
+ if (n == -1 && errno != EAGAIN)
+ fatal("msgbuf_write");
+ if (n == 0) /* Connection closed. */
+ shut = 1;
+ }
+
+ while (err == NULL && check_cancelled(NULL) == NULL) {
+ if ((n = imsg_get(ibuf, &imsg)) == -1)
+ fatal("%s: imsg_get", __func__);
+ if (n == 0) /* No more messages. */
break;
+
+ switch (imsg.hdr.type) {
+ case GOTD_IMSG_CONNECT_REPO_CHILD:
+ err = recv_connect(&imsg);
+ break;
default:
log_debug("%s: unexpected imsg %d", repo_write.title,
imsg.hdr.type);
repo_write.pid = getpid();
repo_write.pack_fds = pack_fds;
repo_write.temp_fds = temp_fds;
+ repo_write.session_fd = -1;
+ repo_write.session_iev.ibuf.fd = -1;
STAILQ_INIT(&repo_write_client.ref_updates);
got_repo_close(repo_write.repo);
got_repo_pack_fds_close(repo_write.pack_fds);
got_repo_temp_fds_close(repo_write.temp_fds);
+ if (repo_write.session_fd != -1)
+ close(repo_write.session_fd);
exit(0);
}
blob - /dev/null
blob + a82805ac538caaec6b52853abee4b175a8462653 (mode 644)
--- /dev/null
+++ gotd/session.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/uio.h>
+
+#include <errno.h>
+#include <event.h>
+#include <limits.h>
+#include <sha1.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_sha1.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"
+
+
+static struct gotd_session {
+ pid_t pid;
+ const char *title;
+ struct got_repository *repo;
+ int *pack_fds;
+ int *temp_fds;
+ struct gotd_imsgev parent_iev;
+ struct timeval request_timeout;
+} gotd_session;
+
+static struct gotd_session_client {
+ enum gotd_client_state state;
+ 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 gotd_imsgev repo_child_iev;
+ struct event tmo;
+ uid_t euid;
+ gid_t egid;
+ char *packfile_path;
+ char *packidx_path;
+ int nref_updates;
+} 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, PROC_SESSION, -1, NULL, 0) == -1)
+ log_warn("imsg compose DISCONNECT");
+
+ imsg_clear(&client->repo_child_iev.ibuf);
+ event_del(&client->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;
+
+ log_warnx("uid %d: %s", client->euid, err->msg);
+ if (err->code != GOT_ERR_EOF) {
+ imsg_init(&ibuf, client->fd);
+ gotd_imsg_send_error(&ibuf, 0, PROC_SESSION, 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(uint32_t *client_id, struct imsg *imsg)
+{
+ struct gotd_imsg_packfile_done idone;
+ size_t datalen;
+
+ log_debug("packfile-done received");
+
+ datalen = imsg->hdr.len - IMSG_HEADER_SIZE;
+ if (datalen != sizeof(idone))
+ return got_error(GOT_ERR_PRIVSEP_LEN);
+ memcpy(&idone, imsg->data, sizeof(idone));
+
+ *client_id = idone.client_id;
+ return NULL;
+}
+
+static const struct got_error *
+recv_packfile_install(uint32_t *client_id, 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));
+
+ *client_id = inst.client_id;
+ return NULL;
+}
+
+static const struct got_error *
+recv_ref_updates_start(uint32_t *client_id, 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));
+
+ *client_id = istart.client_id;
+ return NULL;
+}
+
+static const struct got_error *
+recv_ref_update(uint32_t *client_id, 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));
+
+ *client_id = iref.client_id;
+ 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));
+ iok.client_id = client->id;
+ 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, 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");
+
+ wbuf->fd = -1;
+ 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, -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));
+ ing.client_id = client->id;
+ 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, 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");
+
+ wbuf->fd = -1;
+ 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;
+ }
+
+ 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 *
+update_ref(struct gotd_session_client *client, const char *repo_path,
+ struct imsg *imsg)
+{
+ const struct got_error *err = NULL;
+ struct got_repository *repo = NULL;
+ struct got_reference *ref = NULL;
+ struct gotd_imsg_ref_update iref;
+ struct got_object_id old_id, new_id;
+ struct got_object_id *id = NULL;
+ struct got_object *obj = NULL;
+ char *refname = NULL;
+ size_t datalen;
+ int locked = 0;
+
+ 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 = malloc(iref.name_len + 1);
+ if (refname == NULL)
+ return got_error_from_errno("malloc");
+ memcpy(refname, imsg->data + sizeof(iref), iref.name_len);
+ refname[iref.name_len] = '\0';
+
+ log_debug("updating ref %s for uid %d", refname, client->euid);
+
+ err = got_repo_open(&repo, repo_path, NULL, NULL);
+ if (err)
+ goto done;
+
+ memcpy(old_id.sha1, iref.old_id, SHA1_DIGEST_LENGTH);
+ memcpy(new_id.sha1, iref.new_id, SHA1_DIGEST_LENGTH);
+ err = got_object_open(&obj, repo, &new_id);
+ 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;
+ } else {
+ err = got_error_fmt(GOT_ERR_REF_BUSY,
+ "%s has been created by someone else "
+ "while transaction was in progress",
+ got_ref_get_name(ref));
+ goto done;
+ }
+ } 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) {
+ err = got_error_fmt(GOT_ERR_REF_BUSY,
+ "%s has been modified by someone else "
+ "while transaction was in progress",
+ got_ref_get_name(ref));
+ goto done;
+ }
+
+ err = got_ref_change_ref(ref, &new_id);
+ if (err)
+ goto done;
+
+ err = got_ref_write(ref, repo);
+ 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);
+
+ }
+ 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);
+ if (obj)
+ got_object_close(obj);
+ if (repo)
+ got_repo_close(repo);
+ free(refname);
+ free(id);
+ return err;
+}
+
+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;
+
+ 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(&client_id, &imsg);
+ break;
+ case GOTD_IMSG_PACKFILE_INSTALL:
+ err = recv_packfile_install(&client_id, &imsg);
+ if (err == NULL)
+ do_packfile_install = 1;
+ break;
+ case GOTD_IMSG_REF_UPDATES_START:
+ err = recv_ref_updates_start(&client_id, &imsg);
+ if (err == NULL)
+ do_ref_updates = 1;
+ break;
+ case GOTD_IMSG_REF_UPDATE:
+ err = recv_ref_update(&client_id, &imsg);
+ if (err == NULL)
+ do_ref_update = 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 {
+ 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(client,
+ gotd_session.repo->path, &imsg);
+ 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 = malloc(icapa.key_len + 1);
+ if (key == NULL)
+ return got_error_from_errno("malloc");
+ if (icapa.value_len > 0) {
+ value = malloc(icapa.value_len + 1);
+ if (value == NULL) {
+ free(key);
+ return got_error_from_errno("malloc");
+ }
+ }
+
+ memcpy(key, imsg->data + sizeof(icapa), icapa.key_len);
+ key[icapa.key_len] = '\0';
+ if (value) {
+ memcpy(value, imsg->data + sizeof(icapa) + icapa.key_len,
+ icapa.value_len);
+ value[icapa.value_len] = '\0';
+ }
+
+ 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);
+ iwant.client_id = client->id;
+
+ if (gotd_imsg_compose_event(&client->repo_child_iev, GOTD_IMSG_WANT,
+ PROC_SESSION, -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);
+
+ iref->client_id = client->id;
+ if (gotd_imsg_compose_event(&client->repo_child_iev,
+ GOTD_IMSG_REF_UPDATE, PROC_SESSION, -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);
+ ihave.client_id = client->id;
+
+ if (gotd_imsg_compose_event(&client->repo_child_iev, GOTD_IMSG_HAVE,
+ PROC_SESSION, -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;
+ struct gotd_imsg_packfile_pipe ipipe;
+ struct gotd_imsg_packidx_file ifile;
+ 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");
+
+ memset(&ipipe, 0, sizeof(ipipe));
+ ipipe.client_id = client->id;
+
+ /* Send pack pipe end 0 to repo child process. */
+ if (gotd_imsg_compose_event(&client->repo_child_iev,
+ GOTD_IMSG_PACKFILE_PIPE, PROC_SESSION, pipe[0],
+ &ipipe, sizeof(ipipe)) == -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, 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;
+
+ 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;
+
+ memset(&ifile, 0, sizeof(ifile));
+ ifile.client_id = client->id;
+ if (gotd_imsg_compose_event(&client->repo_child_iev,
+ GOTD_IMSG_PACKIDX_FILE, PROC_SESSION,
+ idxfd, &ifile, sizeof(ifile)) == -1) {
+ err = got_error_from_errno("imsg compose PACKIDX_FILE");
+ idxfd = -1;
+ goto done;
+ }
+ idxfd = -1;
+
+ memset(&ipack, 0, sizeof(ipack));
+ ipack.client_id = client->id;
+ if (client_has_capability(client, GOT_CAPA_REPORT_STATUS))
+ ipack.report_status = 1;
+
+ if (gotd_imsg_compose_event(&client->repo_child_iev,
+ GOTD_IMSG_RECV_PACKFILE, PROC_SESSION, 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;
+ struct gotd_imsg_packfile_pipe ipipe;
+ int pipe[2];
+
+ if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, pipe) == -1)
+ return got_error_from_errno("socketpair");
+
+ memset(&ipack, 0, sizeof(ipack));
+ memset(&ipipe, 0, sizeof(ipipe));
+
+ ipack.client_id = client->id;
+ 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(&client->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;
+ }
+
+ ipipe.client_id = client->id;
+
+ /* Send pack pipe end 0 to repo child process. */
+ if (gotd_imsg_compose_event(&client->repo_child_iev,
+ GOTD_IMSG_PACKFILE_PIPE, PROC_GOTD,
+ pipe[0], &ipipe, sizeof(ipipe)) == -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_listener(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 ((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;
+ break;
+ }
+
+ evtimer_del(&client->tmo);
+
+ switch (imsg.hdr.type) {
+ case GOTD_IMSG_CAPABILITIES:
+ if (client->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 (client->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) {
+ client->state = GOTD_STATE_EXPECT_WANT;
+ log_debug("uid %d: expecting want-lines",
+ client->euid);
+ } else if (client->is_writing) {
+ client->state = GOTD_STATE_EXPECT_REF_UPDATE;
+ 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 (client->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;
+ err = forward_want(client, &imsg);
+ break;
+ case GOTD_IMSG_REF_UPDATE:
+ if (client->state != GOTD_STATE_EXPECT_REF_UPDATE) {
+ 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;
+ client->state = GOTD_STATE_EXPECT_MORE_REF_UPDATES;
+ break;
+ case GOTD_IMSG_HAVE:
+ if (client->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;
+ break;
+ case GOTD_IMSG_FLUSH:
+ if (client->state == GOTD_STATE_EXPECT_WANT ||
+ client->state == GOTD_STATE_EXPECT_HAVE) {
+ err = ensure_client_is_reading(client);
+ if (err)
+ break;
+ } else if (client->state ==
+ GOTD_STATE_EXPECT_MORE_REF_UPDATES) {
+ err = ensure_client_is_writing(client);
+ if (err)
+ break;
+ } else {
+ err = got_error_msg(GOT_ERR_BAD_REQUEST,
+ "unexpected flush-pkt received");
+ break;
+ }
+ log_debug("received flush-pkt from uid %d",
+ client->euid);
+ if (client->state == GOTD_STATE_EXPECT_WANT) {
+ client->state = GOTD_STATE_EXPECT_HAVE;
+ log_debug("uid %d: expecting have-lines",
+ client->euid);
+ } else if (client->state == GOTD_STATE_EXPECT_HAVE) {
+ client->state = GOTD_STATE_EXPECT_DONE;
+ log_debug("uid %d: expecting 'done'",
+ client->euid);
+ } else if (client->state ==
+ GOTD_STATE_EXPECT_MORE_REF_UPDATES) {
+ client->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;
+ case GOTD_IMSG_DONE:
+ if (client->state != GOTD_STATE_EXPECT_HAVE &&
+ client->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;
+ client->state = GOTD_STATE_DONE;
+ 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 ||
+ client->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 = &client->repo_child_iev;
+ struct gotd_imsg_list_refs_internal ilref;
+ int fd;
+
+ if (client->state != GOTD_STATE_EXPECT_LIST_REFS)
+ return got_error(GOT_ERR_PRIVSEP_MSG);
+
+ memset(&ilref, 0, sizeof(ilref));
+ ilref.client_id = client->id;
+
+ 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, fd, &ilref, sizeof(ilref)) == -1) {
+ err = got_error_from_errno("imsg compose LIST_REFS_INTERNAL");
+ close(fd);
+ return err;
+ }
+
+ client->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 (client->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 (imsg->fd == -1)
+ return got_error(GOT_ERR_PRIVSEP_NO_FD);
+
+ client->fd = imsg->fd;
+ client->euid = iconnect.euid;
+ client->egid = iconnect.egid;
+
+ imsg_init(&client->iev.ibuf, client->fd);
+ client->iev.handler = session_dispatch_listener;
+ client->iev.events = EV_READ;
+ client->iev.handler_arg = NULL;
+ event_set(&client->iev.ev, client->iev.ibuf.fd, EV_READ,
+ session_dispatch_listener, &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;
+
+ if (client->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));
+
+ client->id = ichild.client_id;
+ 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");
+
+ if (imsg->fd == -1)
+ return got_error(GOT_ERR_PRIVSEP_NO_FD);
+
+ imsg_init(&client->repo_child_iev.ibuf, imsg->fd);
+ client->repo_child_iev.handler = session_dispatch_repo_child;
+ client->repo_child_iev.events = EV_READ;
+ client->repo_child_iev.handler_arg = NULL;
+ event_set(&client->repo_child_iev.ev, client->repo_child_iev.ibuf.fd,
+ EV_READ, session_dispatch_repo_child, &client->repo_child_iev);
+ gotd_imsg_event_add(&client->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_main(const char *title, const char *repo_path,
+ int *pack_fds, int *temp_fds, struct timeval *request_timeout)
+{
+ 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));
+
+ 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_client.state = GOTD_STATE_EXPECT_LIST_REFS;
+ gotd_session_client.fd = -1;
+ gotd_session_client.nref_updates = -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, -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)
+{
+ log_debug("%s: shutting down", gotd_session.title);
+ 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);
+ exit(0);
+}
blob - /dev/null
blob + 671359022739ca8e501ab0fbd98de3e76447490e (mode 644)
--- /dev/null
+++ gotd/session.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_main(const char *, const char *, int *, int *, struct timeval *);