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 }