feat(bluetooth): install native services and publish UART properties

This commit is contained in:
bluseliu50
2026-11-05 05:12:37 +08:00
Unverified
parent fbd0f92db9
commit 5486dabe1d
8 changed files with 288 additions and 4 deletions
+3 -1
View File
@@ -57,7 +57,7 @@
"dram_size" = "0x80000000"
"skip_setup_tool" = "scripts/seed-data-skip-setup.py"
"panic_screen" = true
"kernel_args" = ["-v", "serial=3", "debug=0x14e", "kt_fixed_zones=120", "-no_slto_panic", "use_contiguous_hint=0", "native-source-storage=1", "native-ufs-root=1", "offline-mobile-graphics-load=1", "offline-mobile-graphics-client=1", "offline-legacy-register=1", "offline-legacy-gop=1", "offline-legacy-unit-scale=2", "offline-legacy-pm=1", "offline-legacy-validate=1", "offline-legacy-transfer=1", "offline-legacy-cpu-present=1", "offline-legacy-phase=4", "offline-key-events=1", "sheng-aks=1", "offline-hid-input=1", "offline-hid-home=1", "sheng-touch=4", "sheng-touch-hid=1", "sheng-buttons=1", "sheng-log=1", "sheng-smp=1", "armcpu-spi-ipi=1", "armcpu-self-ipi=1", "sheng-data-persist=2", "sheng-adsp=2", "sheng-audio=12", "sheng-wifi=5", "sheng-backlight=1", "sheng-cpufreq=4", "sheng-gpu-power=9", "sheng-gpu-metal=1", "sheng-fb-gpu=1", "sheng-fb-bgra=1", "sheng-dpu-scanout=2", "sheng-no-lc=1", "sheng-vsync=120", "sheng-vsync-fixed=1", "sheng-gpu-dcvs=1", "sheng-disable-watchdogd=1", "sheng-lite=1", "sheng-gpu-addon=1"]
"kernel_args" = ["-v", "serial=3", "debug=0x14e", "kt_fixed_zones=120", "-no_slto_panic", "use_contiguous_hint=0", "native-source-storage=1", "native-ufs-root=1", "offline-mobile-graphics-load=1", "offline-mobile-graphics-client=1", "offline-legacy-register=1", "offline-legacy-gop=1", "offline-legacy-unit-scale=2", "offline-legacy-pm=1", "offline-legacy-validate=1", "offline-legacy-transfer=1", "offline-legacy-cpu-present=1", "offline-legacy-phase=4", "offline-key-events=1", "sheng-aks=1", "offline-hid-input=1", "offline-hid-home=1", "sheng-touch=4", "sheng-touch-hid=1", "sheng-buttons=1", "sheng-log=1", "sheng-smp=1", "armcpu-spi-ipi=1", "armcpu-self-ipi=1", "sheng-data-persist=2", "sheng-adsp=2", "sheng-audio=12", "sheng-wifi=5", "sheng-bluetooth=1", "sheng-backlight=1", "sheng-cpufreq=4", "sheng-gpu-power=9", "sheng-gpu-metal=1", "sheng-fb-gpu=1", "sheng-fb-bgra=1", "sheng-dpu-scanout=2", "sheng-no-lc=1", "sheng-vsync=120", "sheng-vsync-fixed=1", "sheng-gpu-dcvs=1", "sheng-disable-watchdogd=1", "sheng-lite=1", "sheng-gpu-addon=1"]
[payload.runtime]
tool = "scripts/build-boot-runtime.py"
@@ -73,6 +73,8 @@ wifi_tool = "scripts/build-wifi-userspace.py"
wifi_target = "ios27"
wifi_launchd_cache = true
wifi_bin_prefix = "wifi-bin"
bluetooth_tool = "scripts/build-bluetooth-userspace.py"
bluetooth_target = "ios27"
[payload.runtime.launchd_job_environment."com.apple.iconservices.iconservicesagent"]
FORCE_SOFTWARE_RENDERER = "0"
+9
View File
@@ -14,6 +14,15 @@
status = "okay";
};
bluetooth {
device_type = "bluetooth";
compatible = "qcom,wcn7850-bt";
unixnu,adt-name = "bluetooth";
transport-encoding = <1>;
transport-speed = <3200000>;
status = "okay";
};
memory@80000000 {
device_type = "memory";
reg = <0 0x80000000 0 0x80000000>;
+36 -3
View File
@@ -1,5 +1,7 @@
/* SPDX-License-Identifier: BSD-2-Clause-Patent */
#include <IOKit/IOLib.h>
#include <IOKit/IOService.h>
#include <IOKit/IOPlatformExpert.h>
#include <IOKit/IODeviceMemory.h>
#include <IOKit/IOBufferMemoryDescriptor.h>
#include <IOKit/IODeviceTreeSupport.h>
@@ -26,6 +28,7 @@ namespace {
struct Region {IOMemoryMap *map;volatile uint32_t *va;};
struct Bluetooth {
Region gcc,uart,wrapper,tlmm,smmu;
IOService *radio;
IOBufferMemoryDescriptor *dma,*tables;
struct sm8550_qup_dma domain;
struct qbt_geni geni;
@@ -163,9 +166,38 @@ bool address() {
const uint8_t *seed=static_cast<const uint8_t *>(id->getBytesNoCopy());
bool nonzero=false;
for(unsigned i=0;i<id->getLength();i++){nonzero|=seed[i]!=0;hash=(hash^seed[i])*0x100000001b3ULL;}
if(nonzero){for(unsigned i=0;i<6;i++)bt.address[i]=(uint8_t)(hash>>(i*8));bt.address[0]=(bt.address[0]&0xfe)|2;
chosen->setProperty("mac-address-bluetooth0",bt.address,6);}
chosen->release();return nonzero;
bool published=false;
if(nonzero) {
for(unsigned i=0;i<6;i++)bt.address[i]=(uint8_t)(hash>>(i*8));
bt.address[0]=(bt.address[0]&0xfe)|2;
IORegistryEntry *node=IORegistryEntry::fromPath("/arm-io/bluetooth",gIODTPlane);
OSData *encoding=node?OSDynamicCast(OSData,node->getProperty("transport-encoding")):nullptr;
OSData *speed=node?OSDynamicCast(OSData,node->getProperty("transport-speed")):nullptr;
uint32_t wire=0,baud=0;
if(encoding && encoding->getLength()==4)memcpy(&wire,encoding->getBytesNoCopy(),4);
if(speed && speed->getLength()==4)memcpy(&baud,speed->getBytesNoCopy(),4);
if(node && wire==1 && baud==3200000) {
published=chosen->setProperty("mac-address-bluetooth0",bt.address,6) &&
node->setProperty("local-mac-address",bt.address,6);
if(published && !bt.radio) {
IOService *radio=new IOService;
if(radio && radio->init()) {
radio->setName("bluetooth");
published=radio->setProperty("device_type","bluetooth") &&
radio->setProperty("transport-encoding",encoding) &&
radio->setProperty("transport-speed",speed) &&
radio->setProperty("local-mac-address",bt.address,6);
IOService *provider=IOService::getPlatform();
if(published && provider && radio->attach(provider)) {
bt.radio=radio;radio->registerService();
} else {published=false;radio->release();}
} else {published=false;if(radio)radio->release();}
}
}
if(node)node->release();
}
chosen->release();return published;
}
int resources() {
if(bt.resources)return 0;
@@ -327,6 +359,7 @@ void sheng_bluetooth_probe(void) {
if(!PE_parse_boot_argn("sheng-bluetooth",&enabled,sizeof(enabled)) || !enabled)return;
uint32_t expected=0;if(!__atomic_compare_exchange_n(&bt.started,&expected,1u,false,__ATOMIC_ACQ_REL,__ATOMIC_ACQUIRE))return;
native_bluetooth_stats[0]=1;
if(!address()){native_bluetooth_stats[0]=3;native_bluetooth_stats[1]=(uint64_t)(int64_t)-ENOENT;return;}
PE_parse_boot_argn("log-bluetooth",&bt.log,sizeof(bt.log));
thread_t thread=nullptr;
if(kernel_thread_start(worker,nullptr,&thread)==KERN_SUCCESS)thread_deallocate(thread);
+1
View File
@@ -8,5 +8,6 @@ zzzzzz-sheng-latency-trace.patch
zzzzzzzz-sheng-exit-latency.patch
zzzzzzzzz-sheng-unnest-trace.patch
sheng-wifi-launchd-cache.patch
sheng-bluetooth-runtime.patch
sheng-wifi-association-event.patch
sheng-main-thread-profile.patch
@@ -0,0 +1,25 @@
diff --git a/bsd/kern/kern_exec.c b/bsd/kern/kern_exec.c
index 119275e..6321d34 100644
--- a/bsd/kern/kern_exec.c
+++ b/bsd/kern/kern_exec.c
@@ -8711,6 +8711,20 @@ native_gpu_addon_prepare(proc_t p, user_addr_t scratch)
wifi_bin_point, sizeof(wifi_bin_point), "/usr/libexec/launchd_cache_loader",
"/usr/libexec/unixnu-wpa_supplicant", &wifi_bin_stage, &wifi_bin_status);
printf("wifi: service executable mount stage %d status %d\n", wifi_bin_stage, wifi_bin_status);
+ /* Standard system paths retain launchd policy and dyld library lookup. */
+ uint32_t bluetooth_enabled = 0;
+ if (PE_parse_boot_argn("sheng-bluetooth", &bluetooth_enabled, sizeof(bluetooth_enabled)) && bluetooth_enabled == 1) {
+ static const char bt_lib_source[] = "/private/var/ngpu/bluetooth-lib", bt_lib_point[] = "/usr/lib";
+ static const char bt_bin_source[] = "/private/var/ngpu/bluetooth-bin", bt_bin_point[] = "/usr/sbin";
+ static volatile int bt_lib_stage, bt_lib_status, bt_bin_stage, bt_bin_status;
+ native_gpu_addon_mount(p, scratch, 0x600, bt_lib_source, sizeof(bt_lib_source),
+ bt_lib_point, sizeof(bt_lib_point), "/usr/lib/dyld", "/usr/lib/QcaBTType0.dylib", &bt_lib_stage, &bt_lib_status);
+ if (bt_lib_stage == 5 && !bt_lib_status) {
+ native_gpu_addon_mount(p, scratch, 0x600, bt_bin_source, sizeof(bt_bin_source),
+ bt_bin_point, sizeof(bt_bin_point), "/usr/sbin/notifyd", "/usr/sbin/QcaBluetooth.runtime", &bt_bin_stage, &bt_bin_status);
+ }
+ printf("[bluetooth-runtime] library=%d/%d executables=%d/%d\n", bt_lib_stage, bt_lib_status, bt_bin_stage, bt_bin_status);
+ }
native_cryptex_os_view(p, scratch);
native_gestalt_cache_drop(p, scratch);
native_ls_store_drop(p, scratch);
+37
View File
@@ -0,0 +1,37 @@
#!/usr/bin/env python3
"""Stage signed Bluetooth services and pinned firmware for the existing Data addon."""
import argparse
import hashlib
import json
import os
from pathlib import Path
import subprocess
import sys
root=Path(__file__).resolve().parents[1]
parser=argparse.ArgumentParser(description=__doc__)
parser.add_argument('output',type=Path)
parser.add_argument('target',choices=('ios27',))
parser.add_argument('--os-root',type=Path,required=True)
parser.add_argument('--firmware-source',type=Path,default=root/'.cache/firmware/qca')
args=parser.parse_args()
out=args.output.resolve();out.mkdir(parents=True,exist_ok=True)
(out/'report.json').unlink(missing_ok=True)
component=Path(os.environ.get('QCA_BLUETOOTH_DIR',root/'drivers/QcaBluetooth')).resolve()
userland=out/'userland'
subprocess.run([sys.executable,component/'tools/build-userland.py',userland,
'--bluetoothd',args.os_root/'usr/sbin/bluetoothd'],check=True)
subprocess.run([sys.executable,root/'scripts/stage-bluetooth-firmware.py',out,
'--source',args.firmware_source,'--fetch'],check=True)
firmware=json.loads((out/'bluetooth-firmware.json').read_text())
report=json.loads((userland/'report.json').read_text())
report['directories']={'bluetooth-bin':'userland/bin','bluetooth-lib':'userland/lib',
firmware['firmware_prefix']:firmware['firmware_prefix']}
report['firmware']=firmware
# A unique upper entry lets the kernel verify the executable union mount.
marker=dict(native_input_sha256=report['native_input_sha256'],
files={name:item['sha256'] for name,item in report['files'].items()})
(userland/'bin/QcaBluetooth.runtime').write_text(json.dumps(marker,sort_keys=True)+'\n')
report['runtime_marker_sha256']=hashlib.sha256((userland/'bin/QcaBluetooth.runtime').read_bytes()).hexdigest()
(out/'report.json').write_text(json.dumps(report,indent=2)+'\n')
print('Staged Bluetooth runtime: '+str(out))
+164
View File
@@ -0,0 +1,164 @@
#!/usr/bin/env python3
"""Verify the packed native Bluetooth runtime and its shared launchd/trust inputs."""
import argparse
import copy
import ctypes
import hashlib
import importlib.util
import json
from pathlib import Path
import plistlib
import struct
import subprocess
import sys
import tempfile
import zlib
root=Path(__file__).resolve().parents[1]
unixnu=root.parent/'UniXNU'
sys.path.insert(0,str(unixnu/'scripts'))
sys.path.insert(0,str(root/'drivers/QcaBluetooth/tools'))
from native_ios27 import _inspect, ORIGINAL_UUID, NATIVE_DYLIB
from unixnu.devicetree import generate
from unixnu.profiles import merge
import tomllib
spec=importlib.util.spec_from_file_location('bluetooth_boot_runtime',unixnu/'scripts/build-boot-runtime.py')
builder=importlib.util.module_from_spec(spec);spec.loader.exec_module(builder)
def digest(path):return hashlib.sha256(path.read_bytes()).hexdigest()
def addon(path):
data=path.read_bytes()
assert data[:8]==b'NGPUADD2' and struct.unpack_from('<Q',data,16)[0]==len(data)
at=24;result={}
for _ in range(struct.unpack_from('<I',data,8)[0]):
length,mode,size,packed=struct.unpack_from('<IIQQ',data,at);at+=24
assert 1<length<=160 and data[at+length-1]==0
name=data[at:at+length-1].decode();at+=(length+7)&~7
assert name not in result and not Path(name).is_absolute() and '..' not in Path(name).parts
body=zlib.decompress(data[at:at+packed]);at+=(packed+7)&~7
assert len(body)==size
result[name]=(mode,body)
assert at==len(data)
return result
def modules(path):
data=path.read_bytes();count=struct.unpack_from('<I',data)[0]
offsets=list(struct.unpack_from('<'+count*'I',data,4))+[len(data)]
assert 1<=count<=128 and offsets[0]==4+4*count and offsets==sorted(set(offsets))
result=[];hashes=set()
for a,b in zip(offsets,offsets[1:]):
body=data[a:b];version,_,count=struct.unpack_from('<I16sI',body)
stride={0:20,1:22,2:24}[version];assert len(body)==24+count*stride
result.append(body)
hashes.update(body[24+i*stride:44+i*stride].hex() for i in range(count))
return result,hashes
def decode_adt(data,at=0):
properties,children=struct.unpack_from('<II',data,at);at+=8;values={}
for _ in range(properties):
name=data[at:at+32].split(b'\0')[0].decode();length=struct.unpack_from('<I',data,at+32)[0]&0x7fffffff;at+=36
values[name]=data[at:at+length];at+=(length+3)&~3
nodes={}
for _ in range(children):
value,at=decode_adt(data,at);nodes[value[0]['name'].rstrip(b'\0').decode()]=value
return (values,nodes),at
def host_xpc(data):
xpc=ctypes.CDLL('/usr/lib/system/libxpc.dylib')
xpc.xpc_create_from_plist.argtypes=[ctypes.c_void_p,ctypes.c_size_t];xpc.xpc_create_from_plist.restype=ctypes.c_void_p
xpc.xpc_dictionary_get_value.argtypes=[ctypes.c_void_p,ctypes.c_char_p];xpc.xpc_dictionary_get_value.restype=ctypes.c_void_p
xpc.xpc_dictionary_get_string.argtypes=[ctypes.c_void_p,ctypes.c_char_p];xpc.xpc_dictionary_get_string.restype=ctypes.c_char_p
xpc.xpc_release.argtypes=[ctypes.c_void_p]
buf=ctypes.create_string_buffer(data);parsed=xpc.xpc_create_from_plist(buf,len(data));assert parsed
jobs=xpc.xpc_dictionary_get_value(parsed,b'LaunchDaemons')
blue=xpc.xpc_dictionary_get_value(jobs,b'/System/Library/LaunchDaemons/com.apple.BlueTool.plist');assert blue
assert xpc.xpc_dictionary_get_string(blue,b'UserName')==b'root'
xpc.xpc_release(parsed)
def main():
parser=argparse.ArgumentParser(description=__doc__)
parser.add_argument('--runtime',type=Path,required=True)
parser.add_argument('--os-root',type=Path,required=True)
parser.add_argument('--seed-record',type=Path,help='existing runtime inputs.json that records the preserved Data seed')
args=parser.parse_args();out=args.runtime.resolve()
request=json.loads((out/'request.json').read_text());generated=json.loads((out/'generated.json').read_text())
service=json.loads((out/'bluetooth-service.json').read_text())
before=addon(Path(request['gpu_addon']));packed=addon(Path(generated['gpu_addon']))
changed=[name for name,value in before.items() if packed.get(name)!=value]
allowed=('bluetooth-bin/','bluetooth-lib/','firmware/qca/','wifi-bin/','etc/wifi/','xpc/','daemons/org.unixnu.wifi.')
assert all(name.startswith(allowed) for name in changed)
old_modules,_=modules(Path(request['trust_cache']));current_modules,trusted=modules(Path(generated['trust_cache']))
assert current_modules[:len(old_modules)]==old_modules
old_inputs=args.seed_record or root.parent/'UniXNU/out/sheng/ipados-27.0.1-24A446/sheng_ipados27/runtime/inputs.json'
prior=json.loads(old_inputs.read_text())['inputs']['data_ramdisk']
seed=Path(request['data_ramdisk']);assert str(seed)==prior['path'] and digest(seed)==prior['sha256']
assert generated['data_ramdisk']==str(seed)
with tempfile.TemporaryDirectory(prefix='bluetooth-verify-',dir=out) as td:
work=Path(td)
for relative,identity in service['files'].items():
prefix,rest=relative.split('/',1);name={'bin':'bluetooth-bin','lib':'bluetooth-lib'}[prefix]+'/'+rest
mode,body=packed[name];assert mode==0o755 and len(body)==identity['bytes']
assert hashlib.sha256(body).hexdigest()==identity['sha256']
path=work/name;path.parent.mkdir(parents=True,exist_ok=True);path.write_bytes(body)
assert builder.verified_cdhash(path)==identity['cdhash'] and identity['cdhash'] in trusted
image=_inspect.Image(path);assert image.subtype==0x80000002
native=work/'bluetooth-bin/bluetoothd'
original=args.os_root/'usr/sbin/bluetoothd'
def entitlements(path):
return plistlib.loads(subprocess.run(['codesign','--display','--entitlements',':-',str(path)],
check=True,capture_output=True).stdout)
assert entitlements(native)==entitlements(original)
image=_inspect.Image(native);assert image.uuid==ORIGINAL_UUID
assert image.data.count(NATIVE_DYLIB+b'\0')==1
marker=json.loads(packed['bluetooth-bin/QcaBluetooth.runtime'][1])
assert marker['files']=={name:item['sha256'] for name,item in service['files'].items()}
manifest=service['firmware']
for name,identity in manifest['files'].items():
mode,body=packed[manifest['firmware_prefix']+'/'+name]
assert mode==0o644 and len(body)==identity['bytes'] and hashlib.sha256(body).hexdigest()==identity['sha256']
cache=work/'xpc/launchd.plist';cache.parent.mkdir(exist_ok=True)
cache.write_bytes(packed['xpc/launchd.plist'][1]);signature=cache.with_suffix('.plist.sig')
signature.write_bytes(packed['xpc/launchd.plist.sig'][1]);assert builder.verified_cdhash(cache,signature) in trusted
original_cache=plistlib.loads((args.os_root/'System/Library/xpc/launchd.plist').read_bytes())
merged=plistlib.loads(cache.read_bytes());jobs=merged['LaunchDaemons'];expected=copy.deepcopy(original_cache)
for key,job in expected['LaunchDaemons'].items():
if job['Label'] in service['launchd_job_overrides']:job.update(service['launchd_job_overrides'][job['Label']])
if job['Label'] in request.get('launchd_job_environment',{}):
job.setdefault('EnvironmentVariables',{}).update(request['launchd_job_environment'][job['Label']])
for name,(_,body) in packed.items():
if name.startswith('daemons/org.unixnu.') and name.endswith('.plist'):
key='/System/Library/LaunchDaemons/'+Path(name).name
expected['LaunchDaemons'][key]=plistlib.loads(body)
assert merged==expected
assert jobs['/System/Library/LaunchDaemons/com.apple.bluetoothd.plist']==original_cache['LaunchDaemons']['/System/Library/LaunchDaemons/com.apple.bluetoothd.plist']
host_xpc(cache.read_bytes())
empty=work/'empty';empty.mkdir()
blue_only=work/'blue-only.plist'
builder.merge_launchd_cache(args.os_root/'System/Library/xpc/launchd.plist',empty,blue_only,
job_overrides=service['launchd_job_overrides'])
assert len(plistlib.loads(blue_only.read_bytes())['LaunchDaemons'])==len(original_cache['LaunchDaemons'])
for bad in ({'ProgramArguments':['/bad']},{'UserName':'../root'},{'ThrottleInterval':True},{'EnablePressuredExit':1}):
try:builder.merge_launchd_cache(args.os_root/'System/Library/xpc/launchd.plist',empty,work/'bad.plist',job_overrides={'com.apple.BlueTool':bad})
except ValueError:pass
else:raise AssertionError('invalid launchd override accepted')
dtb=work/'hardware.dtb'
subprocess.run(['dtc','-I','dts','-O','dtb','-i',str(root.parent/'UniXNU-sm8550-common/dts'),
'-o',str(dtb),str(root/'dts/sheng.dts')],check=True,capture_output=True)
config=merge(tomllib.loads((unixnu/'profiles/os/ipados-27.0.1-24A446/profile.toml').read_text()),tomllib.loads((root/'board.toml').read_text()))
encoded=generate(dtb,config,unique_chip_id='11223344')
(tree,end)=decode_adt(encoded);assert end==len(encoded)
radio=tree[1]['arm-io'][1]['bluetooth'][0]
assert radio['device_type']==b'bluetooth\0' and radio['transport-encoding']==struct.pack('<I',1)
assert radio['transport-speed']==struct.pack('<I',3200000)
report=dict(passed=True,hardware_verified=False,files=len(packed),stock_jobs=len(original_cache['LaunchDaemons']),
runtime_jobs=len(jobs),signed_bluetooth_files=3,original_entitlements_preserved=True,
stock_bluetoothd_job_preserved=True,blue_tool_user='root',host_libxpc_parsed=True,
original_trust_modules_preserved=len(old_modules),trust_modules=len(current_modules),
data_seed_preserved=True,bluetooth_adt_encoding='H4 UART.SK',bluetooth_uart_rate=3200000,
changed_base_files=changed,addon_sha256=digest(Path(generated['gpu_addon'])),trust_sha256=digest(Path(generated['trust_cache'])))
(out/'verification.json').write_text(json.dumps(report,indent=2)+'\n')
print(f"Bluetooth runtime: {len(packed)} packed files, three signed arm64e binaries, {len(jobs)} launchd jobs; firmware, entitlements, trust modules, preserved Data seed and little-endian ADT verified")
if __name__=='__main__':main()
+13
View File
@@ -5,6 +5,8 @@ import hashlib
import json
from pathlib import Path
import re
import os
import tempfile
import urllib.request
root = Path(__file__).resolve().parents[1]
@@ -21,14 +23,25 @@ for name, expected in manifest['files'].items():
if not re.fullmatch(r'hmtbtfw[0-9a-f]{2}\.tlv|hmtnv[0-9a-f]{2}\.(bin|b[0-9a-f]{2,4})', name):
raise SystemExit('Unsupported Bluetooth firmware name: '+name)
source = a.source/name
fetched = False
if not source.exists() and a.fetch:
url = manifest['repository'].replace('github.com', 'raw.githubusercontent.com')+'/'+manifest['commit']+'/qca/'+name
with urllib.request.urlopen(url, timeout=30) as response:
data = response.read()
fetched = True
else:
data = source.read_bytes()
if len(data) != expected['bytes'] or hashlib.sha256(data).hexdigest() != expected['sha256']:
raise SystemExit('Bluetooth firmware identity mismatch: '+name)
if fetched:
source.parent.mkdir(parents=True, exist_ok=True)
with tempfile.NamedTemporaryFile(dir=source.parent, prefix=name+'.', delete=False) as cache:
temporary = Path(cache.name)
cache.write(data)
try:
os.replace(temporary, source)
finally:
temporary.unlink(missing_ok=True)
staged[name] = data
destination = a.output.resolve()/prefix
destination.mkdir(parents=True, exist_ok=True)