romele / git-trace public
62 commits · 1 branch · 1 tag · 1 contributor (top: Romele) · first commit 3d ago · last push 52m ago
src/repo.c
#include "repo.h" #include "auth.h" #include "db.h" #include "hooks.h" #include "util.h" #include "web.h" /* app_ctx */ #include <ctype.h> #include <limits.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/stat.h> #include <sys/wait.h> #include <unistd.h> int name_ok(const char *s) { size_t n = strlen(s); if (n == 0 || n > 63) return 0; if (!isalnum((unsigned char)s[0])) return 0; for (size_t i = 0; i < n; i++) { unsigned char c = (unsigned char)s[i]; if (!(isalnum(c) || c == '.' || c == '_' || c == '-')) return 0; } return strstr(s, "..") == NULL; } /* owner names: same charset as name_ok, plus "_" — the ownerless * (platform) namespace whose repos live at <root>/_/<name>.git */ int owner_ok(const char *s) { return strcmp(s, "_") == 0 || name_ok(s); } int repo_path(char *out, size_t outlen, const char *root, const char *owner, const char *name) { if (!owner_ok(owner) || !name_ok(name)) return -1; char cand[PATH_MAX]; int n = snprintf(cand, sizeof cand, "%s/%s/%s.git", root, owner, name); if (n < 0 || (size_t)n >= sizeof cand) return -1; char real[PATH_MAX], realroot[PATH_MAX]; if (!realpath(cand, real) || !realpath(root, realroot)) return -1; size_t rl = strlen(realroot); if (strncmp(real, realroot, rl) != 0 || (real[rl] != '/' && real[rl] != '\0')) return -1; n = snprintf(out, outlen, "%s", real); return (n < 0 || (size_t)n >= outlen) ? -1 : 0; } int repo_exists(const char *root, const char *owner, const char *name) { char p[PATH_MAX]; return repo_path(p, sizeof p, root, owner, name) == 0; } /* ' -> '\'' : embed a path inside single quotes safely */ static char *shq(const char *s, char *out, size_t n) { size_t w = 0; for (const char *p = s; *p && w + 5 < n; p++) { if (*p == '\'') { memcpy(out + w, "'\\''", 4); w += 4; } else { out[w++] = *p; } } out[w] = '\0'; return out; } int repo_install_hooks(struct app_ctx *c, const char *rdir) { char bin[PATH_MAX]; ssize_t bl = readlink("/proc/self/exe", bin, sizeof bin - 1); if (bl > 0) bin[bl] = '\0'; else snprintf(bin, sizeof bin, "git-trace"); const struct { const char *file, *body; } hh[] = { { "update", UPDATE_HOOK }, { "post-receive", POSTRECV_HOOK }, }; for (size_t i = 0; i < sizeof hh / sizeof hh[0]; i++) { char hp[PATH_MAX]; if (snprintf(hp, sizeof hp, "%s/hooks/%s", rdir, hh[i].file) >= (int)sizeof hp) return -1; FILE *f = fopen(hp, "w"); if (!f) return -1; /* shebang must stay on line 1: emit it here plus the env the * hook needs, then append the static body minus its shebang */ char qr[PATH_MAX * 2], qd[PATH_MAX * 2], qb[PATH_MAX * 2]; fprintf(f, "#!/bin/sh\nexport GIT_TRACE_REPOS='%s'\n" "export GIT_TRACE_DB='%s'\nGIT_TRACE_BIN='%s'\n", shq(c->repos_root, qr, sizeof qr), shq(c->db_path, qd, sizeof qd), shq(bin, qb, sizeof qb)); const char *body = strchr(hh[i].body, '\n'); fputs(body ? body + 1 : hh[i].body, f); fclose(f); chmod(hp, 0755); } return 0; } /* * Create <repos_root>/<owner>/<name>.git on disk, seed it with * `git init --bare`, install the update hook, register it in the DB. * owner "_" = ownerless (owner_uid 0). Shared by `mkrepo` and /new. */ int repo_create(struct app_ctx *c, int owner_uid, const char *owner, const char *name, const char *desc, int is_private) { char odir[PATH_MAX]; if (snprintf(odir, sizeof odir, "%s/%s", c->repos_root, owner) >= (int)sizeof odir) return -1; if (mkdir_p(odir) != 0) return -1; char rdir[PATH_MAX]; if (snprintf(rdir, sizeof rdir, "%s/%s.git", odir, name) >= (int)sizeof rdir) return -1; if (access(rdir, F_OK) != 0) { pid_t pid = fork(); if (pid == 0) { execlp("git", "git", "init", "--bare", rdir, (char *)NULL); _exit(127); } int st = 0; waitpid(pid, &st, 0); if (!WIFEXITED(st) || WEXITSTATUS(st) != 0) return -1; } if (desc) { char dp[PATH_MAX]; FILE *f = NULL; if (snprintf(dp, sizeof dp, "%s/description", rdir) < (int)sizeof dp) f = fopen(dp, "w"); if (f) { fputs(desc, f); fputc('\n', f); fclose(f); } } if (repo_install_hooks(c, rdir) != 0) return -1; return repo_register(c->db, owner_uid, name, !is_private, desc); }