erisPatchHook: patch ELF images to load ERIS URNs
Add this hook to the Genode stdenv.post-mortem
parent
83c36784ff
commit
e3524c4277
@ -0,0 +1,17 @@
|
||||
{ lib, stdenv, patchelf, nimblePackages }:
|
||||
|
||||
stdenv.mkDerivation {
|
||||
name = "eris_patch";
|
||||
nativeBuildInputs = with nimblePackages; [ nim ];
|
||||
buildInputs = with nimblePackages; [ eris ];
|
||||
inherit patchelf;
|
||||
dontUnpack = true;
|
||||
nimFlags = [ "-d:release" ];
|
||||
buildPhase = ''
|
||||
HOME=$TMPDIR
|
||||
cp ${./eris_patch.nim} eris_patch.nim
|
||||
nim c $nimFlags eris_patch
|
||||
'';
|
||||
installPhase = "install -Dt $out/bin eris_patch";
|
||||
setupHook = ./eris_patch.sh;
|
||||
}
|
@ -0,0 +1,189 @@
|
||||
import eris
|
||||
|
||||
import std/asyncdispatch, std/deques, std/json, std/os, std/osproc, std/streams,
|
||||
std/strutils, std/tables
|
||||
|
||||
if getEnv("dontErisPatch") != "": quit 0
|
||||
|
||||
let
|
||||
patchelf = getEnv("ERIS_PATCHELF", "patchelf")
|
||||
nixStore = getEnv("NIX_STORE", "/nix/store")
|
||||
manifestSubPath = "nix-support" / "eris-manifest.json"
|
||||
|
||||
const erisBlockSize = 32 shl 10
|
||||
# fix the block size for now
|
||||
|
||||
proc isElf(path: string): bool =
|
||||
var magic: array[4, char]
|
||||
let file = open(path)
|
||||
discard readChars(file, magic, 0, 4)
|
||||
close(file)
|
||||
magic == [0x7f.char, 'E', 'L', 'F']
|
||||
|
||||
type PendingFile = ref object
|
||||
outputRoot, filePath: string
|
||||
replacements: Table[string, string]
|
||||
|
||||
var
|
||||
outputManifests = initTable[string, JsonNode]()
|
||||
pendingFiles = initDeque[PendingFile]()
|
||||
failed = false
|
||||
for outputName in getEnv("outputs").splitWhitespace:
|
||||
let outputRoot = getEnv(outputName)
|
||||
if fileExists(outputRoot / manifestSubPath):
|
||||
echo "Not running ERIS patch hook again"
|
||||
quit 0
|
||||
outputManifests[outputRoot] = newJObject()
|
||||
|
||||
let buildInputs = getEnv("buildInputs").splitWhitespace
|
||||
|
||||
proc resolveNeed(rpath: seq[string]; need: string): string =
|
||||
if need.isAbsolute:
|
||||
return need
|
||||
for libdir in rpath:
|
||||
let absNeed = libdir / need
|
||||
if fileExists(absNeed):
|
||||
return absNeed
|
||||
for outputRoot in outputManifests.keys:
|
||||
for relPath in [need, "lib" / need]:
|
||||
let absNeed = outputRoot / relPath
|
||||
if fileExists(absNeed):
|
||||
return absNeed
|
||||
for buildInput in buildInputs:
|
||||
for relPath in [need, "lib" / need]:
|
||||
let absNeed = buildInput / relPath
|
||||
if fileExists(absNeed):
|
||||
return absNeed
|
||||
|
||||
proc resolveFile(outputRoot, filePath: string): PendingFile =
|
||||
result = PendingFile(
|
||||
outputRoot: outputRoot,
|
||||
filePath: filePath,
|
||||
replacements: initTable[string, string](8))
|
||||
let needs = splitWhitespace(execProcess(
|
||||
patchelf, args = ["--print-needed", filePath], options = {poUsePath}))
|
||||
let rpath = splitWhitespace(execProcess(
|
||||
patchelf, args = ["--print-rpath", filePath], options = {poUsePath}))
|
||||
for need in needs:
|
||||
if need == "ld.lib.so" or need.startsWith("urn:"): continue
|
||||
result.replacements[need] = resolveNeed(rpath, need)
|
||||
|
||||
var capCache = initTable[string, Cap]()
|
||||
|
||||
proc fileUrn(filePath: string; blockSize: Natural): string =
|
||||
## Determine the ERIS URN for ``filePath``.
|
||||
var cap: Cap
|
||||
if capCache.hasKey(filePath):
|
||||
cap = capCache[filePath]
|
||||
else:
|
||||
try:
|
||||
let str = newFileStream(filePath)
|
||||
doAssert(not str.isNil) # yes, that happens
|
||||
cap = waitFor encode(newDiscardStore(), blockSize, str)
|
||||
capCache["filePath"] = cap
|
||||
close(str)
|
||||
except:
|
||||
stderr.writeLine("failed to read \"", filePath, "\"")
|
||||
quit 1
|
||||
$cap # & "#" & encodeUrl(extractFilename(filePath), usePlus = false)
|
||||
|
||||
var closureCache = initTable[string, TableRef[string, string]]()
|
||||
|
||||
proc fileClosure(filePath: string): TableRef[string, string] =
|
||||
## Recusively find the dependency closure of ``filePath``.
|
||||
let filePath = expandFilename filePath
|
||||
if closureCache.hasKey(filePath):
|
||||
result = closureCache[filePath]
|
||||
else:
|
||||
result = newTable[string, string]()
|
||||
var storePath = filePath
|
||||
for p in parentDirs(filePath):
|
||||
# find the top directory of the ``filePath`` derivation
|
||||
if p == nixStore: break
|
||||
storePath = p
|
||||
if storePath.startsWith nixStore:
|
||||
# read the closure manifest of the dependency
|
||||
let manifestPath = storePath / manifestSubPath
|
||||
if fileExists(manifestPath):
|
||||
let
|
||||
manifest = parseFile(manifestPath)
|
||||
entry = manifest[filePath]
|
||||
for path, cap in entry["closure"].pairs:
|
||||
result[path] = cap.getStr
|
||||
let otherClosure = fileClosure(path)
|
||||
for otherPath, otherCap in otherClosure.pairs:
|
||||
# merge the closure of the dependency
|
||||
result[otherPath] = otherCap
|
||||
closureCache[filePath] = result
|
||||
|
||||
for outputRoot in outputManifests.keys:
|
||||
let manifestPath = outputRoot / manifestSubPath
|
||||
if fileExists manifestPath: continue
|
||||
for filePath in walkDirRec(outputRoot, relative = false):
|
||||
# Populate the queue of files to patch
|
||||
if filePath.isElf:
|
||||
pendingFiles.addLast(resolveFile(outputRoot, filePath))
|
||||
|
||||
var
|
||||
prevLen = pendingFiles.len
|
||||
prevPrevLen = prevLen.succ
|
||||
# used to detect reference cycles
|
||||
while pendingFiles.len != 0:
|
||||
block selfReferenceCheck:
|
||||
# process the files that have been collected
|
||||
# taking care not to take a the URN of an
|
||||
# unprocessed file
|
||||
let
|
||||
pendingFile = pendingFiles.popFirst()
|
||||
filePath = pendingFile.filePath
|
||||
for need, replacementPath in pendingFile.replacements.pairs:
|
||||
# search for self-references
|
||||
if replacementPath == "":
|
||||
echo need, " not found for ", filePath
|
||||
failed = true
|
||||
continue
|
||||
for outputRoot in outputManifests.keys:
|
||||
if replacementPath.startsWith(outputRoot):
|
||||
for other in pendingFiles.items:
|
||||
echo "compare for self-reference:"
|
||||
echo '\t', replacementPath
|
||||
echo '\t', other.filePath
|
||||
if replacementPath == other.filePath:
|
||||
echo "defering patch of ", filePath, " with reference to ", other.filePath
|
||||
pendingFiles.addLast(pendingFile)
|
||||
break selfReferenceCheck
|
||||
var
|
||||
closure = newJObject()
|
||||
replaceCmd = patchelf & " --set-rpath '' " & filePath
|
||||
for need, replacementPath in pendingFile.replacements.pairs:
|
||||
if replacementPath == "": continue
|
||||
let urn = fileUrn(replacementPath, erisBlockSize)
|
||||
echo "replace reference to ", need, " with ", urn
|
||||
replaceCmd.add(" --replace-needed $# $#" % [need, urn])
|
||||
closure[replacementPath] = %urn
|
||||
for path, urn in fileClosure(replacementPath).pairs:
|
||||
closure[path] = %urn
|
||||
if pendingFile.replacements.len != 0:
|
||||
let (msg, exitCode) = execCmdEx(replaceCmd, options = {poUsePath})
|
||||
if exitCode != 0:
|
||||
echo msg
|
||||
quit exitCode
|
||||
outputManifests[pendingFile.outputRoot][filePath] = %* {
|
||||
"cap": fileUrn(filePath, erisBlockSize),
|
||||
"closure": closure,
|
||||
}
|
||||
if pendingFiles.len == prevPrevLen:
|
||||
failed = true
|
||||
echo "reference cycle detected in the following:"
|
||||
for remain in pendingFiles.items:
|
||||
echo '\t', " ", remain.filePath
|
||||
break
|
||||
prevPrevLen = prevLen
|
||||
prevLen = pendingFiles.len
|
||||
|
||||
if failed:
|
||||
quit -1
|
||||
|
||||
for outputRoot, manifest in outputManifests:
|
||||
createDir(outputRoot / "nix-support")
|
||||
writeFile(outputRoot / manifestSubPath, $manifest)
|
@ -0,0 +1,2 @@
|
||||
export ERIS_PATCHELF=@patchelf@/bin/patchelf
|
||||
postFixupHooks+=('@out@/bin/eris_patch')
|
Loading…
Reference in New Issue