Skip to content

Commit

Permalink
bootstrap.py: guard against GC in NixOS patching support.
Browse files Browse the repository at this point in the history
  • Loading branch information
eddyb committed Jul 17, 2020
1 parent c2dbebd commit d866160
Showing 1 changed file with 42 additions and 31 deletions.
73 changes: 42 additions & 31 deletions src/bootstrap/bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,7 @@ def __init__(self):
self.use_vendored_sources = ''
self.verbose = False
self.git_version = None
self.nix_deps_dir = None

def download_stage0(self):
"""Fetch the build system for Rust, written in Rust
Expand Down Expand Up @@ -440,8 +441,7 @@ def _download_stage0_helper(self, filename, pattern, tarball_suffix, date=None):
get("{}/{}".format(url, filename), tarball, verbose=self.verbose)
unpack(tarball, tarball_suffix, self.bin_root(), match=pattern, verbose=self.verbose)

@staticmethod
def fix_executable(fname):
def fix_executable(self, fname):
"""Modifies the interpreter section of 'fname' to fix the dynamic linker
This method is only required on NixOS and uses the PatchELF utility to
Expand Down Expand Up @@ -472,38 +472,49 @@ def fix_executable(fname):
nix_os_msg = "info: you seem to be running NixOS. Attempting to patch"
print(nix_os_msg, fname)

try:
interpreter = subprocess.check_output(
["patchelf", "--print-interpreter", fname])
interpreter = interpreter.strip().decode(default_encoding)
except subprocess.CalledProcessError as reason:
print("warning: failed to call patchelf:", reason)
return

loader = interpreter.split("/")[-1]

try:
ldd_output = subprocess.check_output(
['ldd', '/run/current-system/sw/bin/sh'])
ldd_output = ldd_output.strip().decode(default_encoding)
except subprocess.CalledProcessError as reason:
print("warning: unable to call ldd:", reason)
return

for line in ldd_output.splitlines():
libname = line.split()[0]
if libname.endswith(loader):
loader_path = libname[:len(libname) - len(loader)]
break
else:
print("warning: unable to find the path to the dynamic linker")
return

correct_interpreter = loader_path + loader
# Only build `stage0/.nix-deps` once.
nix_deps_dir = self.nix_deps_dir
if not nix_deps_dir:
nix_deps_dir = "{}/.nix-deps".format(self.bin_root())
if not os.path.exists(nix_deps_dir):
os.makedirs(nix_deps_dir)

nix_deps = [
# Needed for the path of `ld-linux.so` (via `nix-support/dynamic-linker`).
"stdenv.cc.bintools",

# Needed for patching ELF binaries (see doc comment above).
"patchelf",
]

# Run `nix-build` to "build" each dependency (which will likely reuse
# the existing `/nix/store` copy, or at most download a pre-built copy).
# Importantly, we don't rely on `nix-build` printing the `/nix/store`
# path on stdout, but use `-o` to symlink it into `stage0/.nix-deps/$dep`,
# ensuring garbage collection will never remove the `/nix/store` path
# (which would break our patched binaries that hardcode those paths).
for dep in nix_deps:
try:
subprocess.check_output([
"nix-build", "<nixpkgs>",
"-A", dep,
"-o", "{}/{}".format(nix_deps_dir, dep),
])
except subprocess.CalledProcessError as reason:
print("warning: failed to call nix-build:", reason)
return

self.nix_deps_dir = nix_deps_dir

patchelf = "{}/patchelf/bin/patchelf".format(nix_deps_dir)
bintools_dir = "{}/stdenv.cc.bintools".format(nix_deps_dir)

with open("{}/nix-support/dynamic-linker".format(bintools_dir)) as dynamic_linker:
interpreter = dynamic_linker.read().rstrip()

try:
subprocess.check_output(
["patchelf", "--set-interpreter", correct_interpreter, fname])
[patchelf, "--set-interpreter", interpreter, fname])
except subprocess.CalledProcessError as reason:
print("warning: failed to call patchelf:", reason)
return
Expand Down

0 comments on commit d866160

Please sign in to comment.