Skip to content

Commit 4b56b3d

Browse files
committed
fix(nixos): add per-version gcroots for patched solc linker
On NixOS, installed solc binaries are patched to use a dynamic linker from the nix store. Without a gcroot, that interpreter path can be garbage-collected, breaking previously installed solc binaries. This change resolves the linker path explicitly, adds a persistent gcroot under the SVM `data_dir` in `.gcroots` for each solc version, and then patches the binary to that resolved interpreter. Using per-version roots avoids repointing a shared root when linker paths change across system updates. Adds a dedicated error variant for gcroot creation failures.
1 parent 030db40 commit 4b56b3d

2 files changed

Lines changed: 63 additions & 3 deletions

File tree

crates/svm-rs/src/error.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ pub enum SvmError {
2424
Timeout(String, u64),
2525
#[error("Unable to patch solc binary for nixos. stdout: {0}. stderr: {1}")]
2626
CouldNotPatchForNixOs(String, String),
27+
#[error("Unable to add nix gcroot for solc runtime dependencies. stdout: {0}. stderr: {1}")]
28+
CouldNotAddNixGcRoot(String, String),
2729
#[error(transparent)]
2830
IoError(#[from] std::io::Error),
2931
#[error(transparent)]

crates/svm-rs/src/install.rs

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ impl Installer<'_> {
224224
&& *self.version >= NIXOS_MIN_PATCH_VERSION
225225
&& *self.version <= NIXOS_MAX_PATCH_VERSION
226226
{
227-
patch_for_nixos(&temp_path)?;
227+
patch_for_nixos(self.version, &temp_path)?;
228228
}
229229

230230
let solc_path = version_binary(&self.version.to_string());
@@ -258,13 +258,17 @@ impl Installer<'_> {
258258
}
259259

260260
/// Patch the given binary to use the dynamic linker provided by nixos.
261-
fn patch_for_nixos(bin: &Path) -> Result<(), SvmError> {
261+
fn patch_for_nixos(version: &Version, bin: &Path) -> Result<(), SvmError> {
262+
let dynamic_linker = nixos_dynamic_linker()?;
263+
add_gc_root_for_store_path(version, &dynamic_linker)?;
264+
262265
let output = Command::new("nix-shell")
263266
.arg("-p")
264267
.arg("patchelf")
265268
.arg("--run")
266269
.arg(format!(
267-
"patchelf --set-interpreter \"$(cat $NIX_CC/nix-support/dynamic-linker)\" {}",
270+
"patchelf --set-interpreter \"{}\" {}",
271+
dynamic_linker,
268272
bin.display()
269273
))
270274
.output()
@@ -279,6 +283,60 @@ fn patch_for_nixos(bin: &Path) -> Result<(), SvmError> {
279283
}
280284
}
281285

286+
/// Resolves the NixOS dynamic linker path from the nix-shell environment.
287+
fn nixos_dynamic_linker() -> Result<String, SvmError> {
288+
let output = Command::new("nix-shell")
289+
.arg("-p")
290+
.arg("patchelf")
291+
.arg("--run")
292+
.arg("cat $NIX_CC/nix-support/dynamic-linker")
293+
.output()
294+
.map_err(|e| SvmError::CouldNotPatchForNixOs(String::new(), e.to_string()))?;
295+
296+
if !output.status.success() {
297+
return Err(SvmError::CouldNotPatchForNixOs(
298+
String::from_utf8_lossy(&output.stdout).into_owned(),
299+
String::from_utf8_lossy(&output.stderr).into_owned(),
300+
));
301+
}
302+
303+
let dynamic_linker = String::from_utf8_lossy(&output.stdout).trim().to_string();
304+
if dynamic_linker.is_empty() {
305+
return Err(SvmError::CouldNotPatchForNixOs(
306+
String::new(),
307+
"empty dynamic linker path from nix-shell".to_string(),
308+
));
309+
}
310+
311+
Ok(dynamic_linker)
312+
}
313+
314+
/// Adds a persistent gcroot for a nix store path used by a specific installed solc version.
315+
fn add_gc_root_for_store_path(version: &Version, store_path: &str) -> Result<(), SvmError> {
316+
let gcroots_dir = data_dir().join(".gcroots");
317+
fs::create_dir_all(&gcroots_dir)?;
318+
319+
// One gcroot per solc version to avoid repointing a shared root when linker paths change.
320+
let root_path = gcroots_dir.join(format!("solc-{version}-dynamic-linker"));
321+
322+
let output = Command::new("nix-store")
323+
.arg("--add-root")
324+
.arg(&root_path)
325+
.arg("--realise")
326+
.arg(store_path)
327+
.output()
328+
.map_err(|e| SvmError::CouldNotAddNixGcRoot(String::new(), e.to_string()))?;
329+
330+
if output.status.success() {
331+
Ok(())
332+
} else {
333+
Err(SvmError::CouldNotAddNixGcRoot(
334+
String::from_utf8_lossy(&output.stdout).into_owned(),
335+
String::from_utf8_lossy(&output.stderr).into_owned(),
336+
))
337+
}
338+
}
339+
282340
fn ensure_checksum(
283341
binbytes: &[u8],
284342
version: &Version,

0 commit comments

Comments
 (0)