diff options
Diffstat (limited to 'src/knot/zone/backup_dir.c')
-rw-r--r-- | src/knot/zone/backup_dir.c | 247 |
1 files changed, 247 insertions, 0 deletions
diff --git a/src/knot/zone/backup_dir.c b/src/knot/zone/backup_dir.c new file mode 100644 index 0000000..7333b21 --- /dev/null +++ b/src/knot/zone/backup_dir.c @@ -0,0 +1,247 @@ +/* Copyright (C) 2023 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_dir.h" + +#include "contrib/files.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 + "server_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 (value >= BACKUP_FORMAT_TERM) { + ret = KNOT_ENOTSUP; + } else if (value > BACKUP_FORMAT_1) { + ctx->backup_format = value; + ret = KNOT_EOK; + } + break; + } + } + +done: + free(line); + fclose(file); + return ret; +} + +int backupdir_init(zone_backup_ctx_t *ctx) +{ + int ret; + struct stat sb; + + // Make sure the source/target backup directory exists. + if (ctx->restore_mode) { + if (stat(ctx->backup_dir, &sb) != 0) { + return knot_map_errno(); + } + if (!S_ISDIR(sb.st_mode)) { + return KNOT_ENOTDIR; + } + } else { + ret = make_dir(ctx->backup_dir, S_IRWXU|S_IRWXG, true); + if (ret != KNOT_EOK) { + return ret; + } + } + + char full_path[path_size(ctx)]; + + // Check for existence of a label file and the backup format used. + if (ctx->restore_mode) { + 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 | S_IRGRP | S_IWGRP); + 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 backupdir_deinit(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; +} |