Skip to content

Commit 85e96d8

Browse files
artem-from-uaclaude
andcommitted
Invalidate nettle and GnuTLS when the GMP provider changes
build_samba4x_nettle() hardcoded "system-gmp" in its stamp name, so a tree that switched from the adopted target GMP to the bundled one still found the old stamp and both libraries in place, reported "already built" and reused them. The libgmp.a underneath had been replaced; nettle and GnuTLS had not. GnuTLS is affected through the same route, linking -lhogweed -lnettle -lgmp. Both stamps now carry the provider and version of the GMP they were built against, so selecting a different one no longer matches an existing stamp. The version the adopted GMP is recorded with moves into a variable shared with gmp.pc, which is where it was already written. The same slot problem applies to GMP itself: both providers install to the same deps/lib/libgmp.a, which is all the bundled stamp checks for. Adopting the target GMP now retires that stamp, so a later rejection rebuilds the bundled GMP instead of reusing the archive the adopt left behind. The __gmpn_zero_p probe now matches the symbol class, since nm also lists "U __gmpn_zero_p" for members that merely reference it. Tests cover a usable target GMP, a missing __gmpn_zero_p, an undefined reference to it, an nm that cannot be run, a stale downstream stamp, and a retired GMP stamp. The fake toolchain gains an nm, without which the symbol probe was skipped in every test. Dependency downloads now point at missing file:// URLs, so a stamp name that stops matching fails at once instead of fetching from ftp.gnu.org. MKCOMPAT joins build/.env.example alongside NO_PTHREADS. Existing build trees rebuild nettle and GnuTLS once. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent e25c68b commit 85e96d8

3 files changed

Lines changed: 216 additions & 6 deletions

File tree

build/.env.example

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ HOST_CFLAGS='-O -fcommon -fgnu89-inline'
5353
HOST_CXXFLAGS='-O -fcommon -fgnu89-inline'
5454
HOST_CPPFLAGS='-D__GNUC_GNU_INLINE__ -D__STDC_WANT_LIB_EXT1__=1'
5555
NO_PTHREADS='1'
56+
# Compat libraries are not needed to link one static earmv4 smbd, and building
57+
# them breaks the distribution phase on a from-scratch tree. Set to 'yes' to
58+
# build them anyway.
59+
MKCOMPAT='no'
5660

5761
# Samba 4 netbsd7 lane
5862
SAMBA4_VERSION='4.8.12'

build/_samba4x.sh

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -761,6 +761,15 @@ find_samba4x_gmp_header() {
761761
return 1
762762
}
763763

