knot-dns/src/knot/ctl/commands.c
Libor Peltan 605ddcc9dc zone/purge: refactoring: perform as zone event instead by main thread...
...but only in case of knotc zone-purge; catalog-induced purges
are still performed by main thread while zone events all frozen
for all zones
2025-11-06 19:27:55 +01:00

2542 lines
69 KiB
C

/* Copyright (C) CZ.NIC, z.s.p.o. and contributors
* SPDX-License-Identifier: GPL-2.0-or-later
* For more information, see <https://www.knot-dns.cz/>
*/
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <time.h>
#include <urcu.h>
#include "knot/common/log.h"
#include "knot/common/stats.h"
#include "knot/conf/confio.h"
#include "knot/ctl/commands.h"
#include "knot/ctl/process.h"
#include "knot/dnssec/key-events.h"
#include "knot/events/events.h"
#include "knot/events/handlers.h"
#include "knot/journal/journal_metadata.h"
#include "knot/nameserver/query_module.h"
#include "knot/updates/zone-update.h"
#include "knot/zone/backup.h"
#include "knot/zone/digest.h"
#include "knot/zone/timers.h"
#include "knot/zone/zonedb-load.h"
#include "knot/zone/zonefile.h"
#include "libknot/libknot.h"
#include "libknot/yparser/yptrafo.h"
#include "contrib/atomic.h"
#include "contrib/files.h"
#include "contrib/string.h"
#include "contrib/strtonum.h"
#include "contrib/openbsd/strlcat.h"
#include "contrib/ucw/lists.h"
#include "libzscanner/scanner.h"
#define MATCH_OR_FILTER(args, code) ((args)->data[KNOT_CTL_IDX_FILTERS] == NULL || \
strchr((args)->data[KNOT_CTL_IDX_FILTERS], (code)[0]) != NULL)
#define MATCH_AND_FILTER(args, code) ((args)->data[KNOT_CTL_IDX_FILTERS] != NULL && \
strchr((args)->data[KNOT_CTL_IDX_FILTERS], (code)[0]) != NULL)
typedef struct {
ctl_args_t *args;
int type_filter; // -1: no specific type, [0, 2^16]: specific type.
knot_dump_style_t style;
knot_ctl_data_t data;
knot_dname_txt_storage_t zone;
knot_dname_txt_storage_t owner;
char ttl[16];
char type[32];
char rdata[2 * 65536];
} send_ctx_t;
static struct {
send_ctx_t send_ctx;
zs_scanner_t scanner;
char txt_rr[sizeof(((send_ctx_t *)0)->owner) +
sizeof(((send_ctx_t *)0)->ttl) +
sizeof(((send_ctx_t *)0)->type) +
sizeof(((send_ctx_t *)0)->rdata)];
} ctl_globals[CTL_MAX_CONCURRENT];
static bool allow_blocking_while_ctl_txn(zone_event_type_t event)
{
// this can be allowed for those events that do NOT create a zone_update_t
switch (event) {
case ZONE_EVENT_UFREEZE:
case ZONE_EVENT_UTHAW:
case ZONE_EVENT_NOTIFY:
case ZONE_EVENT_FLUSH:
return true;
default:
return false;
}
}
/*!
* Evaluates a filter pair and checks for conflicting filters.
*
* \param[in] args Command arguments.
* \param[out] param The filter to be set.
* \param[in] dflt Default filter value.
* \param[in] filter Name of the filter.
* \param[in] neg_filter Name of the negative filter.
*
* \return false if there is a filter conflict, true otherwise.
*/
static bool eval_opposite_filters(ctl_args_t *args, bool *param, bool dflt,
char *filter, char *neg_filter)
{
bool set = MATCH_AND_FILTER(args, filter);
bool unset = MATCH_AND_FILTER(args, neg_filter);
*param = dflt ? (set || !unset) : (set && !unset);
return !(set && unset);
}
static bool eval_backup_filters(ctl_args_t *args, knot_backup_params_t *filters,
const backup_filter_list_t *item, knot_backup_params_t dflts)
{
bool val;
bool ret = eval_opposite_filters(args, &val, dflts & item->param,
item->filter, item->neg_filter);
if (ret) {
*filters |= item->param * val;
}
return ret;
}
static int schedule_trigger(zone_t *zone, ctl_args_t *args, zone_event_type_t event,
bool user)
{
int ret = KNOT_EOK;
if (ctl_has_flag(args->data[KNOT_CTL_IDX_FLAGS], CTL_FLAG_BLOCKING)) {
if (!allow_blocking_while_ctl_txn(event) &&
zone->control_update != NULL) {
return KNOT_TXN_EEXISTS;
}
ret = zone_events_schedule_blocking(zone, event, user);
} else if (user) {
zone_events_schedule_user(zone, event);
} else {
zone_events_schedule_now(zone, event);
}
return ret;
}
static void ctl_log_conf_data(knot_ctl_data_t *data)
{
if (data == NULL) {
return;
}
const char *section = (*data)[KNOT_CTL_IDX_SECTION];
const char *item = (*data)[KNOT_CTL_IDX_ITEM];
const char *id = (*data)[KNOT_CTL_IDX_ID];
if (section != NULL) {
log_ctl_debug("control, config item '%s%s%s%s%s%s'", section,
(id != NULL ? "[" : ""),
(id != NULL ? id : ""),
(id != NULL ? "]" : ""),
(item != NULL ? "." : ""),
(item != NULL ? item : ""));
}
}
void ctl_send_error(ctl_args_t *args, const char *msg)
{
knot_ctl_data_t data;
memcpy(&data, args->data, sizeof(data));
data[KNOT_CTL_IDX_ERROR] = msg;
int ret = knot_ctl_send(args->ctl, KNOT_CTL_TYPE_DATA, &data);
if (ret != KNOT_EOK) {
log_ctl_debug("control, failed to send error (%s)", knot_strerror(ret));
}
}
static int get_zone(ctl_args_t *args, zone_t **zone)
{
const char *name = args->data[KNOT_CTL_IDX_ZONE];
assert(name != NULL);
knot_dname_storage_t buff;
knot_dname_t *dname = knot_dname_from_str(buff, name, sizeof(buff));
if (dname == NULL) {
return KNOT_EINVAL;
}
knot_dname_to_lower(dname);
*zone = knot_zonedb_find(args->server->zone_db, dname);
if (*zone == NULL) {
return KNOT_ENOZONE;
}
return KNOT_EOK;
}
static int zones_apply(ctl_args_t *args, int (*fcn)(zone_t *, ctl_args_t *))
{
int ret = KNOT_EOK;
// Process all configured zones if none is specified.
if (args->data[KNOT_CTL_IDX_ZONE] == NULL) {
bool failed = false;
knot_zonedb_iter_t *it = knot_zonedb_iter_begin(args->server->zone_db);
while (!knot_zonedb_iter_finished(it)) {
args->suppress = false;
ret = fcn((zone_t *)knot_zonedb_iter_val(it), args);
if (ret != KNOT_EOK && !args->suppress) {
failed = true;
}
knot_zonedb_iter_next(it);
}
knot_zonedb_iter_free(it);
if (failed && ret != KNOT_EPARSEFAIL) {
ret = KNOT_CTL_EZONE;
log_ctl_error("control, error (%s)", knot_strerror(ret));
ctl_send_error(args, knot_strerror(ret));
}
return KNOT_EOK;
}
while (true) {
zone_t *zone;
ret = get_zone(args, &zone);
if (ret == KNOT_EOK) {
ret = fcn(zone, args);
}
if (ret != KNOT_EOK && ret != KNOT_EPARSEFAIL) {
log_ctl_zone_str_error(args->data[KNOT_CTL_IDX_ZONE],
"control, error (%s)", knot_strerror(ret));
ctl_send_error(args, knot_strerror(ret));
}
// Get next zone name.
ret = knot_ctl_receive(args->ctl, &args->type, &args->data);
if (ret != KNOT_EOK || args->type != KNOT_CTL_TYPE_DATA || args->data[KNOT_CTL_IDX_ZONE] == NULL) {
break;
}
strtolower((char *)args->data[KNOT_CTL_IDX_ZONE]);
// Log the other zones the same way as the first one from process.c.
log_ctl_zone_str_info(args->data[KNOT_CTL_IDX_ZONE],
"control, received command '%s'",
args->data[KNOT_CTL_IDX_CMD]);
}
return ret;
}
static int zone_status(zone_t *zone, ctl_args_t *args)
{
knot_dname_txt_storage_t name;
if (knot_dname_to_str(name, zone->name, sizeof(name)) == NULL) {
return KNOT_EINVAL;
}
char filters[16] = "";
knot_ctl_data_t data = {
[KNOT_CTL_IDX_ZONE] = name,
[KNOT_CTL_IDX_FILTERS] = filters,
};
const bool slave = zone_is_slave(conf(), zone);
if (slave) {
strlcat(filters, CTL_FILTER_STATUS_SLAVE_R, sizeof(filters));
}
const bool empty = (zone->contents == NULL);
if (empty) {
strlcat(filters, CTL_FILTER_STATUS_EMPTY_R, sizeof(filters));
}
const bool member = (zone->flags & ZONE_IS_CAT_MEMBER);
if (member) {
strlcat(filters, CTL_FILTER_STATUS_MEMBER_R, sizeof(filters));
}
int ret;
char buff[128];
knot_ctl_type_t type = KNOT_CTL_TYPE_DATA;
if (MATCH_OR_FILTER(args, CTL_FILTER_STATUS_ROLE)) {
data[KNOT_CTL_IDX_TYPE] = "role";
if (slave) {
data[KNOT_CTL_IDX_DATA] = "slave";
} else {
data[KNOT_CTL_IDX_DATA] = "master";
}
ret = knot_ctl_send(args->ctl, type, &data);
if (ret != KNOT_EOK) {
return ret;
} else {
type = KNOT_CTL_TYPE_EXTRA;
}
}
if (MATCH_OR_FILTER(args, CTL_FILTER_STATUS_SERIAL)) {
data[KNOT_CTL_IDX_TYPE] = "serial";
rcu_read_lock();
if (zone->contents == NULL) {
ret = snprintf(buff, sizeof(buff), STATUS_EMPTY);
} else {
knot_rdataset_t *soa = node_rdataset(zone->contents->apex,
KNOT_RRTYPE_SOA);
ret = snprintf(buff, sizeof(buff), "%u", knot_soa_serial(soa->rdata));
}
rcu_read_unlock();
if (ret < 0 || ret >= sizeof(buff)) {
return KNOT_ESPACE;
}
data[KNOT_CTL_IDX_DATA] = buff;
ret = knot_ctl_send(args->ctl, type, &data);
if (ret != KNOT_EOK) {
return ret;
} else {
type = KNOT_CTL_TYPE_EXTRA;
}
}
if (MATCH_OR_FILTER(args, CTL_FILTER_STATUS_TRANSACTION)) {
data[KNOT_CTL_IDX_TYPE] = "transaction";
const char *value = STATUS_EMPTY;
if (zone->control_update != NULL) {
value = (zone->control_update->flags & UPDATE_WFEV) ?
"open-external" : "open";
}
data[KNOT_CTL_IDX_DATA] = value;
ret = knot_ctl_send(args->ctl, type, &data);
if (ret != KNOT_EOK) {
return ret;
} else {
type = KNOT_CTL_TYPE_EXTRA;
}
}
const bool ufrozen = zone->events.ufrozen;
if (MATCH_OR_FILTER(args, CTL_FILTER_STATUS_FREEZE)) {
data[KNOT_CTL_IDX_TYPE] = "freeze";
if (ufrozen) {
if (zone_events_get_time(zone, ZONE_EVENT_UTHAW) < time(NULL)) {
data[KNOT_CTL_IDX_DATA] = "yes";
} else {
data[KNOT_CTL_IDX_DATA] = "thawing";
}
} else {
if (zone_events_get_time(zone, ZONE_EVENT_UFREEZE) < time(NULL)) {
data[KNOT_CTL_IDX_DATA] = STATUS_EMPTY;
} else {
data[KNOT_CTL_IDX_DATA] = "freezing";
}
}
ret = knot_ctl_send(args->ctl, type, &data);
if (ret != KNOT_EOK) {
return ret;
} else {
type = KNOT_CTL_TYPE_EXTRA;
}
data[KNOT_CTL_IDX_TYPE] = "XFR-freeze";
if (zone_get_flag(zone, ZONE_XFR_FROZEN, false)) {
data[KNOT_CTL_IDX_DATA] = "yes";
} else {
data[KNOT_CTL_IDX_DATA] = STATUS_EMPTY;
}
ret = knot_ctl_send(args->ctl, type, &data);
if (ret != KNOT_EOK) {
return ret;
}
}
if (MATCH_OR_FILTER(args, CTL_FILTER_STATUS_CATALOG)) {
char buf[1 + KNOT_DNAME_TXT_MAXLEN + 1 + CATALOG_GROUP_MAXLEN + 1] = "";
data[KNOT_CTL_IDX_TYPE] = "catalog";
data[KNOT_CTL_IDX_DATA] = buf;
if (member) {
const knot_dname_t *catz;
const char *group;
void *to_free;
ret = catalog_get_catz(zone_catalog(zone), zone->name,
&catz, &group, &to_free);
if (ret == KNOT_EOK) {
if (knot_dname_to_str(buf, catz, sizeof(buf)) == NULL) {
buf[0] = '\0';
}
if (group[0] != '\0') {
size_t idx = strlcat(buf, "#", sizeof(buf));
(void)strlcat(buf + idx, group, sizeof(buf) - idx);
}
free(to_free);
}
} else {
conf_val_t val = conf_zone_get(conf(), C_CATALOG_ROLE, zone->name);
switch (conf_opt(&val)) {
case CATALOG_ROLE_INTERPRET:
data[KNOT_CTL_IDX_DATA] = "interpret";
break;
case CATALOG_ROLE_GENERATE:
data[KNOT_CTL_IDX_DATA] = "generate";
break;
case CATALOG_ROLE_MEMBER:
buf[0] = '@';
val = conf_zone_get(conf(), C_CATALOG_ZONE, zone->name);
if (knot_dname_to_str(buf + 1, conf_dname(&val), sizeof(buf) - 1) == NULL) {
buf[1] = '\0';
}
val = conf_zone_get(conf(), C_CATALOG_GROUP, zone->name);
if (val.code == KNOT_EOK) {
size_t idx = strlcat(buf, "#", sizeof(buf));
(void)strlcat(buf + idx, conf_str(&val), sizeof(buf) - idx);
}
break;
default:
data[KNOT_CTL_IDX_DATA] = STATUS_EMPTY;
}
}
ret = knot_ctl_send(args->ctl, type, &data);
if (ret != KNOT_EOK) {
return ret;
} else {
type = KNOT_CTL_TYPE_EXTRA;
}
}
if (MATCH_OR_FILTER(args, CTL_FILTER_STATUS_EVENTS)) {
for (zone_event_type_t i = 0; i < ZONE_EVENT_COUNT; i++) {
// Events not worth showing or used elsewhere.
if (i == ZONE_EVENT_UFREEZE || i == ZONE_EVENT_UTHAW) {
continue;
}
data[KNOT_CTL_IDX_TYPE] = zone_events_get_name(i);
time_t ev_time = zone_events_get_time(zone, i);
time_t running = zone->events.running;
knot_time_print_t format = TIME_PRINT_HUMAN_MIXED;
if (ctl_has_flag(args->data[KNOT_CTL_IDX_FLAGS],
CTL_FILTER_STATUS_UNIXTIME)) {
format = TIME_PRINT_UNIX;
}
if (running && zone->events.type == i) {
char val_str[16];
ret = knot_time_print(format, running, val_str, sizeof(val_str));
if (ret == 0) {
ret = snprintf(buff, sizeof(buff), "running(%s)", val_str);
}
} else if (ev_time <= 0) {
ret = snprintf(buff, sizeof(buff), STATUS_EMPTY);
} else if (ev_time <= time(NULL)) {
bool frozen = ufrozen && ufreeze_applies(i);
char val_str[16];
ret = knot_time_print(format, ev_time, val_str, sizeof(val_str));
if (ret == 0) {
ret = snprintf(buff, sizeof(buff), "%s(%s)",
frozen ? "frozen" : "pending", val_str);
}
} else {
ret = knot_time_print(format, ev_time, buff, sizeof(buff));
}
if (ret < 0 || ret >= sizeof(buff)) {
return KNOT_ESPACE;
}
data[KNOT_CTL_IDX_DATA] = buff;
ret = knot_ctl_send(args->ctl, type, &data);
if (ret != KNOT_EOK) {
return ret;
} else {
type = KNOT_CTL_TYPE_EXTRA;
}
}
}
return KNOT_EOK;
}
static int zone_reload(zone_t *zone, _unused_ ctl_args_t *args)
{
if (ctl_has_flag(args->data[KNOT_CTL_IDX_FLAGS], CTL_FLAG_FORCE)) {
return zone_reload_modules(conf(), args->server, zone->name);
}
return schedule_trigger(zone, args, ZONE_EVENT_LOAD, true);
}
static int zone_refresh(zone_t *zone, _unused_ ctl_args_t *args)
{
if (!zone_is_slave(conf(), zone)) {
args->suppress = true;
return KNOT_ENOTSUP;
}
zone->zonefile.bootstrap_cnt = 0; // restart delays
return schedule_trigger(zone, args, ZONE_EVENT_REFRESH, true);
}
static int zone_retransfer(zone_t *zone, _unused_ ctl_args_t *args)
{
if (!zone_is_slave(conf(), zone)) {
args->suppress = true;
return KNOT_ENOTSUP;
}
zone_set_flag(zone, ZONE_FORCE_AXFR);
zone->zonefile.bootstrap_cnt = 0; // restart delays
return schedule_trigger(zone, args, ZONE_EVENT_REFRESH, true);
}
static void common_failure(_unused_ ctl_args_t *args, int err, const char *msg)
{
if (args->data[KNOT_CTL_IDX_ZONE] == NULL) {
log_ctl_error("%s", msg);
} else {
log_ctl_zone_str_error(args->data[KNOT_CTL_IDX_ZONE], "%s", msg);
}
/* Warning: zone name in the control command params discarded here. */
args->data[KNOT_CTL_IDX_ZONE] = NULL;
ctl_send_error(args, knot_strerror(err));
}
static int zone_notify(zone_t *zone, _unused_ ctl_args_t *args)
{
zone_notifailed_clear(zone);
return schedule_trigger(zone, args, ZONE_EVENT_NOTIFY, true);
}
static int zone_flush(zone_t *zone, ctl_args_t *args)
{
zone_set_flag(zone, ZONE_USER_FLUSH);
if (ctl_has_flag(args->data[KNOT_CTL_IDX_FLAGS], CTL_FLAG_FORCE)) {
zone_set_flag(zone, ZONE_FORCE_FLUSH);
}
return schedule_trigger(zone, args, ZONE_EVENT_FLUSH, true);
}
static int zone_flush_outdir(zone_t *zone, ctl_args_t *args)
{
rcu_read_lock();
int ret = zone_dump_to_dir(conf(), zone, args->data[KNOT_CTL_IDX_DATA]);
rcu_read_unlock();
if (ret != KNOT_EOK) {
log_zone_warning(zone->name, "failed to update zone file (%s)",
knot_strerror(ret));
}
return ret;
}
static int zones_apply_flush(ctl_args_t *args)
{
if (MATCH_AND_FILTER(args, CTL_FILTER_FLUSH_OUTDIR)) {
const char *dir = args->data[KNOT_CTL_IDX_DATA];
if (dir == NULL) {
char *msg = "flush, output directory not specified";
common_failure(args, KNOT_ENOPARAM, msg);
return KNOT_CTL_EZONE;
}
int ret = make_path(dir, S_IRUSR | S_IWUSR | S_IXUSR |
S_IRGRP | S_IWGRP | S_IXGRP);
if (ret != KNOT_EOK) {
char *msg = sprintf_alloc("flush, failed to create output directory '%s' (%s)",
dir, knot_strerror(ret));
common_failure(args, ret, msg);
free(msg);
return KNOT_CTL_EZONE;
}
return zones_apply(args, zone_flush_outdir);
}
return zones_apply(args, zone_flush);
}
static void report_insufficient_backup(ctl_args_t *args, zone_backup_ctx_t *ctx)
{
const char *msg = "missing in backup:%s";
char list[128]; // It must hold the longest list of components + 1.
int remain = sizeof(list);
char *buf = list;
for (const backup_filter_list_t *item = backup_filters;
item->name != NULL; item++) {
if (ctx->backup_params & item->param) {
int n = snprintf(buf, remain, " %s,", item->name);
buf += n;
remain -= n;
}
}
assert(remain > 1);
assert(buf > list);
*(--buf) = '\0';
if (args->data[KNOT_CTL_IDX_ZONE] == NULL) {
log_warning(msg, list);
} else {
log_zone_str_warning(args->data[KNOT_CTL_IDX_ZONE], msg, list);
}
}
static int init_backup(ctl_args_t *args, bool restore_mode)
{
if (!MATCH_AND_FILTER(args, CTL_FILTER_BACKUP_OUTDIR)) {
return KNOT_ENOPARAM;
}
// Make sure that the backup outdir is not the same as the server DB storage.
conf_val_t db_storage_val = conf_db_param(conf(), C_STORAGE);
const char *db_storage = conf_str(&db_storage_val);
const char *backup_dir = args->data[KNOT_CTL_IDX_DATA];
if (backup_dir == NULL) {
return KNOT_ENOPARAM;
}
if (same_path(backup_dir, db_storage)) {
char *msg = sprintf_alloc("%s the database storage directory not allowed",
restore_mode ? "restore from" : "backup to");
if (args->data[KNOT_CTL_IDX_ZONE] == NULL) {
log_ctl_error("%s", msg);
} else {
log_ctl_zone_str_error(args->data[KNOT_CTL_IDX_ZONE], "%s", msg);
}
free(msg);
return KNOT_EINVAL;
}
// Evaluate filters (and possibly fail) before writing to the filesystem.
knot_backup_params_t filters = 0;
knot_backup_params_t dflts = restore_mode ? BACKUP_PARAM_DFLT_R : BACKUP_PARAM_DFLT_B;
// Filter '+keysonly' silently changes all defaults to '+no...'.
dflts = MATCH_AND_FILTER(args, CTL_FILTER_BACKUP_KEYSONLY) ? BACKUP_PARAM_EMPTY : dflts;
for (const backup_filter_list_t *item = backup_filters; item->name != NULL; item++) {
if (!eval_backup_filters(args, &filters, item, dflts)) {
return KNOT_EXPARAM;
}
}
// Priority of '+kaspdb' over '+keysonly'.
filters &= ~((bool)(filters & BACKUP_PARAM_KASPDB) * BACKUP_PARAM_KEYSONLY);
bool forced = ctl_has_flag(args->data[KNOT_CTL_IDX_FLAGS], CTL_FLAG_FORCE);
zone_backup_ctx_t *ctx;
// The present timer db size is not up-to-date, use the maximum one.
conf_val_t timer_db_size = conf_db_param(conf(), C_TIMER_DB_MAX_SIZE);
int ret = zone_backup_init(restore_mode, filters, forced, backup_dir,
knot_lmdb_copy_size(&args->server->kaspdb),
conf_int(&timer_db_size),
knot_lmdb_copy_size(&args->server->journaldb),
knot_lmdb_copy_size(&args->server->catalog.db),
&ctx);
if (ret == KNOT_EBACKUPDATA) {
report_insufficient_backup(args, ctx);
free(ctx);
}
if (ret != KNOT_EOK) {
return ret;
}
assert(ctx != NULL);
zone_backups_add(&args->server->backup_ctxs, ctx);
return ret;
}
static zone_backup_ctx_t *latest_backup_ctx(ctl_args_t *args)
{
// no need to mutex in this case
return (zone_backup_ctx_t *)TAIL(args->server->backup_ctxs.ctxs);
}
static int deinit_backup(ctl_args_t *args)
{
return zone_backup_deinit(latest_backup_ctx(args));
}
static int zone_keys_load(zone_t *zone, _unused_ ctl_args_t *args);
static int zone_backup_cmd(zone_t *zone, ctl_args_t *args)
{
zone_backup_ctx_t *ctx = latest_backup_ctx(args);
if (!ctx->restore_mode && ctx->failed) {
// No need to proceed with already faulty backup.
return KNOT_EOK;
}
int ret = KNOT_EOK;
pthread_mutex_lock(&zone->cu_lock);
if (ATOMIC_GET(zone->backup_ctx) != NULL) {
log_zone_warning(zone->name, "backup or restore already in progress, skipping zone");
ctx->failed = true;
ret = KNOT_EPROGRESS;
}
if (ctx->restore_mode && zone->control_update != NULL && ret == KNOT_EOK) {
log_zone_warning(zone->name, "restoring backup not possible due to open control transaction");
ctx->failed = true;
ret = KNOT_TXN_EEXISTS;
}
if (ret == KNOT_EOK) {
ATOMIC_SET(zone->backup_ctx, ctx);
}
pthread_mutex_unlock(&zone->cu_lock);
ctx->zone_count++;
if (!ctx->backup_global && ret == KNOT_EOK) {
ret = global_backup(ctx, zone_catalog(zone), zone->name);
}
bool finish = false;
if ((ctx->backup_params & BACKUP_PARAM_KEYSONLY) && ret == KNOT_EOK) {
ret = zone_backup_keysonly(ctx, conf(), zone);
if (ctx->restore_mode && ret == KNOT_EOK) {
ret = zone_keys_load(zone, args);
}
if (!(ctx->backup_params & BACKUP_PARAM_EVENT)) {
finish = true;
}
}
if (ret != KNOT_EOK || finish) {
ATOMIC_SET(zone->backup_ctx, NULL);
return ret;
}
pthread_mutex_lock(&ctx->readers_mutex);
ctx->readers++;
pthread_mutex_unlock(&ctx->readers_mutex);
return schedule_trigger(zone, args, ZONE_EVENT_BACKUP, true);
}
static int zones_apply_backup(ctl_args_t *args, bool restore_mode)
{
int ret_deinit;
int ret = init_backup(args, restore_mode);
if (ret != KNOT_EOK) {
char *msg = sprintf_alloc("%s init failed (%s)",
restore_mode ? "restore" : "backup",
knot_strerror(ret));
common_failure(args, ret, msg);
free (msg);
return KNOT_CTL_EZONE;
}
zone_backup_ctx_t *ctx = latest_backup_ctx(args);
/* QUIC - server key and cert backup. */
ret = backup_quic(ctx, args->server->quic_active || args->server->tls_active);
if (ret != KNOT_EOK) {
log_ctl_error("control, QUIC %s error (%s)",
restore_mode ? "restore" : "backup",
knot_strerror(ret));
ctl_send_error(args, knot_strerror(ret));
ret = KNOT_EOK;
goto done;
}
/* Global catalog zones backup. */
if (args->data[KNOT_CTL_IDX_ZONE] == NULL) {
ctx->backup_global = true;
ret = global_backup(ctx, &args->server->catalog, NULL);
if (ret != KNOT_EOK) {
log_ctl_error("control, error (%s)", knot_strerror(ret));
ctl_send_error(args, knot_strerror(ret));
ret = KNOT_EOK;
goto done;
}
}
ret = zones_apply(args, zone_backup_cmd);
done:
ret_deinit = deinit_backup(args);
return ret != KNOT_EOK ? ret : ret_deinit;
}
static int zone_sign(zone_t *zone, _unused_ ctl_args_t *args)
{
conf_val_t val = conf_zone_get(conf(), C_DNSSEC_SIGNING, zone->name);
if (!conf_bool(&val)) {
args->suppress = true;
return KNOT_ENOTSUP;
}
zone_set_flag(zone, ZONE_FORCE_RESIGN);
return schedule_trigger(zone, args, ZONE_EVENT_DNSSEC, true);
}
static int zone_validate(zone_t *zone, _unused_ ctl_args_t *args)
{
return schedule_trigger(zone, args, ZONE_EVENT_VALIDATE, true);
}
static int zone_keys_load(zone_t *zone, _unused_ ctl_args_t *args)
{
conf_val_t val = conf_zone_get(conf(), C_DNSSEC_SIGNING, zone->name);
if (!conf_bool(&val)) {
args->suppress = true;
return KNOT_ENOTSUP;
}
if (zone->contents == NULL) {
log_zone_notice(zone->name, "zone is not loaded yet");
return KNOT_EOK;
}
return schedule_trigger(zone, args, ZONE_EVENT_DNSSEC, true);
}
static int zone_key_roll(zone_t *zone, ctl_args_t *args)
{
conf_val_t val = conf_zone_get(conf(), C_DNSSEC_SIGNING, zone->name);
if (!conf_bool(&val)) {
args->suppress = true;
return KNOT_ENOTSUP;
}
const char *key_type = args->data[KNOT_CTL_IDX_TYPE];
if (strncasecmp(key_type, CMD_ROLLOVER_KSK, 3) == 0) {
zone_set_flag(zone, ZONE_FORCE_KSK_ROLL);
} else if (strncasecmp(key_type, CMD_ROLLOVER_ZSK, 3) == 0) {
zone_set_flag(zone, ZONE_FORCE_ZSK_ROLL);
} else {
return KNOT_EINVAL;
}
return schedule_trigger(zone, args, ZONE_EVENT_DNSSEC, true);
}
static int zone_ksk_sbm_confirm(zone_t *zone, _unused_ ctl_args_t *args)
{
kdnssec_ctx_t ctx = { 0 };
int ret = kdnssec_ctx_init(conf(), &ctx, zone->name, zone_kaspdb(zone), NULL);
if (ret != KNOT_EOK) {
return ret;
}
ret = knot_dnssec_ksk_sbm_confirm(&ctx, 0);
kdnssec_ctx_deinit(&ctx);
conf_val_t val = conf_zone_get(conf(), C_DNSSEC_SIGNING, zone->name);
if (ret == KNOT_EOK && conf_bool(&val)) {
// NOT zone_events_schedule_user() or schedule_trigger(), intentionally!
zone_events_schedule_now(zone, ZONE_EVENT_DNSSEC);
}
return ret;
}
static int zone_freeze(zone_t *zone, _unused_ ctl_args_t *args)
{
return schedule_trigger(zone, args, ZONE_EVENT_UFREEZE, false);
}
static int zone_thaw(zone_t *zone, _unused_ ctl_args_t *args)
{
return schedule_trigger(zone, args, ZONE_EVENT_UTHAW, false);
}
static int zone_xfr_freeze(zone_t *zone, _unused_ ctl_args_t *args)
{
zone_set_flag(zone, ZONE_XFR_FROZEN);
log_zone_info(zone->name, "outgoing XFR frozen");
return KNOT_EOK;
}
static int zone_xfr_thaw(zone_t *zone, _unused_ ctl_args_t *args)
{
zone_unset_flag(zone, ZONE_XFR_FROZEN);
log_zone_info(zone->name, "outgoing XFR unfrozen");
return KNOT_EOK;
}
static int zone_txn_begin_l(zone_t *zone, _unused_ ctl_args_t *args)
{
if (zone->control_update != NULL || conf()->io.txn != NULL) {
return KNOT_TXN_EEXISTS;
}
struct zone_backup_ctx *backup_ctx = ATOMIC_GET(zone->backup_ctx);
if (backup_ctx != NULL && backup_ctx->restore_mode) {
log_zone_warning(zone->name, "zone restore pending, try opening control transaction later");
return KNOT_ETRYAGAIN;
}
if (zone->events.running && zone->events.type >= 0 && zone->events.blocking[zone->events.type] != NULL) {
log_zone_warning(zone->name, "some blocking event running, try opening control transaction later");
return KNOT_ETRYAGAIN;
}
zone->control_update = malloc(sizeof(zone_update_t));
if (zone->control_update == NULL) {
return KNOT_ENOMEM;
}
zone_update_flags_t type = (zone->contents == NULL) ? UPDATE_FULL : UPDATE_INCREMENTAL;
zone_update_flags_t strict = (MATCH_AND_FILTER(args, CTL_FILTER_BEGIN_BENEVOLENT)) ? 0 : UPDATE_STRICT;
int ret = zone_update_init(zone->control_update, zone, type | strict);
if (ret != KNOT_EOK) {
free(zone->control_update);
zone->control_update = NULL;
}
return ret;
}
static int zone_txn_begin(zone_t *zone, ctl_args_t *args)
{
pthread_mutex_lock(&zone->cu_lock);
int ret = zone_txn_begin_l(zone, args);
pthread_mutex_unlock(&zone->cu_lock);
return ret;
}
static int zone_txn_commit_l(zone_t *zone, _unused_ ctl_args_t *args)
{
if (zone->control_update == NULL) {
args->suppress = true;
return KNOT_TXN_ENOTEXISTS;
}
if (zone->control_update->flags & UPDATE_WFEV) {
zone->control_update->flags |= UPDATE_EVOK;
knot_sem_post(&zone->control_update->external);
return KNOT_EOK;
}
int ret = zone_update_semcheck(conf(), zone->control_update);
if (ret != KNOT_EOK) {
return ret; // Recoverable error.
}
// NOOP if empty changeset/contents.
if (zone_update_no_change(zone->control_update)) {
zone_control_clear(zone);
return KNOT_EOK;
}
// Sign update.
conf_val_t val = conf_zone_get(conf(), C_DNSSEC_SIGNING, zone->name);
bool dnssec_enable = conf_bool(&val);
val = conf_zone_get(conf(), C_ZONEMD_GENERATE, zone->name);
unsigned digest_alg = conf_opt(&val);
if (dnssec_enable) {
if (zone->control_update->flags & UPDATE_FULL) {
zone_sign_reschedule_t resch = { 0 };
zone_sign_roll_flags_t rflags = KEY_ROLL_ALLOW_ALL;
ret = knot_dnssec_zone_sign(zone->control_update, conf(), 0, rflags, 0, &resch);
event_dnssec_reschedule(conf(), zone, &resch, false);
} else {
ret = knot_dnssec_sign_update(zone->control_update, conf());
}
} else if (digest_alg != ZONE_DIGEST_NONE) {
if (zone_update_to(zone->control_update) == NULL) {
ret = zone_update_increment_soa(zone->control_update, conf());
}
if (ret == KNOT_EOK) {
ret = zone_update_add_digest(zone->control_update, digest_alg, false);
}
}
if (ret != KNOT_EOK) {
zone_control_clear(zone);
return ret;
}
ret = zone_update_commit(conf(), zone->control_update);
if (ret != KNOT_EOK) {
zone_control_clear(zone);
return ret;
}
free(zone->control_update);
zone->control_update = NULL;
zone_schedule_notify(conf(), zone, 0);
return KNOT_EOK;
}
static int zone_txn_commit(zone_t *zone, ctl_args_t *args)
{
pthread_mutex_lock(&zone->cu_lock);
int ret = zone_txn_commit_l(zone, args);
pthread_mutex_unlock(&zone->cu_lock);
return ret;
}
static int zone_txn_abort_l(zone_t *zone, _unused_ ctl_args_t *args)
{
if (zone->control_update == NULL) {
args->suppress = true;
return KNOT_TXN_ENOTEXISTS;
}
if (zone->control_update->flags & UPDATE_WFEV) {
knot_sem_post(&zone->control_update->external);
return KNOT_EOK;
}
zone_control_clear(zone);
return KNOT_EOK;
}
static int zone_txn_abort(zone_t *zone, ctl_args_t *args)
{
pthread_mutex_lock(&zone->cu_lock);
int ret = zone_txn_abort_l(zone, args);
pthread_mutex_unlock(&zone->cu_lock);
return ret;
}
static int zone_serial_set(zone_t *zone, ctl_args_t *args)
{
int ret = KNOT_EOK;
const char *serial_set_str = args->data[KNOT_CTL_IDX_DATA];
const char *serial_inc_str = args->data[KNOT_CTL_IDX_TYPE];
bool forced = ctl_has_flag(args->data[KNOT_CTL_IDX_FLAGS], CTL_FLAG_FORCE);
bool serial_set_do = (serial_set_str != NULL);
bool serial_inc_do = (serial_inc_str != NULL && serial_inc_str[0] == '+');
uint32_t serial_set = 0;
if (serial_set_do) {
ret = str_to_u32(serial_set_str, &serial_set);
if (ret != KNOT_EOK) {
return ret;
}
}
rcu_read_lock();
const zone_contents_t *c = zone->contents;
if (c == NULL) {
rcu_read_unlock();
return KNOT_EEMPTYZONE;
}
uint32_t return_serial = zone_contents_serial(c);
rcu_read_unlock();
pthread_mutex_lock(&zone->cu_lock);
bool cu_exists = (zone->control_update != NULL);
if (serial_set_do && !cu_exists) {
ret = zone_txn_begin_l(zone, args);
}
if (zone->control_update != NULL && ret == KNOT_EOK &&
!node_rrtype_exists(zone->control_update->new_cont->apex, KNOT_RRTYPE_SOA)) {
ret = KNOT_EEMPTYZONE;
}
if (zone->control_update != NULL && ret == KNOT_EOK) {
return_serial = zone_update_current_serial(zone->control_update);
if (serial_inc_do) {
serial_set += return_serial;
}
if (serial_set_do) {
ret = zone_update_set_soa(zone->control_update, serial_set, !forced);
}
}
if (serial_set_do && !cu_exists) {
if (ret == KNOT_EOK) {
ret = zone_txn_commit_l(zone, args);
}
if (ret != KNOT_EOK) {
zone_txn_abort_l(zone, args);
}
}
pthread_mutex_unlock(&zone->cu_lock);
if (!serial_set_do && ret == KNOT_EOK) {
send_ctx_t *ctx = &ctl_globals[args->thread_idx].send_ctx;
ctx->data[KNOT_CTL_IDX_DATA] = ctx->ttl;
(void)snprintf(ctx->ttl, sizeof(ctx->ttl), "%u", return_serial);
ret = knot_ctl_send(args->ctl, KNOT_CTL_TYPE_DATA, &ctx->data);
}
return ret;
}
static int init_send_ctx(send_ctx_t *ctx, const knot_dname_t *zone_name,
ctl_args_t *args)
{
memset(ctx, 0, sizeof(*ctx));
ctx->args = args;
// Set the dump style.
ctx->style.show_ttl = true;
ctx->style.original_ttl = true;
ctx->style.human_timestamp = true;
// Set the output data buffers.
ctx->data[KNOT_CTL_IDX_ZONE] = ctx->zone;
ctx->data[KNOT_CTL_IDX_OWNER] = ctx->owner;
ctx->data[KNOT_CTL_IDX_TTL] = ctx->ttl;
ctx->data[KNOT_CTL_IDX_TYPE] = ctx->type;
ctx->data[KNOT_CTL_IDX_DATA] = ctx->rdata;
// Set the ZONE.
if (knot_dname_to_str(ctx->zone, zone_name, sizeof(ctx->zone)) == NULL) {
return KNOT_EINVAL;
}
// Set the TYPE filter.
if (args->data[KNOT_CTL_IDX_TYPE] != NULL) {
uint16_t type;
if (knot_rrtype_from_string(args->data[KNOT_CTL_IDX_TYPE], &type) != 0) {
return KNOT_EINVAL;
}
ctx->type_filter = type;
} else {
ctx->type_filter = -1;
}
return KNOT_EOK;
}
static int send_rrset(const knot_rrset_t *rrset, send_ctx_t *ctx)
{
if (rrset->type != KNOT_RRTYPE_RRSIG) {
int ret = snprintf(ctx->ttl, sizeof(ctx->ttl), "%u", rrset->ttl);
if (ret <= 0 || ret >= sizeof(ctx->ttl)) {
return KNOT_ESPACE;
}
}
if (knot_rrtype_to_string(rrset->type, ctx->type, sizeof(ctx->type)) < 0) {
return KNOT_ESPACE;
}
for (size_t i = 0; i < rrset->rrs.count; ++i) {
if (rrset->type == KNOT_RRTYPE_RRSIG) {
int ret = snprintf(ctx->ttl, sizeof(ctx->ttl), "%u",
knot_rrsig_original_ttl(knot_rdataset_at(&rrset->rrs, i)));
if (ret <= 0 || ret >= sizeof(ctx->ttl)) {
return KNOT_ESPACE;
}
}
int ret = knot_rrset_txt_dump_data(rrset, i, ctx->rdata,
sizeof(ctx->rdata), &ctx->style);
if (ret < 0) {
return ret;
}
ret = knot_ctl_send(ctx->args->ctl, KNOT_CTL_TYPE_DATA, &ctx->data);
if (ret != KNOT_EOK) {
return ret;
}
}
return KNOT_EOK;
}
static int send_rrset_callback(const knot_rrset_t *rrset, void *ctx_void)
{
send_ctx_t *ctx = ctx_void;
char *owner = knot_dname_to_str(ctx->owner, rrset->owner, sizeof(ctx->owner));
if (owner == NULL) {
return KNOT_EINVAL;
}
return send_rrset(rrset, ctx);
}
static int send_node(zone_node_t *node, void *ctx_void)
{
send_ctx_t *ctx = ctx_void;
if (knot_dname_to_str(ctx->owner, node->owner, sizeof(ctx->owner)) == NULL) {
return KNOT_EINVAL;
}
for (size_t i = 0; i < node->rrset_count; ++i) {
knot_rrset_t rrset = node_rrset_at(node, i);
// Check for requested TYPE.
if (ctx->type_filter != -1 && rrset.type != ctx->type_filter) {
continue;
}
int ret = send_rrset(&rrset, ctx);
if (ret != KNOT_EOK) {
return ret;
}
}
return KNOT_EOK;
}
static int get_owner(uint8_t *out, size_t out_len, knot_dname_t *origin,
ctl_args_t *args)
{
const char *owner = args->data[KNOT_CTL_IDX_OWNER];
assert(owner != NULL);
bool fqdn = false;
size_t prefix_len = 0;
size_t owner_len = strlen(owner);
if (owner_len > 0 && (owner_len != 1 || owner[0] != '@')) {
// Check if the owner is FQDN.
if (owner[owner_len - 1] == '.') {
fqdn = true;
}
if (knot_dname_from_str(out, owner, out_len) == NULL) {
return KNOT_EINVAL;
}
knot_dname_to_lower(out);
prefix_len = knot_dname_size(out);
if (prefix_len == 0) {
return KNOT_EINVAL;
}
// Ignore trailing dot.
prefix_len--;
}
// Append the origin.
if (!fqdn) {
size_t origin_len = knot_dname_size(origin);
if (origin_len == 0 || origin_len > out_len - prefix_len) {
return KNOT_EINVAL;
}
memcpy(out + prefix_len, origin, origin_len);
}
return KNOT_EOK;
}
static int zone_read(zone_t *zone, ctl_args_t *args)
{
send_ctx_t *ctx = &ctl_globals[args->thread_idx].send_ctx;
int ret = init_send_ctx(ctx, zone->name, args);
if (ret != KNOT_EOK) {
return ret;
}
rcu_read_lock();
zone_contents_t *contents = zone->contents;
if (args->data[KNOT_CTL_IDX_OWNER] != NULL) {
knot_dname_storage_t owner;
ret = get_owner(owner, sizeof(owner), zone->name, args);
if (ret != KNOT_EOK) {
rcu_read_unlock();
return ret;
}
const zone_node_t *node = zone_contents_node_or_nsec3(contents, owner);
if (node == NULL) {
rcu_read_unlock();
return KNOT_ENONODE;
}
ret = send_node((zone_node_t *)node, ctx);
} else if (contents != NULL) {
ret = zone_contents_apply(contents, send_node, ctx);
if (ret == KNOT_EOK) {
ret = zone_contents_nsec3_apply(contents, send_node, ctx);
}
}
rcu_read_unlock();
return ret;
}
static int zone_flag_txn_get(zone_t *zone, ctl_args_t *args, const char *flag)
{
if (zone->control_update == NULL) {
args->suppress = true;
return KNOT_TXN_ENOTEXISTS;
}
send_ctx_t *ctx = &ctl_globals[args->thread_idx].send_ctx;
int ret = init_send_ctx(ctx, zone->name, args);
if (ret != KNOT_EOK) {
return ret;
}
ctx->data[KNOT_CTL_IDX_FLAGS] = flag;
if (args->data[KNOT_CTL_IDX_OWNER] != NULL) {
knot_dname_storage_t owner;
ret = get_owner(owner, sizeof(owner), zone->name, args);
if (ret != KNOT_EOK) {
return ret;
}
const zone_node_t *node = zone_contents_node_or_nsec3(zone->control_update->new_cont, owner);
if (node == NULL) {
return KNOT_ENONODE;
}
ret = send_node((zone_node_t *)node, ctx);
} else {
zone_tree_it_t it = { 0 };
ret = zone_tree_it_double_begin(zone->control_update->new_cont->nodes,
zone->control_update->new_cont->nsec3_nodes,
&it);
while (ret == KNOT_EOK && !zone_tree_it_finished(&it)) {
ret = send_node(zone_tree_it_val(&it), ctx);
zone_tree_it_next(&it);
}
zone_tree_it_free(&it);
}
return ret;
}
static int zone_txn_get(zone_t *zone, ctl_args_t *args)
{
pthread_mutex_lock(&zone->cu_lock);
int ret = zone_flag_txn_get(zone, args, NULL);
pthread_mutex_unlock(&zone->cu_lock);
return ret;
}
static int zone_txn_diff_l(zone_t *zone, ctl_args_t *args)
{
if (zone->control_update == NULL) {
args->suppress = true;
return KNOT_TXN_ENOTEXISTS;
}
// FULL update has no changeset to print, do a 'get' instead.
if (zone->control_update->flags & UPDATE_FULL) {
return zone_flag_txn_get(zone, args, CTL_FILTER_DIFF_ADD_R);
}
send_ctx_t *ctx = &ctl_globals[args->thread_idx].send_ctx;
int ret = init_send_ctx(ctx, zone->name, args);
if (ret == KNOT_EOK) {
ctx->data[KNOT_CTL_IDX_FILTERS] = CTL_FILTER_DIFF_REM_R;
ret = zone_update_foreach(zone->control_update, false, send_rrset_callback, ctx);
}
if (ret == KNOT_EOK) {
ctx->data[KNOT_CTL_IDX_FILTERS] = CTL_FILTER_DIFF_ADD_R;
ret = zone_update_foreach(zone->control_update, true, send_rrset_callback, ctx);
}
return ret;
}
static int zone_txn_diff(zone_t *zone, ctl_args_t *args)
{
pthread_mutex_lock(&zone->cu_lock);
int ret = zone_txn_diff_l(zone, args);
pthread_mutex_unlock(&zone->cu_lock);
return ret;
}
static int get_ttl(zone_t *zone, ctl_args_t *args, uint32_t *ttl)
{
knot_dname_storage_t owner;
int ret = get_owner(owner, sizeof(owner), zone->name, args);
if (ret != KNOT_EOK) {
return ret;
}
const zone_node_t *node = zone_contents_node_or_nsec3(zone->control_update->new_cont, owner);
if (node == NULL) {
return KNOT_ENOTTL;
}
uint16_t type;
if (knot_rrtype_from_string(args->data[KNOT_CTL_IDX_TYPE], &type) != 0) {
return KNOT_EINVAL;
}
knot_rrset_t rrset = node_rrset(node, type);
if (knot_rrset_empty(&rrset)) {
return KNOT_ENOTTL;
}
*ttl = rrset.ttl;
return KNOT_EOK;
}
static int create_rrset(knot_rrset_t **rrset, zone_t *zone, ctl_args_t *args,
bool need_ttl)
{
knot_dname_txt_storage_t origin_buff;
char *origin = knot_dname_to_str(origin_buff, zone->name, sizeof(origin_buff));
if (origin == NULL) {
return KNOT_EINVAL;
}
const char *owner = args->data[KNOT_CTL_IDX_OWNER];
const char *type = args->data[KNOT_CTL_IDX_TYPE];
const char *data = args->data[KNOT_CTL_IDX_DATA];
const char *ttl = need_ttl ? args->data[KNOT_CTL_IDX_TTL] : NULL;
// Prepare a buffer for a reconstructed record.
const size_t buff_len = sizeof(ctl_globals[args->thread_idx].txt_rr);
char *buff = ctl_globals[args->thread_idx].txt_rr;
// Choose default TTL if none was specified.
uint32_t default_ttl = 0;
if (ttl == NULL && need_ttl) {
if (get_ttl(zone, args, &default_ttl) != KNOT_EOK) {
conf_val_t val = conf_zone_get(conf(), C_DEFAULT_TTL, zone->name);
default_ttl = conf_int(&val);
}
}
// Reconstruct the record.
int ret = snprintf(buff, buff_len, "%s %s %s %s\n",
(owner != NULL ? owner : ""),
(ttl != NULL ? ttl : ""),
(type != NULL ? type : ""),
(data != NULL ? data : ""));
if (ret <= 0 || ret >= buff_len) {
return KNOT_ESPACE;
}
size_t rdata_len = ret;
// Parse the record.
zs_scanner_t *scanner = &ctl_globals[args->thread_idx].scanner;
if (zs_init(scanner, origin, KNOT_CLASS_IN, default_ttl) != 0 ||
zs_set_input_string(scanner, buff, rdata_len) != 0 ||
zs_parse_record(scanner) != 0 ||
scanner->state != ZS_STATE_DATA) {
args->data[KNOT_CTL_IDX_ZONE] = origin; // Needed if called for all zones.
if (scanner->error.code == ZS_OK) { // If not ZS_STATE_DATA.
scanner->error.code = ZS_EINVAL;
}
char msg[128] = "parser failed, ";
knot_strlcat(msg, zs_strerror(scanner->error.code), sizeof(msg));
// Send this user mistake directly to the client (don't log it).
ctl_send_error(args, msg);
ret = KNOT_EPARSEFAIL;
goto parser_failed;
}
// Create output rrset.
*rrset = knot_rrset_new(scanner->r_owner, scanner->r_type,
scanner->r_class, scanner->r_ttl, NULL);
if (*rrset == NULL) {
ret = KNOT_ENOMEM;
goto parser_failed;
}
ret = knot_rrset_add_rdata(*rrset, scanner->r_data, scanner->r_data_length,
NULL);
if (ret != KNOT_EOK) {
goto parser_failed;
}
ret = knot_rrset_rr_to_canonical(*rrset);
parser_failed:
zs_deinit(scanner);
return ret;
}
static int zone_txn_set_l(zone_t *zone, ctl_args_t *args)
{
if (zone->control_update == NULL) {
args->suppress = true;
return KNOT_TXN_ENOTEXISTS;
}
if (args->data[KNOT_CTL_IDX_OWNER] == NULL ||
args->data[KNOT_CTL_IDX_TYPE] == NULL) {
return KNOT_EINVAL;
}
knot_rrset_t *rrset;
int ret = create_rrset(&rrset, zone, args, true);
if (ret != KNOT_EOK) {
return ret;
}
ret = zone_update_add(zone->control_update, rrset);
knot_rrset_free(rrset, NULL);
return ret;
}
static int zone_txn_set(zone_t *zone, ctl_args_t *args)
{
pthread_mutex_lock(&zone->cu_lock);
int ret = zone_txn_set_l(zone, args);
pthread_mutex_unlock(&zone->cu_lock);
return ret;
}
static int zone_txn_unset_l(zone_t *zone, ctl_args_t *args)
{
if (zone->control_update == NULL) {
args->suppress = true;
return KNOT_TXN_ENOTEXISTS;
}
if (args->data[KNOT_CTL_IDX_OWNER] == NULL) {
return KNOT_EINVAL;
}
// Remove specific record.
if (args->data[KNOT_CTL_IDX_DATA] != NULL) {
if (args->data[KNOT_CTL_IDX_TYPE] == NULL) {
return KNOT_EINVAL;
}
knot_rrset_t *rrset;
int ret = create_rrset(&rrset, zone, args, false);
if (ret != KNOT_EOK) {
return ret;
}
ret = zone_update_remove(zone->control_update, rrset);
knot_rrset_free(rrset, NULL);
return ret;
} else {
knot_dname_storage_t owner;
int ret = get_owner(owner, sizeof(owner), zone->name, args);
if (ret != KNOT_EOK) {
return ret;
}
// Remove whole rrset.
if (args->data[KNOT_CTL_IDX_TYPE] != NULL) {
uint16_t type;
if (knot_rrtype_from_string(args->data[KNOT_CTL_IDX_TYPE],
&type) != 0) {
return KNOT_EINVAL;
}
return zone_update_remove_rrset(zone->control_update, owner, type);
// Remove whole node.
} else {
return zone_update_remove_node(zone->control_update, owner);
}
}
}
static int zone_txn_unset(zone_t *zone, ctl_args_t *args)
{
pthread_mutex_lock(&zone->cu_lock);
int ret = zone_txn_unset_l(zone, args);
pthread_mutex_unlock(&zone->cu_lock);
return ret;
}
static bool zone_exists(const knot_dname_t *zone, void *data)
{
assert(zone);
assert(data);
knot_zonedb_t *db = data;
return knot_zonedb_find(db, zone) != NULL;
}
static bool zone_names_distinct(const knot_dname_t *zone, void *data)
{
assert(zone);
assert(data);
knot_dname_t *zone_to_purge = data;
return !knot_dname_is_equal(zone, zone_to_purge);
}
static int drop_journal_if_orphan(const knot_dname_t *for_zone, void *ctx)
{
server_t *server = ctx;
zone_journal_t j = { &server->journaldb, for_zone };
if (!zone_exists(for_zone, server->zone_db)) {
return journal_scrape_with_md(j, false);
}
return KNOT_EOK;
}
static int purge_orphan_member_cb(const knot_dname_t *member, const knot_dname_t *owner,
const knot_dname_t *catz, const char *group, void *ctx)
{
server_t *server = ctx;
if (zone_exists(member, server->zone_db)) {
return KNOT_EOK;
}
const char *err_str = NULL;
zone_t *cat_z = knot_zonedb_find(server->zone_db, catz);
if (cat_z == NULL) {
err_str = "existing";
} else if (!cat_z->is_catalog_flag) {
err_str = "catalog";
}
if (err_str == NULL) {
return KNOT_EOK;
}
knot_dname_txt_storage_t catz_str;
(void)knot_dname_to_str(catz_str, catz, sizeof(catz_str));
log_zone_info(member, "member of a non-%s zone %s, purging",
err_str, catz_str);
// Single-purpose fake zone_t containing only minimal data.
zone_t *orphan = calloc(1, sizeof(zone_t));
if (orphan == NULL) {
return KNOT_ENOMEM;
}
orphan->name = (knot_dname_t *)member;
orphan->server = server;
const purge_flag_t params =
PURGE_ZONE_TIMERS | PURGE_ZONE_JOURNAL | PURGE_ZONE_KASPDB |
PURGE_ZONE_BEST | PURGE_ZONE_LOG;
int ret = selective_zone_purge(conf(), orphan, params);
free(orphan);
if (ret != KNOT_EOK) {
log_zone_error(member, "purge of an orphaned zone failed (%s)",
knot_strerror(ret));
}
// this deleting inside catalog DB iteration is OK, since
// the deletion happens in RW txn, while the iteration in persistent RO txn
ret = catalog_del(&server->catalog, member);
if (ret != KNOT_EOK) {
log_zone_error(member, "remove of an orphan from catalog failed (%s)",
knot_strerror(ret));
}
return KNOT_EOK;
}
static int catalog_orphans_sweep(server_t *server)
{
catalog_t *cat = &server->catalog;
int ret2 = KNOT_EOK;
int ret = catalog_begin(cat);
if (ret == KNOT_EOK) {
ret = catalog_apply(cat, NULL,
purge_orphan_member_cb,
server, false);
if (ret != KNOT_EOK) {
log_error("failed to purge orphan members data (%s)",
knot_strerror(ret));
}
ret2 = catalog_commit(cat);
synchronize_rcu();
catalog_commit_cleanup(cat);
if (ret2 != KNOT_EOK) {
log_error("failed to update catalog (%s)",
knot_strerror(ret));
}
} else {
log_error("can not open catalog for purging (%s)",
knot_strerror(ret));
}
return (ret == KNOT_EOK) ? ret2 : ret;
}
static void log_if_orphans_error(knot_dname_t *zone_name, int err, char *db_type,
bool *failed)
{
if (err == KNOT_EOK || err == KNOT_ENOENT || err == KNOT_EFILE) {
return;
}
*failed = true;
const char *error = knot_strerror(err);
char *msg = sprintf_alloc("control, failed to purge orphan from %s database (%s)",
db_type, error);
if (msg == NULL) {
return;
}
if (zone_name == NULL) {
log_error("%s", msg);
} else {
log_zone_error(zone_name, "%s", msg);
}
free(msg);
}
static int orphans_purge(ctl_args_t *args)
{
assert(args->data[KNOT_CTL_IDX_FILTERS] != NULL);
bool only_orphan = (strlen(args->data[KNOT_CTL_IDX_FILTERS]) == 1);
int ret;
bool failed = false;
if (args->data[KNOT_CTL_IDX_ZONE] == NULL) {
// Purge KASP DB.
if (only_orphan || MATCH_AND_FILTER(args, CTL_FILTER_PURGE_KASPDB)) {
ret = kasp_db_sweep(&args->server->kaspdb,
zone_exists, args->server->zone_db);
log_if_orphans_error(NULL, ret, "KASP", &failed);
}
// Purge zone journals of unconfigured zones.
if (only_orphan || MATCH_AND_FILTER(args, CTL_FILTER_PURGE_JOURNAL)) {
ret = journals_walk(&args->server->journaldb,
drop_journal_if_orphan, args->server);
log_if_orphans_error(NULL, ret, "journal", &failed);
}
// Purge timers of unconfigured zones.
if (only_orphan || MATCH_AND_FILTER(args, CTL_FILTER_PURGE_TIMERS)) {
ret = zone_timers_sweep(&args->server->timerdb,
zone_exists, args->server->zone_db);
log_if_orphans_error(NULL, ret, "timer", &failed);
}
// Purge and remove orphan members of non-existing/non-catalog zones.
if (only_orphan || MATCH_AND_FILTER(args, CTL_FILTER_PURGE_CATALOG)) {
ret = catalog_orphans_sweep(args->server);
log_if_orphans_error(NULL, ret, "catalog", &failed);
}
if (failed) {
ctl_send_error(args, knot_strerror(KNOT_CTL_EZONE));
}
} else {
knot_dname_storage_t buff;
while (true) {
knot_dname_t *zone_name =
knot_dname_from_str(buff, args->data[KNOT_CTL_IDX_ZONE],
sizeof(buff));
if (zone_name == NULL) {
log_ctl_zone_str_error(args->data[KNOT_CTL_IDX_ZONE],
"control, error (%s)",
knot_strerror(KNOT_EINVAL));
ctl_send_error(args, knot_strerror(KNOT_EINVAL));
return KNOT_EINVAL;
}
knot_dname_to_lower(zone_name);
if (!zone_exists(zone_name, args->server->zone_db)) {
// Purge KASP DB.
if (only_orphan || MATCH_AND_FILTER(args, CTL_FILTER_PURGE_KASPDB)) {
if (knot_lmdb_open(&args->server->kaspdb) == KNOT_EOK) {
ret = kasp_db_delete_all(&args->server->kaspdb, zone_name);
log_if_orphans_error(zone_name, ret, "KASP", &failed);
}
}
// Purge zone journal.
if (only_orphan || MATCH_AND_FILTER(args, CTL_FILTER_PURGE_JOURNAL)) {
zone_journal_t j = { &args->server->journaldb, zone_name };
ret = journal_scrape_with_md(j, true);
log_if_orphans_error(zone_name, ret, "journal", &failed);
}
// Purge zone timers.
if (only_orphan || MATCH_AND_FILTER(args, CTL_FILTER_PURGE_TIMERS)) {
ret = zone_timers_sweep(&args->server->timerdb,
zone_names_distinct, zone_name);
log_if_orphans_error(zone_name, ret, "timer", &failed);
}
// Purge Catalog.
if (only_orphan || MATCH_AND_FILTER(args, CTL_FILTER_PURGE_CATALOG)) {
ret = catalog_zone_purge(args->server, NULL, zone_name);
log_if_orphans_error(zone_name, ret, "catalog", &failed);
}
if (failed) {
ctl_send_error(args, knot_strerror(KNOT_ERROR));
failed = false;
}
}
// Get next zone name.
ret = knot_ctl_receive(args->ctl, &args->type, &args->data);
if (ret != KNOT_EOK || args->type != KNOT_CTL_TYPE_DATA) {
break;
}
strtolower((char *)args->data[KNOT_CTL_IDX_ZONE]);
// Log the other zones the same way as the first one from process.c.
log_ctl_zone_str_info(args->data[KNOT_CTL_IDX_ZONE],
"control, received command '%s'",
args->data[KNOT_CTL_IDX_CMD]);
}
}
return KNOT_EOK;
}
static int zone_purge(zone_t *zone, ctl_args_t *args)
{
const purge_flag_t params =
MATCH_OR_FILTER(args, CTL_FILTER_PURGE_TIMERS) * PURGE_ZONE_TIMERS |
MATCH_OR_FILTER(args, CTL_FILTER_PURGE_ZONEFILE) * PURGE_ZONE_ZONEFILE |
MATCH_OR_FILTER(args, CTL_FILTER_PURGE_JOURNAL) * PURGE_ZONE_JOURNAL |
MATCH_OR_FILTER(args, CTL_FILTER_PURGE_KASPDB) * PURGE_ZONE_KASPDB |
MATCH_OR_FILTER(args, CTL_FILTER_PURGE_CATALOG) * PURGE_ZONE_CATALOG |
MATCH_OR_FILTER(args, CTL_FILTER_PURGE_EXPIRE) * PURGE_ZONE_EXPIRE |
PURGE_ZONE_NOSYNC; // Purge even zonefiles with disabled syncing.
zone_set_flag(zone, (zone_flag_t)params);
return schedule_trigger(zone, args, ZONE_EVENT_PURGE, true);
}
int ctl_dump_ctr(stats_dump_params_t *params, stats_dump_ctx_t *ctx)
{
ctl_args_t *args = ctx->ctx;
if (ctx->item != NULL && strcasecmp(ctx->item, params->item) != 0) {
return KNOT_EOK;
}
ctx->match = true;
if (params->value == 0 &&
!ctl_has_flag(args->data[KNOT_CTL_IDX_FLAGS], CTL_FLAG_FORCE)) {
return KNOT_EOK;
}
char value[32];
int ret = snprintf(value, sizeof(value), "%"PRIu64, params->value);
if (ret <= 0 || ret >= sizeof(value)) {
return KNOT_ESPACE;
}
knot_ctl_data_t data = {
[KNOT_CTL_IDX_SECTION] = params->section,
[KNOT_CTL_IDX_ITEM] = params->item,
[KNOT_CTL_IDX_ID] = params->id,
[KNOT_CTL_IDX_ZONE] = params->zone,
[KNOT_CTL_IDX_DATA] = value,
};
knot_ctl_type_t type = (params->value_pos == 0) ?
KNOT_CTL_TYPE_DATA : KNOT_CTL_TYPE_EXTRA;
return knot_ctl_send(args->ctl, type, &data);
}
static int common_stats(ctl_args_t *args, zone_t *zone)
{
stats_dump_ctx_t dump_ctx = {
.server = args->server,
.zone = zone,
.section = args->data[KNOT_CTL_IDX_SECTION],
.item = args->data[KNOT_CTL_IDX_ITEM],
.ctx = args,
};
#define STATS_CHECK(ret, send) { \
if (ret != KNOT_EOK) { \
if ((send)) { /* Prevents duplicit zone error logs. */ \
ctl_send_error(args, knot_strerror(ret)); \
} \
return ret; \
} \
}
if (zone == NULL) {
int ret = stats_server(ctl_dump_ctr, &dump_ctx);
STATS_CHECK(ret, true);
ret = stats_xdp(ctl_dump_ctr, &dump_ctx);
STATS_CHECK(ret, true);
dump_ctx.query_modules = conf()->query_modules;
ret = stats_modules(ctl_dump_ctr, &dump_ctx);
STATS_CHECK(ret, true);
} else {
int ret = stats_zone(ctl_dump_ctr, &dump_ctx);
STATS_CHECK(ret, false);
dump_ctx.query_modules = &zone->query_modules;
ret = stats_modules(ctl_dump_ctr, &dump_ctx);
STATS_CHECK(ret, false);
}
if (!dump_ctx.match) {
STATS_CHECK(KNOT_EINVAL, zone == NULL);
}
#undef STATS_CHECK
return KNOT_EOK;
}
static int zone_stats(zone_t *zone, ctl_args_t *args)
{
return common_stats(args, zone);
}
static int ctl_zone(ctl_args_t *args, ctl_cmd_t cmd)
{
switch (cmd) {
case CTL_ZONE_STATUS:
return zones_apply(args, zone_status);
case CTL_ZONE_RELOAD:
return zones_apply(args, zone_reload);
case CTL_ZONE_REFRESH:
return zones_apply(args, zone_refresh);
case CTL_ZONE_RETRANSFER:
return zones_apply(args, zone_retransfer);
case CTL_ZONE_NOTIFY:
return zones_apply(args, zone_notify);
case CTL_ZONE_FLUSH:
return zones_apply_flush(args);
case CTL_ZONE_BACKUP:
return zones_apply_backup(args, false);
case CTL_ZONE_RESTORE:
return zones_apply_backup(args, true);
case CTL_ZONE_SIGN:
return zones_apply(args, zone_sign);
case CTL_ZONE_VALIDATE:
return zones_apply(args, zone_validate);
case CTL_ZONE_KEYS_LOAD:
return zones_apply(args, zone_keys_load);
case CTL_ZONE_KEY_ROLL:
return zones_apply(args, zone_key_roll);
case CTL_ZONE_KSK_SBM:
return zones_apply(args, zone_ksk_sbm_confirm);
case CTL_ZONE_FREEZE:
return zones_apply(args, zone_freeze);
case CTL_ZONE_THAW:
return zones_apply(args, zone_thaw);
case CTL_ZONE_XFR_FREEZE:
return zones_apply(args, zone_xfr_freeze);
case CTL_ZONE_XFR_THAW:
return zones_apply(args, zone_xfr_thaw);
case CTL_ZONE_READ:
return zones_apply(args, zone_read);
case CTL_ZONE_BEGIN:
return zones_apply(args, zone_txn_begin);
case CTL_ZONE_COMMIT:
return zones_apply(args, zone_txn_commit);
case CTL_ZONE_ABORT:
return zones_apply(args, zone_txn_abort);
case CTL_ZONE_DIFF:
return zones_apply(args, zone_txn_diff);
case CTL_ZONE_GET:
return zones_apply(args, zone_txn_get);
case CTL_ZONE_SET:
return zones_apply(args, zone_txn_set);
case CTL_ZONE_UNSET:
return zones_apply(args, zone_txn_unset);
case CTL_ZONE_PURGE:
if (MATCH_AND_FILTER(args, CTL_FILTER_PURGE_ORPHAN)) {
return orphans_purge(args);
} else {
return zones_apply(args, zone_purge);
}
case CTL_ZONE_STATS:
return zones_apply(args, zone_stats);
case CTL_ZONE_SERIAL_SET:
return zones_apply(args, zone_serial_set);
default:
assert(0);
return KNOT_EINVAL;
}
}
static void check_zone_txn(zone_t *zone, const knot_dname_t **exists)
{
if (zone->control_update != NULL && !(zone->control_update->flags & UPDATE_WFEV)) {
*exists = zone->name;
}
}
static int check_no_zone_txn(server_t *server, const char *action)
{
const knot_dname_t *zone_txn_exists = NULL;
knot_zonedb_foreach(server->zone_db, check_zone_txn, &zone_txn_exists);
if (zone_txn_exists != NULL) {
knot_dname_txt_storage_t zone_str;
knot_dname_to_str(zone_str, zone_txn_exists, sizeof(zone_str));
log_warning("%s rejected due to existing transaction for zone %s",
action, zone_str);
return KNOT_TXN_EEXISTS;
}
return KNOT_EOK;
}
static int server_status(ctl_args_t *args)
{
char buff[4096] = "";
const char *type = args->data[KNOT_CTL_IDX_TYPE];
if (type == NULL || strlen(type) == 0) {
if (!(args->server->state & ServerAnswering)) {
args->data[KNOT_CTL_IDX_FILTERS] = CTL_FILTER_STATUS_LOADING;
}
} else {
int ret;
if (strcasecmp(type, CMD_STATUS_VERSION) == 0) {
ret = snprintf(buff, sizeof(buff), "%s", PACKAGE_VERSION);
} else if (strcasecmp(type, CMD_STATUS_WORKERS) == 0) {
int running_bkg_wrk, wrk_queue;
worker_pool_status(args->server->workers, false, &running_bkg_wrk, &wrk_queue);
ret = snprintf(buff, sizeof(buff), "UDP workers: %zu, TCP workers: %zu, "
"XDP workers: %zu, background workers: %zu (running: %d, pending: %d)",
conf()->cache.srv_udp_threads, conf()->cache.srv_tcp_threads,
conf()->cache.srv_xdp_threads, conf()->cache.srv_bg_threads,
running_bkg_wrk, wrk_queue);
} else if (strcasecmp(type, CMD_STATUS_CONFIG) == 0) {
ret = snprintf(buff, sizeof(buff), "%s", configure_summary);
} else if (strcasecmp(type, CMD_STATUS_CERT) == 0) {
uint8_t pin[128];
size_t pin_len = server_cert_pin(args->server, pin, sizeof(pin));
if (pin_len > 0) {
ret = snprintf(buff, sizeof(buff), "%.*s", (int)pin_len, pin);
} else {
ret = snprintf(buff, sizeof(buff), STATUS_EMPTY);
}
} else {
return KNOT_EINVAL;
}
if (ret <= 0 || ret >= sizeof(buff)) {
return KNOT_ESPACE;
}
args->data[KNOT_CTL_IDX_DATA] = buff;
}
return knot_ctl_send(args->ctl, KNOT_CTL_TYPE_DATA, &args->data);
}
static int ctl_server(ctl_args_t *args, ctl_cmd_t cmd)
{
int ret = KNOT_EOK;
switch (cmd) {
case CTL_STATUS:
ret = server_status(args);
if (ret != KNOT_EOK) {
ctl_send_error(args, knot_strerror(ret));
}
break;
case CTL_STOP:
ret = KNOT_CTL_ESTOP;
break;
case CTL_RELOAD:
ret = check_no_zone_txn(args->server, "server reload");
if (ret == KNOT_EOK) {
ret = server_reload(args->server, RELOAD_FULL);
}
if (ret != KNOT_EOK) {
ctl_send_error(args, knot_strerror(ret));
}
break;
default:
assert(0);
ret = KNOT_EINVAL;
}
return ret;
}
static int ctl_stats(ctl_args_t *args, _unused_ ctl_cmd_t cmd)
{
return common_stats(args, NULL);
}
static int send_block_data(conf_io_t *io, knot_ctl_data_t *data)
{
knot_ctl_t *ctl = (knot_ctl_t *)io->misc;
const yp_item_t *item = (io->key1 != NULL) ? io->key1 : io->key0;
assert(item != NULL);
char buff[YP_MAX_TXT_DATA_LEN + 1] = "\0";
(*data)[KNOT_CTL_IDX_DATA] = buff;
// Format explicit binary data value.
if (io->data.bin != NULL) {
size_t buff_len = sizeof(buff);
int ret = yp_item_to_txt(item, io->data.bin, io->data.bin_len, buff,
&buff_len, YP_SNOQUOTE);
if (ret != KNOT_EOK) {
return ret;
}
return knot_ctl_send(ctl, KNOT_CTL_TYPE_DATA, data);
// Format all multivalued item data if no specified index.
} else if ((item->flags & YP_FMULTI) && io->data.index == 0) {
size_t values = conf_val_count(io->data.val);
for (size_t i = 0; i < values; i++) {
conf_val(io->data.val);
size_t buff_len = sizeof(buff);
int ret = yp_item_to_txt(item, io->data.val->data,
io->data.val->len, buff,&buff_len,
YP_SNOQUOTE);
if (ret != KNOT_EOK) {
return ret;
}
knot_ctl_type_t type = (i == 0) ? KNOT_CTL_TYPE_DATA :
KNOT_CTL_TYPE_EXTRA;
ret = knot_ctl_send(ctl, type, data);
if (ret != KNOT_EOK) {
return ret;
}
conf_val_next(io->data.val);
}
return KNOT_EOK;
// Format singlevalued item data or a specified one from multivalued.
} else {
conf_val(io->data.val);
size_t buff_len = sizeof(buff);
int ret = yp_item_to_txt(item, io->data.val->data, io->data.val->len,
buff, &buff_len, YP_SNOQUOTE);
if (ret != KNOT_EOK) {
return ret;
}
return knot_ctl_send(ctl, KNOT_CTL_TYPE_DATA, data);
}
}
static int send_block(conf_io_t *io)
{
knot_ctl_t *ctl = (knot_ctl_t *)io->misc;
// Get possible error message.
const char *err = io->error.str;
if (err == NULL && io->error.code != KNOT_EOK) {
err = knot_strerror(io->error.code);
}
knot_ctl_data_t data = {
[KNOT_CTL_IDX_ERROR] = err,
};
if (io->key0 != NULL) {
data[KNOT_CTL_IDX_SECTION] = io->key0->name + 1;
}
if (io->key1 != NULL) {
data[KNOT_CTL_IDX_ITEM] = io->key1->name + 1;
}
// Get the item prefix.
switch (io->type) {
case NEW: data[KNOT_CTL_IDX_FILTERS] = CTL_FILTER_DIFF_ADD_R; break;
case OLD: data[KNOT_CTL_IDX_FILTERS] = CTL_FILTER_DIFF_REM_R; break;
default: break;
}
knot_dname_txt_storage_t id;
// Get the textual item id.
if (io->id_len > 0 && io->key0 != NULL) {
size_t id_len = sizeof(id);
int ret = yp_item_to_txt(io->key0->var.g.id, io->id, io->id_len,
id, &id_len, YP_SNOQUOTE);
if (ret != KNOT_EOK) {
return ret;
}
if (io->id_as_data) {
data[KNOT_CTL_IDX_DATA] = id;
} else {
data[KNOT_CTL_IDX_ID] = id;
}
}
if (io->data.val == NULL && io->data.bin == NULL) {
return knot_ctl_send(ctl, KNOT_CTL_TYPE_DATA, &data);
} else {
return send_block_data(io, &data);
}
}
static int ctl_conf_txn(ctl_args_t *args, ctl_cmd_t cmd)
{
conf_io_t io = {
.fcn = send_block,
.misc = args->ctl
};
int ret = KNOT_EOK;
switch (cmd) {
case CTL_CONF_BEGIN:
ret = check_no_zone_txn(args->server, "config, transaction");
if (ret == KNOT_EOK) {
ret = conf_io_begin(false);
}
break;
case CTL_CONF_ABORT:
ret = conf_io_abort(false);
break;
case CTL_CONF_COMMIT:
// First check the database.
ret = conf_io_check(&io);
if (ret != KNOT_EOK) {
// A semantic error is already sent by the check function.
if (io.error.code != KNOT_EOK) {
return KNOT_EOK;
}
// No transaction abort!
break;
}
ret = conf_io_commit(false);
if (ret != KNOT_EOK) {
(void)conf_io_abort(false);
break;
}
ret = server_reload(args->server, RELOAD_COMMIT);
break;
default:
assert(0);
ret = KNOT_EINVAL;
}
if (ret != KNOT_EOK) {
ctl_send_error(args, knot_strerror(ret));
}
return ret;
}
static void list_zone(zone_t *zone, knot_ctl_t *ctl)
{
knot_dname_txt_storage_t buff;
knot_dname_to_str(buff, zone->name, sizeof(buff));
knot_ctl_data_t data = {
[KNOT_CTL_IDX_SECTION] = "zone",
[KNOT_CTL_IDX_ID] = buff
};
(void)knot_ctl_send(ctl, KNOT_CTL_TYPE_DATA, &data);
}
static int list_zones(knot_zonedb_t *zonedb, knot_ctl_t *ctl)
{
assert(zonedb != NULL && ctl != NULL);
knot_zonedb_foreach(zonedb, list_zone, ctl);
return KNOT_EOK;
}
static int ctl_conf_list(ctl_args_t *args, ctl_cmd_t cmd)
{
conf_io_t io = {
.fcn = send_block,
.misc = args->ctl
};
int ret = KNOT_EOK;
while (true) {
const char *key0 = args->data[KNOT_CTL_IDX_SECTION];
const char *key1 = args->data[KNOT_CTL_IDX_ITEM];
const char *id = args->data[KNOT_CTL_IDX_ID];
const char *filters = args->data[KNOT_CTL_IDX_FILTERS];
bool schema = ctl_has_flag(filters, CTL_FILTER_LIST_SCHEMA);
bool current = !ctl_has_flag(filters, CTL_FILTER_LIST_TXN);
bool zones = ctl_has_flag(filters, CTL_FILTER_LIST_ZONES);
if (zones) {
ret = list_zones(args->server->zone_db, args->ctl);
} else {
ret = conf_io_list(key0, key1, id, schema, current, &io);
}
if (ret != KNOT_EOK) {
ctl_send_error(args, knot_strerror(ret));
break;
}
// Get next data unit.
ret = knot_ctl_receive(args->ctl, &args->type, &args->data);
if (ret != KNOT_EOK || args->type != KNOT_CTL_TYPE_DATA) {
break;
}
}
return ret;
}
static int ctl_conf_read(ctl_args_t *args, ctl_cmd_t cmd)
{
conf_io_t io = {
.fcn = send_block,
.misc = args->ctl
};
int ret = KNOT_EOK;
while (true) {
const char *key0 = args->data[KNOT_CTL_IDX_SECTION];
const char *key1 = args->data[KNOT_CTL_IDX_ITEM];
const char *id = args->data[KNOT_CTL_IDX_ID];
ctl_log_conf_data(&args->data);
switch (cmd) {
case CTL_CONF_READ:
ret = conf_io_get(key0, key1, id, true, &io);
break;
case CTL_CONF_DIFF:
ret = conf_io_diff(key0, key1, id, &io);
break;
case CTL_CONF_GET:
ret = conf_io_get(key0, key1, id, false, &io);
break;
default:
assert(0);
ret = KNOT_EINVAL;
}
if (ret != KNOT_EOK) {
ctl_send_error(args, knot_strerror(ret));
break;
}
// Get next data unit.
ret = knot_ctl_receive(args->ctl, &args->type, &args->data);
if (ret != KNOT_EOK || args->type != KNOT_CTL_TYPE_DATA) {
break;
}
}
return ret;
}
static int ctl_conf_modify(ctl_args_t *args, ctl_cmd_t cmd)
{
// Start child transaction.
int ret = conf_io_begin(true);
if (ret != KNOT_EOK) {
ctl_send_error(args, knot_strerror(ret));
return ret;
}
while (true) {
const char *key0 = args->data[KNOT_CTL_IDX_SECTION];
const char *key1 = args->data[KNOT_CTL_IDX_ITEM];
const char *id = args->data[KNOT_CTL_IDX_ID];
const char *data = args->data[KNOT_CTL_IDX_DATA];
ctl_log_conf_data(&args->data);
switch (cmd) {
case CTL_CONF_SET:
ret = conf_io_set(key0, key1, id, data);
break;
case CTL_CONF_UNSET:
ret = conf_io_unset(key0, key1, id, data);
break;
default:
assert(0);
ret = KNOT_EINVAL;
}
if (ret != KNOT_EOK) {
ctl_send_error(args, knot_strerror(ret));
break;
}
// Get next data unit.
ret = knot_ctl_receive(args->ctl, &args->type, &args->data);
if (ret != KNOT_EOK || args->type != KNOT_CTL_TYPE_DATA) {
break;
}
}
// Finish child transaction.
if (ret == KNOT_EOK) {
ret = conf_io_commit(true);
if (ret != KNOT_EOK) {
ctl_send_error(args, knot_strerror(ret));
}
} else {
(void)conf_io_abort(true);
}
return ret;
}
typedef enum {
CTL_LOCK_NONE = 0x00,
CTL_LOCK_SRV_R = 0x01, // Can run in parallel with other R commands.
CTL_LOCK_SRV_W = 0x02, // Cannot run in parallel with other commands.
} ctl_lock_flag_t;
typedef struct {
const char *name;
int (*fcn)(ctl_args_t *, ctl_cmd_t);
ctl_lock_flag_t locks;
} desc_t;
static const desc_t cmd_table[] = {
[CTL_NONE] = { "" },
[CTL_STATUS] = { "status", ctl_server, CTL_LOCK_SRV_R },
[CTL_STOP] = { "stop", ctl_server, CTL_LOCK_SRV_R },
[CTL_RELOAD] = { "reload", ctl_server, CTL_LOCK_SRV_W },
[CTL_STATS] = { "stats", ctl_stats, CTL_LOCK_SRV_R },
[CTL_ZONE_STATUS] = { "zone-status", ctl_zone, CTL_LOCK_SRV_R },
[CTL_ZONE_RELOAD] = { "zone-reload", ctl_zone, CTL_LOCK_SRV_R },
[CTL_ZONE_REFRESH] = { "zone-refresh", ctl_zone, CTL_LOCK_SRV_R },
[CTL_ZONE_RETRANSFER] = { "zone-retransfer", ctl_zone, CTL_LOCK_SRV_R },
[CTL_ZONE_NOTIFY] = { "zone-notify", ctl_zone, CTL_LOCK_SRV_R },
[CTL_ZONE_FLUSH] = { "zone-flush", ctl_zone, CTL_LOCK_SRV_R },
[CTL_ZONE_BACKUP] = { "zone-backup", ctl_zone, CTL_LOCK_SRV_W }, // Backup and restore must be exclusive as the global backup ctx is accessed.
[CTL_ZONE_RESTORE] = { "zone-restore", ctl_zone, CTL_LOCK_SRV_W },
[CTL_ZONE_SIGN] = { "zone-sign", ctl_zone, CTL_LOCK_SRV_R },
[CTL_ZONE_VALIDATE] = { "zone-validate", ctl_zone, CTL_LOCK_SRV_R },
[CTL_ZONE_KEYS_LOAD] = { "zone-keys-load", ctl_zone, CTL_LOCK_SRV_R },
[CTL_ZONE_KEY_ROLL] = { "zone-key-rollover", ctl_zone, CTL_LOCK_SRV_R },
[CTL_ZONE_KSK_SBM] = { "zone-ksk-submitted", ctl_zone, CTL_LOCK_SRV_R },
[CTL_ZONE_FREEZE] = { "zone-freeze", ctl_zone, CTL_LOCK_SRV_R },
[CTL_ZONE_THAW] = { "zone-thaw", ctl_zone, CTL_LOCK_SRV_R },
[CTL_ZONE_XFR_FREEZE] = { "zone-xfr-freeze", ctl_zone, CTL_LOCK_SRV_R },
[CTL_ZONE_XFR_THAW] = { "zone-xfr-thaw", ctl_zone, CTL_LOCK_SRV_R },
[CTL_ZONE_READ] = { "zone-read", ctl_zone, CTL_LOCK_SRV_R },
[CTL_ZONE_BEGIN] = { "zone-begin", ctl_zone, CTL_LOCK_SRV_R },
[CTL_ZONE_COMMIT] = { "zone-commit", ctl_zone, CTL_LOCK_SRV_R },
[CTL_ZONE_ABORT] = { "zone-abort", ctl_zone, CTL_LOCK_SRV_R },
[CTL_ZONE_DIFF] = { "zone-diff", ctl_zone, CTL_LOCK_SRV_R },
[CTL_ZONE_GET] = { "zone-get", ctl_zone, CTL_LOCK_SRV_R },
[CTL_ZONE_SET] = { "zone-set", ctl_zone, CTL_LOCK_SRV_R },
[CTL_ZONE_UNSET] = { "zone-unset", ctl_zone, CTL_LOCK_SRV_R },
[CTL_ZONE_PURGE] = { "zone-purge", ctl_zone, CTL_LOCK_SRV_W },
[CTL_ZONE_STATS] = { "zone-stats", ctl_zone, CTL_LOCK_SRV_R },
[CTL_ZONE_SERIAL_SET] = { "zone-serial-set", ctl_zone, CTL_LOCK_SRV_R },
[CTL_CONF_LIST] = { "conf-list", ctl_conf_list, CTL_LOCK_SRV_R }, // Can either read live conf or conf txn. The latter would deserve CTL_LOCK_SRV_W, but when conf txn exists, all cmds are done by single thread anyway.
[CTL_CONF_READ] = { "conf-read", ctl_conf_read, CTL_LOCK_SRV_R },
[CTL_CONF_BEGIN] = { "conf-begin", ctl_conf_txn, CTL_LOCK_SRV_W }, // It's locked only during conf-begin, not for the whole duration of the transaction.
[CTL_CONF_COMMIT] = { "conf-commit", ctl_conf_txn, CTL_LOCK_SRV_W },
[CTL_CONF_ABORT] = { "conf-abort", ctl_conf_txn, CTL_LOCK_SRV_W },
[CTL_CONF_DIFF] = { "conf-diff", ctl_conf_read, CTL_LOCK_SRV_W },
[CTL_CONF_GET] = { "conf-get", ctl_conf_read, CTL_LOCK_SRV_W },
[CTL_CONF_SET] = { "conf-set", ctl_conf_modify, CTL_LOCK_SRV_W },
[CTL_CONF_UNSET] = { "conf-unset", ctl_conf_modify, CTL_LOCK_SRV_W },
};
#define MAX_CTL_CODE (sizeof(cmd_table) / sizeof(desc_t) - 1)
const char *ctl_cmd_to_str(ctl_cmd_t cmd)
{
if (cmd <= CTL_NONE || cmd > MAX_CTL_CODE) {
return NULL;
}
return cmd_table[cmd].name;
}
ctl_cmd_t ctl_str_to_cmd(const char *cmd_str)
{
if (cmd_str == NULL) {
return CTL_NONE;
}
for (ctl_cmd_t cmd = CTL_NONE + 1; cmd <= MAX_CTL_CODE; cmd++) {
if (strcmp(cmd_str, cmd_table[cmd].name) == 0) {
return cmd;
}
}
return CTL_NONE;
}
static int ctl_lock(server_t *server, ctl_lock_flag_t flags, uint64_t timeout_ms)
{
struct timespec ts;
int ret = clock_gettime(CLOCK_REALTIME, &ts);
if (ret != 0) {
return KNOT_ERROR;
}
ts.tv_sec += timeout_ms / 1000;
ts.tv_nsec += (timeout_ms % 1000) * 1000000LU;
if ((flags & CTL_LOCK_SRV_W)) {
assert(!(flags & CTL_LOCK_SRV_R));
#if !defined(__APPLE__)
ret = pthread_rwlock_timedwrlock(&server->ctl_lock, &ts);
#else
ret = pthread_rwlock_wrlock(&server->ctl_lock);
#endif
}
if ((flags & CTL_LOCK_SRV_R)) {
#if !defined(__APPLE__)
ret = pthread_rwlock_timedrdlock(&server->ctl_lock, &ts);
#else
ret = pthread_rwlock_rdlock(&server->ctl_lock);
#endif
}
return (ret != 0 ? KNOT_EBUSY : KNOT_EOK);
}
static void ctl_unlock(server_t *server)
{
pthread_rwlock_unlock(&server->ctl_lock);
}
int ctl_exec(ctl_cmd_t cmd, ctl_args_t *args)
{
if (args == NULL) {
return KNOT_EINVAL;
}
int ret = ctl_lock(args->server, cmd_table[cmd].locks, conf()->cache.ctl_timeout);
if (ret == KNOT_EOK) {
ret = cmd_table[cmd].fcn(args, cmd);
ctl_unlock(args->server);
}
return ret;
}
bool ctl_has_flag(const char *flags, const char *flag)
{
if (flags == NULL || flag == NULL) {
return false;
}
return strstr(flags, flag) != NULL;
}