diff --git a/keyring.c b/keyring.c index cdd64f5d..a116fe73 100644 --- a/keyring.c +++ b/keyring.c @@ -1502,10 +1502,11 @@ static keyring_identity *keyring_new_identity() keyring_identity *keyring_inmemory_identity(){ keyring_identity *id = keyring_new_identity(); - keyring_finalise_identity(NULL, id); - if (id) + if (id) { + keyring_finalise_identity(NULL, id); add_subscriber(id); - INFOF("created in-memory identity SID=%s", alloca_tohex_sid_t(id->subscriber->sid)); + INFOF("created in-memory identity SID=%s", alloca_tohex_sid_t(id->subscriber->sid)); + } return id; } diff --git a/overlay_address.c b/overlay_address.c index abfc638b..908fa728 100644 --- a/overlay_address.c +++ b/overlay_address.c @@ -1,5 +1,6 @@ /* Serval DNA MDP addressing +Copyright (C) 2016-2018 Flinders University Copyright (C) 2012-2015 Serval Project Inc. Copyright (C) 2012 Paul Gardner-Stephen @@ -28,6 +29,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. #include #include +#include "lang.h" // for bool_t #include "serval.h" #include "conf.h" #include "keyring.h" @@ -54,37 +56,64 @@ static struct broadcast bpilist[MAX_BPIS]; static __thread struct tree_root root={.index_size_bytes=SID_SIZE}; -static __thread struct subscriber *my_subscriber=NULL; +static __thread bool_t primary_sid_written = 0; +static __thread struct subscriber *primary_sid = NULL; +static __thread struct subscriber *my_subscriber = NULL; -struct subscriber *get_my_subscriber(bool_t create){ +struct subscriber *get_my_subscriber(bool_t create) +{ if (!serverMode) return NULL; - assert(keyring != NULL); if (my_subscriber && my_subscriber->reachable != REACHABLE_SELF) my_subscriber = NULL; - if (!my_subscriber){ - keyring_identity *id = keyring->identities; - while(id){ - if (id->subscriber->reachable == REACHABLE_SELF){ + // Look for a reachable self-identity in the keyring. + if (!my_subscriber) { + assert(keyring != NULL); + keyring_identity *id; + for (id = keyring->identities; id; id = id->next) + if (id->subscriber->reachable == REACHABLE_SELF) { my_subscriber = id->subscriber; - return my_subscriber; + break; } - id = id->next; - } - // If there is no reachable self-identity in the keyring, then roll one in-memory, which will - // persist until the server terminates. - if (create){ - id = keyring_inmemory_identity(); + } + // If there is no reachable self-identity in the keyring, then roll one in-memory, which will + // persist until the server terminates. + if (!my_subscriber && create) { + keyring_identity *id = keyring_inmemory_identity(); + if (id) my_subscriber = id->subscriber; + } + // Normally, the server creates files in proc/ before it creates its pidfile, and does not modify + // those files while running. This avoids any race conditions with other processes that read the + // files. In this case, the proc/primary_sid file is written _after_ the pidfile, and could + // potentially be re-written while running (eg, if the primary identity is deleted, causing the + // server to choose another). Potential race conditions are avoided because (1) the size of the + // file never alters, and (2) the server_write_proc_state() function overwrites any existing + // content using a single, indivisible write(2) system call. + if (!primary_sid_written || primary_sid != my_subscriber) { + primary_sid = my_subscriber; + if (my_subscriber) { + const char *sidhex = alloca_tohex_sid_t(primary_sid->sid); + server_write_proc_state("primary_sid", "%s", sidhex); + INFOF("PRIMARY IDENTITY sid=%s", sidhex); } + else { + server_unlink_proc_state("primary_sid"); + INFOF("NO PRIMARY IDENTITY"); + } + primary_sid_written = 1; } return my_subscriber; } -void release_my_subscriber(){ +void release_my_subscriber() +{ if (my_subscriber && my_subscriber->identity->slot==0) keyring_free_identity(my_subscriber->identity); + server_unlink_proc_state("primary_sid"); my_subscriber = NULL; + primary_sid = NULL; + primary_sid_written = 0; } static int free_node(void **record, void *UNUSED(context)) @@ -98,6 +127,8 @@ static int free_node(void **record, void *UNUSED(context)) alloca_tohex_sid_t(subscriber->sid), subscriber->sync_state); if (subscriber->identity) FATAL("Can't free a subscriber that is unlocked in the keyring"); + if (subscriber == my_subscriber) + FATAL("Can't free a subscriber that is the primary identity"); free(subscriber); *record=NULL; return 0; diff --git a/server.c b/server.c index 9311f9eb..b0d9e49b 100644 --- a/server.c +++ b/server.c @@ -77,7 +77,6 @@ static void serverCleanUp(); static const char *_server_pidfile_path(struct __sourceloc __whence); #define server_pidfile_path() (_server_pidfile_path(__WHENCE__)) -static int server_write_proc_state(const char *path, const char *fmt, ...); static int server_get_proc_state(const char *path, char *buff, size_t buff_len); static void server_stop_alarms(); @@ -343,6 +342,9 @@ static int server_write_pid() server_write_proc_state("http_port", "%d", httpd_server_port); server_write_proc_state("mdp_inet_port", "%d", mdp_loopback_port); + // Create or unlink the "primary_sid" proc state file. + get_my_subscriber(0); + // Create a locked pidfile to advertise that the server is now running. const char *pidfile_path = server_pidfile_path(); if (pidfile_path == NULL) @@ -452,29 +454,46 @@ static int get_proc_path(const char *path, char *buf, size_t bufsiz) return 0; } -static int server_write_proc_state(const char *path, const char *fmt, ...) +int server_write_proc_state(const char *path, const char *fmt, ...) { char path_buf[400]; if (get_proc_path(path, path_buf, sizeof path_buf)==-1) return -1; + // Create the directory that contains the path, if it does not already exist. size_t dirsiz = strlen(path_buf) + 1; char dir_buf[dirsiz]; strcpy(dir_buf, path_buf); const char *dir = dirname(dir_buf); // modifies dir_buf[] - if (mkdirs_info(dir, 0700) == -1) - return WHY_perror("mkdirs()"); + if (emkdirs_info(dir, 0700) == -1) + return -1; + + // Format the file's new content in a local buffer on the stack. + strbuf sb; + STRBUF_ALLOCA_FIT(sb, 1024, strbuf_va_printf(sb, fmt)); - FILE *f = fopen(path_buf, "w"); - if (!f) - return WHY_perror("fopen()"); - - va_list ap; - va_start(ap, fmt); - vfprintf(f, fmt, ap); - va_end(ap); - - fclose(f); + // Overwrite the file, creating it if necessary, using a single write(2) system call, followed by + // a ftruncate(2) system call (in case the file already existed and was longer than its new + // content). This allows potential race conditions to be avoided for files that are overwritten + // while the server runs, as long as the written contents are always the same size, or always + // contain a terminating sequence (eg, newline). + int fd = open(path_buf, O_CREAT | O_WRONLY, 0700); + if (fd == -1) + return WHYF_perror("open(%s, O_CREAT|O_WRONLY, 0700)", alloca_str_toprint(path_buf)); + int ret = write_all(fd, strbuf_str(sb), strbuf_len(sb)); + if (ret != -1 && (ret = ftruncate(fd, strbuf_len(sb)) == -1)) + ret = WHYF_perror("ftruncate(%s, %zu)", alloca_str_toprint(path_buf), strbuf_len(sb)); + close(fd); + return ret; +} + +int server_unlink_proc_state(const char *path) +{ + char path_buf[400]; + if (get_proc_path(path, path_buf, sizeof path_buf)==-1) + return -1; + if (unlink(path) == -1 && errno != ENOENT) + return WHYF_perror("unlink(%s)", alloca_str_toprint(path)); return 0; } @@ -483,16 +502,20 @@ static int server_get_proc_state(const char *path, char *buff, size_t buff_len) char path_buf[400]; if (get_proc_path(path, path_buf, sizeof path_buf)==-1) return -1; - FILE *f = fopen(path_buf, "r"); - if (!f) - return -1; - - int ret=0; - - if (!fgets(buff, buff_len, f)) - ret = WHY_perror("fgets"); - + if (!f) { + if (errno != ENOENT) + return WHYF_perror("fopen(%s)", alloca_str_toprint(path_buf)); + return 1; + } + int ret = 0; + errno = 0; // fgets() does not set errno on end-of-file + if (!fgets(buff, buff_len, f)) { + if (errno) + ret = WHYF_perror("fgets from %s", alloca_str_toprint(path_buf)); + else + ret = 1; + } fclose(f); return ret; } @@ -788,11 +811,15 @@ static void cli_server_details(struct cli_context *context, const struct pid_tid cli_put_long(context, id->tid, "\n"); } char buff[256]; - if (server_get_proc_state("http_port", buff, sizeof buff)!=-1){ + if (server_get_proc_state("primary_sid", buff, sizeof buff) == 0){ + cli_field_name(context, "primary_sid", ":"); + cli_put_string(context, buff, "\n"); + } + if (server_get_proc_state("http_port", buff, sizeof buff) == 0){ cli_field_name(context, "http_port", ":"); cli_put_string(context, buff, "\n"); } - if (server_get_proc_state("mdp_inet_port", buff, sizeof buff)!=-1){ + if (server_get_proc_state("mdp_inet_port", buff, sizeof buff) == 0){ cli_field_name(context, "mdp_inet_port", ":"); cli_put_string(context, buff, "\n"); } diff --git a/server.h b/server.h index ea5bfa87..9cb085c1 100644 --- a/server.h +++ b/server.h @@ -49,6 +49,9 @@ void server_close(); void server_loop(time_ms_t (*waiting)(time_ms_t, time_ms_t, time_ms_t), void (*wokeup)()); +int server_write_proc_state(const char *path, const char *fmt, ...); +int server_unlink_proc_state(const char *path); + void server_rhizome_add_bundle(uint64_t rowid); DECLARE_TRIGGER(shutdown); diff --git a/testdefs.sh b/testdefs.sh index 4d8d9bde..55223e2b 100644 --- a/testdefs.sh +++ b/testdefs.sh @@ -421,6 +421,22 @@ servald_start() { SERVALD_SERVER_CHDIR="$instance_dir" SERVALD_LOG_FILE="$instance_servald_log" $servald start "$@" } +# Utility function: +# - fetch the daemon's primary SID +get_servald_primary_sid() { + local _instance="$2" + [ -z "$_instance" ] || push_and_set_instance $_instance || return $? + local _var="$1" + local _sid=$(<"$SERVALINSTANCE_PATH/proc/primary_sid") + assert --message="instance $instance_name primary SID is known" [ -n "$_sid" ] + if [ -n "$_var" ]; then + eval "$_var=\$_sid" + tfw_log "$_var=$_sid" + fi + [ -z "$_instance" ] || pop_instance + return 0 +} + # Utility function: # - test whether the daemon's HTTP server has started servald_http_server_started() {