764+
# The GMP in the NetBSD tree reports itself as 6.1.0. Keep that in one place so
765+
# gmp.pc and the dependency stamps cannot drift apart.
766+
SAMBA4X_TARGET_GMP_VERSION="6.1.0"
767+
768+
# Records which GMP the nettle and GnuTLS artifacts were built against. Both are
769+
# linked to libgmp, so replacing it has to invalidate them rather than let them
770+
# be reused. Each GMP path below sets this, and the stamps interpolate it.
771+
SAMBA4X_GMP_STAMP_ID="unknown-gmp"
772+
764773
install_samba4x_target_gmp() {
765774
gmp_lib="$OBJ/external/lgpl3/gmp/lib/libgmp/libgmp.a"
766775
if ! gmp_header="$(find_samba4x_gmp_header)"; then
@@ -776,9 +785,13 @@ install_samba4x_target_gmp() {
776785
# build our own GMP instead of adopting the one from the NetBSD tree.
777786
# Only reject on a successful nm run, so a missing or renamed nm leaves
778787
# the existing behaviour untouched.
788+
# Match the symbol class rather than the bare name: nm also lists
789+
# "U __gmpn_zero_p" for members that only reference it, which would read as
790+
# a definition. Grepping for a defined class avoids nm --defined-only,
791+
# which is not portable across the nm builds this toolchain ships.
779792
if gmp_syms="$("$TOOLDIR/bin/$TRIPLE-nm" "$gmp_lib" 2>/dev/null)"; then
780793
case "$gmp_syms" in
781-
*__gmpn_zero_p*) ;;
794+
*[TDRtdrBb]" __gmpn_zero_p"*) ;;
782795
*)
783796
echo "NetBSD target libgmp.a lacks __gmpn_zero_p; nettle would"
784797
echo "build without public-key support."
@@ -788,13 +801,23 @@ install_samba4x_target_gmp() {
788801
fi
789802

790803
mkdir -p "$SAMBA4X_DEPS/lib" "$SAMBA4X_DEPS/include" "$SAMBA4X_DEPS/lib/pkgconfig"
804+
# Both providers install to the same libgmp.a, and the bundled stamp treats
805+
# that file as proof of its own build. Drop the stamp while overwriting the
806+
# archive, so a later run that rejects this GMP rebuilds the bundled one
807+
# instead of adopting what is left here.
808+
rm -f "$SAMBA4X_DEPS/.stamp-gmp-$SAMBA4X_GMP_VERSION"
791809
cp "$gmp_lib" "$SAMBA4X_DEPS/lib/libgmp.a"
792810
cp "$gmp_header" "$SAMBA4X_DEPS/include/gmp.h"
793811
gmp_mparam="$(dirname "$gmp_header")/gmp-mparam.h"
794812
if [ -f "$gmp_mparam" ]; then
795813
cp "$gmp_mparam" "$SAMBA4X_DEPS/include/gmp-mparam.h"
796814
fi
797-
write_samba4x_gmp_pc "6.1.0"
815+
# Claim the stamp id only here, past every rejection above: a function called
816+
# as an if condition runs in the current shell, so an assignment made before
817+
# a "return 1" would still reach the caller and mislabel the downstream
818+
# stamps as system when the bundled GMP is what actually gets used.
819+
SAMBA4X_GMP_STAMP_ID="system-$SAMBA4X_TARGET_GMP_VERSION"
820+
write_samba4x_gmp_pc "$SAMBA4X_TARGET_GMP_VERSION"
798821
}
799822

800823
write_samba4x_gmp_pc() {
@@ -814,6 +837,9 @@ EOF
814837
}
815838

816839
build_samba4x_gmp() {
840+
# Set before the early return below so a cached bundled GMP labels the
841+
# downstream stamps the same way a freshly built one does.
842+
SAMBA4X_GMP_STAMP_ID="bundled-$SAMBA4X_GMP_VERSION"
817843
stamp="$SAMBA4X_DEPS/.stamp-gmp-$SAMBA4X_GMP_VERSION"
818844
if [ -f "$stamp" ] && [ -f "$SAMBA4X_DEPS/lib/libgmp.a" ]; then
819845
echo "GMP $SAMBA4X_GMP_VERSION already built."
@@ -859,7 +885,7 @@ EOF
859885
}
860886

