Skip to content

Commit dae4384

Browse files
committed
fix: reconcile runtime bindings with SubOS views
1 parent 08857c7 commit dae4384

3 files changed

Lines changed: 198 additions & 3 deletions

File tree

src/platform/runtime_binding.cppm

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,79 @@ std::filesystem::path subos_path(
121121
/ selection.subosName;
122122
}
123123

124+
// `subos_info.runtime` is the provider's declared identity, while the SubOS
125+
// view is the provider's resolved result. Older xlings states can retain the
126+
// declaration after a package transaction has atomically repointed the view
127+
// (observed in CI as runtime=glibc@2.39 with libc.so.6 resolving to the managed
128+
// 2.44 payload). When the physical target can be proven to be one exact
129+
// provider payload, use that immutable identity. This is not directory
130+
// discovery: the selected SubOS link supplies the one path, and mcpp merely
131+
// canonicalizes its provenance.
132+
std::optional<std::string> managed_glibc_identity(
133+
const std::filesystem::path& libraryDir,
134+
const std::filesystem::path& xlingsRoot) {
135+
std::error_code bec, lec;
136+
auto base = std::filesystem::weakly_canonical(
137+
xlingsRoot / "data" / "xpkgs" / "xim-x-glibc", bec);
138+
auto real = std::filesystem::weakly_canonical(libraryDir, lec);
139+
if (bec || lec || base.empty() || real.empty()) return std::nullopt;
140+
141+
auto relative = real.lexically_relative(base);
142+
if (relative.empty() || relative.is_absolute()) return std::nullopt;
143+
std::vector<std::string> parts;
144+
for (auto const& part : relative) parts.push_back(part.string());
145+
if (parts.size() != 2 || (parts[1] != "lib" && parts[1] != "lib64"))
146+
return std::nullopt;
147+
auto const& version = parts[0];
148+
if (version.empty() || version == "." || version == ".."
149+
|| version.find('@') != std::string::npos
150+
|| version.find('/') != std::string::npos
151+
|| version.find('\\') != std::string::npos)
152+
return std::nullopt;
153+
return "glibc@" + version;
154+
}
155+
156+
struct ManagedGlibcPayload {
157+
std::filesystem::path libraryDir;
158+
std::filesystem::path loader;
159+
};
160+
161+
std::optional<ManagedGlibcPayload> exact_declared_glibc_payload(
162+
std::string_view runtimeId,
163+
const std::filesystem::path& xlingsRoot) {
164+
constexpr std::string_view prefix = "glibc@";
165+
if (!runtimeId.starts_with(prefix)) return std::nullopt;
166+
auto version = runtimeId.substr(prefix.size());
167+
if (version.empty() || version == "." || version == ".."
168+
|| version.find('@') != std::string_view::npos
169+
|| version.find('/') != std::string_view::npos
170+
|| version.find('\\') != std::string_view::npos)
171+
return std::nullopt;
172+
173+
auto payload = xlingsRoot / "data" / "xpkgs" / "xim-x-glibc"
174+
/ std::string(version);
175+
for (auto const& leaf : {"lib64", "lib"}) {
176+
auto lib = payload / leaf;
177+
std::error_code ec;
178+
if (!std::filesystem::is_regular_file(lib / "libc.so.6", ec))
179+
continue;
180+
std::vector<std::filesystem::path> loaders;
181+
ec.clear();
182+
for (auto it = std::filesystem::directory_iterator(lib, ec);
183+
!ec && it != std::filesystem::directory_iterator{};
184+
it.increment(ec)) {
185+
auto name = it->path().filename().string();
186+
if (name.starts_with("ld-linux-") && name.find(".so") != std::string::npos
187+
&& it->is_regular_file(ec))
188+
loaders.push_back(it->path());
189+
}
190+
std::sort(loaders.begin(), loaders.end());
191+
if (loaders.size() == 1)
192+
return ManagedGlibcPayload{lib, loaders.front()};
193+
}
194+
return std::nullopt;
195+
}
196+
124197
} // namespace detail
125198

