28 lines
1.8 KiB
Python
28 lines
1.8 KiB
Python
"""Restore historical P23 impact proof into a fresh directory; no overwrite."""
|
|
import argparse,gzip,hashlib,json,os,tarfile
|
|
from pathlib import Path
|
|
|
|
def sha(p):
|
|
with p.open('rb') as f:return hashlib.file_digest(f,'sha256').hexdigest()
|
|
p=argparse.ArgumentParser();p.add_argument('manifest',type=Path);p.add_argument('archive',type=Path);p.add_argument('target',type=Path);a=p.parse_args()
|
|
with gzip.open(a.manifest,'rt') as f:m=json.load(f)
|
|
assert sha(a.archive)==m['archive']['sha256'] and a.archive.stat().st_size==m['archive']['bytes']
|
|
assert not a.target.exists();a.target.mkdir(parents=True)
|
|
files={x['path']:x for x in m['files']};dirs={x['path']:x for x in m['directories']}
|
|
with tarfile.open(a.archive,'r:gz') as t:
|
|
members=t.getmembers();assert len(members)==len(files)+len(dirs) and {x.name for x in members}==set(files)|set(dirs)
|
|
for x in members:
|
|
n=Path(x.name);assert not n.is_absolute() and '..' not in n.parts and not x.issym() and not x.islnk()
|
|
dest=a.target/n
|
|
if x.isdir():assert x.name in dirs;dest.mkdir(parents=True,exist_ok=True)
|
|
else:
|
|
assert x.isfile() and x.name in files
|
|
dest.parent.mkdir(parents=True,exist_ok=True)
|
|
with t.extractfile(x) as source,dest.open('xb') as out:
|
|
while block:=source.read(1024**2):out.write(block)
|
|
for row in m['files']:
|
|
f=a.target/row['path'];assert f.stat().st_size==row['bytes'] and sha(f)==row['sha256'];os.chmod(f,row['mode']);os.utime(f,ns=(row['mtime_ns'],row['mtime_ns']))
|
|
for row in sorted(m['directories'],key=lambda x:len(Path(x['path']).parts),reverse=True):
|
|
f=a.target/row['path'];os.chmod(f,row['mode']);os.utime(f,ns=(row['mtime_ns'],row['mtime_ns']))
|
|
print(json.dumps({'restored_files':len(files),'all_bytes_sha256_verified':True,'mode_mtime_ns_restored':True,'target':str(a.target)}))
|