Initial Builder
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Apply delta atomically, rejecting gaps or mismatched base database."""
|
||||
import argparse
|
||||
import base64
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import sqlite3
|
||||
import tempfile
|
||||
import subprocess
|
||||
|
||||
def fingerprint(conn):
|
||||
h = hashlib.sha256()
|
||||
for cve_id, row_hash in conn.execute('SELECT cve_id,row_hash FROM cve ORDER BY cve_id'):
|
||||
h.update(cve_id.encode() + b'\x00' + row_hash.encode() + b'\n')
|
||||
return h.hexdigest()
|
||||
|
||||
def apply(db, delta):
|
||||
db = Path(db)
|
||||
with tempfile.TemporaryDirectory(dir=db.parent) as tmp:
|
||||
work = Path(tmp) / 'master.db'
|
||||
shutil.copy2(db, work)
|
||||
conn = sqlite3.connect(work)
|
||||
proc = subprocess.Popen(['zstd', '-q', '-dc', str(delta)], stdout=subprocess.PIPE, text=True)
|
||||
try:
|
||||
lines = proc.stdout
|
||||
header = json.loads(next(lines))
|
||||
if header.get('format') != 1 or header.get('type') != 'header':
|
||||
raise ValueError('Invalid delta header')
|
||||
if fingerprint(conn) != header['base']:
|
||||
raise ValueError('Delta base mismatch: full master or missing prior delta required')
|
||||
conn.execute('BEGIN IMMEDIATE')
|
||||
for line in lines:
|
||||
op = json.loads(line)
|
||||
if op['type'] == 'delete':
|
||||
conn.execute('DELETE FROM cve WHERE cve_id=?', (op['id'],))
|
||||
elif op['type'] == 'upsert':
|
||||
blob = base64.b64decode(op['affected_b64'], validate=True)
|
||||
expected = hashlib.sha256(json.dumps([op['id'], op['state'], op['updated']], separators=(',', ':')).encode() + blob).hexdigest()
|
||||
if expected != op['hash']:
|
||||
raise ValueError('Delta row hash mismatch')
|
||||
conn.execute('INSERT OR REPLACE INTO cve VALUES (?,?,?,?,?)', (op['id'], op['state'], op['updated'], blob, op['hash']))
|
||||
else:
|
||||
raise ValueError('Unknown delta operation')
|
||||
if proc.wait() != 0:
|
||||
raise ValueError('zstd decompression failed')
|
||||
if fingerprint(conn) != header['target']:
|
||||
raise ValueError('Delta target mismatch')
|
||||
conn.commit()
|
||||
finally:
|
||||
proc.stdout.close()
|
||||
if proc.poll() is None:
|
||||
proc.kill()
|
||||
proc.wait()
|
||||
conn.close()
|
||||
os.replace(work, db)
|
||||
|
||||
if __name__ == '__main__':
|
||||
p = argparse.ArgumentParser()
|
||||
p.add_argument('--database', required=True, type=Path)
|
||||
p.add_argument('--delta', required=True, type=Path)
|
||||
a = p.parse_args()
|
||||
apply(a.database, a.delta)
|
||||
Reference in New Issue
Block a user