Prepare Sargo installations with private per-device workspaces
Bind backups, storage layout, RAM builds, file staging and recovery to each phone. Verify atomic transfers without replacing existing state and record the unresolved Android HAL source provenance. Validated with 53 native tooling tests, 5 changelog tests and two RAM boots with persistent Gaia state on the development Sargo. No new flash or native binary release.
This commit is contained in:
parent
978542bc87
commit
d04824ec89
16 changed files with 707 additions and 7 deletions
70
tundra/tools/sargo_workspace.py
Normal file
70
tundra/tools/sargo_workspace.py
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
# SPDX-License-Identifier: MPL-2.0
|
||||
"""Private, portable inputs for one Sargo. No shared device inventory paths."""
|
||||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
import re
|
||||
import struct
|
||||
|
||||
from boot_backup import verify
|
||||
from storage_layout import mount_table
|
||||
from tundra import digest
|
||||
|
||||
# The system/HAL has only been exercised with this kernel and boot geometry.
|
||||
KERNEL_SHA256 = 'e214e5e363bcf4db6387668ba723299e75009bc128fb0088f706cfb311db0080'
|
||||
|
||||
|
||||
def boot_kernel(path):
|
||||
with Path(path).open('rb') as stream:
|
||||
header = stream.read(608)
|
||||
if len(header) != 608 or header[:8] != b'ANDROID!':
|
||||
raise ValueError('Expected an Android boot image')
|
||||
fields = struct.unpack_from('<10I', header, 8)
|
||||
size, address, ramdisk_size, ramdisk_address, second_size, _, tags, page, version, _ = fields
|
||||
if (version != 0 or page != 4096 or address != 0x8000
|
||||
or ramdisk_address != 0x1000000 or tags != 0x100 or second_size
|
||||
or not 0 < size < 64*1024**2 or not 0 < ramdisk_size < 64*1024**2):
|
||||
raise ValueError('Boot geometry is not supported by this Sargo adaptation')
|
||||
ramdisk_offset = page + ((size+page-1)//page)*page
|
||||
if ramdisk_offset+ramdisk_size > Path(path).stat().st_size:
|
||||
raise ValueError('Boot payload exceeds the backup')
|
||||
stream.seek(page)
|
||||
kernel = stream.read(size)
|
||||
if len(kernel) != size:
|
||||
raise ValueError('Truncated kernel')
|
||||
result = hashlib.sha256(kernel).hexdigest()
|
||||
if result != KERNEL_SHA256:
|
||||
raise ValueError('Kernel has not been qualified with this system/HAL')
|
||||
return result
|
||||
|
||||
|
||||
def checked_workspace(folder):
|
||||
folder = Path(folder).resolve()
|
||||
if (folder/'.incomplete').exists():
|
||||
raise ValueError('Device workspace preparation did not finish')
|
||||
value = json.loads((folder/'device.json').read_text())
|
||||
if (value.get('schema') != 1 or value.get('device') != 'sargo'
|
||||
or value.get('activeSlot') != 'a'
|
||||
or not re.fullmatch(r'[a-f0-9]{64}', str(value.get('serialSha256', '')))):
|
||||
raise ValueError('Expected a Sargo slot A workspace with a device identity')
|
||||
for name, sha in value.get('files', {}).items():
|
||||
path = folder/name
|
||||
if (name not in ('backup/manifest.json', 'layout.json') or path.is_symlink()
|
||||
or not path.resolve().is_relative_to(folder) or digest(path) != sha):
|
||||
raise ValueError('Workspace input changed: '+name)
|
||||
if set(value.get('files', {})) != {'backup/manifest.json', 'layout.json'}:
|
||||
raise ValueError('Workspace is missing its backup or storage inventory')
|
||||
backup = verify(folder/'backup')
|
||||
layout = json.loads((folder/'layout.json').read_text())
|
||||
for record in (backup, layout):
|
||||
if record.get('serialSha256') != value['serialSha256'] or record.get('activeSlot') != 'a':
|
||||
raise ValueError('Storage and boot backup must belong to the same phone on slot A')
|
||||
mount_table(layout)
|
||||
kernel = boot_kernel(folder/'backup/boot_a.img')
|
||||
if kernel != value.get('kernelSha256'):
|
||||
raise ValueError('Workspace kernel checksum changed')
|
||||
plan = {'device': 'sargo', 'activeSlot': 'a', 'serialSha256': value['serialSha256'],
|
||||
'backupDirectory': str(folder/'backup'),
|
||||
'backupManifestSha256': value['files']['backup/manifest.json'],
|
||||
'workspaceSha256': digest(folder/'device.json')}
|
||||
return plan, layout
|
||||
Loading…
Reference in a new issue