Files

33 lines
1.9 KiB
Python

"""Restore new path mappings from the byte-verified r06 content-addressed archive."""
import argparse,contextlib,gzip,hashlib,json,os,tarfile
from pathlib import Path
p=argparse.ArgumentParser();p.add_argument('archive');p.add_argument('manifest');p.add_argument('destination');a=p.parse_args();mp=Path(a.manifest);raw=gzip.decompress(mp.read_bytes())if mp.suffix=='.gz'else mp.read_bytes();m=json.loads(raw);dest=Path(a.destination).resolve();h=hashlib.sha256()
with open(a.archive,'rb')as f:
for b in iter(lambda:f.read(1024**2),b''):h.update(b)
assert h.hexdigest()==m['source_archive_sha256'];rows=m['members'];by_hash={};seen=set();restored=0
assert len({x['path']for x in rows})==len(rows)
for x in rows:
q=dest/x['path'];assert q.resolve().is_relative_to(dest)and not q.exists()and not q.is_symlink()
if x['type']=='file':by_hash.setdefault(x['sha256'],[]).append(x)
for x in rows:
if x['type']=='directory':(dest/x['path']).mkdir(parents=True,exist_ok=True)
with tarfile.open(a.archive,'r|gz')as t:
for e in t:
assert e.isfile()
if not e.name.startswith('objects/'):continue
key=e.name[8:]
if key not in by_hash:continue
assert key not in seen;seen.add(key);group=by_hash[key];assert all(x['bytes']==e.size for x in group);v=hashlib.sha256()
with contextlib.ExitStack()as stack:
out=[stack.enter_context((dest/x['path']).open('xb'))for x in group];f=stack.enter_context(t.extractfile(e))
for b in iter(lambda:f.read(1024**2),b''):
v.update(b)
for o in out:o.write(b)
assert v.hexdigest()==key
for x in group:
q=dest/x['path'];assert q.stat().st_size==x['bytes'];os.chmod(q,x['mode']);os.utime(q,ns=(x['mtime_ns'],x['mtime_ns']));restored+=1
assert seen==set(by_hash)and restored==m['files']
for x in reversed(rows):
if x['type']=='directory':q=dest/x['path'];os.chmod(q,x['mode']);os.utime(q,ns=(x['mtime_ns'],x['mtime_ns']))
print('Restored and verified',restored,'files')