Files

22 lines
1.3 KiB
Python

"""Restore content-addressed archive without overwriting existing files."""
import argparse,hashlib,json,os,tarfile
from pathlib import Path
p=argparse.ArgumentParser();p.add_argument('archive');p.add_argument('destination');a=p.parse_args();dest=Path(a.destination).resolve()
with tarfile.open(a.archive,'r:gz')as t:
m=json.loads(t.extractfile('MANIFEST.json').read());rows=m['members'];expected={'MANIFEST.json'}|{'objects/'+x['sha256']for x in rows if x['type']=='file'}
assert {x.name for x in t.getmembers()}==expected and len(t.getmembers())==len(expected)
for x in rows:
target=dest/x['path'];assert target.resolve().is_relative_to(dest) and not target.exists()
for x in rows:
q=dest/x['path']
if x['type']=='directory':q.mkdir(parents=True,exist_ok=True)
else:
q.parent.mkdir(parents=True,exist_ok=True);h=hashlib.sha256()
with q.open('xb')as out,t.extractfile('objects/'+x['sha256'])as f:
for b in iter(lambda:f.read(1048576),b''):h.update(b);out.write(b)
assert q.stat().st_size==x['bytes'] and h.hexdigest()==x['sha256']
os.chmod(q,x['mode']);os.utime(q,ns=(x['mtime_ns'],x['mtime_ns']))
for x in reversed(rows):
if x['type']=='directory':os.utime(dest/x['path'],ns=(x['mtime_ns'],x['mtime_ns']))
print('Restored and verified',m['files'],'files')