/* * Copyright (c) Rhizome * SPDX-License-Identifier: AGPL-3.0-only */ #include "rotagen.h" #include #include #include #include #define MAX_LINE_LENGTH 64 enum conf_section { CONF_SECTION_PEOPLE, CONF_SECTION_JOBS, CONF_SECTION_SLOTS, CONF_SECTION_CONSTRAINTS, CONF_SECTION_COUNT, }; static const char *section_names[CONF_SECTION_COUNT] = { [CONF_SECTION_PEOPLE] = "people", [CONF_SECTION_JOBS] = "jobs", [CONF_SECTION_SLOTS] = "slots", [CONF_SECTION_CONSTRAINTS] = "constraints", }; static FILE *stream; static int line_len; static char line[MAX_LINE_LENGTH]; static bool section_read[CONF_SECTION_COUNT]; static void next_line(void); static bool line_is_heading(void); static enum conf_section read_heading(void); static bool all_sections(void); static void read_people(struct config *conf_out); static void read_jobs(struct config *conf_out); static void read_slots(struct config *conf_out); static void read_constraints(struct config *conf_out); static bool parse_constraint(const struct config *conf, char *input, int len, struct constraint *out); void read_config(const char *path, struct config *config_out) { stream = fopen(path, "r"); if (stream == NULL) { fprintf(stderr, "Fatal error: can't open config file %s\n", path); exit(1); } memset(section_read, 0, sizeof(section_read)); do next_line(); while (!line_is_heading()); do { const enum conf_section section = read_heading(); switch (section) { case CONF_SECTION_PEOPLE: read_people(config_out); break; case CONF_SECTION_JOBS: read_jobs(config_out); break; case CONF_SECTION_SLOTS: read_slots(config_out); break; case CONF_SECTION_CONSTRAINTS: read_constraints(config_out); break; default: fprintf(stderr, "Warning: unhandled conf section %d\n", section); continue; } section_read[section] = true; } while (!all_sections()); fclose(stream); } static void next_line(void) { line_len = 0; int c; while (1) { if (line_len >= MAX_LINE_LENGTH) { fprintf(stderr, "Warning: line length limit reached, line truncated\n"); return; } c = fgetc(stream); if (c == '\n' || c == EOF) { line[line_len] = '\0'; return; } line[line_len++] = c; } } static bool line_is_heading(void) { return line[0] == '[' && line[line_len - 1] == ']'; } static enum conf_section read_heading(void) { for (int i = 0; i < CONF_SECTION_COUNT; ++i) { if (strncasecmp(section_names[i], &line[1], line_len - 2) == 0) return (enum conf_section)i; } fprintf(stderr, "Fatal error: unexpected section %s\n", line); exit(1); } static bool all_sections(void) { for (int i = 0; i < CONF_SECTION_COUNT; ++i) { if (!section_read[i]) return false; } return true; } static void read_people(struct config *conf_out) { while (1) { next_line(); if (line_is_heading()) { return; } else if (line_len == 0) { if (feof(stream)) return; else continue; } strcpy(conf_out->people[conf_out->num_people++], line); } } static void read_jobs(struct config *conf_out) { while (1) { next_line(); if (line_is_heading()) { return; } else if (line_len == 0) { if (feof(stream)) return; else continue; } strcpy(conf_out->jobs[conf_out->num_jobs++], line); } } static void read_slots(struct config *conf_out) { while (1) { next_line(); if (line_is_heading()) { return; } else if (line_len == 0) { if (feof(stream)) return; else continue; } strcpy(conf_out->slots[conf_out->num_slots++], line); } } static void read_constraints(struct config *conf_out) { while (1) { next_line(); if (line_is_heading()) { return; } else if (line_len == 0) { if (feof(stream)) return; else continue; } struct constraint *out = &conf_out->constraints[conf_out->num_constraints]; if (parse_constraint(conf_out, line, line_len, out)) ++conf_out->num_constraints; else fprintf( stderr, "Warning: failed to parse constraint \"%s\"; will be ignored\n", line); } } static bool parse_constraint(const struct config *conf, char *input, int len, struct constraint *out) { bool match = false; for (int i = 0; i < conf->num_people; ++i) { const int person_len = strlen(conf->people[i]); if (strncasecmp(input, conf->people[i], person_len) == 0) { out->person = i; input += person_len + 1; len -= person_len + 1; match = true; } } if (!match) { fprintf(stderr, "Unknown person in constraint\n"); return false; } const char *filler = "can't do "; const int filler_len = strlen(filler); if (strncasecmp(input, filler, filler_len) == 0) { input += filler_len; len -= filler_len; } else { fprintf(stderr, "Invalid constraint syntax\n"); return false; } const char *job_str = "job ", *slot_str = "slot "; const int job_str_len = strlen(job_str), slot_str_len = strlen(slot_str); if (strncasecmp(input, job_str, job_str_len) == 0) { out->type = JOB_EXEMPTION_CONSTRAINT; input += job_str_len; len -= job_str_len; } else if (strncasecmp(input, slot_str, slot_str_len) == 0) { out->type = SLOT_EXEMPTION_CONSTRAINT; input += slot_str_len; len -= slot_str_len; } else { fprintf(stderr, "Unknown constraint type\n"); return false; } if (input[0] == '"' && input[len - 1] == '"') { ++input; len -= 2; } else { fprintf(stderr, "Missing quotes in constraint\n"); return false; } switch (out->type) { case SLOT_EXEMPTION_CONSTRAINT: match = false; for (int i = 0; i < conf->num_slots; ++i) { const int slot_len = strlen(conf->slots[i]); if (strncasecmp(input, conf->slots[i], slot_len) == 0) { out->object.slot = i; input += slot_len + 1; len -= slot_len + 1; match = true; } } if (!match) { fprintf(stderr, "Unknown slot in constraint\n"); return false; } break; case JOB_EXEMPTION_CONSTRAINT: match = false; for (int i = 0; i < conf->num_jobs; ++i) { const int job_len = strlen(conf->jobs[i]); if (strncasecmp(input, conf->jobs[i], job_len) == 0) { out->object.job = i; input += job_len + 1; len -= job_len + 1; match = true; } } if (!match) { fprintf(stderr, "Unknown job in constraint\n"); return false; } break; default: fprintf(stderr, "Warning: unhandled constraint type in %s()", __func__); break; } return true; }