diff options
| author | David Lamparter <equinox@opensourcerouting.org> | 2024-01-25 18:09:17 +0100 |
|---|---|---|
| committer | David Lamparter <equinox@opensourcerouting.org> | 2024-01-27 19:01:19 +0100 |
| commit | cd35ecc575a7b4b1a7ad134ccb3c76011ff5a347 (patch) | |
| tree | ed248e858bb785c4e8cb728ac0dde24284b36b57 /lib/libfrr.c | |
| parent | a97d0c5875a562a16a9e3cbae03e615c16e47c87 (diff) | |
lib: create `frr_daemon_state_{load,save}`
These functions load daemon-specific persistent state from
`/var/lib/frr` and supersede open-coded variants of similar calls in
ospfd, ospf6d and isisd to save GR state and/or sequence numbers.
Unlike the open-coded variants, the save call correctly `fsync()`s the
saved data to ensure disk contents are consistent.
Signed-off-by: David Lamparter <equinox@opensourcerouting.org>
Diffstat (limited to 'lib/libfrr.c')
| -rw-r--r-- | lib/libfrr.c | 155 |
1 files changed, 155 insertions, 0 deletions
diff --git a/lib/libfrr.c b/lib/libfrr.c index 85a906a7bc..131453c4de 100644 --- a/lib/libfrr.c +++ b/lib/libfrr.c @@ -1272,6 +1272,161 @@ void frr_fini(void) } } +struct json_object *frr_daemon_state_load(void) +{ + struct json_object *state; + char **state_path; + + assertf(di->state_paths, + "CODE BUG: daemon trying to load state, but no state path in frr_daemon_info"); + + for (state_path = di->state_paths; *state_path; state_path++) { + state = json_object_from_file(*state_path); + if (state) + return state; + } + + return json_object_new_object(); +} + +/* cross-reference file_write_config() in command.c + * the code there is similar but not identical (configs use a unique temporary + * name for writing and keep a backup of the previous config.) + */ +void frr_daemon_state_save(struct json_object **statep) +{ + struct json_object *state = *statep; + char *state_path, *slash, *temp_name, **other; + size_t name_len, json_len; + const char *json_str; + int dirfd, fd; + + assertf(di->state_paths, + "CODE BUG: daemon trying to save state, but no state path in frr_daemon_info"); + + state_path = di->state_paths[0]; + json_str = json_object_to_json_string_ext(state, + JSON_C_TO_STRING_PRETTY); + json_len = strlen(json_str); + + /* To correctly fsync() and ensure we have either consistent old state + * or consistent new state but no fs-damage garbage inbetween, we need + * to work with a directory fd. If we need that anyway we might as + * well use the dirfd with openat() & co in fd-relative operations. + */ + + slash = strrchr(state_path, '/'); + if (slash) { + char *state_dir; + + state_dir = XSTRDUP(MTYPE_TMP, state_path); + state_dir[slash - state_path] = '\0'; + dirfd = open(state_dir, O_DIRECTORY | O_RDONLY); + XFREE(MTYPE_TMP, state_dir); + + if (dirfd < 0) { + zlog_err("failed to open directory %pSQq for saving daemon state: %m", + state_dir); + return; + } + + /* skip to file name */ + slash++; + } else { + dirfd = open(".", O_DIRECTORY | O_RDONLY); + if (dirfd < 0) { + zlog_err( + "failed to open current directory for saving daemon state: %m"); + return; + } + + /* file name = path */ + slash = state_path; + } + + /* unlike saving configs, a temporary unique filename is unhelpful + * here as it might litter files on limited write-heavy storage + * (think switch with small NOR flash for frequently written data.) + * + * => always use filename with .sav suffix, worst case it litters one + * file. + */ + name_len = strlen(slash); + temp_name = XMALLOC(MTYPE_TMP, name_len + 5); + memcpy(temp_name, slash, name_len); + memcpy(temp_name + name_len, ".sav", 5); + + /* state file is always 0600, it's by and for FRR itself only */ + fd = openat(dirfd, temp_name, O_WRONLY | O_CREAT | O_TRUNC, 0600); + if (fd < 0) { + zlog_err("failed to open temporary daemon state save file for %pSQq: %m", + state_path); + goto out_closedir_free; + } + + while (json_len) { + ssize_t nwr = write(fd, json_str, json_len); + + if (nwr <= 0) { + zlog_err("failed to write temporary daemon state to %pSQq: %m", + state_path); + + close(fd); + unlinkat(dirfd, temp_name, 0); + goto out_closedir_free; + } + + json_str += nwr; + json_len -= nwr; + } + + /* fsync is theoretically implicit in close(), but... */ + if (fsync(fd) < 0) + zlog_warn("fsync for daemon state %pSQq failed: %m", state_path); + close(fd); + + /* this is the *actual* fsync that ensures we're consistent. The + * file fsync only syncs the inode, but not the directory entry + * referring to it. + */ + if (fsync(dirfd) < 0) + zlog_warn("directory fsync for daemon state %pSQq failed: %m", + state_path); + + /* atomic, hopefully. */ + if (renameat(dirfd, temp_name, dirfd, slash) < 0) { + zlog_err("renaming daemon state %pSQq to %pSQq failed: %m", + temp_name, state_path); + /* no unlink here, give the user a chance to investigate */ + goto out_closedir_free; + } + + /* and the rename needs to be synced too */ + if (fsync(dirfd) < 0) + zlog_warn("directory fsync for daemon state %pSQq failed after rename: %m", + state_path); + + /* daemon may specify other deprecated paths to load from; since we + * just saved successfully we should delete those. + */ + for (other = di->state_paths + 1; *other; other++) { + if (unlink(*other) == 0) + continue; + if (errno == ENOENT || errno == ENOTDIR) + continue; + + zlog_warn("failed to remove deprecated daemon state file %pSQq: %m", + *other); + } + +out_closedir_free: + XFREE(MTYPE_TMP, temp_name); + close(dirfd); + + json_object_free(state); + *statep = NULL; +} + #ifdef INTERP static const char interp[] __attribute__((section(".interp"), used)) = INTERP; |
