linkd

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

daemon.c (8811B)


      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 static struct cnf_directive *h_authfile(FILE *fd, struct cnf_directive *dir, void *user) {
     92   (void)fd; (void)user;
     93   if (dir->argc != 1) {
     94     cfg_error("authfile needs exactly one path");
     95     cnf_directive_free(dir);
     96     return NULL;
     97   }
     98   free(cfg.authfile);
     99   cfg.authfile = strdup(dir->argv[0]);
    100   if (!cfg.authfile) cfg_error("out of memory reading authfile");
    101   cnf_directive_free(dir);
    102   return NULL;
    103 }
    104 
    105 // Opens a stanza. Known sub-directives handled here; the first unknown one
    106 // ends the stanza and is handed back for re-dispatch.
    107 static struct cnf_directive *h_plugin(FILE *fd, struct cnf_directive *dir, void *user) {
    108   (void)user;
    109 
    110   if (dir->argc != 1) {
    111     cfg_error("plugin needs exactly one executable path or tcp:// address");
    112     cnf_directive_free(dir);
    113     return NULL;
    114   }
    115 
    116   struct linkd_plugin_cfg *p = calloc(1, sizeof(*p));
    117   if (!p) {
    118     cfg_error("out of memory reading plugin");
    119     cnf_directive_free(dir);
    120     return NULL;
    121   }
    122   p->target = strdup(dir->argv[0]);
    123   if (!p->target) {
    124     free(p);
    125     cfg_error("out of memory reading plugin");
    126     cnf_directive_free(dir);
    127     return NULL;
    128   }
    129   cnf_directive_free(dir);
    130 
    131   // Append: plugins are broadcast to in declaration order.
    132   struct linkd_plugin_cfg **tail = &cfg.plugins;
    133   while (*tail) tail = &(*tail)->next;
    134   *tail = p;
    135 
    136   for (;;) {
    137     struct cnf_directive *sub = cnf_directive_read(fd);
    138     if (!sub) return NULL; // EOF ends the stanza
    139 
    140     if (!strcasecmp(sub->name, "optional")) {
    141       if (sub->argc != 0) {
    142         cfg_error("plugin %s: optional takes no arguments", p->target);
    143         cnf_directive_free(sub);
    144         return NULL;
    145       }
    146       p->optional = 1;
    147       cnf_directive_free(sub);
    148       continue;
    149     }
    150 
    151     // Unknown to us: the plugin gets first refusal. Starting it here is what
    152     // makes its config vocabulary discoverable at all -- CONFIG LIST cannot
    153     // be asked of a process that is not running.
    154     struct dp_plugin *dp = dp_plugin_for(p->target);
    155     if (dp && dp_plugin_config_supports(dp, sub->name)) {
    156       if (dp_plugin_config_set(dp, sub->name, sub->argv, sub->argc) != 0) {
    157         cfg_error("plugin %s rejected `%s'", p->target, sub->name);
    158         cnf_directive_free(sub);
    159         return NULL;
    160       }
    161       cnf_directive_free(sub);
    162       continue;
    163     }
    164 
    165     // Neither side claims it: ends the stanza, hand it back for re-dispatch.
    166     return sub;
    167   }
    168 }
    169 
    170 // ---------------------------------------------------------------------------
    171 
    172 void daemon_cfg_register(void) {
    173   struct cfg_ns *ns = cfg_ns_get("linkd");
    174   cfg_register_directive(ns, "config_ports", h_config_ports);
    175   cfg_register_directive(ns, "config_iface", h_config_iface);
    176   cfg_register_directive(ns, "listen",       h_listen);
    177   cfg_register_directive(ns, "authfile",     h_authfile);
    178   cfg_register_directive(ns, "plugin",       h_plugin);
    179 }
    180 
    181 // Per-field, so a config setting only `listen` still gets working paths.
    182 static void apply_defaults(void) {
    183   if (cfg_defaults_applied) return;
    184   cfg_defaults_applied = 1;
    185 
    186   if (cfg.ports_n == 0) {
    187     list_append(&cfg.ports, &cfg.ports_n, "/etc/network/ports");
    188     list_append(&cfg.ports, &cfg.ports_n, "/etc/network/ports.d/*.cnf");
    189   }
    190   if (cfg.iface_n == 0) {
    191     list_append(&cfg.iface, &cfg.iface_n, "/etc/network/interfaces");
    192     list_append(&cfg.iface, &cfg.iface_n, "/etc/network/interfaces.d/*.cnf");
    193   }
    194   if (cfg.listen_n == 0) {
    195     list_append(&cfg.listen, &cfg.listen_n, "unix:///var/run/linkd.sock");
    196   }
    197 }
    198 
    199 int daemon_cfg_load(const char *path) {
    200   if (!path) path = LINKD_CONFIG_PATH;
    201 
    202   // Missing file is fine: defaults are a working configuration.
    203   if (access(path, R_OK) != 0) {
    204     apply_defaults();
    205     return CFG_RET_OK;
    206   }
    207 
    208   FILE *fd = fopen(path, "r");
    209   if (!fd) {
    210     fprintf(stderr, "config: cannot open %s\n", path);
    211     return CFG_RET_ERROR;
    212   }
    213 
    214   // Relative `source` resolves against the config file's own directory.
    215   char *dup = strdup(path);
    216   char *wd  = dup ? dirname(dup) : NULL;
    217 
    218   cfg_error_reset();
    219   int rc = cfg_parse(cfg_ns_get("linkd"), wd, fd, NULL);
    220   fclose(fd);
    221   free(dup);
    222 
    223   if (rc != CFG_RET_OK) return rc;
    224 
    225   apply_defaults();
    226   return CFG_RET_OK;
    227 }
    228 
    229 int load_namespace(const char *ns_name, char **patterns, size_t n) {
    230   if (n == 0) return CFG_RET_OK;
    231 
    232   size_t len = strlen("source");
    233   for (size_t i = 0; i < n; i++) len += 1 + strlen(patterns[i]);
    234   len += 2; // newline + NUL
    235 
    236   char *stub = malloc(len);
    237   if (!stub) {
    238     fprintf(stderr, "config: out of memory building %s source list\n", ns_name);
    239     return CFG_RET_ERROR;
    240   }
    241 
    242   char *p = stub + sprintf(stub, "source");
    243   for (size_t i = 0; i < n; i++) p += sprintf(p, " %s", patterns[i]);
    244   *p++ = '\n';
    245   *p = '\0';
    246 
    247   FILE *fd = fmemopen(stub, strlen(stub), "r");
    248   if (!fd) {
    249     perror("Could not open in-memory config");
    250     free(stub);
    251     return CFG_RET_ERROR;
    252   }
    253 
    254   // wd NULL: patterns come from linkd.cnf and are expected to be absolute.
    255   cfg_error_reset();
    256   int rc = cfg_parse(cfg_ns_get(ns_name), NULL, fd, NULL);
    257   if (rc < 0) {
    258     fprintf(stderr, "config: error reading %s configuration\n", ns_name);
    259   }
    260 
    261   fclose(fd);
    262   free(stub);
    263   return rc;
    264 }
    265 
    266 const char *daemon_cfg_unix_socket(void) {
    267   apply_defaults();
    268   for (size_t i = 0; i < cfg.listen_n; i++) {
    269     if (!strncmp(cfg.listen[i], "unix://", 7)) return cfg.listen[i] + 7;
    270   }
    271   return NULL;
    272 }
    273 
    274 const char *linkd_client_socket(void) {
    275   static int loaded = 0;
    276   if (!loaded) {
    277     loaded = 1;
    278     daemon_cfg_register();
    279     // Failure is not fatal here: fall through to the default rather than
    280     // leaving the operator with no way to reach a running daemon.
    281     daemon_cfg_load(getenv("LINKD_CONFIG"));
    282   }
    283   const char *p = daemon_cfg_unix_socket();
    284   return p ? p : "/var/run/linkd.sock";
    285 }
    286 
    287 const struct linkd_daemon_cfg *daemon_cfg(void) {
    288   apply_defaults();
    289   return &cfg;
    290 }
    291 
    292 void daemon_cfg_free(void) {
    293   list_free(&cfg.ports,  &cfg.ports_n);
    294   list_free(&cfg.iface,  &cfg.iface_n);
    295   list_free(&cfg.listen, &cfg.listen_n);
    296   free(cfg.authfile);
    297   cfg.authfile = NULL;
    298 
    299   struct linkd_plugin_cfg *p = cfg.plugins;
    300   while (p) {
    301     struct linkd_plugin_cfg *next = p->next;
    302     free(p->target);
    303     free(p);
    304     p = next;
    305   }
    306   cfg.plugins = NULL;
    307   cfg_defaults_applied = 0;
    308 }