#!/usr/bin/env python3 """Repackage the fixed Ubuntu v2 install with pinned qpdf notices, without rebuilding.""" from pathlib import Path import hashlib import json import os import subprocess import sys import tarfile import time HOME = Path('/home/docview') ROOT = Path(__file__).resolve().parents[2] OUTPUT = HOME / 'validation/qpdf-notice-package' PRIOR_SOURCE = HOME / 'docview-context-source' INSTALL = HOME / 'docview-context-install' VERSION = '0.1.0~validation5' def sha(path): with path.open('rb') as stream: return hashlib.file_digest(stream, 'sha256').hexdigest() def main(): assert Path.home() == HOME and os.getuid() == 1000 assert ROOT == HOME / 'validation/qpdf-notice-tools' OUTPUT.mkdir(parents=True, exist_ok=False) sys.path.insert(0, str(ROOT / 'tools')) from verify_ubuntu_package import inspect, verify, MANIFEST from collect_ubuntu_validation_runtime import QPDF_NOTICE_PIN env = {**os.environ, 'PATH': '/opt/docview-deps/bin:/opt/docview-qt/6.11.2/gcc_64/bin:' + os.environ['PATH'], 'LD_LIBRARY_PATH': '/opt/docview-deps/lib:/opt/docview-qt/6.11.2/gcc_64/lib'} for key in ('LD_PRELOAD', 'LD_AUDIT', 'QTWEBENGINE_DISABLE_SANDBOX'): env.pop(key, None) preserved = [INSTALL/'bin'/name for name in ('docview','docview-pdf-worker','docview-archive-worker')] preserved += [INSTALL/'lib/libpdfium.so', HOME/'validation/pdfium-context-prefix/BUILDINFO.json', Path('/opt/docview-deps/lib/libqpdf.so.30')] before = {str(p):sha(p) for p in preserved} report = {'success': False, 'version': VERSION, 'scope': 'Notice collection and packaging only; no C++ rebuild or repeated full CTest', 'runtimeBefore': before, 'runnerSha256': sha(Path(__file__)), 'commands': []} def run(name, command): start = time.monotonic() with (OUTPUT/(name+'.log')).open('w') as log: result = subprocess.run(command, env=env, stdout=log, stderr=subprocess.STDOUT, timeout=900) report['commands'].append({'name':name, 'command':command, 'exitCode':result.returncode, 'seconds':time.monotonic()-start, 'logSha256':sha(OUTPUT/(name+'.log'))}) assert result.returncode == 0, name try: for name in ('test_ubuntu_validation_runtime','test_qpdf_notice_package','test_pdfium_candidate_integration'): run(name,[sys.executable,str(ROOT/'tests'/(name+'.py')),'-v']) command = [sys.executable,str(ROOT/'tools/package_ubuntu.py'),'--prefix',str(INSTALL), '--qtpaths','/opt/docview-qt/6.11.2/gcc_64/bin/qtpaths','--dependency-prefix','/opt/docview-deps', '--source-root',str(HOME/'dependency-sources/qpdf-12.4.1'), '--source-root',str(HOME/'dependency-sources/libzip-1.11.4'), '--qpdf-source-archive',str(HOME/'incoming/qpdf-12.4.1.tar.gz'), '--input-manifest',str(HOME/'incoming/sdk-downloads.json'), '--sdk-notices',str(PRIOR_SOURCE/'tests/results/source-archives/qt-sdk-notices-final'), '--sdk-supplement',str(PRIOR_SOURCE/'tests/results/qt-notice-supplement/collection'), '--qt-licenses',str(PRIOR_SOURCE/'tests/results/ubuntu-package/inputs/qt-licenses'), '--version',VERSION] archives = [] for name in ('package','repeat'): run(name,[*command,'--output',str(OUTPUT/name)]) archive = OUTPUT/name/('docview_'+VERSION+'_amd64.deb'); archives.append(archive) run(name+'-verify',[sys.executable,str(ROOT/'tools/verify_ubuntu_package.py'),'--archive',str(archive), '--output',str(OUTPUT/name/'verification.json')]) assert sha(archives[0]) == sha(archives[1]) prior = HOME/'validation/pdfium-context-package/package/package/docview_0.1.0~validation4_amd64.deb' assert sha(prior) == 'bd4f00912078d406cb466cd6910ed01c0c9fdd32b76cb2f47b74cfb9719f1bd9' def inventory(path): data, _, _ = inspect(path, '--fsys-tarfile') control, _, _ = inspect(path, '--ctrl-tarfile') return {**data, **{'DEBIAN/'+name:dict(row,path='DEBIAN/'+name) for name,row in control.items()}} old, new = inventory(prior), inventory(archives[0]) differences = {'added':[new[k] for k in sorted(new.keys()-old.keys())], 'removed':[old[k] for k in sorted(old.keys()-new.keys())], 'changed':[{'path':k,'before':old[k],'after':new[k]} for k in sorted(old.keys()&new.keys()) if old[k]!=new[k]]} protected = lambda k: not k.startswith(('DEBIAN/','opt/docview/share/doc/docview/')) protected_paths = {k for k in old.keys()|new.keys() if protected(k)} unchanged = all(old.get(k)==new.get(k) for k in protected_paths) assert unchanged, 'Runtime or application payload changed' diff = {'success': True, 'method':'Bounded raw data/control inventory; old validation4 is not accepted by the current notice verifier', 'oldArchiveSha256':sha(prior),'newArchiveSha256':sha(archives[0]),'oldFiles':len(old),'newFiles':len(new), 'allRuntimeAndApplicationFilesUnchanged':unchanged,'protectedFileCount':len(protected_paths),**differences} (OUTPUT/'payload-difference.json').write_text(json.dumps(diff,indent=2)+'\n') result = verify(archives[0]) notice = next(row for row in result['qpdfNoticeCorrespondence']['notices'] if row['source']=='NOTICE.md') wanted = './opt/docview/share/doc/docview/third-party/runtime/'+notice['copiedPath'] process = subprocess.Popen(['dpkg-deb','--fsys-tarfile',str(archives[0])],stdout=subprocess.PIPE) found = None with tarfile.open(fileobj=process.stdout,mode='r|') as tar: for member in tar: if member.name == wanted: assert member.isfile() and member.size==QPDF_NOTICE_PIN['notices']['NOTICE.md']['size'] found = tar.extractfile(member).read() process.stdout.close(); assert process.wait(timeout=15)==0 assert found is not None and hashlib.sha256(found).hexdigest()==QPDF_NOTICE_PIN['notices']['NOTICE.md']['sha256'] assert found==(HOME/'dependency-sources/qpdf-12.4.1/NOTICE.md').read_bytes() (OUTPUT/'NOTICE.from-deb.md').write_bytes(found) report.update(success=True, archive=str(archives[0]),archiveSha256=sha(archives[0]), repeatSha256=sha(archives[1]),archiveBytes=archives[0].stat().st_size, qpdfNoticeCorrespondence=result['qpdfNoticeCorrespondence'],pdfiumCorrespondence=result['pdfiumCorrespondence'], actualNoticeBytesMatchSelectedSource=True, actualPayloadCompared=True) finally: report['runtimeAfter']={str(p):sha(p) for p in preserved} report['runtimeUnchanged']=report['runtimeBefore']==report['runtimeAfter'] report['success']=report['success'] and report['runtimeUnchanged'] (OUTPUT/'report.json').write_text(json.dumps(report,indent=2)+'\n') print(json.dumps({k:report[k] for k in ('success','archiveSha256','archiveBytes','runtimeUnchanged')})) if __name__=='__main__': main()