src/confd: improved coverage of ietf-system

- Call external programs safely with run()
 - Use augeas to update /etc/hostname and /etc/hosts
 - Add support for generating /etc/chrony.conf
 - Update license copyright due to significant contrib.

Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
Joachim Wiberg
2023-04-02 16:24:08 +02:00
parent 7079a35088
commit b3b25ed4d6
3 changed files with 355 additions and 73 deletions
+1
View File
@@ -1,4 +1,5 @@
Copyright (c) 2020 Denis Kalashnikov
Copyright (c) 2023 The KernelKit Authors
All rights reserved.
Redistribution and use in source and binary forms, with or without
+2 -2
View File
@@ -3,7 +3,7 @@ AM_CPPFLAGS = -DYANG_PATH_=\"$(YANGDIR)/\"
plugindir = $(srpdplugindir)
plugin_LTLIBRARIES = confd-plugin.la
confd_plugin_la_CFLAGS = $(sysrepo_CFLAGS) $(CFLAGS)
confd_plugin_la_LIBADD = $(sysrepo_LIBS) $(CFLAGS)
confd_plugin_la_CFLAGS = $(augeas_CFLAGS) $(sysrepo_CFLAGS) $(CFLAGS)
confd_plugin_la_LIBADD = $(augeas_LIBS) $(sysrepo_LIBS) $(CFLAGS)
confd_plugin_la_LDFLAGS = -module -avoid-version -shared
confd_plugin_la_SOURCES = ietf-system.c
+352 -71
View File
@@ -1,5 +1,6 @@
/* Sysrepo plugin of ietf-system@2014-08-06.yang module */
/* SPDX-License-Identifier: BSD-3-Clause */
#include <augeas.h>
#include <stdio.h>
#include <syslog.h>
#include <stdlib.h>
@@ -15,15 +16,72 @@
#include <libyang/libyang.h>
#include <sysrepo.h>
#include <sysrepo/values.h>
#include <sysrepo/xpath.h>
#define CLOCK_PATH_ "/ietf-system:system-state/clock"
#define PLATFORM_PATH_ "/ietf-system:system-state/platform"
#define NELEMS(arr) (sizeof(arr) / sizeof(arr[0]))
#define DEBUG(frmt, ...)
//#define DEBUG(frmt, ...) syslog(LOG_DEBUG, "%s: "frmt, __func__, ##__VA_ARGS__)
#define ERROR(frmt, ...) syslog(LOG_ERR, "%s: " frmt, __func__, ##__VA_ARGS__)
#define ERRNO(frmt, ...) syslog(LOG_ERR, "%s: " frmt ": %s", __func__, ##__VA_ARGS__, strerror(errno))
static augeas *aug;
/**
* Like system(), but takes a formatted string as argument.
* @param fmt printf style format list to command to run
*
* This system() wrapper greatly simplifies operations that usually
* consist of composing a command from parts into a dynamic buffer
* before calling it. The return value from system() is also parsed,
* checking for proper exit and signals.
*
* @returns If the command exits normally, the return code of the command
* is returned. Otherwise, if the command is signalled, the return code
* is -1 and @a errno is set to @c EINTR.
*/
static int run(const char *fmt, ...)
{
va_list ap;
char *cmd;
int len;
int rc;
va_start(ap, fmt);
len = vsnprintf(NULL, 0, fmt, ap);
va_end(ap);
cmd = alloca(++len);
if (!cmd) {
errno = ENOMEM;
return -1;
}
va_start(ap, fmt);
vsnprintf(cmd, len, fmt, ap);
va_end(ap);
rc = system(cmd);
if (rc == -1)
return -1;
if (WIFEXITED(rc)) {
errno = 0;
rc = WEXITSTATUS(rc);
} else if (WIFSIGNALED(rc)) {
errno = EINTR;
rc = -1;
}
return rc;
}
/* Return seconds since boot */
static long get_uptime(void)
{
@@ -143,29 +201,26 @@ static int platform_cb(sr_session_ctx_t *session, uint32_t sub_id, const char *m
return SR_ERR_OK;
}
static int exec_rpc_cb(sr_session_ctx_t *session, uint32_t sub_id, const char *path,
const sr_val_t *input, const size_t input_cnt,
sr_event_t event, unsigned request_id,
sr_val_t **output, size_t *output_cnt,
void *priv)
static int rpc_exec(sr_session_ctx_t *session, uint32_t sub_id, const char *path,
const sr_val_t *input, const size_t input_cnt,
sr_event_t event, unsigned request_id,
sr_val_t **output, size_t *output_cnt,
void *priv)
{
int rc;
DEBUG("path: %s", path);
rc = system(priv);
if (WIFSIGNALED(rc) || (WIFEXITED(rc) && WEXITSTATUS(rc)))
if (run(priv))
return SR_ERR_INTERNAL;
return SR_ERR_OK;
}
/* '/ietf-system:set-current-date-time' */
static int set_datetime_rpc_cb(sr_session_ctx_t *session, uint32_t sub_id,
const char *path, const sr_val_t *input,
const size_t input_cnt, sr_event_t event,
unsigned request_id, sr_val_t **output,
size_t *output_cnt, void *priv)
static int rpc_set_datetime(sr_session_ctx_t *session, uint32_t sub_id,
const char *path, const sr_val_t *input,
const size_t input_cnt, sr_event_t event,
unsigned request_id, sr_val_t **output,
size_t *output_cnt, void *priv)
{
struct timeval tv;
struct tm tm;
@@ -201,51 +256,287 @@ static int set_datetime_rpc_cb(sr_session_ctx_t *session, uint32_t sub_id,
return SR_ERR_OK;
}
int hostname_change_cb(sr_session_ctx_t *session, uint32_t sub_id, const char *module,
const char *xpath, sr_event_t event,
unsigned request_id, void *priv)
static int sys_reload_services(void)
{
sr_val_t *old_val, *new_val, *val;
sr_change_iter_t *iter;
sr_change_oper_t op;
return run("initctl -nbq touch sysklogd lldpd");
}
static void print_val(sr_val_t *val)
{
char *str;
if (sr_print_val_mem(&str, val))
return;
ERROR("%s", str);
free(str);
}
int sr_get_bool(sr_session_ctx_t *session, const char *fmt, ...)
{
sr_val_t *val = NULL;
char *xpath;
va_list ap;
int rc = 0;
int len;
va_start(ap, fmt);
len = vsnprintf(NULL, 0, fmt, ap) + 1;
va_end(ap);
xpath = alloca(len);
if (!xpath)
goto fail;
va_start(ap, fmt);
vsnprintf(xpath, len, fmt, ap);
va_end(ap);
if (sr_get_item(session, xpath, 0, &val))
goto fail;
if (!val || val->type != SR_BOOL_T)
goto fail;
rc = val->data.bool_val;
fail:
if (val)
sr_free_val(val);
return rc;
}
char *sr_get_str(sr_session_ctx_t *session, const char *fmt, ...)
{
char *str = NULL;
sr_val_t *val;
char *xpath;
va_list ap;
int len;
va_start(ap, fmt);
len = vsnprintf(NULL, 0, fmt, ap) + 1;
va_end(ap);
xpath = alloca(len);
if (!xpath)
return NULL;
va_start(ap, fmt);
vsnprintf(xpath, len, fmt, ap);
va_end(ap);
if (sr_get_item(session, xpath, 0, &val)) {
ERROR("Failed reading string value from xpath %s", xpath);
goto fail;
}
if (!val || val->type != SR_STRING_T)
goto fail;
str = strdup(val->data.string_val);
fail:
if (val)
sr_free_val(val);
return str;
}
#define CHRONY_CONF "/etc/chrony.conf"
#define CHRONY_PREV CHRONY_CONF "-"
#define CHRONY_NEXT CHRONY_CONF "+"
static int change_ntp(sr_session_ctx_t *session, uint32_t sub_id, const char *module,
const char *xpath, sr_event_t event, unsigned request_id, void *priv)
{
const char *fn = CHRONY_NEXT;
int valid = -1;
sr_data_t *sdt;
sr_val_t *val;
size_t cnt;
FILE *fp;
int rc;
if (event != SR_EV_ENABLED && event != SR_EV_DONE)
switch (event) {
case SR_EV_ENABLED: /* first time, on register. */
case SR_EV_CHANGE: /* regular change (copy cand running) */
/* Generate next config */
break;
case SR_EV_ABORT: /* User abort, or other plugin failed */
remove(CHRONY_NEXT);
return SR_ERR_OK;
rc = sr_get_changes_iter(session, "//.", &iter);
if (rc) {
ERROR("failed to get changes iter: %s", sr_strerror(rc));
return rc;
case SR_EV_DONE:
/* Check if passed validation in previous event */
if (access(CHRONY_NEXT, F_OK))
return SR_ERR_OK;
remove(CHRONY_PREV);
rename(CHRONY_CONF, CHRONY_PREV);
rename(CHRONY_NEXT, CHRONY_CONF);
if (!sr_get_bool(session, "/ietf-system:system/ntp/enabled")) {
run("initctl -nbq disable chronyd");
return SR_ERR_OK;
}
/*
* If chrony is alrady enabled we tell Finit it's been
* modified , so Finit restarts it, otherwise enable it.
*/
run("initctl -nbq touch chronyd");
run("initctl -nbq enable chronyd");
return SR_ERR_OK;
}
while (sr_get_change_next(session, iter, &op, &old_val, &new_val) == SR_ERR_OK) {
val = new_val ? new_val : old_val;
if (strcmp(val->xpath, "/ietf-system:system/hostname"))
goto free_vals;
if (rc = sr_get_items(session, "/ietf-system:system/ntp/server", 0, 0, &val, &cnt)) {
remove(CHRONY_NEXT);
return rc;
}
fp = fopen(fn, "w");
if (!fp) {
ERROR("failed updating %s: %s", fn, strerror(errno));
sr_free_values(val, cnt);
return SR_ERR_SYS;
}
switch (op) {
case SR_OP_CREATED:
case SR_OP_MODIFIED:
if (sethostname(new_val->data.string_val,
strlen(new_val->data.string_val))) {
ERRNO("Failed to set hostname");
return SR_ERR_SYS;
for (size_t i = 0; i < cnt; i++) {
const char *xpath = val[i].xpath;
char *type, *ptr;
int server = 0;
/*
* Handle empty startup-config on SR_EV_ENABLED,
* prevents subscribe failure due to false invalid.
*/
if (i == 0)
valid = 0;
/* Get /ietf-system:system/ntp/server[name='foo'] */
ptr = sr_get_str(session, "%s/udp/address", xpath);
if (ptr) {
type = sr_get_str(session, "%s/association-type", xpath);
fprintf(fp, "%s %s", type ?: "server", ptr);
server++;
if (type)
free(type);
free(ptr);
if (ptr = sr_get_str(session, "%s/udp/port", xpath)) {
fprintf(fp, " port %s", ptr);
free(ptr);
}
DEBUG("Set hostname to '%s'", new_val->data.string_val);
break;
}
free_vals:
sr_free_val(old_val);
sr_free_val(new_val);
if (server) {
if (sr_get_bool(session, "%s/iburst", xpath))
fprintf(fp, " iburst");
if (sr_get_bool(session, "%s/prefer", xpath))
fprintf(fp, " prefer");
fprintf(fp, "\n");
valid++;
}
}
sr_free_values(val, cnt);
fprintf(fp, "driftfile /var/lib/chrony/drift\n");
fprintf(fp, "makestep 1.0 3\n");
fprintf(fp, "maxupdateskew 100.0\n");
fprintf(fp, "dumpdir /var/lib/chrony\n");
fprintf(fp, "rtcfile /var/lib/chrony/rtc\n");
fclose(fp);
if (!valid) {
remove(fn);
return SR_ERR_VALIDATION_FAILED;
}
return SR_ERR_OK;
}
int sr_plugin_init_cb(sr_session_ctx_t *sess, void **priv)
static int change_hostname(sr_session_ctx_t *session, uint32_t sub_id, const char *module,
const char *xpath, sr_event_t event, unsigned request_id, void *priv)
{
const char *host, *nm, *tmp;
char **hosts, *current;
int err, i, nhosts;
int rc;
switch (event) {
case SR_EV_ENABLED: /* first time, on register. */
case SR_EV_CHANGE: /* regular change (copy cand running) */
/* Wait for DONE to activate changes */
return SR_ERR_OK;
case SR_EV_ABORT: /* User abort, or other plugin failed */
return SR_ERR_OK;
case SR_EV_DONE:
/* Activate changes */
break;
}
nm = sr_get_str(session, xpath);
if (!nm) {
ERROR("No valid hostname in current session");
nm = "infix"; /* XXX: derive from global "options.h" */
}
ERROR("Got hostname %s", nm);
aug_get(aug, "etc/hostname/hostname", &tmp);
current = strdup(tmp);
ERROR("Current hostname %s", current);
err = sethostname(nm, strlen(nm));
err = err ? : aug_set(aug, "etc/hostname/hostname", nm);
nhosts = aug_match(aug, "etc/hosts/*/canonical", &hosts);
for (i = 0; i < nhosts; i++) {
aug_get(aug, hosts[i], &host);
if (!strcmp(host, current))
err = err ? : aug_set(aug, hosts[i], nm);
free(hosts[i]);
}
free(hosts);
free(current);
if (err) {
ERROR("Failed activating changes.");
return err;
}
ERROR("Reload hostname-dependent services ...");
err = err ? : aug_save(aug);
if (sys_reload_services())
return SR_ERR_SYS;
return SR_ERR_OK;
}
static int register_change(sr_session_ctx_t *session, const char *xpath,
sr_module_change_cb cb, void *arg, sr_subscription_ctx_t **sub)
{
int rc = sr_module_change_subscribe(session, "ietf-system", xpath, cb, arg, 0,
SR_SUBSCR_DEFAULT | SR_SUBSCR_ENABLED, sub);
if (rc)
ERROR("failed subscribing to changes of %s: %s", xpath, sr_strerror(rc));
return rc;
}
static int register_rpc(sr_session_ctx_t *session, const char *xpath,
sr_rpc_cb cb, void *arg, sr_subscription_ctx_t **sub)
{
int rc = sr_rpc_subscribe(session, xpath, cb, arg, 0, SR_SUBSCR_DEFAULT, sub);
if (rc)
ERROR("failed subscribing to %s rpc: %s", xpath, sr_strerror(rc));
return rc;
}
#define REGISTER_CHANGE(s,x,c,a,u) \
if (rc = register_change(s, x, c, a, u))\
goto err
#define REGISTER_RPC(s,x,c,a,u) \
if (rc = register_rpc(s, x, c, a, u)) \
goto err
int sr_plugin_init_cb(sr_session_ctx_t *session, void **priv)
{
sr_subscription_ctx_t *sub = NULL;
sr_conn_ctx_t *conn;
@@ -257,44 +548,34 @@ int sr_plugin_init_cb(sr_session_ctx_t *sess, void **priv)
};
int rc;
openlog("sysrepo ietf-system plugin", LOG_USER, 0);
conn = sr_session_get_connection(sess);
aug = aug_init(NULL, "", 0);
if (!aug ||
aug_load_file(aug, "/etc/hostname") ||
aug_load_file(aug, "/etc/hosts")) {
ERROR("ietf-system: Augeas initialization failed");
goto err;
}
openlog("ietf-system", LOG_USER, 0);
conn = sr_session_get_connection(session);
sr_install_module(conn, YANG_PATH_"ietf-system@2014-08-06.yang", NULL, features);
rc = sr_oper_get_subscribe(sess, "ietf-system", CLOCK_PATH_,
rc = sr_oper_get_subscribe(session, "ietf-system", CLOCK_PATH_,
clock_cb, NULL, SR_SUBSCR_DEFAULT, &sub);
if (rc != SR_ERR_OK)
goto err;
rc = sr_oper_get_subscribe(sess, "ietf-system", PLATFORM_PATH_,
rc = sr_oper_get_subscribe(session, "ietf-system", PLATFORM_PATH_,
platform_cb, NULL, SR_SUBSCR_DEFAULT, &sub);
if (rc != SR_ERR_OK)
goto err;
rc = sr_rpc_subscribe(sess, "/ietf-system:system-restart",
exec_rpc_cb, "reboot",
0, SR_SUBSCR_DEFAULT, &sub);
if (rc != SR_ERR_OK)
goto err;
REGISTER_RPC(session, "/ietf-system:system-restart", rpc_exec, "reboot", &sub);
REGISTER_RPC(session, "/ietf-system:system-shutdown", rpc_exec, "poweroff", &sub);
REGISTER_RPC(session, "/ietf-system:set-current-datetime", rpc_set_datetime, NULL, &sub);
rc = sr_rpc_subscribe(sess, "/ietf-system:system-shutdown",
exec_rpc_cb, "poweroff", 0, SR_SUBSCR_DEFAULT, &sub);
if (rc != SR_ERR_OK)
goto err;
rc = sr_rpc_subscribe(sess, "/ietf-system:set-current-datetime",
set_datetime_rpc_cb, NULL, 0, SR_SUBSCR_DEFAULT, &sub);
if (rc != SR_ERR_OK)
goto err;
rc = sr_module_change_subscribe(sess, "ietf-system", "/ietf-system:system/hostname",
hostname_change_cb, NULL, 0,
SR_SUBSCR_DEFAULT | SR_SUBSCR_ENABLED, &sub);
if (rc != SR_ERR_OK) {
ERROR("failed to subscribe to changes of hostname: %s",
sr_strerror(rc));
goto err;
}
REGISTER_CHANGE(session, "/ietf-system:system/hostname", change_hostname, NULL, &sub);
REGISTER_CHANGE(session, "/ietf-system:system/ntp", change_ntp, NULL, &sub);
*(sr_subscription_ctx_t **)priv = sub;
DEBUG("init ok");