Commit 2fb654ef16 for bind

commit 2fb654ef16f94f48b3f30ab891dc02a8951aaccb
Author: Nicki Křížek <nicki@isc.org>
Date:   Fri Oct 23 09:36:29 2026 +0000

    Ensure zone files rewritten before a reload get a newer mtime

    Where file timestamps resolve whole seconds only, these rewrites can
    land in the same second as the previous version, and named skips the
    reload. Only the time the preceding steps take keeps them apart now.

    Assisted-by: Claude:claude-opus-5-5

diff --git a/bin/tests/system/cyclic_glue/tests_cyclic_glue.py b/bin/tests/system/cyclic_glue/tests_cyclic_glue.py
index 3ae1d0bcb3..83d1b54481 100644
--- a/bin/tests/system/cyclic_glue/tests_cyclic_glue.py
+++ b/bin/tests/system/cyclic_glue/tests_cyclic_glue.py
@@ -47,7 +47,7 @@ def test_cyclic_glues(ns1, ns4, templates):
     # in total we should have test, foo.test, and bar.test, nothing else:
     assert len(dump.grep(Re("test. [0-9]* DELEG"))) == 3

-    templates.render("ns1/root.db", {"broken_ns": True})
+    templates.render("ns1/root.db", {"broken_ns": True}, newer=True)
     with ns1.watch_log_from_here() as watcher:
         ns1.rndc("reload")
         watcher.wait_for_all(["running", "zone ./IN: loaded serial"])
diff --git a/bin/tests/system/dnssec/tests_validation.py b/bin/tests/system/dnssec/tests_validation.py
index 9a560ffcf5..16568ffd86 100644
--- a/bin/tests/system/dnssec/tests_validation.py
+++ b/bin/tests/system/dnssec/tests_validation.py
@@ -19,7 +19,7 @@ from dns.edns import EDECode

 import pytest

-from isctest.util import param
+from isctest.util import newer_mtime, param

 import isctest
 import isctest.mark
@@ -925,9 +925,9 @@ def test_validation_recovery(ns2, ns4):
         watcher.wait_for_line("10.53.0.100")

     # then reload server with properly signed zone
-    shutil.copyfile(
-        "ns2/peer.peer-ns-spoof.db.next", "ns2/peer.peer-ns-spoof.db.signed"
-    )
+    zone_file = "ns2/peer.peer-ns-spoof.db.signed"
+    with newer_mtime(zone_file):
+        shutil.copyfile("ns2/peer.peer-ns-spoof.db.next", zone_file)
     with ns2.watch_log_from_here() as watcher:
         ns2.rndc("reload peer.peer-ns-spoof")
         watcher.wait_for_line("zone peer.peer-ns-spoof/IN: loaded serial 2000042408")
@@ -946,9 +946,9 @@ def test_validation_recovery(ns2, ns4):
     isctest.check.rr_count_eq(res.answer, 2)

     # then reload server with properly signed zone