861887
build_samba4x_nettle() {
862-
stamp="$SAMBA4X_DEPS/.stamp-nettle-$SAMBA4X_NETTLE_VERSION-system-gmp"
888+
stamp="$SAMBA4X_DEPS/.stamp-nettle-$SAMBA4X_NETTLE_VERSION-$SAMBA4X_GMP_STAMP_ID"
863889
if [ -f "$stamp" ] &&
864890
[ -f "$SAMBA4X_DEPS/lib/libnettle.a" ] &&
865891
[ -f "$SAMBA4X_DEPS/lib/libhogweed.a" ]; then
@@ -935,7 +961,9 @@ rewrite_samba4x_gnutls_pc() {
935961
}
936962

937963
build_samba4x_gnutls() {
938-
gnutls_stamp_suffix="system-nettle-oaep-no-thread-local"
964+
# GnuTLS links -lhogweed -lnettle -lgmp, so a different GMP invalidates it
965+
# just as it invalidates nettle.
966+
gnutls_stamp_suffix="system-nettle-oaep-no-thread-local-$SAMBA4X_GMP_STAMP_ID"
939967
stamp="$SAMBA4X_DEPS/.stamp-gnutls-$SAMBA4X_GNUTLS_VERSION-$gnutls_stamp_suffix"
940968
if [ -f "$stamp" ] && [ -f "$SAMBA4X_DEPS/lib/libgnutls.a" ]; then
941969
echo "GnuTLS $SAMBA4X_GNUTLS_VERSION already built."

tests/test_build_samba4x.py

Lines changed: 180 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,15 @@
1111

1212
REPO_ROOT = Path(__file__).resolve().parent.parent
1313

14+
# The dependency stamps carry the GMP the artifact was built against, so these
15+
# have to match what _samba4x.sh computes. A mismatch makes the build scripts
16+
# treat every dependency as unbuilt and try to download it.
17+
NETTLE_SYSTEM_GMP_STAMP = ".stamp-nettle-3.10.1-system-6.1.0"
18+
NETTLE_BUNDLED_GMP_STAMP = ".stamp-nettle-3.10.1-bundled-6.3.0"
19+
GNUTLS_SYSTEM_GMP_STAMP = (
20+
".stamp-gnutls-3.8.5-system-nettle-oaep-no-thread-local-system-6.1.0"
21+
)
22+
1423

1524
class Samba4XBuildScriptTests(unittest.TestCase):
1625
def make_executable(self, path: Path, text: str) -> None:
@@ -94,6 +103,26 @@ def prepare_fake_toolchain(self, out: Path, triple: str) -> None:
94103
"""
95104
),
96105
)
106+
# nm drives the __gmpn_zero_p probe that decides whether the GMP in the
107+
# NetBSD tree is usable. It cannot join the loop below: those exit 0
108+
# without printing, which would read as a GMP missing every symbol.
109+
self.make_executable(
110+
tools / f"{triple}-nm",
111+
textwrap.dedent(
112+
"""\
113+
#!/bin/sh
114+
if [ "${TEST_NM_RC:-0}" != "0" ]; then
115+
exit "$TEST_NM_RC"
116+
fi
117+
printf '0000 T __gmpn_add_n\\n'
118+
case "${TEST_NM_ZERO_P:-1}" in
119+
1) printf '0000 T __gmpn_zero_p\\n' ;;
120+
undefined) printf ' U __gmpn_zero_p\\n' ;;
121+
esac
122+
exit 0
123+
"""
124+
),
125+
)
97126
for name in ("ar", "ranlib", "readelf", "strip"):
98127
self.make_executable(tools / f"{triple}-{name}", "#!/bin/sh\nexit 0\n")
99128

@@ -222,12 +251,12 @@ def prepare_fake_netbsd_inputs(self, root: Path, *, lane: str) -> dict[str, Path
222251
self.make_file(build_src / "external" / "lgpl3" / "gmp" / "lib" / "libgmp" / "arch" / gmp_arch / "gmp.h")
223252

224253
deps = samba_build / "deps"
225-
self.make_file(deps / ".stamp-nettle-3.10.1-system-gmp")
254+
self.make_file(deps / NETTLE_SYSTEM_GMP_STAMP)
226255
self.make_file(deps / "lib" / "libnettle.a")
227256
self.make_file(deps / "lib" / "libhogweed.a")
228257
self.make_file(deps / ".stamp-libtasn1-4.20.0")
229258
self.make_file(deps / "lib" / "libtasn1.a")
230-
self.make_file(deps / ".stamp-gnutls-3.8.5-system-nettle-oaep-no-thread-local")
259+
self.make_file(deps / GNUTLS_SYSTEM_GMP_STAMP)
231260
self.make_file(deps / "lib" / "libgnutls.a")
232261
self.make_file(deps / "lib" / "pkgconfig" / "gnutls.pc", "Libs: -L${libdir} -lgnutls\n")
233262

@@ -239,9 +268,25 @@ def prepare_fake_netbsd_inputs(self, root: Path, *, lane: str) -> dict[str, Path
239268
"samba_stage": samba_stage,
240269
}
241270

271+
def offline_dependency_urls(self, root: Path) -> dict[str, str]:
272+
"""Point every dependency download at a file that does not exist.
273+
274+
The fixture pre-creates the dependency stamps so nothing is downloaded,
275+
but a stamp name that stops matching what the build scripts compute
276+
would silently turn these tests into real fetches from ftp.gnu.org. A
277+
missing file:// URL fails curl at once instead.
278+
"""
279+
return {
280+
"SAMBA4X_GMP_URL": f"file://{root / 'no-such-gmp.tar.xz'}",
281+
"SAMBA4X_NETTLE_URL": f"file://{root / 'no-such-nettle.tar.gz'}",
282+
"SAMBA4X_LIBTASN1_URL": f"file://{root / 'no-such-libtasn1.tar.gz'}",
283+
"SAMBA4X_GNUTLS_URL": f"file://{root / 'no-such-gnutls.tar.xz'}",
284+
}
285+
242286
def env_for_lane(self, root: Path, lane: str, capture: Path) -> dict[str, str]:
243287
paths = self.prepare_fake_netbsd_inputs(root, lane=lane)
244288
env = os.environ.copy()
289+
env.update(self.offline_dependency_urls(root))
245290
env.update(
246291
{
247292
"TC_ENV_FILE": "/dev/null",
@@ -724,6 +769,139 @@ def test_generation_fails_when_independent_probe_disagrees(self) -> None:
724769
)
725770
self.assertFalse((output_dir / "samba4x-4.24.3-netbsd4be.answers").exists())
726771

772+
def test_usable_target_gmp_is_adopted_and_stamped_as_system(self) -> None:
773+
with tempfile.TemporaryDirectory() as tmp:
774+
root = Path(tmp)
775+
capture = root / "configure-args.txt"
776+
env = self.env_for_lane(root, "netbsd7", capture)
777+
778+
result = self.run_wrapper("samba4x.sh", env)
779+
780+
self.assertEqual(result.returncode, 0, result.stdout + result.stderr)
781+
log = Path(env["SAMBA4X_NETBSD7_LOG"]).read_text()
782+
self.assertIn("Using NetBSD target GMP", log)
783+
self.assertIn("nettle 3.10.1 already built.", log)
784+
self.assertIn("GnuTLS 3.8.5 already built.", log)
785+
deps = Path(env["SAMBA4X_NETBSD7_BUILD"]) / "deps"
786+
self.assertTrue((deps / NETTLE_SYSTEM_GMP_STAMP).exists())
787+
self.assertTrue((deps / GNUTLS_SYSTEM_GMP_STAMP).exists())
788+
pc = (deps / "lib" / "pkgconfig" / "gmp.pc").read_text()
789+
self.assertIn("Version: 6.1.0", pc)
790+
self.assertNotIn("Version: 6.3.0", pc)
791+
792+
def test_target_gmp_without_zero_p_falls_back_to_bundled_gmp(self) -> None:
793+
with tempfile.TemporaryDirectory() as tmp:
794+
root = Path(tmp)
795+
capture = root / "configure-args.txt"
796+
env = self.env_for_lane(root, "netbsd7", capture)
797+
env["TEST_NM_ZERO_P"] = "0"
798+
799+
result = self.run_wrapper("samba4x.sh", env)
800+
801+
self.assertNotEqual(result.returncode, 0)
802+
log = Path(env["SAMBA4X_NETBSD7_LOG"]).read_text()
803+
self.assertIn("lacks __gmpn_zero_p", log)
804+
self.assertIn("NetBSD target GMP is unavailable; building GMP 6.3.0.", log)
805+
self.assertNotIn("Using NetBSD target GMP", log)
806+
self.assertFalse(capture.exists())
807+
808+
def test_unusable_nm_leaves_target_gmp_adoption_untouched(self) -> None:
809+
cases = ("nm exits nonzero", "nm missing")
810+
for label in cases:
811+
with self.subTest(label=label):
812+
with tempfile.TemporaryDirectory() as tmp:
813+
root = Path(tmp)
814+
capture = root / "configure-args.txt"
815+
env = self.env_for_lane(root, "netbsd7", capture)
816+
if label == "nm exits nonzero":
817+
env["TEST_NM_RC"] = "1"
818+
else:
819+
tools = Path(env["BUILD_OUT"]) / "tools" / "bin"
820+
(tools / "arm--netbsdelf-nm").unlink()
821+
822+
result = self.run_wrapper("samba4x.sh", env)
823+
824+
self.assertEqual(
825+
result.returncode, 0, result.stdout + result.stderr
826+
)
827+
log = Path(env["SAMBA4X_NETBSD7_LOG"]).read_text()
828+
self.assertIn("Using NetBSD target GMP", log)
829+
self.assertNotIn("lacks __gmpn_zero_p", log)
830+
self.assertIn("nettle 3.10.1 already built.", log)
831+
832+
def test_switching_gmp_provider_invalidates_nettle_and_gnutls(self) -> None:
833+
with tempfile.TemporaryDirectory() as tmp:
834+
root = Path(tmp)
835+
capture = root / "configure-args.txt"
836+
env = self.env_for_lane(root, "netbsd7", capture)
837+
deps = Path(env["SAMBA4X_NETBSD7_BUILD"]) / "deps"
838+
# The fixture is stamped for the adopted target GMP. Reject that GMP
839+
# and present the bundled one as already built, so the run reaches
840+
# the nettle decision with the other provider selected.
841+
env["TEST_NM_ZERO_P"] = "0"
842+
self.make_file(deps / ".stamp-gmp-6.3.0")
843+
self.make_file(deps / "lib" / "libgmp.a")
844+
845+
result = self.run_wrapper("samba4x.sh", env)
846+
847+
self.assertNotEqual(result.returncode, 0)
848+
log = Path(env["SAMBA4X_NETBSD7_LOG"]).read_text()
849+
self.assertIn("GMP 6.3.0 already built.", log)
850+
self.assertNotIn("nettle 3.10.1 already built.", log)
851+
self.assertNotIn("GnuTLS 3.8.5 already built.", log)
852+
self.assertTrue((deps / NETTLE_SYSTEM_GMP_STAMP).exists())
853+
self.assertFalse((deps / NETTLE_BUNDLED_GMP_STAMP).exists())
854+
self.assertFalse(capture.exists())
855+
856+
def test_undefined_zero_p_reference_does_not_count_as_a_definition(self) -> None:
857+
with tempfile.TemporaryDirectory() as tmp:
858+
root = Path(tmp)
859+
capture = root / "configure-args.txt"
860+
env = self.env_for_lane(root, "netbsd7", capture)
861+
# nm lists "U __gmpn_zero_p" for a member that only references the
862+
# symbol. Matching the bare name would read that as a definition.
863+
env["TEST_NM_ZERO_P"] = "undefined"
864+
865+
result = self.run_wrapper("samba4x.sh", env)
866+
867+
self.assertNotEqual(result.returncode, 0)
868+
log = Path(env["SAMBA4X_NETBSD7_LOG"]).read_text()
869+
self.assertIn("lacks __gmpn_zero_p", log)
870+
self.assertNotIn("Using NetBSD target GMP", log)
871+
872+
def test_adopting_target_gmp_drops_the_bundled_gmp_stamp(self) -> None:
873+
with tempfile.TemporaryDirectory() as tmp:
874+
root = Path(tmp)
875+
capture = root / "configure-args.txt"
876+
env = self.env_for_lane(root, "netbsd7", capture)
877+
deps = Path(env["SAMBA4X_NETBSD7_BUILD"]) / "deps"
878+
# An earlier run built the bundled GMP. Both providers install to
879+
# the same libgmp.a, so adopting the target one has to retire that
880+
# stamp; otherwise a later rejection reuses the adopted archive
881+
# while calling it the bundled build.
882+
self.make_file(deps / ".stamp-gmp-6.3.0")
883+
self.make_file(deps / "lib" / "libgmp.a", "bundled gmp\n")
884+
885+
adopt = self.run_wrapper("samba4x.sh", env)
886+
887+
self.assertEqual(adopt.returncode, 0, adopt.stdout + adopt.stderr)
888+
self.assertIn(
889+
"Using NetBSD target GMP",
890+
Path(env["SAMBA4X_NETBSD7_LOG"]).read_text(),
891+
)
892+
self.assertFalse((deps / ".stamp-gmp-6.3.0").exists())
893+
894+
# The target GMP is unusable from here on, so the bundled build has
895+
# to run again rather than claim it is already done.
896+
env["TEST_NM_ZERO_P"] = "0"
897+
898+
reject = self.run_wrapper("samba4x.sh", env)
899+
900+
self.assertNotEqual(reject.returncode, 0)
901+
log = Path(env["SAMBA4X_NETBSD7_LOG"]).read_text()
902+
self.assertIn("NetBSD target GMP is unavailable", log)
903+
self.assertNotIn("GMP 6.3.0 already built.", log)
904+
727905

728906
if __name__ == "__main__":
729907
unittest.main()

0 commit comments

Comments
 (0)