71 lines
3.1 KiB
Python
71 lines
3.1 KiB
Python
#!/usr/bin/env python3
|
|
"""Deterministic public-runtime transfer; no runtime launch or network access."""
|
|
import argparse
|
|
import gzip
|
|
import hashlib
|
|
import importlib.util
|
|
import io
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import stat
|
|
import tarfile
|
|
|
|
ROOT = Path('/tmp/netkingdom-runtime-canonical-20261016/runtime')
|
|
EXPECTED = 'f59959b605a51abd2901becde14e7abd852c2bd3f848004b51d250d1cb81af6d'
|
|
HERE = Path(__file__).resolve().parent
|
|
|
|
|
|
class CountingHash:
|
|
def __init__(self, target=None):
|
|
self.target, self.hash, self.size = target, hashlib.sha256(), 0
|
|
def write(self, value):
|
|
self.hash.update(value); self.size += len(value)
|
|
if self.target: self.target.write(value)
|
|
return len(value)
|
|
def flush(self):
|
|
if self.target: self.target.flush()
|
|
|
|
|
|
def runtime_module():
|
|
p = HERE/'runtime.py'
|
|
spec = importlib.util.spec_from_file_location('reviewed_runtime_digest', p)
|
|
module = importlib.util.module_from_spec(spec); spec.loader.exec_module(module)
|
|
return module
|
|
|
|
|
|
def build(target=None):
|
|
if runtime_module().runtime_digest(ROOT) != EXPECTED:
|
|
raise ValueError('source_runtime_changed')
|
|
result = CountingHash(target); logical = 0; members = 0
|
|
with gzip.GzipFile(filename='', mode='wb', compresslevel=6, mtime=0, fileobj=result) as stream:
|
|
with tarfile.open(fileobj=stream, mode='w|', format=tarfile.PAX_FORMAT) as archive:
|
|
for p in [ROOT, *sorted(ROOT.rglob('*'))]:
|
|
s=p.lstat(); name='runtime' + ('/'+p.relative_to(ROOT).as_posix() if p!=ROOT else '')
|
|
info=tarfile.TarInfo(name);info.uid=1000;info.gid=1000;info.mtime=0;info.mode=stat.S_IMODE(s.st_mode)
|
|
if stat.S_ISDIR(s.st_mode):info.type=tarfile.DIRTYPE
|
|
elif stat.S_ISLNK(s.st_mode):
|
|
info.type=tarfile.SYMTYPE; info.linkname=os.readlink(p)
|
|
if Path(info.linkname).is_absolute() or not p.resolve(strict=True).is_relative_to(ROOT):
|
|
raise ValueError('unsafe_runtime_symlink')
|
|
elif stat.S_ISREG(s.st_mode):info.type=tarfile.REGTYPE; info.size=s.st_size; logical += s.st_size
|
|
else:raise ValueError('runtime_special_file')
|
|
with p.open('rb') if stat.S_ISREG(s.st_mode) else io.BytesIO() as data:
|
|
archive.addfile(info,data if stat.S_ISREG(s.st_mode) else None)
|
|
members+=1
|
|
if runtime_module().runtime_digest(ROOT) != EXPECTED:
|
|
raise ValueError('source_runtime_changed_during_archive')
|
|
return {'sha256':'sha256:'+result.hash.hexdigest(),'bytes':result.size,'logical_file_bytes':logical,'members':members,
|
|
'format':'gzip mtime0; PAX tar; sorted tree; root runtime; UID/GID1000; modes and relative symlinks retained'}
|
|
|
|
|
|
def main():
|
|
parser=argparse.ArgumentParser(description=__doc__);parser.add_argument('--output',type=Path)
|
|
args=parser.parse_args()
|
|
if args.output:
|
|
with args.output.open('xb') as stream:result=build(stream)
|
|
else:result=build()
|
|
print(json.dumps(result,sort_keys=True))
|
|
|
|
|
|
if __name__=='__main__':main()
|