Lindenii Project Forge
git2d: Use BARE to send contents too, instead of using raw write
/*- * SPDX-License-Identifier: MIT * SPDX-FileCopyrightText: Copyright (c) 2022 Frank Smit <https://61924.nl/> */ #include <string.h> #include <stdbool.h> #include "bare.h" #include "utf8.h" #define UNUSED(x) (void)(x) enum { U8SZ = 1, U16SZ = 2, U32SZ = 4, U64SZ = 8, MAXVARINTSZ = 10, }; static bool checkstr(const char *x, uint64_t sz) { if (x == NULL || sz == 0) { return true; } int err = 0; uint32_t cp = 0; char *buf = (void *)x; uint64_t chunk = 4; char *pad = (char *)(char[4]){0, 0, 0, 0}; #define _utf8_decode(buf) \ do { \ buf = utf8_decode(buf, &cp, &err); \ if (err > 0) { \ return false; \ } \ } while (0) for (; sz >= chunk; sz -= chunk) { _utf8_decode(buf); } if (sz > 0) { memcpy(pad, buf, sz); _utf8_decode(pad); } #undef _utf8_decode return true; } bare_error bare_put_uint(struct bare_writer *ctx, uint64_t x) { uint64_t i = 0; uint8_t b[MAXVARINTSZ]; while (x >= 0x80) { b[i] = (uint8_t)x | 0x80; x >>= 7; i++; } b[i] = (uint8_t)x; i++; return ctx->write(ctx->buffer, b, i); } bare_error bare_get_uint(struct bare_reader *ctx, uint64_t *x) { bare_error err = BARE_ERROR_NONE; uint8_t shift = 0; uint64_t result = 0; for (uint8_t i = 0;i < 10;i++) { uint8_t b; err = ctx->read(ctx->buffer, &b, U8SZ); if (err != BARE_ERROR_NONE) { break; } if (b < 0x80) { result |= (uint64_t)b << shift; break; } else { result |= ((uint64_t)b & 0x7f) << shift; shift += 7; } } *x = result; return err; } bare_error bare_put_int(struct bare_writer *ctx, int64_t x) { uint64_t ux = (uint64_t)x << 1; if (x < 0) { ux = ~ux; } return bare_put_uint(ctx, ux); } bare_error bare_get_int(struct bare_reader *ctx, int64_t *x) { uint64_t ux; bare_error err = bare_get_uint(ctx, &ux); if (err == BARE_ERROR_NONE) { *x = (int64_t)(ux >> 1); if ((ux & 1) != 0) { *x = ~(*x); } } return err; } bare_error bare_put_u8(struct bare_writer *ctx, uint8_t x) { return ctx->write(ctx->buffer, &x, U8SZ); } bare_error bare_get_u8(struct bare_reader *ctx, uint8_t *x) { return ctx->read(ctx->buffer, x, U8SZ); } bare_error bare_put_u16(struct bare_writer *ctx, uint16_t x) { return ctx->write(ctx->buffer, (uint8_t[U16SZ]){x, x >> 8}, U16SZ); } bare_error bare_get_u16(struct bare_reader *ctx, uint16_t *x) { bare_error err = ctx->read(ctx->buffer, x, U16SZ); if (err == BARE_ERROR_NONE) { *x = (uint16_t)((uint8_t *)x)[0] | (uint16_t)((uint8_t *)x)[1] << 8; } return err; } bare_error bare_put_u32(struct bare_writer *ctx, uint32_t x) { uint8_t buf[U32SZ]; buf[0] = (uint8_t)(x); buf[1] = (uint8_t)(x >> 8); buf[2] = (uint8_t)(x >> 16); buf[3] = (uint8_t)(x >> 24); return ctx->write(ctx->buffer, buf, U32SZ); } bare_error bare_get_u32(struct bare_reader *ctx, uint32_t *x) { bare_error err = ctx->read(ctx->buffer, x, U32SZ); if (err == BARE_ERROR_NONE) { *x = (uint32_t)(((uint8_t *)x)[0]) | (uint32_t)(((uint8_t *)x)[1] << 8) | (uint32_t)(((uint8_t *)x)[2] << 16) | (uint32_t)(((uint8_t *)x)[3] << 24); } return err; } bare_error bare_put_u64(struct bare_writer *ctx, uint64_t x) { uint8_t buf[U64SZ]; buf[0] = x; buf[1] = x >> 8; buf[2] = x >> 16; buf[3] = x >> 24; buf[4] = x >> 32; buf[5] = x >> 40; buf[6] = x >> 48; buf[7] = x >> 56; return ctx->write(ctx->buffer, buf, U64SZ); } bare_error bare_get_u64(struct bare_reader *ctx, uint64_t *x) { bare_error err = ctx->read(ctx->buffer, x, U64SZ); if (err == BARE_ERROR_NONE) { *x = (uint64_t)((uint8_t *)x)[0] | (uint64_t)((uint8_t *)x)[1] << 8 | (uint64_t)((uint8_t *)x)[2] << 16 | (uint64_t)((uint8_t *)x)[3] << 24 | (uint64_t)((uint8_t *)x)[4] << 32 | (uint64_t)((uint8_t *)x)[5] << 40 | (uint64_t)((uint8_t *)x)[6] << 48 | (uint64_t)((uint8_t *)x)[7] << 56; } return err; } bare_error bare_put_i8(struct bare_writer *ctx, int8_t x) { return bare_put_u8(ctx, x); } bare_error bare_get_i8(struct bare_reader *ctx, int8_t *x) { return bare_get_u8(ctx, (uint8_t *)x); } bare_error bare_put_i16(struct bare_writer *ctx, int16_t x) { return bare_put_u16(ctx, x); } bare_error bare_get_i16(struct bare_reader *ctx, int16_t *x) { return bare_get_u16(ctx, (uint16_t *)x); } bare_error bare_put_i32(struct bare_writer *ctx, int32_t x) { return bare_put_u32(ctx, x); } bare_error bare_get_i32(struct bare_reader *ctx, int32_t *x) { return bare_get_u32(ctx, (uint32_t *)x); } bare_error bare_put_i64(struct bare_writer *ctx, int64_t x) { return bare_put_u64(ctx, x); } bare_error bare_get_i64(struct bare_reader *ctx, int64_t *x) { return bare_get_u64(ctx, (uint64_t *)x); } bare_error bare_put_f32(struct bare_writer *ctx, float x) { uint32_t b; memcpy(&b, &x, U32SZ); return bare_put_u32(ctx, b); } bare_error bare_get_f32(struct bare_reader *ctx, float *x) { return ctx->read(ctx->buffer, x, U32SZ); } bare_error bare_put_f64(struct bare_writer *ctx, double x) { uint64_t b; memcpy(&b, &x, U64SZ); return bare_put_u64(ctx, b); } bare_error bare_get_f64(struct bare_reader *ctx, double *x) { return ctx->read(ctx->buffer, x, U64SZ); } bare_error bare_put_bool(struct bare_writer *ctx, bool x) { return bare_put_u8(ctx, (uint8_t)x); } bare_error bare_get_bool(struct bare_reader *ctx, bool *x) { return bare_get_u8(ctx, (uint8_t *)x); } bare_error
bare_put_fixed_data(struct bare_writer *ctx, uint8_t *src, uint64_t sz)
bare_put_fixed_data(struct bare_writer *ctx, const uint8_t *src, uint64_t sz)
{ return ctx->write(ctx->buffer, (void *)src, sz); } bare_error bare_get_fixed_data(struct bare_reader *ctx, uint8_t *dst, uint64_t sz) { return ctx->read(ctx->buffer, dst, sz); } bare_error
bare_put_data(struct bare_writer *ctx, uint8_t *src, uint64_t sz)
bare_put_data(struct bare_writer *ctx, const uint8_t *src, uint64_t sz)
{ bare_error err = BARE_ERROR_NONE; err = bare_put_uint(ctx, sz); if (err == BARE_ERROR_NONE) { err = bare_put_fixed_data(ctx, src, sz); } return err; } bare_error bare_get_data(struct bare_reader *ctx, uint8_t *dst, uint64_t sz) { bare_error err = BARE_ERROR_NONE; uint64_t ssz = 0; err = bare_get_uint(ctx, &ssz); if (err == BARE_ERROR_NONE) { err = ssz <= sz \ ? bare_get_fixed_data(ctx, dst, ssz) \ : BARE_ERROR_BUFFER_TOO_SMALL; } return err; } bare_error
bare_put_str(struct bare_writer *ctx, char *src, uint64_t sz)
bare_put_str(struct bare_writer *ctx, const char *src, uint64_t sz)
{ if (!checkstr(src, sz)) { return BARE_ERROR_INVALID_UTF8; } return bare_put_data(ctx, (uint8_t *)src, sz); } bare_error bare_get_str(struct bare_reader *ctx, char *dst, uint64_t sz) { bare_error err = bare_get_data(ctx, (uint8_t *)dst, sz);\ if (err == BARE_ERROR_NONE) { err = !checkstr(dst, sz) ? BARE_ERROR_INVALID_UTF8 : err; } return err; }
/*- * SPDX-License-Identifier: MIT * SPDX-FileCopyrightText: Copyright (c) 2022 Frank Smit <https://61924.nl/> */ #ifndef BARE_H #define BARE_H #include <stdint.h> #include <stdbool.h> typedef enum { BARE_ERROR_NONE, BARE_ERROR_WRITE_FAILED, BARE_ERROR_READ_FAILED, BARE_ERROR_BUFFER_TOO_SMALL, BARE_ERROR_INVALID_UTF8, } bare_error;
typedef bare_error (*bare_write_func)(void *buffer, void *src, uint64_t sz);
typedef bare_error (*bare_write_func)(void *buffer, const void *src, uint64_t sz);
typedef bare_error (*bare_read_func)(void *buffer, void *dst, uint64_t sz); struct bare_writer { void *buffer; bare_write_func write; }; struct bare_reader { void *buffer; bare_read_func read; }; bare_error bare_put_uint(struct bare_writer *ctx, uint64_t x); /* varuint */ bare_error bare_get_uint(struct bare_reader *ctx, uint64_t *x); /* varuint */ bare_error bare_put_u8(struct bare_writer *ctx, uint8_t x); bare_error bare_get_u8(struct bare_reader *ctx, uint8_t *x); bare_error bare_put_u16(struct bare_writer *ctx, uint16_t x); bare_error bare_get_u16(struct bare_reader *ctx, uint16_t *x); bare_error bare_put_u32(struct bare_writer *ctx, uint32_t x); bare_error bare_get_u32(struct bare_reader *ctx, uint32_t *x); bare_error bare_put_u64(struct bare_writer *ctx, uint64_t x); bare_error bare_get_u64(struct bare_reader *ctx, uint64_t *x); bare_error bare_put_int(struct bare_writer *ctx, int64_t x); /* varint */ bare_error bare_get_int(struct bare_reader *ctx, int64_t *x); /* varint */ bare_error bare_put_i8(struct bare_writer *ctx, int8_t x); bare_error bare_get_i8(struct bare_reader *ctx, int8_t *x); bare_error bare_put_i16(struct bare_writer *ctx, int16_t x); bare_error bare_get_i16(struct bare_reader *ctx, int16_t *x); bare_error bare_put_i32(struct bare_writer *ctx, int32_t x); bare_error bare_get_i32(struct bare_reader *ctx, int32_t *x); bare_error bare_put_i64(struct bare_writer *ctx, int64_t x); bare_error bare_get_i64(struct bare_reader *ctx, int64_t *x); bare_error bare_put_f32(struct bare_writer *ctx, float x); bare_error bare_get_f32(struct bare_reader *ctx, float *x); bare_error bare_put_f64(struct bare_writer *ctx, double x); bare_error bare_get_f64(struct bare_reader *ctx, double *x); bare_error bare_put_bool(struct bare_writer *ctx, bool x); bare_error bare_get_bool(struct bare_reader *ctx, bool *x);
bare_error bare_put_fixed_data(struct bare_writer *ctx, uint8_t *src, uint64_t sz);
bare_error bare_put_fixed_data(struct bare_writer *ctx, const uint8_t *src, uint64_t sz);
bare_error bare_get_fixed_data(struct bare_reader *ctx, uint8_t *dst, uint64_t sz);
bare_error bare_put_data(struct bare_writer *ctx, uint8_t *src, uint64_t sz);
bare_error bare_put_data(struct bare_writer *ctx, const uint8_t *src, uint64_t sz);
bare_error bare_get_data(struct bare_reader *ctx, uint8_t *dst, uint64_t sz);
bare_error bare_put_str(struct bare_writer *ctx, char *src, uint64_t sz);
bare_error bare_put_str(struct bare_writer *ctx, const char *src, uint64_t sz);
bare_error bare_get_str(struct bare_reader *ctx, char *dst, uint64_t sz); #endif /* BARE_H */
/*- * SPDX-License-Identifier: AGPL-3.0-only * SPDX-FileCopyrightText: Copyright (c) 2025 Runxi Yu <https://runxiyu.org> */
/* * TODO: Pool repositories (and take care of thread safety) * libgit2 has a nice builtin per-repo cache that we could utilize this way. */
#include <err.h> #include <errno.h> #include <git2.h> #include <pthread.h> #include <signal.h> #include <sys/socket.h> #include <sys/stat.h> #include <sys/types.h> #include <sys/un.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include "bare.h" typedef struct { int fd; } conn_io_t; static bare_error conn_read(void *buffer, void *dst, uint64_t sz) { conn_io_t *io = buffer; ssize_t rsz = read(io->fd, dst, sz); return (rsz == (ssize_t)sz) ? BARE_ERROR_NONE : BARE_ERROR_READ_FAILED; }
static bare_error conn_write(void *buffer, const void *src, uint64_t sz) { conn_io_t *io = buffer; const uint8_t *data = src; uint64_t total = 0; while (total < sz) { ssize_t written = write(io->fd, data + total, sz - total); if (written < 0) { if (errno == EINTR) continue; return BARE_ERROR_WRITE_FAILED; } if (written == 0) break; total += written; } return (total == sz) ? BARE_ERROR_NONE : BARE_ERROR_WRITE_FAILED; }
void * session(void *_conn) { int conn = *(int *)_conn; free((int *)_conn);
#pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-but-set-variable" int ret = 0; #pragma GCC diagnostic pop
int err; git_repository *repo = NULL; char path[4096]; conn_io_t io = {.fd = conn}; struct bare_reader reader = { .buffer = &io, .read = conn_read, };
struct bare_writer writer = { .buffer = &io, .write = conn_write, };
err = bare_get_data(&reader, (uint8_t *)path, sizeof(path) - 1); if (err != BARE_ERROR_NONE) {
ret = 10;
goto close; } path[sizeof(path) - 1] = '\0'; err = git_repository_open_ext(&repo, path, GIT_REPOSITORY_OPEN_NO_SEARCH | GIT_REPOSITORY_OPEN_BARE | GIT_REPOSITORY_OPEN_NO_DOTGIT, NULL); if (err != 0) {
ret = 1;
bare_put_uint(&writer, 1);
goto close; } git_object *obj = NULL; err = git_revparse_single(&obj, repo, "HEAD^{tree}"); if (err != 0) {
ret = 2;
bare_put_uint(&writer, 2);
goto free_repo; } git_tree *tree = (git_tree *) obj; git_tree_entry *entry = NULL; err = git_tree_entry_bypath(&entry, tree, "README.md"); if (err != 0) {
ret = 3;
bare_put_uint(&writer, 3);
goto free_tree; } git_otype objtype = git_tree_entry_type(entry); if (objtype != GIT_OBJECT_BLOB) {
ret = 4;
bare_put_uint(&writer, 4);
goto free_tree_entry; } git_object *obj2 = NULL; err = git_tree_entry_to_object(&obj2, repo, entry); if (err != 0) {
ret = 5;
bare_put_uint(&writer, 5);
goto free_tree_entry; } git_blob *blob = (git_blob *) obj2; const void *content = git_blob_rawcontent(blob); if (content == NULL) {
ret = 6;
bare_put_uint(&writer, 6);
goto free_blob; }
write(conn, content, git_blob_rawsize(blob));
bare_put_uint(&writer, 0); bare_put_data(&writer, content, git_blob_rawsize(blob));
free_blob: git_blob_free(blob); free_tree_entry: git_tree_entry_free(entry); free_tree: git_tree_free(tree); free_repo: git_repository_free(repo); close:
// TODO: Implement proper error handling dprintf(conn, "%d\n", ret);
close(conn); return NULL; } int main(int argc, char **argv) { if (argc != 2) { errx(1, "provide one argument: the socket path"); } signal(SIGPIPE, SIG_IGN); git_libgit2_init(); int sock; if ((sock = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0)) < 0) err(1, "socket"); struct sockaddr_un addr; memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; strcpy(addr.sun_path, argv[1]); umask(0077); if (bind(sock, (struct sockaddr *)&addr, sizeof(struct sockaddr_un))) { if (errno == EADDRINUSE) { unlink(argv[1]); if (bind(sock, (struct sockaddr *)&addr, sizeof(struct sockaddr_un))) err(1, "bind"); } else { err(1, "bind"); } } listen(sock, 0); for (;;) { int *conn = malloc(sizeof(int)); if (conn == NULL) err(1, "malloc"); *conn = accept(sock, 0, 0); if (*conn == -1) err(1, "accept"); pthread_t thread; pthread_create(&thread, NULL, session, (void *)conn); } close(sock); git_libgit2_shutdown(); }