mirror of
https://github.com/kernelkit/infix.git
synced 2026-07-22 01:13:00 +02:00
confd: replace sysrepo-plugind + bootstrap + load with single daemon
On single-core Cortex-A7, the YANG bootstrap and config loading is the dominant boot bottleneck. The current sequence spawns three serial phases (bootstrap, sysrepo-plugind, load), each performing independent sr_connect()/sr_disconnect() cycles, and every sysrepoctl/sysrepocfg invocation is a fork+exec that rebuilds SHM from scratch. Replace all three with a single confd binary that does one sr_connect() and performs all datastore operations in-process: - Wipe stale /dev/shm/sr_* for a clean slate - sr_install_factory_config() from the generated JSON - Smart migration: compare config version via libjansson, only fork+exec the migrate script when versions actually differ - Load startup-config (or test-config) via lyd_parse_data() + sr_replace_config(), mirroring what sysrepocfg -I does internally - On failure: revert to factory-default, load failure-config, set login banners (Fail Secure mode) - On first boot: copy factory-default to running, export to file - dlopen plugins and enter event loop The bootstrap shell script is split: config generation (gen-hostname, gen-interfaces, etc.) stays in the new gen-config script, while all sysrepo operations move into the C daemon. The finit boot sequence collapses from 5 stanzas to 2 (gen-config -> confd). Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
@@ -1,20 +0,0 @@
|
||||
#!/bin/sh
|
||||
# Check if /cfg/startup-config.cfg needs to be migrated to new syntax.
|
||||
# Backup of the original is created in /cfg/backup/ for old versions,
|
||||
# the migrate tool inserts old version in name before .cfg extension.
|
||||
CONFIG_FILE="/cfg/startup-config.cfg"
|
||||
BACKUP_FILE="/cfg/backup/startup-config.cfg"
|
||||
BACKUP_DIR="$(dirname "$BACKUP_FILE")"
|
||||
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
chown root:wheel "$BACKUP_DIR"
|
||||
chmod 0770 "$BACKUP_DIR"
|
||||
|
||||
if [ ! -f "$CONFIG_FILE" ]; then
|
||||
logger -I $$ -k -p user.notice -t $(basename "$0") "No $(basename "$CONFIG_FILE" .cfg) yet, likely factory reset."
|
||||
exit 0
|
||||
elif migrate -cq "$CONFIG_FILE"; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
migrate -i -b "$BACKUP_FILE" "$CONFIG_FILE"
|
||||
+11
-19
@@ -1,27 +1,19 @@
|
||||
#set DEBUG=1
|
||||
|
||||
# Phase 1: Generate factory/failure/test configs (shell)
|
||||
run name:bootstrap log:prio:user.notice norestart <usr/ixinit> \
|
||||
[S] /usr/libexec/confd/bootstrap \
|
||||
-- Bootstrapping YANG datastore
|
||||
[S] /usr/libexec/confd/gen-config \
|
||||
-- Generating factory configuration
|
||||
|
||||
run name:error :1 log:console norestart if:<run/bootstrap/failure> \
|
||||
[S] /usr/libexec/confd/error --
|
||||
|
||||
service name:confd log:prio:daemon.err <run/bootstrap/success> \
|
||||
[S12345] sysrepo-plugind -f -p /run/confd.pid -n -v warning \
|
||||
# Phase 2: Single daemon handles datastore init, config load, and plugins
|
||||
# log:prio:daemon.err
|
||||
service name:confd <run/bootstrap/success> env:/etc/default/confd \
|
||||
[S12345] confd -f -n -v warning -p /run/confd.pid \
|
||||
-F /etc/factory-config.cfg \
|
||||
-S /cfg/startup-config.cfg \
|
||||
-E /etc/failure-config.cfg \
|
||||
-t $CONFD_TIMEOUT \
|
||||
-- Configuration daemon
|
||||
|
||||
# Bootstrap system with startup-config
|
||||
run name:startup log:prio:user.notice norestart <pid/confd> env:/etc/default/confd \
|
||||
[S] /usr/libexec/confd/load -t $CONFD_TIMEOUT startup-config \
|
||||
-- Loading startup-config
|
||||
|
||||
# Run if loading startup-config fails for some reason
|
||||
run name:failure log:prio:user.crit norestart env:/etc/default/confd \
|
||||
if:<run/startup/failure> \
|
||||
[S] <pid/confd> /usr/libexec/confd/load -t $CONFD_TIMEOUT failure-config \
|
||||
-- Loading failure-config
|
||||
|
||||
run name:error :2 log:console norestart \
|
||||
if:<run/failure/failure> \
|
||||
[S] /usr/libexec/confd/error --
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
pkglibexec_SCRIPTS = bootstrap error load gen-hostname \
|
||||
gen-interfaces gen-motd gen-hardware gen-version \
|
||||
mstpd-wait-online wait-interface
|
||||
pkglibexec_SCRIPTS = gen-hostname gen-interfaces gen-motd gen-hardware \
|
||||
gen-version mstpd-wait-online wait-interface
|
||||
sbin_SCRIPTS = dagger migrate firewall
|
||||
|
||||
@@ -1,42 +1,32 @@
|
||||
#!/bin/sh
|
||||
# Bootstrap system factory-config, failure-config, test-config and sysrepo db.
|
||||
# Generate factory-config, failure-config, and test-config files.
|
||||
#
|
||||
########################################################################
|
||||
# The system factory-config, failure-config and test-config are derived
|
||||
# from default settings snippets, from /usr/share/confd/factory.d, and
|
||||
# some generated snippets, e.g., hostname (based on base MAC address)
|
||||
# and number of interfaces.
|
||||
# These configs are derived from default settings snippets in
|
||||
# /usr/share/confd/factory.d, and generated snippets (e.g., hostname
|
||||
# based on base MAC address, number of interfaces).
|
||||
#
|
||||
# The resulting factory-config is used to create the syrepo db (below)
|
||||
# {factory} datastore. Hence, the factory-config file must match the
|
||||
# the YANG models of the active image.
|
||||
# The sysrepo datastore operations (loading factory defaults, startup
|
||||
# config, migration) are handled by the confd daemon.
|
||||
########################################################################
|
||||
# NOTE: with the Infix defaults, a br2-external can provide a build-time
|
||||
# /etc/factory-config.cfg to override the behavior of this script.
|
||||
#
|
||||
# This applies also for /etc/failure-config.cfg, but we recommend
|
||||
# strongly that you instead provide gen-err-custom, see below.
|
||||
#
|
||||
# TODO: Look for statically defined factory-config, based on system's
|
||||
# product ID, or just custom site-specific factory on /cfg.
|
||||
########################################################################
|
||||
STATUS=""
|
||||
|
||||
# Log functions
|
||||
critical()
|
||||
{
|
||||
logger -i -p user.crit -t bootstrap "$1" 2>/dev/null || echo "$1"
|
||||
}
|
||||
|
||||
err()
|
||||
{
|
||||
logger -i -p user.err -t bootstrap "$1" 2>/dev/null || echo "$1"
|
||||
logger -i -p user.err -t gen-config "$1" 2>/dev/null || echo "$1"
|
||||
}
|
||||
|
||||
# When logging errors, generating /etc/issue* or /etc/banner (SSH)
|
||||
. /etc/os-release
|
||||
log()
|
||||
{
|
||||
logger -i -p user.notice -t gen-config "$1" 2>/dev/null || echo "$1"
|
||||
}
|
||||
|
||||
# /etc/confdrc controls the behavior or most of the gen-scripts,
|
||||
# /etc/confdrc controls the behavior of most of the gen-scripts,
|
||||
# customize in an overlay when using Infix as an br2-external.
|
||||
RC=/etc/confdrc
|
||||
if [ "$1" = "-f" ] && [ -f "$2" ]; then
|
||||
@@ -79,25 +69,6 @@ collate()
|
||||
fi
|
||||
}
|
||||
|
||||
# Report error on console, syslog, and set login banners for getty + ssh
|
||||
console_error()
|
||||
{
|
||||
critical "$1"
|
||||
|
||||
# shellcheck disable=SC3037
|
||||
/bin/echo -e "\n\n\e[31mCRITICAL BOOTSTRAP ERROR\n$1\e[0m\n" > /dev/console
|
||||
|
||||
[ -z "$STATUS" ] || return
|
||||
STATUS="CRITICAL ERROR: $1"
|
||||
|
||||
printf "\n$STATUS\n" | tee -a \
|
||||
/etc/banner \
|
||||
/etc/issue \
|
||||
/etc/issue.net \
|
||||
>/dev/null
|
||||
return 0
|
||||
}
|
||||
|
||||
gen_factory_cfg()
|
||||
{
|
||||
# Fetch defaults, simplifies sort in collate()
|
||||
@@ -145,49 +116,18 @@ gen_test_cfg()
|
||||
collate "$TEST_GEN" "$TEST_CFG" "$TEST_D"
|
||||
}
|
||||
|
||||
# Both factory-config and failure-config are generated every boot
|
||||
# regardless if there is a static /etc/factory-config.cfg or not.
|
||||
log "Starting up, calling gen_factory_cfg()"
|
||||
gen_factory_cfg
|
||||
log "Starting up, calling gen_failure_cfg()"
|
||||
gen_failure_cfg
|
||||
|
||||
if [ -f "/mnt/aux/test-mode" ]; then
|
||||
gen_test_cfg
|
||||
sysrepoctl -c infix-test -e test-mode-enable
|
||||
fi
|
||||
|
||||
if [ -n "$TESTING" ]; then
|
||||
echo "Done."
|
||||
exit 0
|
||||
fi
|
||||
mkdir -p /etc/sysrepo/
|
||||
if [ -f "$FACTORY_CFG" ]; then
|
||||
cp "$FACTORY_CFG" "$INIT_DATA"
|
||||
else
|
||||
cp "$FAILURE_CFG" "$INIT_DATA"
|
||||
fi
|
||||
rc=$?
|
||||
# Ensure 'admin' group users always have access to /cfg
|
||||
mkdir -p "$CFG_PATH_"
|
||||
chgrp wheel "$CFG_PATH_" 2>/dev/null
|
||||
chmod g+w "$CFG_PATH_" 2>/dev/null
|
||||
|
||||
# Ensure 'admin' group users always have access
|
||||
chgrp wheel "$CFG_PATH_"
|
||||
chmod g+w "$CFG_PATH_"
|
||||
|
||||
# Ensure factory-config has correct syntax
|
||||
if ! migrate -cq "$INIT_DATA"; then
|
||||
if migrate -iq -b "${INIT_DATA%.*}.bak" "$INIT_DATA"; then
|
||||
err "${INIT_DATA}: found and fixed old syntax!"
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! sysrepoctl -z "$INIT_DATA"; then
|
||||
rc=$?
|
||||
err "Failed loading factory-default datastore"
|
||||
else
|
||||
# Clear running-config so we can load/create startup in the next step
|
||||
temp=$(mktemp)
|
||||
echo "{}" > "$temp"
|
||||
sysrepocfg -f json -I"$temp" -d running
|
||||
rc=$?
|
||||
rm "$temp"
|
||||
fi
|
||||
|
||||
exit $rc
|
||||
log "All done."
|
||||
@@ -1,143 +0,0 @@
|
||||
#!/bin/sh
|
||||
# load [-b] <startup-config | failure-config | test-config>
|
||||
#
|
||||
# Import a configuration to the sysrepo datastore using `sysrepocfg -Ifile`
|
||||
#
|
||||
# If the '-b' option is used we set the Finit <usr/bootstrap> condition if
|
||||
# sysrepocfg returns OK. This to be able to detect and trigger the Infix
|
||||
# Fail Secure Mode at boot.
|
||||
#
|
||||
|
||||
banner_append()
|
||||
{
|
||||
printf "\n%s\n" "$*" | tee -a \
|
||||
/etc/banner \
|
||||
/etc/issue \
|
||||
/etc/issue.net \
|
||||
>/dev/null
|
||||
return 0
|
||||
}
|
||||
|
||||
# Ensure correct ownership and permissions, in particular after factory reset
|
||||
# Created by the system, writable by any user in the admin group.
|
||||
perms()
|
||||
{
|
||||
chown root:wheel "$1"
|
||||
chmod 0660 "$1"
|
||||
}
|
||||
|
||||
note()
|
||||
{
|
||||
msg="$*"
|
||||
logger -I $$ -p user.notice -t load -- "$msg"
|
||||
}
|
||||
|
||||
err()
|
||||
{
|
||||
msg="$*"
|
||||
logger -I $$ -p user.error -t load -- "$msg"
|
||||
}
|
||||
|
||||
|
||||
# shellcheck disable=SC1091
|
||||
. /etc/confdrc
|
||||
|
||||
sysrepocfg=sysrepocfg
|
||||
while getopts "t:" opt; do
|
||||
case ${opt} in
|
||||
t)
|
||||
sysrepocfg="$sysrepocfg -t $OPTARG"
|
||||
;;
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
done
|
||||
shift $((OPTIND - 1))
|
||||
|
||||
if [ $# -lt 1 ]; then
|
||||
err "No configuration file supplied"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
|
||||
config=$1
|
||||
|
||||
if [ -f "/mnt/aux/test-mode" ] && [ "$config" = "startup-config" ]; then
|
||||
|
||||
if [ -f "/mnt/aux/test-override-startup" ]; then
|
||||
rm -f "/mnt/aux/test-override-startup"
|
||||
else
|
||||
note "Test mode detected, switching to test-config"
|
||||
config="test-config"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -f "$config" ]; then
|
||||
fn="$config"
|
||||
else
|
||||
if [ -f "$CFG_PATH_/${config}.cfg" ]; then
|
||||
fn="$CFG_PATH_/${config}.cfg"
|
||||
else
|
||||
fn="$SYS_PATH_/${config}.cfg"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ ! -f "$fn" ]; then
|
||||
case "$config" in
|
||||
startup-config)
|
||||
note "startup-config missing, initializing running datastore from factory-config"
|
||||
$sysrepocfg -C factory-default
|
||||
rc=$?
|
||||
note "saving factory-config to $STARTUP_CFG ..."
|
||||
$sysrepocfg -f json -X"$STARTUP_CFG"
|
||||
perms "$STARTUP_CFG"
|
||||
exit $rc
|
||||
;;
|
||||
*)
|
||||
err "No such file, $fn, aborting!"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
note "Loading $config ..."
|
||||
if ! $sysrepocfg -v2 -I"$fn" -f json; then
|
||||
case "$config" in
|
||||
startup-config)
|
||||
err "Failed loading $fn, reverting to Fail Secure mode!"
|
||||
# On failure to load startup-config the system is in an undefined state
|
||||
cat <<-EOF >/tmp/factory.json
|
||||
{
|
||||
"infix-factory-default:factory-default": {}
|
||||
}
|
||||
EOF
|
||||
|
||||
if ! $sysrepocfg -f json -R /tmp/factory.json; then
|
||||
rm -f /etc/sysrepo/data/*startup*
|
||||
rm -f /etc/sysrepo/data/*running*
|
||||
rm -f /dev/shm/sr_*
|
||||
killall sysrepo-plugind
|
||||
fi
|
||||
;;
|
||||
failure-config)
|
||||
err "Failed loading $fn, aborting!"
|
||||
banner_append "CRITICAL ERROR: Logins are disabled, no credentials available"
|
||||
initctl -nbq runlevel 9
|
||||
;;
|
||||
*)
|
||||
err "Unknown config $config, aborting!"
|
||||
;;
|
||||
esac
|
||||
|
||||
exit 1
|
||||
else
|
||||
note "Success, syncing with startup datastore."
|
||||
$sysrepocfg -v2 -d startup -C running
|
||||
fi
|
||||
|
||||
note "Loaded $fn successfully."
|
||||
if [ "$config" = "failure-config" ]; then
|
||||
banner_append "ERROR: Corrupt startup-config, system has reverted to default login credentials"
|
||||
else
|
||||
perms "$fn"
|
||||
fi
|
||||
@@ -84,7 +84,8 @@ PKG_CHECK_MODULES([crypt], [libxcrypt >= 4.4.27])
|
||||
PKG_CHECK_MODULES([glib], [glib-2.0 >= 2.50 gio-2.0 gio-unix-2.0])
|
||||
PKG_CHECK_MODULES([jansson], [jansson >= 2.0.0])
|
||||
PKG_CHECK_MODULES([libite], [libite >= 2.6.1])
|
||||
PKG_CHECK_MODULES([sysrepo], [sysrepo >= 2.2.36])
|
||||
PKG_CHECK_MODULES([sysrepo], [sysrepo >= 4.2.10])
|
||||
PKG_CHECK_MODULES([libyang], [libyang >= 4.2.2])
|
||||
PKG_CHECK_MODULES([libsrx], [libsrx >= 1.0.0])
|
||||
|
||||
# Control build with automake flags
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
AM_CPPFLAGS = -D_DEFAULT_SOURCE -D_XOPEN_SOURCE -D_GNU_SOURCE -DYANG_PATH_=\"$(YANGDIR)\"
|
||||
AM_CPPFLAGS += -DCONFD_VERSION=\"$(PACKAGE_VERSION)\"
|
||||
CLEANFILES = $(rauc_installer_sources)
|
||||
plugindir = $(srpdplugindir)
|
||||
plugin_LTLIBRARIES = confd-plugin.la
|
||||
|
||||
sbin_PROGRAMS = confd
|
||||
|
||||
confd_CFLAGS = $(sysrepo_CFLAGS) $(libyang_CFLAGS) $(jansson_CFLAGS) $(libite_CFLAGS)
|
||||
confd_LDADD = $(sysrepo_LIBS) $(libyang_LIBS) $(jansson_LIBS) $(libite_LIBS) -ldl
|
||||
confd_SOURCES = main.c
|
||||
|
||||
confd_plugin_la_LDFLAGS = -module -avoid-version -shared
|
||||
|
||||
confd_plugin_la_CFLAGS = \
|
||||
|
||||
@@ -0,0 +1,840 @@
|
||||
/* SPDX-License-Identifier: BSD-3-Clause */
|
||||
/*
|
||||
* confd - Infix configuration daemon
|
||||
*
|
||||
* Replaces sysrepo-plugind + bootstrap + load with a single binary.
|
||||
* One sr_connect(), all datastore operations in-process, then load
|
||||
* plugins and enter the event loop.
|
||||
*
|
||||
* Copyright (c) 2018 - 2021 Deutsche Telekom AG.
|
||||
* Copyright (c) 2018 - 2021 CESNET, z.s.p.o.
|
||||
*/
|
||||
|
||||
#include <dirent.h>
|
||||
#include <dlfcn.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <getopt.h>
|
||||
#include <glob.h>
|
||||
#include <pthread.h>
|
||||
#include <signal.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <jansson.h>
|
||||
#include <libyang/libyang.h>
|
||||
#include <libite/lite.h>
|
||||
#include <sysrepo.h>
|
||||
#include <sysrepo/version.h>
|
||||
|
||||
/* Callback type names from sysrepo plugin API */
|
||||
#define SRP_INIT_CB "sr_plugin_init_cb"
|
||||
#define SRP_CLEANUP_CB "sr_plugin_cleanup_cb"
|
||||
|
||||
#ifndef CONFD_VERSION
|
||||
#define CONFD_VERSION PACKAGE_VERSION
|
||||
#endif
|
||||
|
||||
#ifndef SRPD_PLUGINS_PATH
|
||||
#define SRPD_PLUGINS_PATH "/usr/lib/sysrepo-plugind/plugins"
|
||||
#endif
|
||||
|
||||
struct plugin {
|
||||
void *handle;
|
||||
char *name;
|
||||
int (*init_cb)(sr_session_ctx_t *session, void **private_data);
|
||||
void (*cleanup_cb)(sr_session_ctx_t *session, void *private_data);
|
||||
void *private_data;
|
||||
int initialized;
|
||||
};
|
||||
|
||||
/* Protected flag for terminating */
|
||||
static int loop_finish;
|
||||
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
|
||||
|
||||
static void error_print(int sr_error, const char *format, ...)
|
||||
{
|
||||
va_list ap;
|
||||
char msg[2048];
|
||||
|
||||
if (!sr_error)
|
||||
snprintf(msg, sizeof(msg), "confd error: %s\n", format);
|
||||
else
|
||||
snprintf(msg, sizeof(msg), "confd error: %s (%s)\n", format, sr_strerror(sr_error));
|
||||
|
||||
va_start(ap, format);
|
||||
vfprintf(stderr, msg, ap);
|
||||
va_end(ap);
|
||||
}
|
||||
|
||||
static void version_print(void)
|
||||
{
|
||||
printf("confd - Infix configuration daemon v%s, compiled with libsysrepo v%s\n\n",
|
||||
CONFD_VERSION, SR_VERSION);
|
||||
}
|
||||
|
||||
static void help_print(void)
|
||||
{
|
||||
printf(
|
||||
"Usage:\n"
|
||||
" confd [-h] [-V] [-v <level>] [-d] [-n] [-f] [-p pidfile]\n"
|
||||
" [-F factory-config] [-S startup-config] [-E failure-config]\n"
|
||||
" [-t timeout]\n"
|
||||
"\n"
|
||||
"Options:\n"
|
||||
" -h, --help Prints usage help.\n"
|
||||
" -V, --version Prints version information.\n"
|
||||
" -v, --verbosity <level>\n"
|
||||
" Change verbosity to a level (none, error, warning, info, debug) or\n"
|
||||
" number (0, 1, 2, 3, 4).\n"
|
||||
" -d, --debug Debug mode - not daemonized and logs to stderr.\n"
|
||||
" -n, --foreground Run in foreground and log to syslog.\n"
|
||||
" -f, --fatal-plugin-fail\n"
|
||||
" Terminate if any plugin initialization fails.\n"
|
||||
" -p, --pid-file <path>\n"
|
||||
" Create a PID file at the specified path.\n"
|
||||
" -F, --factory-config <path>\n"
|
||||
" Factory default config file (default: /etc/factory-config.cfg).\n"
|
||||
" -S, --startup-config <path>\n"
|
||||
" Startup config file (default: /cfg/startup-config.cfg).\n"
|
||||
" -E, --failure-config <path>\n"
|
||||
" Failure fallback config file (default: /etc/failure-config.cfg).\n"
|
||||
" -t, --timeout <ms> Sysrepo operation timeout in seconds (default: 60).\n"
|
||||
"\n"
|
||||
"Environment variable $SRPD_PLUGINS_PATH overwrites the default plugins directory.\n"
|
||||
"\n");
|
||||
}
|
||||
|
||||
static void signal_handler(int sig)
|
||||
{
|
||||
switch (sig) {
|
||||
case SIGINT:
|
||||
case SIGQUIT:
|
||||
case SIGABRT:
|
||||
case SIGTERM:
|
||||
case SIGHUP:
|
||||
pthread_mutex_lock(&lock);
|
||||
if (!loop_finish) {
|
||||
loop_finish = 1;
|
||||
pthread_cond_signal(&cond);
|
||||
} else {
|
||||
error_print(0, "Exiting without a proper cleanup");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
pthread_mutex_unlock(&lock);
|
||||
break;
|
||||
default:
|
||||
error_print(0, "Exiting on receiving an unhandled signal");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
static void handle_signals(void)
|
||||
{
|
||||
struct sigaction action;
|
||||
sigset_t block_mask;
|
||||
|
||||
sigfillset(&block_mask);
|
||||
action.sa_handler = signal_handler;
|
||||
action.sa_mask = block_mask;
|
||||
action.sa_flags = 0;
|
||||
sigaction(SIGINT, &action, NULL);
|
||||
sigaction(SIGQUIT, &action, NULL);
|
||||
sigaction(SIGABRT, &action, NULL);
|
||||
sigaction(SIGTERM, &action, NULL);
|
||||
sigaction(SIGHUP, &action, NULL);
|
||||
|
||||
action.sa_handler = SIG_IGN;
|
||||
sigaction(SIGPIPE, &action, NULL);
|
||||
sigaction(SIGTSTP, &action, NULL);
|
||||
sigaction(SIGTTIN, &action, NULL);
|
||||
sigaction(SIGTTOU, &action, NULL);
|
||||
}
|
||||
|
||||
static void daemon_init(int debug, sr_log_level_t log_level)
|
||||
{
|
||||
pid_t pid = 0, sid = 0;
|
||||
int fd = -1;
|
||||
|
||||
if (debug) {
|
||||
handle_signals();
|
||||
if (debug < 0)
|
||||
goto done;
|
||||
sr_log_stderr(log_level);
|
||||
return;
|
||||
}
|
||||
|
||||
pid = fork();
|
||||
if (pid < 0) {
|
||||
error_print(0, "fork() failed (%s).", strerror(errno));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (pid > 0)
|
||||
exit(EXIT_SUCCESS);
|
||||
|
||||
handle_signals();
|
||||
|
||||
sid = setsid();
|
||||
if (sid < 0) {
|
||||
error_print(0, "setsid() failed (%s).", strerror(errno));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (chdir("/") < 0) {
|
||||
error_print(0, "chdir() failed (%s).", strerror(errno));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
fd = open("/dev/null", O_RDWR, 0);
|
||||
if (fd != -1) {
|
||||
dup2(fd, STDIN_FILENO);
|
||||
dup2(fd, STDOUT_FILENO);
|
||||
dup2(fd, STDERR_FILENO);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
done:
|
||||
sr_log_syslog("confd", log_level);
|
||||
}
|
||||
|
||||
static int open_pidfile(const char *pidfile)
|
||||
{
|
||||
int pidfd;
|
||||
|
||||
pidfd = open(pidfile, O_RDWR | O_CREAT, 0640);
|
||||
if (pidfd < 0) {
|
||||
error_print(0, "Unable to open the PID file \"%s\" (%s).", pidfile, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (lockf(pidfd, F_TLOCK, 0) < 0) {
|
||||
if (errno == EACCES || errno == EAGAIN)
|
||||
error_print(0, "Another instance of confd is running.");
|
||||
else
|
||||
error_print(0, "Unable to lock the PID file \"%s\" (%s).", pidfile, strerror(errno));
|
||||
close(pidfd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return pidfd;
|
||||
}
|
||||
|
||||
static int write_pidfile(int pidfd)
|
||||
{
|
||||
char pid[30] = {0};
|
||||
int pid_len;
|
||||
|
||||
if (ftruncate(pidfd, 0)) {
|
||||
error_print(0, "Failed to truncate pid file (%s).", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
snprintf(pid, sizeof(pid) - 1, "%ld\n", (long)getpid());
|
||||
pid_len = strlen(pid);
|
||||
if (write(pidfd, pid, pid_len) < pid_len) {
|
||||
error_print(0, "Failed to write PID into pid file (%s).", strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Plugin loading -- external .so files only (no internal plugins)
|
||||
*/
|
||||
static size_t path_len_no_ext(const char *path)
|
||||
{
|
||||
const char *dot;
|
||||
|
||||
dot = strrchr(path, '.');
|
||||
if (!dot || dot == path)
|
||||
return 0;
|
||||
|
||||
return dot - path;
|
||||
}
|
||||
|
||||
static int load_plugins(struct plugin **plugins, int *plugin_count)
|
||||
{
|
||||
void *mem, *handle;
|
||||
struct plugin *plugin;
|
||||
DIR *dir;
|
||||
struct dirent *ent;
|
||||
const char *plugins_dir;
|
||||
char *path;
|
||||
size_t name_len;
|
||||
int rc = 0;
|
||||
|
||||
*plugins = NULL;
|
||||
*plugin_count = 0;
|
||||
|
||||
plugins_dir = getenv("SRPD_PLUGINS_PATH");
|
||||
if (!plugins_dir)
|
||||
plugins_dir = SRPD_PLUGINS_PATH;
|
||||
|
||||
dir = opendir(plugins_dir);
|
||||
if (!dir) {
|
||||
error_print(0, "Opening \"%s\" directory failed (%s).", plugins_dir, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
while ((ent = readdir(dir))) {
|
||||
if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, ".."))
|
||||
continue;
|
||||
|
||||
if (asprintf(&path, "%s/%s", plugins_dir, ent->d_name) == -1) {
|
||||
error_print(0, "asprintf() failed (%s).", strerror(errno));
|
||||
rc = -1;
|
||||
break;
|
||||
}
|
||||
handle = dlopen(path, RTLD_LAZY);
|
||||
if (!handle) {
|
||||
error_print(0, "Opening plugin \"%s\" failed (%s).", path, dlerror());
|
||||
free(path);
|
||||
rc = -1;
|
||||
break;
|
||||
}
|
||||
free(path);
|
||||
|
||||
mem = realloc(*plugins, (*plugin_count + 1) * sizeof(**plugins));
|
||||
if (!mem) {
|
||||
error_print(0, "realloc() failed (%s).", strerror(errno));
|
||||
dlclose(handle);
|
||||
rc = -1;
|
||||
break;
|
||||
}
|
||||
*plugins = mem;
|
||||
plugin = &(*plugins)[*plugin_count];
|
||||
memset(plugin, 0, sizeof(*plugin));
|
||||
|
||||
*(void **)&plugin->init_cb = dlsym(handle, SRP_INIT_CB);
|
||||
if (!plugin->init_cb) {
|
||||
error_print(0, "Failed to find function \"%s\" in plugin \"%s\".",
|
||||
SRP_INIT_CB, ent->d_name);
|
||||
dlclose(handle);
|
||||
rc = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
*(void **)&plugin->cleanup_cb = dlsym(handle, SRP_CLEANUP_CB);
|
||||
if (!plugin->cleanup_cb) {
|
||||
error_print(0, "Failed to find function \"%s\" in plugin \"%s\".",
|
||||
SRP_CLEANUP_CB, ent->d_name);
|
||||
dlclose(handle);
|
||||
rc = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
plugin->handle = handle;
|
||||
|
||||
name_len = path_len_no_ext(ent->d_name);
|
||||
if (name_len == 0) {
|
||||
error_print(0, "Wrong filename \"%s\".", ent->d_name);
|
||||
dlclose(handle);
|
||||
rc = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
plugin->name = strndup(ent->d_name, name_len);
|
||||
if (!plugin->name) {
|
||||
error_print(0, "strndup() failed.");
|
||||
dlclose(handle);
|
||||
rc = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
++(*plugin_count);
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
* Wipe stale sysrepo SHM files for a clean slate every boot.
|
||||
*/
|
||||
static void wipe_sysrepo_shm(void)
|
||||
{
|
||||
glob_t gl;
|
||||
|
||||
if (glob("/dev/shm/sr_*", 0, NULL, &gl) == 0) {
|
||||
for (size_t i = 0; i < gl.gl_pathc; i++)
|
||||
unlink(gl.gl_pathv[i]);
|
||||
globfree(&gl);
|
||||
}
|
||||
}
|
||||
|
||||
const char *basenm(const char *path)
|
||||
{
|
||||
const char *slash;
|
||||
|
||||
if (!path)
|
||||
return NULL;
|
||||
|
||||
slash = strrchr(path, '/');
|
||||
if (slash)
|
||||
return slash[1] ? slash + 1 : NULL;
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
/*
|
||||
* Append error message to login banners.
|
||||
*/
|
||||
static void banner_append(const char *msg)
|
||||
{
|
||||
const char *files[] = {
|
||||
"/etc/banner",
|
||||
"/etc/issue",
|
||||
"/etc/issue.net",
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < sizeof(files) / sizeof(files[0]); i++) {
|
||||
FILE *fp = fopen(files[i], "a");
|
||||
|
||||
if (fp) {
|
||||
fprintf(fp, "\n%s\n", msg);
|
||||
fclose(fp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Smart migration: only fork+exec the migrate script if the version
|
||||
* in the config file doesn't match the current confd version.
|
||||
*/
|
||||
static int maybe_migrate(const char *path)
|
||||
{
|
||||
const char *backup_dir = "/cfg/backup";
|
||||
json_t *root, *meta, *ver;
|
||||
const char *file_ver;
|
||||
char backup[256];
|
||||
int rc;
|
||||
|
||||
root = json_load_file(path, 0, NULL);
|
||||
if (!root)
|
||||
return -1;
|
||||
|
||||
meta = json_object_get(root, "infix-meta:meta");
|
||||
ver = meta ? json_object_get(meta, "version") : NULL;
|
||||
file_ver = ver ? json_string_value(ver) : "0.0";
|
||||
|
||||
if (!strcmp(file_ver, CONFD_VERSION)) {
|
||||
json_decref(root);
|
||||
return 0;
|
||||
}
|
||||
json_decref(root);
|
||||
|
||||
SRPLG_LOG_INF("confd", "%s config version %s vs confd %s, migrating ...",
|
||||
path, file_ver, CONFD_VERSION);
|
||||
|
||||
mkpath(backup_dir, 0770);
|
||||
chown(backup_dir, 0, 10); /* root:wheel */
|
||||
|
||||
snprintf(backup, sizeof(backup), "%s/%s", backup_dir, basenm(path));
|
||||
rc = systemf("migrate -i -b \"%s\" \"%s\"", backup, path);
|
||||
if (rc)
|
||||
SRPLG_LOG_ERR("confd", "Migration of %s failed (rc=%d)", path, rc);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int file_exists(const char *path)
|
||||
{
|
||||
return access(path, F_OK) == 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Load a JSON config file into the running datastore.
|
||||
* Mirrors what sysrepocfg -I does: lyd_parse_data() + sr_replace_config().
|
||||
*/
|
||||
static int load_config(sr_conn_ctx_t *conn, sr_session_ctx_t *sess,
|
||||
const char *path, uint32_t timeout_ms)
|
||||
{
|
||||
const struct ly_ctx *ly_ctx;
|
||||
struct lyd_node *data = NULL;
|
||||
struct ly_in *in = NULL;
|
||||
LY_ERR lyrc;
|
||||
int r;
|
||||
|
||||
ly_ctx = sr_acquire_context(conn);
|
||||
|
||||
lyrc = ly_in_new_filepath(path, 0, &in);
|
||||
if (lyrc == LY_EINVAL) {
|
||||
/* empty file */
|
||||
char *empty = strdup("");
|
||||
|
||||
ly_in_new_memory(empty, &in);
|
||||
} else if (lyrc) {
|
||||
error_print(0, "Failed to open \"%s\" for reading", path);
|
||||
sr_release_context(conn);
|
||||
return -1;
|
||||
}
|
||||
|
||||
lyrc = lyd_parse_data(ly_ctx, NULL, in, LYD_JSON,
|
||||
LYD_PARSE_NO_STATE | LYD_PARSE_ONLY | LYD_PARSE_STRICT,
|
||||
0, &data);
|
||||
ly_in_free(in, 1);
|
||||
|
||||
if (lyrc) {
|
||||
SRPLG_LOG_ERR("confd", "Parsing %s failed", path);
|
||||
sr_release_context(conn);
|
||||
return -1;
|
||||
}
|
||||
|
||||
sr_release_context(conn);
|
||||
|
||||
r = sr_replace_config(sess, NULL, data, timeout_ms);
|
||||
if (r != SR_ERR_OK) {
|
||||
SRPLG_LOG_ERR("confd", "sr_replace_config failed: %s", sr_strerror(r));
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Export running datastore to a JSON file.
|
||||
*/
|
||||
static int export_running(sr_session_ctx_t *sess, const char *path, uint32_t timeout_ms)
|
||||
{
|
||||
sr_data_t *data = NULL;
|
||||
FILE *fp;
|
||||
int r;
|
||||
|
||||
r = sr_get_data(sess, "/*", 0, timeout_ms, 0, &data);
|
||||
if (r != SR_ERR_OK) {
|
||||
SRPLG_LOG_ERR("confd", "sr_get_data failed: %s", sr_strerror(r));
|
||||
return -1;
|
||||
}
|
||||
|
||||
fp = fopen(path, "w");
|
||||
if (!fp) {
|
||||
SRPLG_LOG_ERR("confd", "Failed to open %s for writing: %s", path, strerror(errno));
|
||||
sr_release_data(data);
|
||||
return -1;
|
||||
}
|
||||
|
||||
lyd_print_file(fp, data ? data->tree : NULL, LYD_JSON, LYD_PRINT_SIBLINGS);
|
||||
fclose(fp);
|
||||
sr_release_data(data);
|
||||
|
||||
chown(path, 0, 0); /* root:root initially */
|
||||
chmod(path, 0660);
|
||||
|
||||
/* Set group to 'wheel' for admin access */
|
||||
systemf("chgrp wheel '%s' 2>/dev/null", path);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Handle startup-config load failure: revert to factory-default,
|
||||
* then load failure-config, set error banners.
|
||||
*/
|
||||
static void handle_startup_failure(sr_session_ctx_t *sess, const char *failure_path,
|
||||
sr_conn_ctx_t *conn, uint32_t timeout_ms)
|
||||
{
|
||||
int r;
|
||||
|
||||
SRPLG_LOG_ERR("confd", "Failed loading startup-config, reverting to Fail Secure mode!");
|
||||
|
||||
/* Reset to factory-default */
|
||||
r = sr_copy_config(sess, NULL, SR_DS_FACTORY_DEFAULT, timeout_ms);
|
||||
if (r != SR_ERR_OK) {
|
||||
SRPLG_LOG_ERR("confd", "sr_copy_config(factory-default) failed: %s", sr_strerror(r));
|
||||
/* Nuclear option: wipe everything */
|
||||
systemf("rm -f /etc/sysrepo/data/*startup* /etc/sysrepo/data/*running* /dev/shm/sr_*");
|
||||
return;
|
||||
}
|
||||
|
||||
/* Load failure-config on top */
|
||||
if (file_exists(failure_path)) {
|
||||
if (load_config(conn, sess, failure_path, timeout_ms)) {
|
||||
SRPLG_LOG_ERR("confd", "Failed loading failure-config, aborting!");
|
||||
banner_append("CRITICAL ERROR: Logins are disabled, no credentials available");
|
||||
systemf("initctl -nbq runlevel 9");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
banner_append("ERROR: Corrupt startup-config, system has reverted to default login credentials");
|
||||
}
|
||||
|
||||
/*
|
||||
* Enable test-mode if the test-mode marker exists.
|
||||
*/
|
||||
static void maybe_enable_test_mode(void)
|
||||
{
|
||||
if (file_exists("/mnt/aux/test-mode"))
|
||||
systemf("sysrepoctl -c infix-test -e test-mode-enable");
|
||||
}
|
||||
|
||||
/*
|
||||
* Determine which config to load:
|
||||
* - test-mode (unless override exists)
|
||||
* - startup-config
|
||||
* - first-boot from factory
|
||||
*/
|
||||
static int bootstrap_config(sr_conn_ctx_t *conn, sr_session_ctx_t *sess,
|
||||
const char *factory_path, const char *startup_path,
|
||||
const char *failure_path, const char *test_path,
|
||||
uint32_t timeout_ms)
|
||||
{
|
||||
const char *config_path;
|
||||
int r;
|
||||
|
||||
/* Test mode support */
|
||||
if (file_exists("/mnt/aux/test-mode")) {
|
||||
if (file_exists("/mnt/aux/test-override-startup")) {
|
||||
unlink("/mnt/aux/test-override-startup");
|
||||
config_path = startup_path;
|
||||
} else {
|
||||
SRPLG_LOG_INF("confd", "Test mode detected, switching to test-config");
|
||||
config_path = test_path;
|
||||
}
|
||||
} else {
|
||||
config_path = startup_path;
|
||||
}
|
||||
|
||||
if (file_exists(config_path)) {
|
||||
/* Run migration if needed */
|
||||
maybe_migrate(config_path);
|
||||
|
||||
/* Load startup (or test) config */
|
||||
SRPLG_LOG_INF("confd", "Loading %s ...", config_path);
|
||||
if (load_config(conn, sess, config_path, timeout_ms)) {
|
||||
handle_startup_failure(sess, failure_path, conn, timeout_ms);
|
||||
return 0; /* continue running even in fail-secure */
|
||||
}
|
||||
|
||||
SRPLG_LOG_INF("confd", "Loaded %s successfully, syncing startup datastore.", config_path);
|
||||
sr_session_switch_ds(sess, SR_DS_STARTUP);
|
||||
r = sr_copy_config(sess, NULL, SR_DS_RUNNING, timeout_ms);
|
||||
sr_session_switch_ds(sess, SR_DS_RUNNING);
|
||||
if (r != SR_ERR_OK)
|
||||
SRPLG_LOG_WRN("confd", "Failed to sync startup datastore: %s", sr_strerror(r));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* First boot: no startup-config, initialize from factory */
|
||||
SRPLG_LOG_INF("confd", "startup-config missing, initializing from factory-config");
|
||||
|
||||
r = sr_copy_config(sess, NULL, SR_DS_FACTORY_DEFAULT, timeout_ms);
|
||||
if (r != SR_ERR_OK) {
|
||||
SRPLG_LOG_ERR("confd", "sr_copy_config(factory-default) failed: %s", sr_strerror(r));
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Export running → startup file */
|
||||
if (export_running(sess, startup_path, timeout_ms))
|
||||
SRPLG_LOG_WRN("confd", "Failed to export running to %s", startup_path);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
struct plugin *plugins = NULL;
|
||||
sr_conn_ctx_t *conn = NULL;
|
||||
sr_session_ctx_t *sess = NULL;
|
||||
sr_log_level_t log_level = SR_LL_ERR;
|
||||
int plugin_count = 0, i, r, rc = EXIT_FAILURE, opt, debug = 0;
|
||||
int pidfd = -1, fatal_fail = 0;
|
||||
const char *pidfile = NULL;
|
||||
const char *factory_path = "/etc/factory-config.cfg";
|
||||
const char *startup_path = "/cfg/startup-config.cfg";
|
||||
const char *failure_path = "/etc/failure-config.cfg";
|
||||
const char *test_path = "/etc/test-config.cfg";
|
||||
uint32_t timeout_s = 60;
|
||||
uint32_t timeout_ms;
|
||||
|
||||
struct option options[] = {
|
||||
{"help", no_argument, NULL, 'h'},
|
||||
{"version", no_argument, NULL, 'V'},
|
||||
{"verbosity", required_argument, NULL, 'v'},
|
||||
{"debug", no_argument, NULL, 'd'},
|
||||
{"foreground", no_argument, NULL, 'n'},
|
||||
{"pid-file", required_argument, NULL, 'p'},
|
||||
{"fatal-plugin-fail", no_argument, NULL, 'f'},
|
||||
{"factory-config", required_argument, NULL, 'F'},
|
||||
{"startup-config", required_argument, NULL, 'S'},
|
||||
{"failure-config", required_argument, NULL, 'E'},
|
||||
{"timeout", required_argument, NULL, 't'},
|
||||
{NULL, 0, NULL, 0},
|
||||
};
|
||||
|
||||
opterr = 0;
|
||||
while ((opt = getopt_long(argc, argv, "hVv:dnp:fF:S:E:t:", options, NULL)) != -1) {
|
||||
switch (opt) {
|
||||
case 'h':
|
||||
version_print();
|
||||
help_print();
|
||||
rc = EXIT_SUCCESS;
|
||||
goto cleanup;
|
||||
case 'V':
|
||||
version_print();
|
||||
rc = EXIT_SUCCESS;
|
||||
goto cleanup;
|
||||
case 'v':
|
||||
if (!strcmp(optarg, "none"))
|
||||
log_level = SR_LL_NONE;
|
||||
else if (!strcmp(optarg, "error"))
|
||||
log_level = SR_LL_ERR;
|
||||
else if (!strcmp(optarg, "warning"))
|
||||
log_level = SR_LL_WRN;
|
||||
else if (!strcmp(optarg, "info"))
|
||||
log_level = SR_LL_INF;
|
||||
else if (!strcmp(optarg, "debug"))
|
||||
log_level = SR_LL_DBG;
|
||||
else if (strlen(optarg) == 1 && optarg[0] >= '0' && optarg[0] <= '4')
|
||||
log_level = atoi(optarg);
|
||||
else {
|
||||
error_print(0, "Invalid verbosity \"%s\"", optarg);
|
||||
goto cleanup;
|
||||
}
|
||||
break;
|
||||
case 'd':
|
||||
debug = 1;
|
||||
break;
|
||||
case 'n':
|
||||
debug = -1;
|
||||
break;
|
||||
case 'p':
|
||||
pidfile = optarg;
|
||||
break;
|
||||
case 'f':
|
||||
fatal_fail = 1;
|
||||
break;
|
||||
case 'F':
|
||||
factory_path = optarg;
|
||||
break;
|
||||
case 'S':
|
||||
startup_path = optarg;
|
||||
break;
|
||||
case 'E':
|
||||
failure_path = optarg;
|
||||
break;
|
||||
case 't':
|
||||
timeout_s = (uint32_t)atoi(optarg);
|
||||
break;
|
||||
default:
|
||||
error_print(0, "Invalid option or missing argument: -%c", optopt);
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
if (optind < argc) {
|
||||
error_print(0, "Redundant parameters");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
timeout_ms = timeout_s * 1000;
|
||||
|
||||
if (pidfile && (pidfd = open_pidfile(pidfile)) < 0)
|
||||
goto cleanup;
|
||||
|
||||
/* Load plugins from disk (dlopen) before daemonizing */
|
||||
if (load_plugins(&plugins, &plugin_count))
|
||||
error_print(0, "load_plugins failed (continuing)");
|
||||
|
||||
/* Daemonize -- after this point, confd no longer logs to stderr */
|
||||
daemon_init(debug, log_level);
|
||||
|
||||
/* Phase 1: Wipe stale SHM for a clean slate */
|
||||
wipe_sysrepo_shm();
|
||||
|
||||
/* Phase 2: Connect to sysrepo (rebuilds SHM from installed YANG modules) */
|
||||
r = sr_connect(0, &conn);
|
||||
if (r != SR_ERR_OK) {
|
||||
error_print(r, "Failed to connect");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* Phase 3: Install factory defaults into all datastores */
|
||||
SRPLG_LOG_INF("confd", "Loading factory-default datastore from %s ...", factory_path);
|
||||
r = sr_install_factory_config(conn, factory_path);
|
||||
if (r != SR_ERR_OK) {
|
||||
SRPLG_LOG_ERR("confd", "sr_install_factory_config failed: %s", sr_strerror(r));
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* Phase 4: Start running-datastore session */
|
||||
r = sr_session_start(conn, SR_DS_RUNNING, &sess);
|
||||
if (r != SR_ERR_OK) {
|
||||
error_print(r, "Failed to start new session");
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* Phase 4b: Clear running datastore so plugin init sees an empty
|
||||
* tree. This matches the original bootstrap flow where running
|
||||
* was cleared with '{}' before sysrepo-plugind started. When we
|
||||
* later load startup-config, the diff will be all-create which is
|
||||
* what the plugin callbacks expect. */
|
||||
r = sr_replace_config(sess, NULL, NULL, timeout_ms);
|
||||
if (r != SR_ERR_OK) {
|
||||
SRPLG_LOG_ERR("confd", "Failed to clear running datastore: %s", sr_strerror(r));
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* Enable test-mode YANG feature if needed */
|
||||
maybe_enable_test_mode();
|
||||
|
||||
/* Phase 5: Initialize plugins (subscribe to YANG module changes) */
|
||||
for (i = 0; i < plugin_count; i++) {
|
||||
r = plugins[i].init_cb(sess, &plugins[i].private_data);
|
||||
if (r) {
|
||||
SRPLG_LOG_ERR("confd", "Plugin \"%s\" initialization failed (%s).",
|
||||
plugins[i].name, sr_strerror(r));
|
||||
if (fatal_fail)
|
||||
goto cleanup;
|
||||
} else {
|
||||
SRPLG_LOG_INF("confd", "Plugin \"%s\" initialized.", plugins[i].name);
|
||||
plugins[i].initialized = 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Phase 6: Load startup config -- plugins are now subscribed, so
|
||||
* sr_replace_config() will trigger their change callbacks. */
|
||||
if (bootstrap_config(conn, sess, factory_path, startup_path,
|
||||
failure_path, test_path, timeout_ms))
|
||||
goto cleanup;
|
||||
|
||||
/* Write PID file after everything is ready */
|
||||
if (pidfile && write_pidfile(pidfd) < 0)
|
||||
goto cleanup;
|
||||
|
||||
/* Wait for a terminating signal */
|
||||
pthread_mutex_lock(&lock);
|
||||
while (!loop_finish)
|
||||
pthread_cond_wait(&cond, &lock);
|
||||
pthread_mutex_unlock(&lock);
|
||||
|
||||
rc = EXIT_SUCCESS;
|
||||
|
||||
cleanup:
|
||||
while (plugin_count > 0) {
|
||||
if (plugins[plugin_count - 1].initialized)
|
||||
plugins[plugin_count - 1].cleanup_cb(sess, plugins[plugin_count - 1].private_data);
|
||||
if (plugins[plugin_count - 1].handle)
|
||||
dlclose(plugins[plugin_count - 1].handle);
|
||||
free(plugins[plugin_count - 1].name);
|
||||
--plugin_count;
|
||||
}
|
||||
free(plugins);
|
||||
|
||||
if (pidfd >= 0) {
|
||||
close(pidfd);
|
||||
unlink(pidfile);
|
||||
}
|
||||
|
||||
sr_disconnect(conn);
|
||||
return rc;
|
||||
}
|
||||
Reference in New Issue
Block a user