linkd

Control plane daemon for unos
git clone git://git.finwo.net/app/linkd
Log | Files | Refs | README

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