/* * The reader for cgit's config files, which are the main cgitrc, any file * it pulls in with an include line, the cgitrc beside a scanned repository, * and the cached repolist. A file is a sequence of name=value lines, each * handed to a callback that decides what it means, so nothing here knows a * single key by name. */ #include "cgit.h" #include "config.h" #define MAX_INCLUDE_NESTING 8 struct config_origin config_origin; // The newlines read so far from the file being parsed, which place each entry // on its line. static int lines_read; /* * Folds away a carriage return that sits right before a newline so a file * saved with DOS line endings parses the same as one saved with Unix endings. */ static int next_char(FILE *f) { int c = fgetc(f); if (c == '\r') { c = fgetc(f); if (c != '\n') { ungetc(c, f); c = '\r'; } } if (c == '\n') lines_read++; return c; } static void skip_line(FILE *f) { int c; while ((c = next_char(f)) != '\n' && c != EOF) ; } static int next_content_char(FILE *f) { int c = next_char(f); for (;;) { if (c == EOF) return EOF; if (c == '#' || c == ';') skip_line(f); else if (!isspace(c)) return c; c = next_char(f); } } static int read_entry(FILE *f, struct strbuf *name, struct strbuf *value, int *line) { int c; strbuf_reset(value); for (;;) { c = next_content_char(f); if (c == EOF) return 0; *line = lines_read + 1; strbuf_reset(name); while (c != '=' && c != '\n' && c != EOF) { strbuf_addch(name, c); c = next_char(f); } if (c == EOF) return 0; // Dropping only the line with no equals sign keeps one typo // from hiding the rest of the file. if (c != '=') continue; c = next_char(f); while (c != '\n' && c != EOF) { strbuf_addch(value, c); c = next_char(f); } return 1; } } int config_file_parse(const char *filename, config_file_value_fn fn) { static int nesting; // An include is parsed from inside a line of the file that names it, // which carries on from that line once the include is done. struct config_origin outer = config_origin; int outer_lines_read = lines_read; struct strbuf name = STRBUF_INIT; struct strbuf value = STRBUF_INIT; struct stat st; FILE *f; int err = 0; // An include line calls back into here, so an include cycle would // recurse until the stack gave out. if (nesting > MAX_INCLUDE_NESTING) return 0; if (!(f = fopen(filename, "r"))) return errno; // Git's fopen refuses a directory only where its build defines // FREAD_READS_DIRECTORIES, and elsewhere read(2) need not fail on one. if (!fstat(fileno(f), &st) && S_ISDIR(st.st_mode)) { fclose(f); return EISDIR; } config_origin.file = filename; lines_read = 0; nesting++; while (read_entry(f, &name, &value, &config_origin.line)) fn(name.buf, value.buf); if (ferror(f)) err = errno; nesting--; fclose(f); config_origin = outer; lines_read = outer_lines_read; strbuf_release(&name); strbuf_release(&value); return err; } int config_int(const char *name, const char *value) { int number = atoi(value); char *end; // Space may follow the digits strtol finds, but nothing else. strtol(value, &end, 10); while (end != value && isspace(*end)) end++; if (end != value && !*end) return number; // The first line alone, since a git config value can hold a newline // that would forge a log line of its own. if (config_origin.file) fprintf(stderr, "[cgit] Reading %s=%.*s in %s line %d as %d, since it is not a whole number\n", name, (int)strcspn(value, "\n"), value, config_origin.file, config_origin.line, number); else fprintf(stderr, "[cgit] Reading %s=%.*s as %d, since it is not a whole number\n", name, (int)strcspn(value, "\n"), value, number); return number; }