config.c (8287B)
1 #include <glob.h> 2 #include <libgen.h> 3 #include <stdarg.h> 4 #include <stdlib.h> 5 #include <string.h> 6 #include <strings.h> 7 8 #include "finwo/cnfparse.h" 9 10 #include "config.h" 11 12 #define CFG_MAX_NS 8 13 #define CFG_MAX_REG 256 14 15 struct cfg_ns { 16 char name[32]; 17 int count; 18 const char *h_name[CFG_MAX_REG]; 19 cfg_directive_fn h_fn[CFG_MAX_REG]; 20 }; 21 22 static struct cfg_ns ns_tab[CFG_MAX_NS]; 23 static int ns_count = 0; 24 25 static struct cnf_directive *cfg_handle_source_directory(FILE *fd, struct cnf_directive *dir, void *user); 26 27 static int cfg_errored = 0; 28 29 void cfg_error(const char *fmt, ...) { 30 va_list ap; 31 va_start(ap, fmt); 32 fprintf(stderr, "config: "); 33 vfprintf(stderr, fmt, ap); 34 fprintf(stderr, "\n"); 35 va_end(ap); 36 cfg_errored = 1; 37 } 38 39 int cfg_error_occurred(void) { return cfg_errored; } 40 void cfg_error_reset(void) { cfg_errored = 0; } 41 42 struct cfg_ns *cfg_ns_get(const char *name) { 43 int i; 44 for (i = 0; i < ns_count; i++) { 45 if (!strcmp(ns_tab[i].name, name)) return &ns_tab[i]; 46 } 47 if (ns_count >= CFG_MAX_NS) { 48 fprintf(stderr, "config: namespace table full, dropping `%s`\n", name); 49 return NULL; 50 } 51 snprintf(ns_tab[ns_count].name, sizeof(ns_tab[ns_count].name), "%s", name); 52 ns_tab[ns_count].count = 0; 53 struct cfg_ns *ns = &ns_tab[ns_count++]; 54 // Every namespace supports source-directory -> source <dir>/* expansion 55 cfg_register_directive(ns, "source-directory", cfg_handle_source_directory); 56 return ns; 57 } 58 59 void cfg_register_directive(struct cfg_ns *ns, const char *name, cfg_directive_fn fn) { 60 if (!ns) return; 61 if (ns->count >= CFG_MAX_REG) { 62 fprintf(stderr, "config: handler table full for ns `%s`, dropping `%s`\n", ns->name, name); 63 return; 64 } 65 ns->h_name[ns->count] = name; 66 ns->h_fn[ns->count] = fn; 67 ns->count++; 68 } 69 70 // Internal directive name used to mark a glob that came from 71 // `source-directory` and must therefore obey run-parts filename rules. Not 72 // reachable from a config file: `@` is not a legal directive name character. 73 #define CFG_SOURCE_DIR_MARKER "@source-directory" 74 75 // run-parts naming, as interfaces(5) specifies for source-directory: a file is 76 // sourced only if its name consists entirely of ASCII letters, digits, 77 // underscores and hyphens. 78 // 79 // This is not pedantry. The rule exists to skip editor backups, dpkg-dist 80 // files and anything else with a dot or tilde in it. The production Cumulus 81 // switch we captured has seven such files sitting in /etc/network/interfaces.d 82 // (030-swp.intf.10469.2026-09-08@09:32:31~ and friends); without this filter 83 // we would source stale duplicate config and die on a duplicate `iface`. 84 // 85 // Note this necessarily excludes `foo.cnf` too -- which is why our own shipped 86 // configs use an explicit `source <dir>/*.cnf` glob rather than 87 // source-directory. Explicit globs are not filtered. 88 static int name_is_runparts(const char *name) { 89 const char *p; 90 if (!name || !*name) return 0; 91 for (p = name; *p; p++) { 92 if (*p >= 'a' && *p <= 'z') continue; 93 if (*p >= 'A' && *p <= 'Z') continue; 94 if (*p >= '0' && *p <= '9') continue; 95 if (*p == '_' || *p == '-') continue; 96 return 0; 97 } 98 return 1; 99 } 100 101 // source-directory handler: transforms `source-directory <dir>` into a 102 // run-parts-filtered glob of <dir>/* 103 static struct cnf_directive *cfg_handle_source_directory(FILE *fd, struct cnf_directive *dir, void *user) { 104 (void)fd; (void)user; 105 struct cnf_directive *ndir = NULL; 106 char *pattern = NULL; 107 108 if (dir->argc != 1) { 109 fprintf(stderr, "`source-directory` directive accepts only 1 argument\n"); 110 // Return as unknown to let caller handle error? For now treat as error via exit-style? 111 // Keep consistent with other handlers: print and return dir as unknown for outer to error. 112 // But we want to synthesize source, so we need at least 1 arg. 113 // If argc !=1, free and return NULL to signal error via cfg_parse's unknown path? 114 cnf_directive_free(dir); 115 return NULL; 116 } 117 118 // Synthesize a `source <dir>/*` directive 119 // Allocate new directive manually 120 ndir = calloc(1, sizeof(*ndir)); 121 ndir->name = strdup(CFG_SOURCE_DIR_MARKER); 122 ndir->argc = 1; 123 ndir->argv = calloc(1, sizeof(char*)); 124 // Append /* if not already ending with /* 125 if (dir->argv[0][strlen(dir->argv[0])-1] == '/') { 126 pattern = malloc(strlen(dir->argv[0]) + 2); // "/" + "*" 127 sprintf(pattern, "%s*", dir->argv[0]); 128 } else { 129 // if dir is like "/etc/network/interfaces.d", we want "/etc/network/interfaces.d/*" 130 pattern = malloc(strlen(dir->argv[0]) + 3); 131 sprintf(pattern, "%s/*", dir->argv[0]); 132 } 133 ndir->argv[0] = pattern; 134 // we own pattern, will be freed via cnf_directive_free later 135 cnf_directive_free(dir); 136 return ndir; 137 } 138 139 int cfg_parse(struct cfg_ns *ns, const char *wd, FILE *fd, void *user) { 140 struct cnf_directive *dir = NULL; 141 glob_t globbuf; 142 int globflags; 143 int i; 144 145 char *strtmp = NULL; 146 FILE *nfd; 147 148 if (!ns) { 149 fprintf(stderr, "config: NULL namespace\n"); 150 return CFG_RET_ERROR; 151 } 152 153 for(;;) { 154 if (dir) cnf_directive_free(dir); 155 dir = cnf_directive_read(fd); 156 if (!dir) break; // EOF = done 157 158 cfg_parse_reparse: 159 160 // Core handlers: source and source-directory (source-directory is via handler table, but we also handle source here as built-in) 161 if (!strcasecmp("source", dir->name) || !strcasecmp(CFG_SOURCE_DIR_MARKER, dir->name)) { 162 // Only source-directory filters filenames; an explicit `source foo/*.cnf` 163 // means exactly what it says. 164 int runparts = !strcasecmp(CFG_SOURCE_DIR_MARKER, dir->name); 165 globflags = GLOB_ERR; 166 for ( i = 0 ; i < (int)dir->argc ; i++ ) { 167 if (dir->argv[i][0] == '/') { 168 strtmp = calloc(strlen(dir->argv[i])+1, 1); 169 strcpy(strtmp, dir->argv[i]); 170 } else { 171 // wd may be NULL for IPC (no filesystem context), then treat as absolute or fail 172 if (wd) { 173 strtmp = malloc(snprintf(NULL, 0, "%s/%s", wd, dir->argv[i])+1); 174 sprintf(strtmp, "%s/%s", wd, dir->argv[i]); 175 } else { 176 strtmp = calloc(strlen(dir->argv[i])+1, 1); 177 strcpy(strtmp, dir->argv[i]); 178 } 179 } 180 glob(strtmp, globflags, NULL, &globbuf); 181 free(strtmp); 182 globflags = GLOB_ERR | GLOB_APPEND; 183 } 184 for ( i = 0 ; i < (int)globbuf.gl_pathc ; i++ ) { 185 if (runparts) { 186 char *bcopy = strdup(globbuf.gl_pathv[i]); 187 const char *base = basename(bcopy); 188 int ok = name_is_runparts(base); 189 if (!ok) { 190 fprintf(stderr, "config: source-directory: skipping `%s` (not run-parts safe)\n", base); 191 } 192 free(bcopy); 193 if (!ok) continue; 194 } 195 strtmp = calloc(strlen(globbuf.gl_pathv[i])+1, 1); 196 strcpy(strtmp, globbuf.gl_pathv[i]); 197 char *dname = strdup(strtmp); 198 dirname(dname); 199 // Use wd as dirname for nested relative sources? Keep dname as wd for nested 200 // But for simplicity, use dname 201 nfd = fopen(globbuf.gl_pathv[i], "r"); 202 if (!nfd) { 203 perror("Could not open config file for reading"); 204 free(strtmp); 205 free(dname); 206 globfree(&globbuf); 207 return CFG_RET_ERROR; 208 } 209 if (cfg_parse(ns, dname, nfd, user) < 0) { 210 fprintf(stderr, "Error during reading configuration from %s\n", globbuf.gl_pathv[i]); 211 fclose(nfd); 212 free(strtmp); 213 free(dname); 214 globfree(&globbuf); 215 return CFG_RET_ERROR; 216 } 217 fclose(nfd); 218 free(strtmp); 219 free(dname); 220 } 221 globfree(&globbuf); 222 continue; 223 } 224 225 // Dynamic handlers 226 for(i = 0 ; i < ns->count ; i++) { 227 if (!strcasecmp(ns->h_name[i], dir->name)) { 228 dir = ns->h_fn[i](fd, dir, user); 229 if (cfg_error_occurred()) { 230 if (dir) cnf_directive_free(dir); 231 return CFG_RET_ERROR; 232 } 233 if (dir) goto cfg_parse_reparse; 234 break; 235 } 236 } 237 238 if (i == ns->count) { 239 // Here = not found 240 fprintf(stderr, "Unknown directive: %s\n", dir->name); 241 cnf_directive_free(dir); 242 return CFG_RET_ERROR; 243 } 244 } 245 246 // Cleanup here 247 if (dir) cnf_directive_free(dir); 248 249 return CFG_RET_OK; 250 } 251 252