#include "pom.h" #include // still needed for sprintf, even if POM_NO_STDIO is defined. #include #include #include #include #include #include #if __GNUC__ >= 6 #define ATTRIBUTE_PRINTF(fmt, args) __attribute__ ((format(printf, fmt, args))) #else #define ATTRIBUTE_PRINTF(fmt, args) #endif #if _MSC_VER >= 1600 #define PRINTF_FORMAT_STRING _Printf_format_string_ #else #define PRINTF_FORMAT_STRING #endif struct pom_error { const pom_error *next; const char *file; uint64_t line; const char *message; }; struct main_conf; struct pom_conf { struct main_conf *main; size_t prefix_len; const struct conf_item *items; size_t items_count; }; struct conf_items { const char *key; const char *value; const pom_conf *section; }; struct main_conf { struct conf_item *items; size_t items_count; }; #ifdef POM_NO_STDIO #define fatal_error(...) abort() #else // fatal_error should only be called when the API is misused // (e.g. `NULL` argument that shouldn't be `NULL`). static void fatal_error(PRINTF_FORMAT_STRING const char *fmt, ...) ATTRIBUTE_PRINTF(1, 2); static void fatal_error(const char *fmt, ...) { va_list args; va_start(args, fmt); vfprintf(stderr, fmt, args); va_end(args); fprintf(stderr, "\n"); abort(); } #endif // Make an error with no next-error. static pom_error *make_error(PRINTF_FORMAT_STRING const char *file, uint64_t line, const char *fmt, ...) ATTRIBUTE_PRINTF(3, 4); static pom_error * make_error(const char *file, uint64_t line, const char *fmt, ...) { va_list args, args_copy; va_start(args, fmt); va_copy(args_copy, args); bool bad_fmt = false; int len = vsnprintf(NULL, 0, fmt, args); if (len < 0 || len > INT_MAX - sizeof(pom_error) - 1) { // Should probably never happen? Who knows though. // In this case, we just use fmt as the error message. bad_fmt = true; len = strlen(fmt); } pom_error *err = malloc(sizeof(pom_error) + len + 1); if (err) { char *message = (char *)(err + 1); if (bad_fmt) { strcpy(message, fmt); } else { vsnprintf(message, len + 1, fmt, args); } err->file = file; err->line = line; err->message = message; err->next = NULL; } return err; } const pom_error * pom_error_next(const pom_error *error) { if (!error) return NULL; return error->next; } const char * pom_error_file(const pom_error *error) { if (!error) fatal_error("%s called with NULL argument", __func__); return error->file; } uint64_t pom_error_line(const pom_error *error) { if (!error) fatal_error("%s called with NULL argument", __func__); return error->line; } const char * pom_error_message(const pom_error *error) { if (!error) fatal_error("%s called with NULL argument", __func__); return error->message; } #ifndef POM_NO_STDIO void pom_error_print(const pom_error *error) { if (!error) { fprintf(stderr, "No error.\n"); return; } fprintf(stderr, "Error:\n"); for (; error; error = pom_error_next(error)) { fprintf(stderr, "%s:%" PRIu64 ": %s\n", error->file, error->line, error->message); } } #endif pom_conf * pom_load(const char *filename, size_t (*read_func)(void *userdata, char *buf, size_t len), void *userdata, pom_error **error) { if (!filename) fatal_error("%s called with NULL file name", __func__); if (!read_func) fatal_error("%s called with NULL read function", __func__); // TODO if (error) *error = NULL; return NULL; } static size_t read_string(void *vpstring, char *buf, size_t len) { const char **pstring = vpstring; const char *string = *pstring; size_t i; for (i = 0; i < len; i++, string++) { if (*string == 0) break; buf[i] = *string; } *pstring = string; return i; } pom_conf * pom_load_string(const char *filename, const char *string, pom_error **error) { return pom_load(filename, read_string, &string, error); } #ifndef POM_NO_STDIO static size_t read_file(void *file, char *buf, size_t len) { return fread(buf, 1, len, file); } pom_conf * pom_load_file(const char *filename, FILE *file, pom_error **error) { if (!filename) fatal_error("%s called with NULL file name", __func__); if (!file) fatal_error("%s called with NULL file", __func__); return pom_load(filename, read_file, file, error); } pom_conf * pom_load_path(const char *path, pom_error **error) { if (!path) fatal_error("%s called with NULL file name", __func__); FILE *fp = fopen(path, "rb"); if (!fp) { if (error) { const char *message = strerror(errno); *error = make_error(path, 1, "Couldn't open file: %s", message); } return NULL; } pom_conf *conf = pom_load_file(path, fp, error); fclose(fp); return conf; } #endif