summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--doc/man_knotc.rst5
-rw-r--r--src/knot/zone/backup_dir.c16
-rw-r--r--tests-extra/tests/zone/backup/test.py36
-rw-r--r--tests-extra/tests/zone/backup_lock/test.py6
4 files changed, 60 insertions, 3 deletions
diff --git a/doc/man_knotc.rst b/doc/man_knotc.rst
index 55f4dfaff..4755a6ac3 100644
--- a/doc/man_knotc.rst
+++ b/doc/man_knotc.rst
@@ -151,8 +151,9 @@ Actions
explicitly. If zone flushing is disabled, the original zone file is backed
up instead of writing out zone contents to a file. When backing-up a catalog
zone, it is recommended to prevent ongoing changes to it by use of
- **zone-freeze**.
- See :ref:`Notes<notes>` below about the directory permissions. (#)
+ **zone-freeze**. The force option allows an already existing backupdir to
+ be overwritten. See :ref:`Notes<notes>` below about the directory permissions.
+ (#)
**zone-restore** [*zone*...] **+backupdir** *directory* [*filter*...]
Trigger a zone data and metadata restore from a specified backup directory.
diff --git a/src/knot/zone/backup_dir.c b/src/knot/zone/backup_dir.c
index 7bf9fd576..da9fd2507 100644
--- a/src/knot/zone/backup_dir.c
+++ b/src/knot/zone/backup_dir.c
@@ -240,6 +240,22 @@ int backupdir_init(zone_backup_ctx_t *ctx)
return KNOT_ENOTDIR;
}
} else {
+ if (ctx->forced) {
+ if (stat(ctx->backup_dir, &sb) == 0) {
+ int ret2 = remove_path(ctx->backup_dir, S_ISDIR(sb.st_mode));
+ if (ret2 != KNOT_EOK) {
+ return ret2;
+ }
+ } else if (errno != ENOENT) {
+ return knot_map_errno();
+ } else if (lstat(ctx->backup_dir, &sb) == 0 && S_ISLNK(sb.st_mode)) {
+ // Stale symlink.
+ if (unlink(ctx->backup_dir) != 0) {
+ return knot_map_errno();
+ }
+ } // Omitting lstat() failure check, make_dir() will do it.
+ }
+
ret = make_dir(ctx->backup_dir, S_IRWXU | S_IRWXG, true);
if (ret != KNOT_EOK) {
return ret;
diff --git a/tests-extra/tests/zone/backup/test.py b/tests-extra/tests/zone/backup/test.py
index 215b2dcdf..68fcd924e 100644
--- a/tests-extra/tests/zone/backup/test.py
+++ b/tests-extra/tests/zone/backup/test.py
@@ -37,6 +37,7 @@ for z in zones:
slave.zonefile_load = "none"
backup_dir = master.dir + "/backup"
+backup_dir2 = master.dir + "/backup2" # Backup of a backup, for debugging.
slave_bck_dir = slave.dir + "/backup"
zone0_expire = 45 # zone zones[0] expiration time in its SOA
@@ -57,7 +58,8 @@ for z in zones:
master.ctl("zone-sign " + z.name)
slave.zone_wait(z, serials_init[z.name])
-master.ctl("zone-backup +backupdir %s" % backup_dir)
+master.ctl("zone-backup +backupdir %s" % backup_dir, wait=True)
+master.ctl("zone-backup +backupdir %s" % backup_dir2)
slave.ctl("zone-backup %s %s +journal +backupdir %s +nozonefile" % \
(zones[0].name, zones[1].name, slave_bck_dir))
@@ -132,4 +134,36 @@ for i in range(start_time + zone0_expire + valgrind_delay - int(t.uptime())):
resp = slave.dig(zones[0].name, "SOA")
resp.check(rcode="SERVFAIL")
+# Test that the older backup is replaced by the newer one.
+shutil.rmtree(keydir)
+master.start()
+master.zones_wait(zones)
+(dnskey1_5, dnskey2_5) = get_dnskeys(master, zones)
+if dnskey1_5 == dnskey1_4 or dnskey2_5 == dnskey2_4:
+ set_err("TEST ERROR 2")
+master.ctl("-f zone-backup +backupdir %s" % backup_dir, wait=True)
+master.stop()
+shutil.rmtree(keydir)
+master.start()
+master.zones_wait(zones)
+(dnskey1_6, dnskey2_6) = get_dnskeys(master, zones)
+if dnskey1_6 == dnskey1_5 or dnskey2_6 == dnskey2_5:
+ set_err("TEST ERROR 3")
+master.ctl("zone-restore +backupdir %s" % backup_dir, wait=True)
+master.zones_wait(zones)
+(dnskey1_7, dnskey2_7) = get_dnskeys(master, zones)
+if dnskey1_7 != dnskey1_5 or dnskey2_7 != dnskey2_5:
+ set_err("KEYS FROM FORCED BACKUP NOT RESTORED")
+
+# Test that even a zonefile not existing in the newer backup gets overwritten.
+master.ctl("-f zone-purge %s" % zones[1].name, wait=True)
+master.ctl("-f zone-backup +backupdir %s" % backup_dir, wait=True)
+master.ctl("-f zone-purge", wait=True)
+master.ctl("zone-restore +backupdir %s" % backup_dir, wait=True)
+t.sleep(3)
+resp = master.dig(zones[0].name, "SOA")
+resp.check(rcode="NOERROR")
+resp = master.dig(zones[1].name, "SOA")
+resp.check(rcode="SERVFAIL")
+
t.stop()
diff --git a/tests-extra/tests/zone/backup_lock/test.py b/tests-extra/tests/zone/backup_lock/test.py
index 1aab2b550..0d2ef8b48 100644
--- a/tests-extra/tests/zone/backup_lock/test.py
+++ b/tests-extra/tests/zone/backup_lock/test.py
@@ -98,6 +98,12 @@ except:
pass
check_log_err(master, "already exists")
+# Attempt to start backup with the "-f" option into already existing backup, expected OK.
+try:
+ master.ctl("-f zone-backup +backupdir %s" % backup_dir, wait=True)
+except:
+ set_err("FORCED BACKUP NOT ALLOWED")
+
# Attempt to start restore from non-existing backup directory, expected (not exists).
try:
master.ctl("zone-restore +backupdir %s" % backup_dir_void, wait=True)