diff --git a/Knot.files b/Knot.files index df5fb284a7e851811ab705f8e4198536b4bec4de..767a1d3752b332b109c4a4c06c744042add99516 100644 --- a/Knot.files +++ b/Knot.files @@ -280,6 +280,8 @@ src/knot/zone/adjust.c src/knot/zone/adjust.h src/knot/zone/backup.c src/knot/zone/backup.h +src/knot/zone/backup_label.c +src/knot/zone/backup_label.h src/knot/zone/contents.c src/knot/zone/contents.h src/knot/zone/digest.c diff --git a/src/knot/Makefile.inc b/src/knot/Makefile.inc index 4bc38c2036b66633f9a56f5e3777ba727b34d6ff..efce8983e13e54589dec705525a547a675ce2f7d 100644 --- a/src/knot/Makefile.inc +++ b/src/knot/Makefile.inc @@ -174,6 +174,8 @@ libknotd_la_SOURCES = \ knot/zone/adjust.h \ knot/zone/backup.c \ knot/zone/backup.h \ + knot/zone/backup_label.c \ + knot/zone/backup_label.h \ knot/zone/contents.c \ knot/zone/contents.h \ knot/zone/digest.c \ diff --git a/src/knot/zone/backup.c b/src/knot/zone/backup.c index bb5a0bf2a3e8df7d09c59246c0b4510dbf487096..038455a28ed05a58c3c2109d244b4a0fdb976231 100644 --- a/src/knot/zone/backup.c +++ b/src/knot/zone/backup.c @@ -34,9 +34,13 @@ #include "knot/dnssec/kasp/kasp_zone.h" #include "knot/dnssec/kasp/keystore.h" #include "knot/journal/journal_metadata.h" +#include "knot/zone/backup_label.h" #include "knot/zone/zonefile.h" #include "libdnssec/error.h" +// Current backup format version for output. +#define BACKUP_VERSION BACKUP_FORMAT_2 // Starting with release 3.1.0. + static void _backup_swap(zone_backup_ctx_t *ctx, void **local, void **remote) { if (ctx->restore_mode) { @@ -46,133 +50,8 @@ static void _backup_swap(zone_backup_ctx_t *ctx, void **local, void **remote) } } -// Current backup format version for output. -#define BACKUP_VERSION BACKUP_FORMAT_2 // Starting with release 3.1.0. - -#define LABEL_FILE "knot_backup.label" -#define LOCK_FILE "lock.knot_backup" - -#define LABEL_FILE_HEAD "label: Knot DNS Backup\n" -#define LABEL_FILE_FORMAT "backup_format: %d\n" -#define LABEL_FILE_TIME_FORMAT "%Y-%m-%d %H:%M:%S %Z" - -#define FNAME_MAX (MAX(sizeof(LABEL_FILE), sizeof(LOCK_FILE))) #define BACKUP_SWAP(ctx, from, to) _backup_swap((ctx), (void **)&(from), (void **)&(to)) -static const char *label_file_name = LABEL_FILE; -static const char *lock_file_name = LOCK_FILE; -static const char *label_file_head = LABEL_FILE_HEAD; - -static int make_label_file(zone_backup_ctx_t *ctx, char *full_path) -{ - FILE *file = fopen(full_path, "w"); - if (file == NULL) { - return knot_map_errno(); - } - - // Prepare the server identity. - conf_val_t val = conf_get(conf(), C_SRV, C_IDENT); - const char *ident = conf_str(&val); - if (ident == NULL || ident[0] == '\0') { - ident = conf()->hostname; - } - - // Prepare the timestamps. - char started_time[64], finished_time[64]; - struct tm tm; - - localtime_r(&ctx->init_time, &tm); - strftime(started_time, sizeof(started_time), LABEL_FILE_TIME_FORMAT, &tm); - - time_t now = time(NULL); - localtime_r(&now, &tm); - strftime(finished_time, sizeof(finished_time), LABEL_FILE_TIME_FORMAT, &tm); - - // Print the label contents. - int ret = fprintf(file, - "%s" - LABEL_FILE_FORMAT - "identity: %s\n" - "started_time: %s\n" - "finished_time: %s\n" - "knot_version: %s\n" - "parameters: +%szonefile +%sjournal +%stimers +%skaspdb +%scatalog " - "+backupdir %s\n" - "zone_count: %d\n", - label_file_head, - BACKUP_VERSION, ident, started_time, finished_time, PACKAGE_VERSION, - ctx->backup_zonefile ? "" : "no", - ctx->backup_journal ? "" : "no", - ctx->backup_timers ? "" : "no", - ctx->backup_kaspdb ? "" : "no", - ctx->backup_catalog ? "" : "no", - ctx->backup_dir, - ctx->zone_count); - - ret = (ret < 0) ? knot_map_errno() : KNOT_EOK; - - fclose(file); - return ret; -} - -static int get_backup_format(const char *full_path, bool forced, knot_backup_format_t *format) -{ - int ret = KNOT_EMALF; - - struct stat sb; - if (stat(full_path, &sb) != 0) { - ret = knot_map_errno(); - if (ret == KNOT_ENOENT) { - if (forced) { - *format = BACKUP_FORMAT_1; - ret = KNOT_EOK; - } else { - ret = KNOT_EMALF; - } - } - return ret; - } - - // getline() from an empty file results in EAGAIN, therefore avoid doing so. - if (!S_ISREG(sb.st_mode) || sb.st_size == 0) { - return ret; - } - - FILE *file = fopen(full_path, "r"); - if (file == NULL) { - return knot_map_errno(); - } - - char *line = NULL; - size_t line_size = 0; - - // Check for the header line first. - if (knot_getline(&line, &line_size, file) == -1) { - ret = knot_map_errno(); - goto done; - } - - if (strcmp(line, label_file_head) != 0) { - goto done; - } - - int value; - while (knot_getline(&line, &line_size, file) != -1) { - if (sscanf(line, LABEL_FILE_FORMAT, &value) != 0) { - if ((BACKUP_FORMAT_1 < value) && (value < BACKUP_FORMAT_TERM)) { - *format = value; - ret = KNOT_EOK; - } - break; - } - } - -done: - free(line); - fclose(file); - return ret; -} - int zone_backup_init(bool restore_mode, bool forced, const char *backup_dir, size_t kasp_db_size, size_t timer_db_size, size_t journal_db_size, size_t catalog_db_size, zone_backup_ctx_t **out_ctx) @@ -188,6 +67,7 @@ int zone_backup_init(bool restore_mode, bool forced, const char *backup_dir, return KNOT_ENOMEM; } ctx->restore_mode = restore_mode; + ctx->forced = forced; ctx->backup_format = BACKUP_VERSION; ctx->backup_global = false; ctx->readers = 1; @@ -218,42 +98,13 @@ int zone_backup_init(bool restore_mode, bool forced, const char *backup_dir, } } - // The \0 terminator is already included in the sizeof() value, thus the sum - // covers one additional char for '/'. - char full_path[backup_dir_len + FNAME_MAX]; - - // Check for existence of a label file and the backup format used. - sprintf(full_path, "%s/%s", (ctx)->backup_dir, label_file_name); - if (restore_mode) { - ret = get_backup_format(full_path, forced, &ctx->backup_format); - if (ret != KNOT_EOK) { - free(ctx); - return ret; - } - } else { - if (stat(full_path, &sb) == 0) { - free(ctx); - return KNOT_EEXIST; - } - } - - // Make (or check for existence of) a lock file. - sprintf(full_path, "%s/%s", (ctx)->backup_dir, lock_file_name); - if (restore_mode) { - // Just check. - if (stat(full_path, &sb) == 0) { - free(ctx); - return KNOT_EBUSY; - } - } else { - // Create it (which also checks for its existence). - int lock_file = open(full_path, O_CREAT|O_EXCL, S_IRUSR|S_IWUSR); - if (lock_file < 0) { - free(ctx); - // Make the reported error better understandable than KNOT_EEXIST. - return errno == EEXIST ? KNOT_EBUSY : knot_map_errno(); - } - close(lock_file); + // Depending on the restore mode, create (or check for existence of) the label file + // and the lock file. If the label file exists, the backup version is identified and + // stored in ctx. + ret = init_backup_label_lock(ctx); + if (ret != KNOT_EOK) { + free(ctx); + return ret; } pthread_mutex_init(&ctx->readers_mutex, NULL); @@ -295,24 +146,8 @@ int zone_backup_deinit(zone_backup_ctx_t *ctx) knot_lmdb_deinit(&ctx->bck_kasp_db); pthread_mutex_destroy(&ctx->readers_mutex); - size_t backup_dir_len = strlen((ctx)->backup_dir) + 1; - char full_path[backup_dir_len + FNAME_MAX]; - - if (!ctx->restore_mode && !ctx->failed) { - // Create the label file first. - sprintf(full_path, "%s/%s", (ctx)->backup_dir, label_file_name); - ret = make_label_file(ctx, full_path); - if (ret == KNOT_EOK) { - // Remove the lock file only when the label file has been created. - sprintf(full_path, "%s/%s", (ctx)->backup_dir, lock_file_name); - unlink(full_path); - } else { - log_error("failed to create a backup label in %s", (ctx)->backup_dir); - } - } - + ret = deinit_backup_label_lock(ctx); zone_backups_rem(ctx); - free(ctx); } diff --git a/src/knot/zone/backup.h b/src/knot/zone/backup.h index 5ac1c3eb05ba15637447577efd96b758a25443f8..b1d0e3e1301fc6a132a9b098116dae7af1e545f4 100644 --- a/src/knot/zone/backup.h +++ b/src/knot/zone/backup.h @@ -32,6 +32,7 @@ typedef enum { typedef struct zone_backup_ctx { node_t n; // ability to be put into list_t bool restore_mode; // if true, this is not a backup, but restore + bool forced; // if true, the force flag has been set bool backup_zonefile; // if true, also backup zone contents to a zonefile (default on) bool backup_journal; // if true, also backup journal (default off) bool backup_timers; // if true, also backup timers (default on) diff --git a/src/knot/zone/backup_label.c b/src/knot/zone/backup_label.c new file mode 100644 index 0000000000000000000000000000000000000000..11d2adf0f9100db688a68f3a27d87d245f4ea3ed --- /dev/null +++ b/src/knot/zone/backup_label.c @@ -0,0 +1,226 @@ +/* Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <time.h> +#include <unistd.h> + +#include "knot/zone/backup_label.h" + +#include "contrib/getline.h" +#include "knot/common/log.h" + +#define LABEL_FILE "knot_backup.label" +#define LOCK_FILE "lock.knot_backup" + +#define LABEL_FILE_HEAD "label: Knot DNS Backup\n" +#define LABEL_FILE_FORMAT "backup_format: %d\n" +#define LABEL_FILE_TIME_FORMAT "%Y-%m-%d %H:%M:%S %Z" + +#define FNAME_MAX (MAX(sizeof(LABEL_FILE), sizeof(LOCK_FILE))) +#define PREPARE_PATH(var, file) \ + char var[path_size(ctx)]; \ + get_full_path(ctx, file, var); + +static const char *label_file_name = LABEL_FILE; +static const char *lock_file_name = LOCK_FILE; +static const char *label_file_head = LABEL_FILE_HEAD; + +static void get_full_path(zone_backup_ctx_t *ctx, const char *filename, char *full_path) +{ + (void)sprintf(full_path, "%s/%s", ctx->backup_dir, filename); +} + +static size_t path_size(zone_backup_ctx_t *ctx) +{ + // The \0 terminator is already included in the sizeof()/FNAME_MAX value, + // thus the sum covers one additional char for '/'. + return (strlen(ctx->backup_dir) + 1 + FNAME_MAX); +} + +static int make_label_file(zone_backup_ctx_t *ctx) +{ + PREPARE_PATH(label_path, label_file_name); + + FILE *file = fopen(label_path, "w"); + if (file == NULL) { + return knot_map_errno(); + } + + // Prepare the server identity. + conf_val_t val = conf_get(conf(), C_SRV, C_IDENT); + const char *ident = conf_str(&val); + if (ident == NULL || ident[0] == '\0') { + ident = conf()->hostname; + } + + // Prepare the timestamps. + char started_time[64], finished_time[64]; + struct tm tm; + + localtime_r(&ctx->init_time, &tm); + strftime(started_time, sizeof(started_time), LABEL_FILE_TIME_FORMAT, &tm); + + time_t now = time(NULL); + localtime_r(&now, &tm); + strftime(finished_time, sizeof(finished_time), LABEL_FILE_TIME_FORMAT, &tm); + + // Print the label contents. + int ret = fprintf(file, + "%s" + LABEL_FILE_FORMAT + "identity: %s\n" + "started_time: %s\n" + "finished_time: %s\n" + "knot_version: %s\n" + "parameters: +%szonefile +%sjournal +%stimers +%skaspdb +%scatalog " + "+backupdir %s\n" + "zone_count: %d\n", + label_file_head, + ctx->backup_format, ident, started_time, finished_time, PACKAGE_VERSION, + ctx->backup_zonefile ? "" : "no", + ctx->backup_journal ? "" : "no", + ctx->backup_timers ? "" : "no", + ctx->backup_kaspdb ? "" : "no", + ctx->backup_catalog ? "" : "no", + ctx->backup_dir, + ctx->zone_count); + + ret = (ret < 0) ? knot_map_errno() : KNOT_EOK; + + fclose(file); + return ret; +} + +static int get_backup_format(zone_backup_ctx_t *ctx) +{ + PREPARE_PATH(label_path, label_file_name); + + int ret = KNOT_EMALF; + + struct stat sb; + if (stat(label_path, &sb) != 0) { + ret = knot_map_errno(); + if (ret == KNOT_ENOENT) { + if (ctx->forced) { + ctx->backup_format = BACKUP_FORMAT_1; + ret = KNOT_EOK; + } else { + ret = KNOT_EMALF; + } + } + return ret; + } + + // getline() from an empty file results in EAGAIN, therefore avoid doing so. + if (!S_ISREG(sb.st_mode) || sb.st_size == 0) { + return ret; + } + + FILE *file = fopen(label_path, "r"); + if (file == NULL) { + return knot_map_errno(); + } + + char *line = NULL; + size_t line_size = 0; + + // Check for the header line first. + if (knot_getline(&line, &line_size, file) == -1) { + ret = knot_map_errno(); + goto done; + } + + if (strcmp(line, label_file_head) != 0) { + goto done; + } + + while (knot_getline(&line, &line_size, file) != -1) { + int value; + if (sscanf(line, LABEL_FILE_FORMAT, &value) != 0) { + if ((BACKUP_FORMAT_1 < value) && (value < BACKUP_FORMAT_TERM)) { + ctx->backup_format = value; + ret = KNOT_EOK; + } + break; + } + } + +done: + free(line); + fclose(file); + return ret; +} + +int init_backup_label_lock(zone_backup_ctx_t *ctx) +{ + char full_path[path_size(ctx)]; + struct stat sb; + + // Check for existence of a label file and the backup format used. + if (ctx->restore_mode) { + int ret = get_backup_format(ctx); + if (ret != KNOT_EOK) { + return ret; + } + } else { + get_full_path(ctx, label_file_name, full_path); + if (stat(full_path, &sb) == 0) { + return KNOT_EEXIST; + } + } + + // Make (or check for existence of) a lock file. + get_full_path(ctx, lock_file_name, full_path); + if (ctx->restore_mode) { + // Just check. + if (stat(full_path, &sb) == 0) { + return KNOT_EBUSY; + } + } else { + // Create it (which also checks for its existence). + int lock_file = open(full_path, O_CREAT|O_EXCL, S_IRUSR|S_IWUSR); + if (lock_file < 0) { + // Make the reported error better understandable than KNOT_EEXIST. + return errno == EEXIST ? KNOT_EBUSY : knot_map_errno(); + } + close(lock_file); + } + + return KNOT_EOK; +} + +int deinit_backup_label_lock(zone_backup_ctx_t *ctx) +{ + int ret = KNOT_EOK; + + if (!ctx->restore_mode && !ctx->failed) { + // Create the label file first. + ret = make_label_file(ctx); + if (ret == KNOT_EOK) { + // Remove the lock file only when the label file has been created. + PREPARE_PATH(lock_path, lock_file_name); + unlink(lock_path); + } else { + log_error("failed to create a backup label in %s", (ctx)->backup_dir); + } + } + + return ret; +} diff --git a/src/knot/zone/backup_label.h b/src/knot/zone/backup_label.h new file mode 100644 index 0000000000000000000000000000000000000000..6a14982c1da782e1d8c5538dc2509741c367d893 --- /dev/null +++ b/src/knot/zone/backup_label.h @@ -0,0 +1,38 @@ +/* Copyright (C) 2021 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "knot/zone/backup.h" + +/*! + * Verifies existence/non-existence of a lock file and a label file, in the + * backup mode it creates them, in the restore mode it sets ctx->backup_format. + * + * \param[in/out] ctx Backup context. + * + * \return Error code, KNOT_EOK if successful. + */ +int init_backup_label_lock(zone_backup_ctx_t *ctx); + +/*! + * If the backup has been successful, it creates the label file + * and removes the lock file. Nothing for the restore mode. + * + * \param[in] ctx Backup context. + * + * \return Error code, KNOT_EOK if successful. + */ +int deinit_backup_label_lock(zone_backup_ctx_t *ctx);