diff --git a/commandline.c b/commandline.c index d10fc980..ba866729 100644 --- a/commandline.c +++ b/commandline.c @@ -627,9 +627,6 @@ int app_server_start(int argc, const char *const *argv, const struct command_lin streams, and start a new process session so that if we are being started by an adb shell session, then we don't receive a SIGHUP when the adb shell process ends. */ close_logging(); - - //TODO close config - int fd; if ((fd = open("/dev/null", O_RDWR, 0)) == -1) _exit(WHY_perror("open")); diff --git a/conf.c b/conf.c index 363df449..e198823f 100644 --- a/conf.c +++ b/conf.c @@ -32,12 +32,14 @@ struct file_meta { off_t size; }; +#define FILE_META_UNKNOWN ((struct file_meta){ .mtime = -1, .size = -1 }) + struct cf_om_node *cf_om_root = NULL; -static struct file_meta conffile_meta = { .mtime = -1, .size = -1 }; +static struct file_meta conffile_meta = FILE_META_UNKNOWN; int cf_limbo = 1; struct config_main config; -static struct file_meta config_meta = { .mtime = -1, .size = -1 }; +static struct file_meta config_meta = FILE_META_UNKNOWN; static const char *conffile_path() { @@ -53,6 +55,8 @@ static int get_meta(const char *path, struct file_meta *metap) if (stat(path, &st) == -1) { if (errno != ENOENT) return WHYF_perror("stat(%s)", path); + // Do not return FILE_META_UNKNOWN on ENOENT, otherwise reload logic breaks. A non-existent + // file is treated as size == 0. metap->size = 0; metap->mtime = -1; } else { @@ -62,29 +66,37 @@ static int get_meta(const char *path, struct file_meta *metap) return 0; } -static int load() +static int cmp_meta(const struct file_meta *a, const struct file_meta *b) +{ + return a->mtime < b->mtime ? -1 : a->mtime > b->mtime ? 1 : a->size < b->size ? -1 : a->size > b->size ? 1 : 0; +} + +static int reload(const char *path, int *resultp) { - const char *path = conffile_path(); struct file_meta meta; - if (get_meta(path, &meta) == -1) - return CFERROR; + if (get_meta(conffile_path(), &meta) == -1) + return -1; + if (cmp_meta(&meta, &conffile_meta) == 0) + return 0; + if (conffile_meta.mtime != -1) + INFOF("config file %s -- detected new version", conffile_path()); char *buf = NULL; if (meta.mtime == -1) INFOF("config file %s does not exist", path); else if (meta.size > CONFIG_FILE_MAX_SIZE) { WHYF("config file %s is too big (%ld bytes exceeds limit %ld)", path, meta.size, CONFIG_FILE_MAX_SIZE); - return CFERROR; + return -1; } else if (meta.size <= 0) { INFOF("config file %s is zero size", path); } else { FILE *f = fopen(path, "r"); if (f == NULL) { WHYF_perror("fopen(%s)", path); - return CFERROR; + return -1; } if ((buf = emalloc(meta.size)) == NULL) { fclose(f); - return CFERROR; + return -1; } if (fread(buf, meta.size, 1, f) != 1) { if (ferror(f)) @@ -93,60 +105,36 @@ static int load() WHYF("fread(%s, %llu) hit EOF", path, (unsigned long long) meta.size); free(buf); fclose(f); - return CFERROR; + return -1; } if (fclose(f) == EOF) { + WHYF_perror("fclose(%s)", path); free(buf); - return WHYF_perror("fclose(%s)", path); + return -1; } INFOF("config file %s successfully read %ld bytes", path, (long) meta.size); } + conffile_meta = meta; struct cf_om_node *new_root = NULL; - int result = cf_om_parse(path, buf, meta.size, &new_root); + *resultp = cf_om_parse(path, buf, meta.size, &new_root); free(buf); - if (result != CFERROR) { - cf_om_free_node(&cf_om_root); - cf_om_root = new_root; - conffile_meta = meta; - } - return result; + if (*resultp == CFERROR) + return -1; + cf_om_free_node(&cf_om_root); + cf_om_root = new_root; + return 1; } -static int has_changed(const struct file_meta *metap) +int cf_om_reload() { - const char *path = conffile_path(); - struct file_meta meta; - if (get_meta(path, &meta) == -1) - return -1; - return metap->size != meta.size || metap->mtime != meta.mtime; + int result; + return reload(conffile_path(), &result); } int cf_om_load() { - return load() == CFERROR ? -1 : 0; -} - -/* Check if the config file has changed since we last read it, and if so, invalidate the buffer so - * that the next call to read_config() will re-load it. Returns 1 if the buffer was invalidated, 0 - * if not, -1 on error. - * - * TODO: when the config system is overhauled to provide proper dynamic config reloading in JNI and - * in the servald daemon, this method will become unnecessary. - * - * @author Andrew Bettison - */ -int cf_om_reload() -{ - switch (has_changed(&conffile_meta)) { - case -1: - return CFERROR; - case 0: - return CFOK; - default: - if (conffile_meta.mtime != -1) - INFOF("config file %s -- detected new version", conffile_path()); - return cf_om_load(); - } + conffile_meta = FILE_META_UNKNOWN; + return cf_om_reload(); } int cf_om_save() @@ -165,6 +153,7 @@ int cf_om_save() fprintf(outf, "%s=%s\n", it.node->fullkey, it.node->text); if (fclose(outf) == EOF) return WHYF_perror("fclose(%s)", tempfile); + // rename(2) is atomic, so no other process will read a half-written file. if (rename(tempfile, path)) { WHYF_perror("rename(%s, %s)", tempfile, path); unlink(tempfile); @@ -182,69 +171,66 @@ int cf_om_save() int cf_init() { cf_limbo = 1; + conffile_meta = config_meta = FILE_META_UNKNOWN; + memset(&config, 0, sizeof config); if (cf_dfl_config_main(&config) == CFERROR) return -1; return 0; } -static int load_and_parse(int permissive) +static int reload_and_parse(int permissive) { int result = CFOK; if (cf_limbo) result = cf_dfl_config_main(&config); - if (result == CFOK) { - result = load(); - if (result == CFOK || result == CFEMPTY) { - result = CFOK; - struct config_main new_config; - memset(&new_config, 0, sizeof new_config); - result = cf_dfl_config_main(&new_config); - if (result == CFOK) { - result = cf_om_root ? cf_opt_config_main(&new_config, cf_om_root) : CFEMPTY; + if (result == CFOK || result == CFEMPTY) { + if (reload(conffile_path(), &result) == -1) + result = CFERROR; + else if (!cf_limbo && cmp_meta(&conffile_meta, &config_meta) == 0) + return 0; + else { + config_meta = conffile_meta; + if (result == CFOK || result == CFEMPTY) { + struct config_main new_config; + memset(&new_config, 0, sizeof new_config); + result = cf_dfl_config_main(&new_config); if (result == CFOK || result == CFEMPTY) { - result = CFOK; - config = new_config; - config_meta = conffile_meta; - cf_limbo = 0; - logFlush(); - } else if (result != CFERROR) { - result &= ~CFEMPTY; - config = new_config; - WARN("limping along with incomplete configuration"); - cf_limbo = 0; + result = cf_om_root ? cf_opt_config_main(&new_config, cf_om_root) : CFEMPTY; + if (result == CFOK || result == CFEMPTY) { + result = CFOK; + config = new_config; + } else if (result != CFERROR) { + result &= ~CFEMPTY; + config = new_config; + WARN("limping along with incomplete configuration"); + } } } } } - if (result == CFOK) - return 0; - cf_limbo = 0; // let log messages out - strbuf b = strbuf_alloca(180); - strbuf_cf_flag_reason(b, result); - if (!permissive) - return WHYF("config file %s not loaded -- %s", conffile_path(), strbuf_str(b)); - WARNF("config file %s loaded despite problems -- %s", conffile_path(), strbuf_str(b)); - return 0; -} - -static int reload_and_parse(int permissive) -{ - if (!cf_limbo && cf_om_root) { - if (!has_changed(&config_meta)) - return 0; - INFOF("config file %s reloading", conffile_path()); + // Let log messages out. + cf_limbo = 0; + logFlush(); + if (result != CFOK) { + strbuf b = strbuf_alloca(180); + strbuf_cf_flag_reason(b, result); + if (!permissive) + return WHYF("config file %s not loaded -- %s", conffile_path(), strbuf_str(b)); + WARNF("config file %s loaded despite problems -- %s", conffile_path(), strbuf_str(b)); } - return load_and_parse(permissive); + return 1; } int cf_load() { - return load_and_parse(0); + conffile_meta = config_meta = FILE_META_UNKNOWN; + return reload_and_parse(0); } int cf_load_permissive() { - return load_and_parse(1); + conffile_meta = config_meta = FILE_META_UNKNOWN; + return reload_and_parse(1); } int cf_reload() diff --git a/overlay.c b/overlay.c index cf3ce722..83923a2c 100644 --- a/overlay.c +++ b/overlay.c @@ -117,6 +117,9 @@ schedule(&_sched_##X); } /* Periodically check for server shut down */ SCHEDULE(server_shutdown_check, 0, 100); + /* Periodically reload configuration */ + SCHEDULE(server_config_reload, SERVER_CONFIG_RELOAD_INTERVAL_MS, SERVER_CONFIG_RELOAD_INTERVAL_MS + 100); + /* Setup up MDP & monitor interface unix domain sockets */ overlay_mdp_setup_sockets(); monitor_setup_sockets(); diff --git a/serval.h b/serval.h index 4d0602f2..392336f5 100644 --- a/serval.h +++ b/serval.h @@ -164,6 +164,8 @@ int create_serval_instance_dir(); int form_serval_instance_path(char *buf, size_t bufsiz, const char *path); void serval_setinstancepath(const char *instancepath); +#define SERVER_CONFIG_RELOAD_INTERVAL_MS 1000 + extern int serverMode; extern int servalShutdown; @@ -710,6 +712,7 @@ int fd_poll(); void overlay_interface_discover(struct sched_ent *alarm); void overlay_dummy_poll(struct sched_ent *alarm); void overlay_route_tick(struct sched_ent *alarm); +void server_config_reload(struct sched_ent *alarm); void server_shutdown_check(struct sched_ent *alarm); void overlay_mdp_poll(struct sched_ent *alarm); int overlay_mdp_try_interal_services(overlay_mdp_frame *mdp); diff --git a/server.c b/server.c index 57c1b5cd..e094849d 100644 --- a/server.c +++ b/server.c @@ -87,8 +87,8 @@ void server_save_argv(int argc, const char *const *argv) int server(char *backing_file) { - /* For testing, it can be very helpful to delay the start of the server - process, for example to check that the start/stop logic is robust. + /* For testing, it can be very helpful to delay the start of the server process, for example to + * check that the start/stop logic is robust. */ const char *delay = getenv("SERVALD_SERVER_START_DELAY"); if (delay) @@ -125,8 +125,7 @@ int server(char *backing_file) FILE *f=fopen(filename,"w"); if (!f) { WHY_perror("fopen"); - WHYF("Could not write to PID file %s", filename); - return -1; + return WHYF("Could not write to PID file %s", filename); } server_getpid = getpid(); fprintf(f,"%d\n", server_getpid); @@ -137,6 +136,28 @@ int server(char *backing_file) return 0; } +/* Called periodically by the server process in its main loop. + */ +void server_config_reload(struct sched_ent *alarm) +{ + switch (cf_reload()) { + case -1: + WARN("server continuing with prior config"); + break; + case 0: + break; + default: + INFO("server config successfully reloaded"); + break; + } + if (alarm) { + time_ms_t now = gettime_ms(); + alarm->alarm = now + SERVER_CONFIG_RELOAD_INTERVAL_MS; + alarm->deadline = alarm->alarm + 1000; + schedule(alarm); + } +} + /* Called periodically by the server process in its main loop. */ void server_shutdown_check(struct sched_ent *alarm)