summaryrefslogtreecommitdiff
path: root/src/config.c
diff options
context:
space:
mode:
authorGravatar Anshul Gupta <ansg191@anshulg.com> 2025-04-09 13:24:53 -0700
committerGravatar Anshul Gupta <ansg191@anshulg.com> 2025-04-11 00:15:18 -0700
commitf277f41d83d450a1f28d350bb7d6da075d1d2741 (patch)
treee197a7de7e26b47cec57a46acdde4b6c74889ad5 /src/config.c
downloadgithub-mirror-f277f41d83d450a1f28d350bb7d6da075d1d2741.tar.gz
github-mirror-f277f41d83d450a1f28d350bb7d6da075d1d2741.tar.zst
github-mirror-f277f41d83d450a1f28d350bb7d6da075d1d2741.zip
Initial Commit
Add cmake github workflow Install dependencies in gh action Fix missing directory Fix compiler errors Fix group name to gid conversion Force cjson to statically link Add header guards to query headers Add install and cpack to CMakeLists.txt Add config search and CLI arg parsing Improve docs for git.c Fix program continuing on -h flag
Diffstat (limited to 'src/config.c')
-rw-r--r--src/config.c307
1 files changed, 307 insertions, 0 deletions
diff --git a/src/config.c b/src/config.c
new file mode 100644
index 0000000..d20f4f5
--- /dev/null
+++ b/src/config.c
@@ -0,0 +1,307 @@
+//
+// Created by Anshul Gupta on 4/6/25.
+//
+
+#include "config.h"
+
+#include <ctype.h>
+#include <fcntl.h>
+#include <grp.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+const char *config_locations[] = {
+ "/etc/github_mirror/config.ini",
+ "/usr/local/etc/github_mirror/config.ini",
+ "/usr/local/github_mirror/config.ini",
+ "config.ini",
+ NULL,
+};
+
+enum config_section {
+ section_none,
+ section_github,
+ section_git,
+};
+
+
+static char *file_read(const char *path, size_t *size_out)
+{
+ char *contents = NULL;
+
+ // Open the file for reading
+ const int fd = open(path, O_RDONLY);
+ if (fd < 0) {
+ perror("Error reading config file");
+ return NULL;
+ }
+
+ // Stat the file to get its size
+ struct stat st;
+ if (fstat(fd, &st) < 0) {
+ perror("Error getting file size");
+ goto end;
+ }
+
+ // Get the size of the file
+ const size_t size = st.st_size;
+ if (size == 0) {
+ fprintf(stderr, "Error reading config file: file is empty\n");
+ goto end;
+ }
+
+ // Map the file into memory
+ contents = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
+ if (contents == MAP_FAILED) {
+ perror("Error reading config file");
+ goto end;
+ }
+
+ // Output file size
+ if (size_out)
+ *size_out = size;
+
+end:
+ close(fd);
+ return contents;
+}
+
+static char *trim(char *start, char *end)
+{
+ while (start < end && isspace(*start))
+ start++;
+ while (end > start && isspace(*(end - 1)))
+ end--;
+ *end = '\0';
+ return start;
+}
+
+static int parse_line_inner(struct config *cfg, enum config_section section,
+ char *key, char *value)
+{
+ char *endptr;
+ struct passwd *pw;
+ struct group *gr;
+
+ switch (section) {
+ case section_none:
+ fprintf(stderr,
+ "Unexpected key-value pair outside of section: %s=%s\n",
+ key, value);
+ return -1;
+ case section_github:
+ if (!strcmp(key, "endpoint"))
+ cfg->endpoint = value;
+ else if (!strcmp(key, "token"))
+ cfg->token = value;
+ else if (!strcmp(key, "user_agent"))
+ cfg->user_agent = value;
+ else if (!strcmp(key, "owner"))
+ cfg->owner = value;
+ else {
+ fprintf(stderr,
+ "Error parsing config file: unknown key: %s\n",
+ key);
+ return -1;
+ }
+ break;
+ case section_git:
+ if (!strcmp(key, "base"))
+ cfg->git_base = value;
+ else if (!strcmp(key, "owner")) {
+ // If the value is a number, set the owner to that
+ // number
+ cfg->git_owner = strtol(value, &endptr, 10);
+ if (*endptr == '\0')
+ return 0;
+
+ // Otherwise, convert the string into a uid
+ if (!((pw = getpwnam(value)))) {
+ fprintf(stderr,
+ "Error parsing config file: unknown "
+ "user: %s\n",
+ value);
+ return -1;
+ }
+ cfg->git_owner = pw->pw_uid;
+ } else if (!strcmp(key, "group")) {
+ // If the value is a number, set the group to that
+ // number
+ cfg->git_group = strtol(value, &endptr, 10);
+ if (*endptr == '\0')
+ return 0;
+
+ // Otherwise, convert the string into a gid
+ if (!((gr = getgrnam(value)))) {
+ fprintf(stderr,
+ "Error parsing config file: unknown "
+ "group: %s\n",
+ value);
+ return -1;
+ }
+ cfg->git_group = gr->gr_gid;
+ } else {
+ fprintf(stderr,
+ "Error parsing config file: unknown key: %s\n",
+ key);
+ return -1;
+ }
+ break;
+ }
+ return 0;
+}
+
+static int parse_line(struct config *cfg, char *line,
+ enum config_section *section)
+{
+ switch (*line) {
+ case ';':
+ case '#':
+ case '\0':
+ // Ignore comments and empty lines
+ return 0;
+ case '[': {
+ // Handle section headers
+ char *close = strchr(line, ']');
+ if (!close) {
+ fprintf(stderr,
+ "Error parsing config file: invalid section "
+ "header: %s\n",
+ line);
+ return -1;
+ }
+ *close = '\0';
+ char *section_name = trim(line + 1, close);
+ if (!strcmp(section_name, "github"))
+ *section = section_github;
+ else if (!strcmp(section_name, "git"))
+ *section = section_git;
+ else {
+ fprintf(stderr,
+ "Error parsing config file: unknown section: "
+ "%s\n",
+ section_name);
+ return -1;
+ }
+ return 0;
+ }
+ default: {
+ // Handle key-value pairs
+ char *line_end = line + strlen(line);
+ char *equals = strchr(line, '=');
+ if (!equals) {
+ fprintf(stderr,
+ "Error parsing config file: invalid line: %s\n",
+ line);
+ return -1;
+ }
+ *equals = '\0';
+ char *key = trim(line, equals);
+ char *value = trim(equals + 1, line_end);
+ return parse_line_inner(cfg, *section, key, value);
+ }
+ }
+}
+
+static int config_parse(struct config *cfg)
+{
+ char *ptr = cfg->contents;
+ char *end = cfg->contents + cfg->contents_len;
+ enum config_section section = section_none;
+
+ while (ptr < end) {
+ // Find the end of the line
+ char *newline = memchr(ptr, '\n', end - ptr);
+ char *line_end = newline ? newline : end;
+
+ // Handle line endings
+ char *actual_end = line_end;
+ if (actual_end > ptr && *(actual_end - 1) == '\r')
+ actual_end--;
+
+ // Null-terminate the line
+ if (line_end < end)
+ *line_end = '\0';
+
+ // Trim whitespace
+ char *line = trim(ptr, actual_end);
+
+ // Parse the line
+ if (parse_line(cfg, line, &section) < 0)
+ return -1;
+
+ // Move to the next line
+ ptr = newline ? newline + 1 : end;
+ }
+
+ return 0;
+}
+
+static void config_defaults(struct config *cfg)
+{
+ cfg->endpoint = GH_DEFAULT_ENDPOINT;
+ cfg->user_agent = GH_DEFAULT_USER_AGENT;
+ cfg->git_base = "/srv/git";
+ cfg->git_owner = getuid();
+ cfg->git_group = getgid();
+}
+
+static int config_validate(const struct config *cfg)
+{
+ if (!cfg->token) {
+ fprintf(stderr,
+ "Error: missing required field: github.token\n");
+ return -1;
+ }
+ if (!cfg->owner) {
+ fprintf(stderr,
+ "Error: missing required field: github.owner\n");
+ return -1;
+ }
+ return 0;
+}
+
+
+struct config *config_read(const char *path)
+{
+ struct config *cfg = calloc(1, sizeof(*cfg));
+ if (!cfg) {
+ perror("error allocating config");
+ return NULL;
+ }
+ config_defaults(cfg);
+
+ // Read the config file
+ cfg->contents = file_read(path, &cfg->contents_len);
+ if (!cfg->contents)
+ goto fail;
+
+ // Parse the config file
+ if (config_parse(cfg) < 0)
+ goto fail2;
+
+ // Validate the config file
+ if (config_validate(cfg) < 0)
+ goto fail2;
+
+ return cfg;
+
+fail2:
+ munmap(cfg->contents, cfg->contents_len);
+fail:
+ free(cfg);
+ return NULL;
+}
+
+void config_free(struct config *config)
+{
+ if (!config || !config->contents)
+ return;
+ munmap(config->contents, config->contents_len);
+ free(config);
+}