-    shutil.copyfile(
-        "ns2/dnskey-rrsigs-stripped.db.next", "ns2/dnskey-rrsigs-stripped.db.signed"
-    )
+    zone_file = "ns2/dnskey-rrsigs-stripped.db.signed"
+    with newer_mtime(zone_file):
+        shutil.copyfile("ns2/dnskey-rrsigs-stripped.db.next", zone_file)
     with ns2.watch_log_from_here() as watcher:
         ns2.rndc("reload dnskey-rrsigs-stripped")
         watcher.wait_for_line(
@@ -970,9 +970,9 @@ def test_validation_recovery(ns2, ns4):
     isctest.check.rr_count_eq(res.answer, 1)

     # then reload server with properly signed zone
-    shutil.copyfile(
-        "ns2/ds-rrsigs-stripped.db.next", "ns2/ds-rrsigs-stripped.db.signed"
-    )
+    zone_file = "ns2/ds-rrsigs-stripped.db.signed"
+    with newer_mtime(zone_file):
+        shutil.copyfile("ns2/ds-rrsigs-stripped.db.next", zone_file)
     with ns2.watch_log_from_here() as watcher:
         ns2.rndc("reload ds-rrsigs-stripped")
         watcher.wait_for_line("zone ds-rrsigs-stripped/IN: loaded serial 2000042408")
diff --git a/bin/tests/system/kasp/tests_kasp.py b/bin/tests/system/kasp/tests_kasp.py
index 28747b5b8c..a030228d22 100644
--- a/bin/tests/system/kasp/tests_kasp.py
+++ b/bin/tests/system/kasp/tests_kasp.py
@@ -27,7 +27,7 @@ import pytest

 from isctest.algorithms import ECDSAP256SHA256, ECDSAP384SHA384, Algorithm
 from isctest.kasp import KeyProperties, KeyTimingMetadata, SettimeOptions
-from isctest.util import param
+from isctest.util import newer_mtime, param

 import isctest
 import isctest.mark
@@ -224,7 +224,8 @@ def cb_ixfr_is_signed(expected_updates, params, ksks=None, zsks=None):
     isctest.log.debug(
         f"expected updates {expected_updates} policy {policy} ksks {ksks} zsks {zsks}"
     )
-    shutil.copyfile(f"ns2/{zone}.db.in2", f"ns2/{zone}.db")
+    with newer_mtime(f"ns2/{zone}.db"):
+        shutil.copyfile(f"ns2/{zone}.db.in2", f"ns2/{zone}.db")
     servers["ns2"].rndc(f"reload {zone}")

     def update_is_signed():
@@ -928,7 +929,8 @@ def test_kasp_default(ns3):

     # modify unsigned zone file and check that new record is signed.
     isctest.log.info("check that an updated zone signs the new record")
-    shutil.copyfile("ns3/template2.db.in", f"ns3/{zone}.db")
+    with newer_mtime(f"ns3/{zone}.db"):
+        shutil.copyfile("ns3/template2.db.in", f"ns3/{zone}.db")
     ns3.rndc(f"reload {zone}")

     def update_is_signed():
@@ -1727,7 +1729,8 @@ def test_kasp_reload_restart(ns6):
     soa1, ttl1 = query_soa(zone)
     assert ttl1 == 300

-    shutil.copyfile(f"ns6/{zone}2.db.in", f"ns6/{zone}.db")
+    with newer_mtime(f"ns6/{zone}.db"):
+        shutil.copyfile(f"ns6/{zone}2.db.in", f"ns6/{zone}.db")
     with ns6.watch_log_from_here() as watcher:
         ns6.rndc("reload")
         watcher.wait_for_line("all zones loaded")
diff --git a/bin/tests/system/nsec_ixfr/tests_nsec_ixfr.py b/bin/tests/system/nsec_ixfr/tests_nsec_ixfr.py
index 93a25b19be..045b2dec48 100644
--- a/bin/tests/system/nsec_ixfr/tests_nsec_ixfr.py
+++ b/bin/tests/system/nsec_ixfr/tests_nsec_ixfr.py
@@ -23,6 +23,8 @@ import shutil
 import dns.name
 import dns.rdatatype

+from isctest.util import newer_mtime
+
 import isctest

 ORIGIN = dns.name.from_text("example.")
@@ -44,7 +46,8 @@ def test_nsec_ixfr_empty_nonterminal(ns1, ns2):
     isctest.query.wait_for_serial(ns2.ip, "example", 1)

     # Step 2: Replace the zone on ns1 with the signed version and reload.
-    shutil.copy("ns1/example.db.signed", "ns1/example.db")
+    with newer_mtime("ns1/example.db"):
+        shutil.copy("ns1/example.db.signed", "ns1/example.db")
     ns1.rndc("reload")

     # Step 3: Wait for ns2 to get the signed zone via IXFR.
diff --git a/bin/tests/system/xfer/tests_xfer.py b/bin/tests/system/xfer/tests_xfer.py
index 0572d3d544..1be47b8ada 100644
--- a/bin/tests/system/xfer/tests_xfer.py
+++ b/bin/tests/system/xfer/tests_xfer.py
@@ -23,7 +23,7 @@ import dns.tsig
 import dns.zone
 import pytest

-from isctest.util import param
+from isctest.util import newer_mtime, param

 import isctest

@@ -165,7 +165,10 @@ def test_zone_is_dumped_after_transfer(ns1, ns2, ns3, ns6, ns7):
         "ns6/primary.db",
         "ns7/primary2.db",
     ]:
-        with fileinput.FileInput(zone_file, inplace=True) as file:
+        with (
+            newer_mtime(zone_file),
+            fileinput.FileInput(zone_file, inplace=True) as file,
+        ):
             for line in file:
                 print(
                     line.replace(" 0.0.0.0", " 0.0.0.1").replace(