linkd

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

daemon.c (9774B)


      1 #define _GNU_SOURCE
      2 #include <libgen.h>
      3 #include <stdio.h>
      4 #include <stdlib.h>
      5 #include <string.h>
      6 #include <strings.h>
      7 #include <unistd.h>
      8 
      9 #include "finwo/cnfparse.h"
     10 
     11 #include "config/daemon.h"
     12 #include "dataplane/plugin.h"
     13 #include "util/config.h"
     14 
     15 static struct linkd_daemon_cfg cfg;
     16 static int cfg_defaults_applied = 0;
     17 
     18 // ---------------------------------------------------------------------------
     19 // string-list helpers
     20 
     21 static int list_append(char ***list, size_t *n, const char *val) {
     22   char **grown = realloc(*list, sizeof(char *) * (*n + 1));
     23   if (!grown) return -1;
     24   *list = grown;
     25   (*list)[*n] = strdup(val);
     26   if (!(*list)[*n]) return -1;
     27   (*n)++;
     28   return 0;
     29 }
     30 
     31 static void list_free(char ***list, size_t *n) {
     32   for (size_t i = 0; i < *n; i++) free((*list)[i]);
     33   free(*list);
     34   *list = NULL;
     35   *n = 0;
     36 }
     37 
     38 // Replace rather than append: a later config_iface overrides, not merges.
     39 static struct cnf_directive *take_list(struct cnf_directive *dir, char ***list, size_t *n) {
     40   if (dir->argc < 1) {
     41     cfg_error("%s needs at least one path or glob", dir->name);
     42     cnf_directive_free(dir);
     43     return NULL;
     44   }
     45   list_free(list, n);
     46   for (size_t i = 0; i < dir->argc; i++) {
     47     if (list_append(list, n, dir->argv[i]) != 0) {
     48       cfg_error("out of memory reading %s", dir->name);
     49       cnf_directive_free(dir);
     50       return NULL;
     51     }
     52   }
     53   cnf_directive_free(dir);
     54   return NULL;
     55 }
     56 
     57 // ---------------------------------------------------------------------------
     58 // directives
     59 
     60 static struct cnf_directive *h_config_ports(FILE *fd, struct cnf_directive *dir, void *user) {
     61   (void)fd; (void)user;
     62   return take_list(dir, &cfg.ports, &cfg.ports_n);
     63 }
     64 
     65 static struct cnf_directive *h_config_iface(FILE *fd, struct cnf_directive *dir, void *user) {
     66   (void)fd; (void)user;
     67   return take_list(dir, &cfg.iface, &cfg.iface_n);
     68 }
     69 
     70 // Additive: listening on several addresses at once is the point.
     71 static struct cnf_directive *h_listen(FILE *fd, struct cnf_directive *dir, void *user) {
     72   (void)fd; (void)user;
     73   if (dir->argc != 1) {
     74     cfg_error("listen needs exactly one address, e.g. unix:///var/run/linkd.sock");
     75     cnf_directive_free(dir);
     76     return NULL;
     77   }
     78   const char *a = dir->argv[0];
     79   if (strncmp(a, "unix://", 7) && strncmp(a, "tcp://", 6)) {
     80     cfg_error("listen: unsupported address `%s' (expected unix:// or tcp://)", a);
     81     cnf_directive_free(dir);
     82     return NULL;
     83   }
     84   if (list_append(&cfg.listen, &cfg.listen_n, a) != 0) {
     85     cfg_error("out of memory reading listen");
     86   }
     87   cnf_directive_free(dir);
     88   return NULL;
     89 }
     90 
     91 // Not split on whitespace here: cnfparse already tokenised, so `dhcp_client
     92 // udhcpc -f -i {{interface}}` arrives as four argv elements and no shell is
     93 // ever involved in reassembling them.
     94 static struct cnf_directive *h_dhcp_client(FILE *fd, struct cnf_directive *dir, void *user) {
     95   (void)fd; (void)user;
     96   return take_list(dir, &cfg.dhcp_client, &cfg.dhcp_client_n);
     97 }
     98 
     99 static struct cnf_directive *h_authfile(FILE *fd, struct cnf_directive *dir, void *user) {
    100   (void)fd; (void)user;
    101   if (dir->argc != 1) {
    102     cfg_error("authfile needs exactly one path");
    103     cnf_directive_free(dir);
    104     return NULL;
    105   }
    106   free(cfg.authfile);
    107   cfg.authfile = strdup(dir->argv[0]);
    108   if (!cfg.authfile) cfg_error("out of memory reading authfile");
    109   cnf_directive_free(dir);
    110   return NULL;
    111 }
    112 
    113 // Opens a stanza. Known sub-directives handled here; the first unknown one
    114 // ends the stanza and is handed back for re-dispatch.
    115 static struct cnf_directive *h_plugin(FILE *fd, struct cnf_directive *dir, void *user) {
    116   (void)user;
    117 
    118   if (dir->argc != 1) {
    119     cfg_error("plugin needs exactly one executable path or tcp:// address");
    120     cnf_directive_free(dir);
    121     return NULL;
    122   }
    123 
    124   struct linkd_plugin_cfg *p = calloc(1, sizeof(*p));
    125   if (!p) {
    126     cfg_error("out of memory reading plugin");
    127     cnf_directive_free(dir);
    128     return NULL;
    129   }
    130   p->target = strdup(dir->argv[0]);
    131   if (!p->target) {
    132     free(p);
    133     cfg_error("out of memory reading plugin");
    134     cnf_directive_free(dir);
    135     return NULL;
    136   }
    137   cnf_directive_free(dir);
    138 
    139   // Append: plugins are broadcast to in declaration order.
    140   struct linkd_plugin_cfg **tail = &cfg.plugins;
    141   while (*tail) tail = &(*tail)->next;
    142   *tail = p;
    143 
    144   for (;;) {
    145     struct cnf_directive *sub = cnf_directive_read(fd);
    146     if (!sub) return NULL; // EOF ends the stanza
    147 
    148     if (!strcasecmp(sub->name, "optional")) {
    149       if (sub->argc != 0) {
    150         cfg_error("plugin %s: optional takes no arguments", p->target);
    151         cnf_directive_free(sub);
    152         return NULL;
    153       }
    154       p->optional = 1;
    155       cnf_directive_free(sub);
    156       continue;
    157     }
    158 
    159     // Unknown to us: the plugin gets first refusal. Starting it here is what
    160     // makes its config vocabulary discoverable at all -- CONFIG LIST cannot
    161     // be asked of a process that is not running.
    162     struct dp_plugin *dp = dp_plugin_for(p->target);
    163     if (dp && dp_plugin_config_supports(dp, sub->name)) {
    164       if (dp_plugin_config_set(dp, sub->name, sub->argv, sub->argc) != 0) {
    165         cfg_error("plugin %s rejected `%s'", p->target, sub->name);
    166         cnf_directive_free(sub);
    167         return NULL;
    168       }
    169       cnf_directive_free(sub);
    170       continue;
    171     }
    172 
    173     // Neither side claims it: ends the stanza, hand it back for re-dispatch.
    174     return sub;
    175   }
    176 }
    177 
    178 // ---------------------------------------------------------------------------
    179 
    180 void daemon_cfg_register(void) {
    181   struct cfg_ns *ns = cfg_ns_get("linkd");
    182   cfg_register_directive(ns, "config_ports", h_config_ports);
    183   cfg_register_directive(ns, "config_iface", h_config_iface);
    184   cfg_register_directive(ns, "listen",       h_listen);
    185   cfg_register_directive(ns, "authfile",     h_authfile);
    186   cfg_register_directive(ns, "dhcp_client",  h_dhcp_client);
    187   cfg_register_directive(ns, "plugin",       h_plugin);
    188 }
    189 
    190 // Per-field, so a config setting only `listen` still gets working paths.
    191 static void apply_defaults(void) {
    192   if (cfg_defaults_applied) return;
    193   cfg_defaults_applied = 1;
    194 
    195   if (cfg.ports_n == 0) {
    196     list_append(&cfg.ports, &cfg.ports_n, "/etc/network/ports");
    197     list_append(&cfg.ports, &cfg.ports_n, "/etc/network/ports.d/*.cnf");
    198   }
    199   if (cfg.iface_n == 0) {
    200     list_append(&cfg.iface, &cfg.iface_n, "/etc/network/interfaces");
    201     list_append(&cfg.iface, &cfg.iface_n, "/etc/network/interfaces.d/*.cnf");
    202   }
    203   if (cfg.listen_n == 0) {
    204     list_append(&cfg.listen, &cfg.listen_n, "unix:///var/run/linkd.sock");
    205   }
    206 }
    207 
    208 // `source` expands the glob, so a directory with no drop-ins is not an error.
    209 static int load_confdir(void) {
    210   static const char stub[] = "source " LINKD_CONFIG_DIR "/*.cnf\n";
    211 
    212   FILE *fd = fmemopen((void *)stub, sizeof(stub) - 1, "r");
    213   if (!fd) {
    214     perror("Could not open in-memory config");
    215     return CFG_RET_ERROR;
    216   }
    217 
    218   cfg_error_reset();
    219   int rc = cfg_parse(cfg_ns_get("linkd"), NULL, fd, NULL);
    220   fclose(fd);
    221   return rc;
    222 }
    223 
    224 int daemon_cfg_load(const char *path) {
    225   int explicit_path = path != NULL;
    226   if (!path) path = LINKD_CONFIG_PATH;
    227 
    228   if (access(path, R_OK) != 0) {
    229     int rc = CFG_RET_OK;
    230     if (!explicit_path) rc = load_confdir();
    231     apply_defaults();
    232     return rc;
    233   }
    234 
    235   FILE *fd = fopen(path, "r");
    236   if (!fd) {
    237     fprintf(stderr, "config: cannot open %s\n", path);
    238     return CFG_RET_ERROR;
    239   }
    240 
    241   // Relative `source` resolves against the config file's own directory.
    242   char *dup = strdup(path);
    243   char *wd  = dup ? dirname(dup) : NULL;
    244 
    245   cfg_error_reset();
    246   int rc = cfg_parse(cfg_ns_get("linkd"), wd, fd, NULL);
    247   fclose(fd);
    248   free(dup);
    249 
    250   if (rc != CFG_RET_OK) return rc;
    251 
    252   apply_defaults();
    253   return CFG_RET_OK;
    254 }
    255 
    256 int load_namespace(const char *ns_name, char **patterns, size_t n) {
    257   if (n == 0) return CFG_RET_OK;
    258 
    259   size_t len = strlen("source");
    260   for (size_t i = 0; i < n; i++) len += 1 + strlen(patterns[i]);
    261   len += 2; // newline + NUL
    262 
    263   char *stub = malloc(len);
    264   if (!stub) {
    265     fprintf(stderr, "config: out of memory building %s source list\n", ns_name);
    266     return CFG_RET_ERROR;
    267   }
    268 
    269   char *p = stub + sprintf(stub, "source");
    270   for (size_t i = 0; i < n; i++) p += sprintf(p, " %s", patterns[i]);
    271   *p++ = '\n';
    272   *p = '\0';
    273 
    274   FILE *fd = fmemopen(stub, strlen(stub), "r");
    275   if (!fd) {
    276     perror("Could not open in-memory config");
    277     free(stub);
    278     return CFG_RET_ERROR;
    279   }
    280 
    281   // wd NULL: patterns come from linkd.cnf and are expected to be absolute.
    282   cfg_error_reset();
    283   int rc = cfg_parse(cfg_ns_get(ns_name), NULL, fd, NULL);
    284   if (rc < 0) {
    285     fprintf(stderr, "config: error reading %s configuration\n", ns_name);
    286   }
    287 
    288   fclose(fd);
    289   free(stub);
    290   return rc;
    291 }
    292 
    293 const char *daemon_cfg_unix_socket(void) {
    294   apply_defaults();
    295   for (size_t i = 0; i < cfg.listen_n; i++) {
    296     if (!strncmp(cfg.listen[i], "unix://", 7)) return cfg.listen[i] + 7;
    297   }
    298   return NULL;
    299 }
    300 
    301 const char *linkd_client_socket(void) {
    302   static int loaded = 0;
    303   if (!loaded) {
    304     loaded = 1;
    305     daemon_cfg_register();
    306     // Failure is not fatal here: fall through to the default rather than
    307     // leaving the operator with no way to reach a running daemon.
    308     daemon_cfg_load(getenv("LINKD_CONFIG"));
    309   }
    310   const char *p = daemon_cfg_unix_socket();
    311   return p ? p : "/var/run/linkd.sock";
    312 }
    313 
    314 const struct linkd_daemon_cfg *daemon_cfg(void) {
    315   apply_defaults();
    316   return &cfg;
    317 }
    318 
    319 void daemon_cfg_free(void) {
    320   list_free(&cfg.ports,  &cfg.ports_n);
    321   list_free(&cfg.iface,  &cfg.iface_n);
    322   list_free(&cfg.listen, &cfg.listen_n);
    323   list_free(&cfg.dhcp_client, &cfg.dhcp_client_n);
    324   free(cfg.authfile);
    325   cfg.authfile = NULL;
    326 
    327   struct linkd_plugin_cfg *p = cfg.plugins;
    328   while (p) {
    329     struct linkd_plugin_cfg *next = p->next;
    330     free(p->target);
    331     free(p);
    332     p = next;
    333   }
    334   cfg.plugins = NULL;
    335   cfg_defaults_applied = 0;
    336 }