Skip to content

File Format ​

A packed onelf file is up to five concatenated sections:

┌────────────────────────────────────┐  offset 0
│ Runtime (static musl ELF)          │
│  670 KB (slim) or ~2 MB (update)   │
├────────────────────────────────────┤
│ Manifest (zstd-compressed)         │
│  - header (50 bytes)               │
│  - entrypoints                     │
│  - file/dir/symlink entries        │
│  - lib_dir_offsets                 │
│  - string_table                    │
├────────────────────────────────────┤
│ Payload                            │
│  256 KB blocks, zstd or raw        │
├────────────────────────────────────┤
│ Dictionary (optional, raw)         │
├────────────────────────────────────┤
│ Footer (76 bytes, fixed)           │  last 76 bytes of file
│  - magic, offsets, checksums       │
└────────────────────────────────────┘

When executed, the runtime reads its own tail to discover where the manifest, payload, and dictionary begin.

76 bytes at the end of the file. All integers little-endian.

OffsetSizeFieldDescription
08magic"ONELF\0\x01\x00"
82format_version1
102flagsbit 0 HAS_DICT, bit 1 MEMFD_HINT, bit 2 SHARUN_COMPAT, bit 3 STORED, bit 4 EXTERNAL_UPDATER, bit 5 NO_HOST_LIB_DIRS, bit 6 HOST_LIBS_ALWAYS, bit 7 CACHE_REQUESTED; other bits reserved
128manifest_offsetAbsolute file offset of the compressed manifest
208manifest_compressedCompressed manifest size in bytes
288manifest_originalUncompressed manifest size
368payload_offsetAbsolute offset of payload start
448payload_sizePayload total size
528dict_offsetDictionary offset (0 if absent)
604dict_sizeDictionary size (0 if absent)
644manifest_checksumXXH32 of uncompressed manifest
688end_magic"FLENONE\0"

Manifest ​

Compressed with zstd. After decompression:

┌────────────────┐
│ Header (50 B)  │
├────────────────┤
│ Entrypoints    │  14 bytes each * entrypoint_count
├────────────────┤
│ Entries        │  variable-size records * entry_count
├────────────────┤
│ lib_dir_offsets│  4 bytes each * lib_dir_count
├────────────────┤
│ string_table   │  null-terminated strings
└────────────────┘

Header (50 bytes) ​

OffsetSizeField
02manifest_version (currently 2)
24entry_count
64string_table_size
102entrypoint_count
122default_entrypoint (index into entrypoints)
142lib_dir_count
162name_offset (into string_table)
1832package_id (BLAKE3 of the serialized manifest bytes minus this field)

Entrypoint (14 bytes each) ​

OffsetSizeField
04name (string_table offset)
44target_entry (index into entries)
84args (string_table offset, 0x1F-separated)
121working_dir (enum)
131flags (bit 0: MEMFD_ELIGIBLE)

Entry (variable) ​

Represents a file, directory, or symlink. Fields:

Fields are serialized in this order (65-byte header, then the blocks):

text
kind:           u8        (0=dir, 1=file, 2=symlink)
parent:         u32       (index into entries, 0xFFFFFFFF for top-level)
name:           u32       (string_table offset)
mode:           u32       (unix mode bits)
mtime_secs:     u64
mtime_nsec:     u32
content_hash:   [u8; 32]  (BLAKE3 of concatenated block contents; zero for non-files)
num_blocks:     u32       (count of block refs that follow; derived from blocks.len())
symlink_target: u32       (string_table offset; 0 unless kind == symlink)
blocks:         [Block; num_blocks]  (file content block refs; empty for non-files)

Each Block is 56 bytes:

payload_offset:  u64
compressed_size: u64
original_size:   u64
content_hash:    [u8; 32]   (BLAKE3 of this block's decompressed bytes)

content_hash arrived with manifest version 2. It lets a reader verify a single block without reassembling the entry, which is what allows a large file to be served a byte at a time rather than buffered whole. In a version 1 manifest the field is absent and a Block is 24 bytes, leaving the whole-entry hash as the only check.

Payload ​

Contiguous sequence of blocks. Block boundaries are defined entirely by the blocks arrays in the manifest. The format allows two entries to point at the same block, but pack.rs currently emits per-entry block lists with no content-level deduplication.

Block size defaults to 256 KB uncompressed. Each block is compressed independently, which lets the runtime decompress them lazily as the entrypoint reads files.

If a dictionary is present (dict_size > 0), each block is decompressed against that dictionary.

When the STORED footer flag is set (onelf pack --no-compress or [compression] store = true), blocks are written raw with no zstd: compressed_size == original_size for every block, and the runtime reads the bytes verbatim with zero decompression. STORED and HAS_DICT are mutually exclusive. The manifest stays zstd-compressed either way.

Reading a packed file ​

In pseudocode:

file = open(path)
file.seek(SeekFrom::End(-76))
footer = read 76 bytes, validate magic
file.seek(footer.manifest_offset)
compressed = read footer.manifest_compressed bytes
manifest_bytes = zstd::decompress(compressed)
assert XXH32(manifest_bytes) == footer.manifest_checksum
manifest = parse(manifest_bytes)

Then the manifest tells you how to read entrypoints, files, etc.

Integrity ​

  • XXH32 over the uncompressed manifest bytes catches accidental corruption of the manifest itself.
  • BLAKE3 over each file's concatenated content bytes is stored in the entry. onelf verify recomputes and compares.
  • BLAKE3 over each block's decompressed bytes is stored alongside the block from manifest version 2 onward. This is what a random-access reader checks, so a single byte can be served and verified without reassembling the file it came from.
  • The package_id (BLAKE3 of the manifest) uniquely identifies the package and drives cache-mode directory names.

Format compatibility ​

The footer's format_version must be 1. It has not moved.

The manifest version has. Readers accept 1 and 2 and reject anything higher with a clear error, so a package written by an older onelf still runs. The only difference between the two is the content_hash on each block, so a version 1 manifest has 24-byte blocks and falls back to the whole-entry hash for verification.

Footer flags are decoded with from_bits_retain, so a bit a reader does not recognise is kept and ignored rather than treated as corruption. That is what lets a flag be added without invalidating existing packages, and why each newer one is set only for the new behaviour: a package written before they existed reads as "embedded updater, host drivers reachable through the resolver, no cache unless asked", which is what it was built expecting. The two host-library bits encode three policies: bit 5 set is never, bit 6 set is always, and neither is auto. Bit 5 wins when both are set.

Released under the MIT License.