126199
std::expected<RuntimeBinding, std::string>
@@ -187,6 +260,11 @@ resolve_runtime_binding(
187260
auto libDir = lec ? candidate.lexically_normal()
188261
: realLibc.parent_path();
189262
out.libraryDirs.push_back(libDir);
263+
if (auto physical = detail::managed_glibc_identity(
264+
libDir, out.subosDir.parent_path().parent_path())) {
265+
out.runtimeId = *physical;
266+
out.libc = *physical;
267+
}
190268

191269
std::vector<std::filesystem::path> loaders;
192270
lec.clear();
@@ -204,6 +282,13 @@ resolve_runtime_binding(
204282
}
205283
break;
206284
}
285+
if (out.libraryDirs.empty()) {
286+
if (auto exact = detail::exact_declared_glibc_payload(
287+
out.runtimeId, out.subosDir.parent_path().parent_path())) {
288+
out.libraryDirs.push_back(exact->libraryDir);
289+
out.loader = exact->loader;
290+
}
291+
}
207292
}
208293

209294
for (auto const& provider : info.providers) {

tests/unit/test_elf_runtime.cpp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include <gtest/gtest.h>
22

33
import std;
4+
import mcpp.platform;
45
import mcpp.platform.elf_runtime;
56
import mcpp.platform.runtime_binding;
67
import mcpp.toolchain.post_install;
@@ -224,6 +225,8 @@ TEST(ElfRuntime, RejectsUnsupportedOrTruncatedElfWithoutGuessing) {
224225
}
225226

226227
TEST(RuntimePhysics, RuleBRejectsInterpreterAndLibcFromDifferentPayloads) {
228+
if constexpr (!mcpp::platform::is_linux)
229+
GTEST_SKIP() << "ELF/glibc runtime physics only apply on Linux";
227230
Tmp t;
228231
auto b = binding_for(t.path / "store");
229232
elf::RuntimeResolution r;
@@ -238,6 +241,8 @@ TEST(RuntimePhysics, RuleBRejectsInterpreterAndLibcFromDifferentPayloads) {
238241
}
239242

240243
TEST(RuntimePhysics, RuleBAcceptsSameSelectedPayload) {
244+
if constexpr (!mcpp::platform::is_linux)
245+
GTEST_SKIP() << "ELF/glibc runtime physics only apply on Linux";
241246
Tmp t;
242247
auto b = binding_for(t.path / "store");
243248
elf::RuntimeResolution r;
@@ -251,6 +256,8 @@ TEST(RuntimePhysics, RuleBAcceptsSameSelectedPayload) {
251256
}
252257

253258
TEST(RuntimePhysics, RuleBRejectsTwoLibcsAcrossTheResolvedClosure) {
259+
if constexpr (!mcpp::platform::is_linux)
260+
GTEST_SKIP() << "ELF/glibc runtime physics only apply on Linux";
254261
Tmp t;
255262
auto b = binding_for(t.path / "store");
256263
elf::RuntimeResolution r;
@@ -269,6 +276,8 @@ TEST(RuntimePhysics, RuleBRejectsTwoLibcsAcrossTheResolvedClosure) {
269276
}
270277

271278
TEST(RuntimePhysics, RuleARejectsRequiredFloorAboveSelectedLibcExports) {
279+
if constexpr (!mcpp::platform::is_linux)
280+
GTEST_SKIP() << "ELF/glibc runtime physics only apply on Linux";
272281
Tmp t;
273282
auto b = binding_for(t.path / "store", "2.39");
274283
elf::RuntimeResolution r;
@@ -285,6 +294,8 @@ TEST(RuntimePhysics, RuleARejectsRequiredFloorAboveSelectedLibcExports) {
285294
}
286295

287296
TEST(RuntimePhysics, RuleAAcceptsEqualOrLowerFloor) {
297+
if constexpr (!mcpp::platform::is_linux)
298+
GTEST_SKIP() << "ELF/glibc runtime physics only apply on Linux";
288299
Tmp t;
289300
auto b = binding_for(t.path / "store");
290301
elf::RuntimeResolution r;
@@ -299,6 +310,8 @@ TEST(RuntimePhysics, RuleAAcceptsEqualOrLowerFloor) {
299310
}
300311

301312
TEST(RuntimePhysics, MissingClosureDataIsInconclusiveNotGreen) {
313+
if constexpr (!mcpp::platform::is_linux)
314+
GTEST_SKIP() << "ELF/glibc runtime physics only apply on Linux";
302315
Tmp t;
303316
auto b = binding_for(t.path / "store");
304317
elf::RuntimeResolution r;
@@ -312,4 +325,19 @@ TEST(RuntimePhysics, MissingClosureDataIsInconclusiveNotGreen) {
312325
EXPECT_NE(verdict.explain().find("libgpu-driver.so"), std::string::npos);
313326
}
314327

328+
TEST(RuntimePhysics, NonLinuxValidatorIsATypedNoop) {
329+
if constexpr (mcpp::platform::is_linux)
330+
GTEST_SKIP() << "the non-Linux boundary is exercised on native runners";
331+
Tmp t;
332+
auto b = binding_for(t.path / "store");
333+
elf::RuntimeResolution r;
334+
r.artifact = facts(t.path / "app");
335+
r.artifact.interp = "/deliberately/mismatched/loader";
336+
r.unresolved = {"libgpu-driver.so"};
337+
338+
auto verdict = elf::validate_runtime_artifact(r.artifact.artifact, b, r);
339+
EXPECT_EQ(verdict.status, elf::RuntimeVerdict::Status::Pass);
340+
EXPECT_NE(verdict.explain().find("non-Linux"), std::string::npos);
341+
}
342+
315343
} // namespace

tests/unit/test_runtime_selection.cpp

Lines changed: 85 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import std;
44
import mcpp.config;
55
import mcpp.manifest;
6+
import mcpp.platform;
67
import mcpp.platform.runtime_binding;
78
import mcpp.xlings.runtime_selection;
89

@@ -64,6 +65,27 @@ struct RuntimeHome {
6465
"workspace": {{}}
6566
}})", runtimeName, envValue);
6667
}
68+
69+
void point_default_glibc_view_at(std::string_view version) const {
70+
auto payload = install_glibc_payload(version);
71+
auto view = cfg.xlingsHome() / "subos" / "default" / "lib";
72+
std::filesystem::create_directories(view);
73+
std::filesystem::create_symlink(
74+
payload / "libc.so.6", view / "libc.so.6");
75+
std::filesystem::create_symlink(
76+
payload / "ld-linux-x86-64.so.2",
77+
view / "ld-linux-x86-64.so.2");
78+
}
79+
80+
std::filesystem::path install_glibc_payload(
81+
std::string_view version) const {
82+
auto payload = cfg.xlingsHome() / "data" / "xpkgs"
83+
/ "xim-x-glibc" / version / "lib";
84+
std::filesystem::create_directories(payload);
85+
std::ofstream(payload / "libc.so.6") << "fixture libc";
86+
std::ofstream(payload / "ld-linux-x86-64.so.2") << "fixture loader";
87+
return payload;
88+
}
6789
};
6890

6991
TEST(RuntimeSelection, AbsenceMeansMcppDefault) {
@@ -134,7 +156,10 @@ TEST(RuntimeBinding, DefaultAlwaysUsesConfiguredMcppHomeDefault) {
134156
ASSERT_TRUE(binding.has_value()) << binding.error();
135157
EXPECT_EQ(binding->subosDir, expected);
136158
EXPECT_EQ(binding->runtimeId, "glibc@2.39");
137-
EXPECT_EQ(binding->libc, std::optional<std::string>("glibc@2.39"));
159+
if constexpr (mcpp::platform::is_linux)
160+
EXPECT_EQ(binding->libc, std::optional<std::string>("glibc@2.39"));
161+
else
162+
EXPECT_FALSE(binding->libc.has_value());
138163
EXPECT_EQ(binding->provenance, "mcpp_default");
139164
EXPECT_FALSE(binding->contractHash.empty());
140165
ASSERT_EQ(binding->runtimeProviders.size(), 1u);
@@ -159,6 +184,59 @@ TEST(RuntimeBinding, ExplicitDefaultUsesGlobalDefaultButKeepsNamedIdentity) {
159184
EXPECT_EQ(binding->selection.mode, runtime::RuntimeSelection::Mode::NamedSubos);
160185
}
161186

187+
TEST(RuntimeBinding, PhysicalSubosViewReconcilesAStaleDeclaredGlibcIdentity) {
188+
if constexpr (!mcpp::platform::is_linux)
189+
GTEST_SKIP() << "glibc SubOS views only exist on Linux";
190+
191+
RuntimeHome h;
192+
const auto subos = h.cfg.xlingsHome() / "subos" / "default";
193+
h.write(subos, "glibc@2.39");
194+
h.point_default_glibc_view_at("2.44");
195+
196+
mcpp::manifest::Manifest root;
197+
auto selection = runtime::select_runtime(root, std::nullopt, h.dir / "repo");
198+
ASSERT_TRUE(selection);
199+
auto binding = mcpp::platform::runtime::resolve_runtime_binding(
200+
*selection, {}, h.cfg);
201+
ASSERT_TRUE(binding.has_value()) << binding.error();
202+
203+
EXPECT_EQ(binding->runtimeId, "glibc@2.44");
204+
EXPECT_EQ(binding->libc, std::optional<std::string>("glibc@2.44"));
205+
ASSERT_EQ(binding->libraryDirs.size(), 1u);
206+
EXPECT_EQ(binding->libraryDirs.front(),
207+
h.cfg.xlingsHome() / "data" / "xpkgs"
208+
/ "xim-x-glibc" / "2.44" / "lib");
209+
}
210+
211+
TEST(RuntimeBinding, BrokenSubosViewFallsBackOnlyToTheExactDeclaredPayload) {
212+
if constexpr (!mcpp::platform::is_linux)
213+
GTEST_SKIP() << "glibc SubOS views only exist on Linux";
214+
215+
RuntimeHome h;
216+
const auto subos = h.cfg.xlingsHome() / "subos" / "default";
217+
h.write(subos, "glibc@2.44");
218+
auto payload = h.install_glibc_payload("2.44");
219+
auto view = subos / "lib";
220+
std::filesystem::create_directories(view);
221+
std::filesystem::create_symlink(
222+
payload.parent_path() / "lib64" / "libc.so.6",
223+
view / "libc.so.6");
224+
225+
mcpp::manifest::Manifest root;
226+
auto selection = runtime::select_runtime(root, std::nullopt, h.dir / "repo");
227+
ASSERT_TRUE(selection);
228+
auto binding = mcpp::platform::runtime::resolve_runtime_binding(
229+
*selection, {}, h.cfg);
230+
ASSERT_TRUE(binding.has_value()) << binding.error();
231+
232+
EXPECT_EQ(binding->runtimeId, "glibc@2.44");
233+
ASSERT_EQ(binding->libraryDirs.size(), 1u);
234+
EXPECT_EQ(binding->libraryDirs.front(), payload);
235+
EXPECT_EQ(binding->loader,
236+
std::optional<std::filesystem::path>(
237+
payload / "ld-linux-x86-64.so.2"));
238+
}
239+
162240
TEST(RuntimeBinding, NamedSubosIsOwnedByTheRootAndMissingIsHardError) {
163241
RuntimeHome h;
164242
auto manifest = named("el8");
@@ -203,8 +281,12 @@ TEST(RuntimeBinding, NamedEnvironmentsHaveDistinctContractsAndRoundTrip) {
203281
auto decoded = mcpp::platform::runtime::deserialize_runtime_binding(encoded);
204282
ASSERT_TRUE(decoded.has_value()) << decoded.error();
205283
EXPECT_EQ(decoded->contractHash, el8->contractHash);
206-
ASSERT_TRUE(decoded->hostLibc.has_value());
207-
EXPECT_EQ(*decoded->hostLibc, "2.43");
284+
if constexpr (mcpp::platform::is_linux) {
285+
ASSERT_TRUE(decoded->hostLibc.has_value());
286+
EXPECT_EQ(*decoded->hostLibc, "2.43");
287+
} else {
288+
EXPECT_FALSE(decoded->hostLibc.has_value());
289+
}
208290
EXPECT_EQ(decoded->subosDir, el8->subosDir);
209291
ASSERT_EQ(decoded->environment.size(), 1u);
210292
EXPECT_EQ(decoded->environment[0].var, "DRIVERS");

0 commit comments

Comments
 (0)