4.2 KiB
Userdata image
The userdata image supplies the root file system to XNU. It holds the unchanged OS and Cryptex1,SystemOS disk images from the target IPSW behind a manifest that the kernel reads.
Build
./unixnu build userdata --ipsw /path/to/restore.ipsw
The step extracts the IPSW components named in [userdata] windows, reads each disk image with hdiutil, and writes a raw image to out/<board>/userdata/userdata-root.img.
The report userdata-root.json records the window offsets, sizes and hashes, the partition capacity and the flash command.
A dry run checks the IPSW and the partition size without writing the image:
./unixnu build userdata --dry-run
Partition
board.toml describes the GPT entry:
[userdata]
first_lba = 0x2f3840
last_lba = 0x3d0abfa
lba_bytes = 4096
windows = [
{ name = "OS", component = "OS" },
{ name = "SystemOS", component = "Cryptex1,SystemOS" },
]
first_lba and last_lba come from the GPT entry named userdata on the device. lba_bytes is the logical block size of the partition.
The image size is the sum of the windows, each aligned to 1 MiB, plus the 1 MiB header region. The step rejects an image larger than the partition.
Layout
0x000000 SHNGROOT manifest, 4096 bytes, zero padded to 1 MiB
0x100000 OS window, whole disk image
.... SystemOS window, whole disk image
The manifest is little endian:
| Offset | Field |
|---|---|
| 0 | SHNGROOT |
| 8 | version, 32-bit, value 1 |
| 12 | window count, 32-bit |
| 16 | image size, 64-bit |
| 24 | window entries, 128 bytes each |
| 4064 | SHA-256 of bytes 0 to 4063 |
Each window entry contains a 32-byte name, a 64-bit offset, a 64-bit size, a 32-bit block size, a 32-bit flags field, a 32-byte SHA-256 and 40 reserved bytes.
Flash
fastboot flash userdata out/<board>/userdata/userdata-root.img
The driver prints the command in the report. The device repository can provide a wrapper script that checks the image and partition before flashing.
Root selection
The kernel mounts the root volume by UUID:
[payload.adt]
root_match_volume_uuid = "00000000-0000-0000-0000-000000000000"
The ADT generator writes a /chosen/root-matching dictionary with IOProviderClass = IOMedia and the UUID. The native block driver publishes the userdata partition, the partition scheme publishes the volumes, and XNU selects the IOMedia with the matching UUID.
Data volume
The writable Data volume comes from the Data ramdisk seed in the payload. The seed is an input to the payload step:
./unixnu build payload --data-ramdisk data.img
A seed can grow to the tablet's storage without mounting a volume:
python3 scripts/seed-data-grow.py --auto data.dbr data-grow.dbr
--auto reads fastboot getvar partition-size:userdata, subtracts the root image, a 4 MiB guard, the log reserve and the store header and bitmap, rounds down to a whole GiB and keeps 1 GiB free. It needs build/userdata-root/userdata-root.json from the userdata build and a tablet in fastboot, unless --userdata-bytes is given. --gib N sets the size directly, --userdata-bytes BYTES skips the fastboot query, and --reserve-mib overrides the 1024 MiB log reserve. The accepted range is 3 to 1024 GiB.
The grown output keeps only the nonzero pages, so it is about as large as the input. The volume UUID is unchanged. The seed identity covers the size, so a different size starts the Data volume over. The kernel refuses a seed whose store window does not fit the real userdata partition.
The kernel accepts a Data disk of up to 1 TiB. The typed block provider holds only the nonzero pages of the seed, and the persistent store writes changed pages through to the end of the userdata partition with forced unit access.
--skip-setup marks Setup as done in a copy of the seed. The payload report records the seed hash and the resulting image hash. See Skip Setup for the preference changes.
Next steps
- Userdata layout explains the manifest and the disk windows.
- Boot the image covers the first boot.
- Device configuration lists every
[userdata]key.