blob: 1dca8933493f984604798b823f11af586ffffc0b [file] [view] [edit]
# Simple Uncompressed Interchange Tape Archive: SUITAR
Status: Draft (as of September 2026). There is no compatibility guarantee yet.
## Overview
SUITAR is a common-denominator, trivial-to-parse interchange format for "the
result of decompressing a compressed file or unpacking an archive file", such
as ZIP, RAR, 7Z, TAR, GZIP, BZIP2, XZ, ZSTD, etc.
SUITAR is to archive files (including `foobar.dat.xz` compressed files as an
archive containing 1 file) as [Farbfeld](https://tools.suckless.org/farbfeld/)
or [NIE](./nie-spec.md) is to image files: an uncompressed, "designed for Unix
pipes" format that is trivial to read or write in a few hundred lines of code,
ideally in a memory-safe programming language. It's a format for what the
Chromium web browser's "Rule of 2" security advice calls
[https://chromium.googlesource.com/chromium/src/+/master/docs/security/rule-of-2.md#normalization](Normalization).
## Subset of TAR
SUITAR is a subset of the well-known and widely-used TAR (Tape Archive, with
GNU extensions) archive file format. Every valid SUITAR file is also a valid
TAR file. These files work with popular tools like `/usr/bin/tar` and with
popular TAR-reading libraries in a variety of programming languages.
SUITAR is a subset of "pure TAR", not of "TAR wrapped in GZIP", "TAR wrapped in
BZIP2", etc. A separate compression step wrapping SUITAR is feasible, just like
wrapping TAR, but is out of scope of this document.
Like TAR, SUITAR files are a sequence of independent entries (files or
directories). Independent means that, like JSON map keys, duplicate names are
valid, although some decoders may choose to reject them.
A file's entry does not need to be preceded by explicit entries for that file's
parent directories.
### Further Restrictions
Compared to plain TAR (and refer to [the GNU TAR
manual](https://ftp.gnu.org/old-gnu/Manuals/tar-1.12/html_node/tar_123.html)),
SUITAR has further restrictions:
- Entries are either regular files (`REGTYPE`), sparse files (`GNUTYPE_SPARSE`)
or directories (`DIRTYPE`). There is no support for hard links, symlinks,
device files or other non-standard files.
- Sparse files must be completely sparse. Their content must be one contiguous
span of NUL (zero) bytes that covers the entire file.
- File and directory names must obey the "File Name Validity" rules, below.
- These names must be encoded by a `GNUTYPE_LONGNAME` header block, regardless
of whether the name's length is over or under 100 or 255 bytes.
- File size and modTime (modification time, seconds since Unix epoch) integers
must use base-256 encoding (not base-8 octal) and must be non-negative and
less than `(1 << 53)`, which is `9007_199254_740992`.
- Mode bits are either 0o644 (`rw-r--r--`) or 0o755 (`rwxr-xr-x`), encoded in
base-8 octal (not base-256).
- UID and GID are hard-coded to 65534 (as a number, equivalent to octal
0o0177776) and "nobody" (as a string).
- Any other fields are unused and must be NUL bytes.
- Padding bytes (as TAR uses 512-byte blocks) must be NUL bytes.
There is no support for various TAR variants, such as "the PAX extensions to
TAR" or "the USTAR extensions to TAR", other than what's implied by the subset
of the GNU extensions that SUITAR explicitly uses.
Encoders have no meaningful choices, bar one exception. There is only one valid
SUITAR encoding (unlike full TAR's backwards-compatible choice between base-8
or base-256 encoding of various sufficiently small numbers) for any given file
or directory entry (its combination of type, name, size, mode, modTime and
contents).
The one exception is that, if a file's contents are all NUL bytes (including
zero-sized files), an encoder can choose between a `REGTYPE` regular file (with
explicit NULs) or a `GNUTYPE_SPARSE` sparse file (with implicit NULs).
### File Name Validity
These rules apply to both file names and directory names.
- Names must be valid UTF-8.
- Names must not contain any ASCII control characters, including `'\n'` or
`'\x00'`, the "new line" or NUL bytes.
- Names must not contain the `'\x7F'` ASCII DEL byte.
- Names must be no longer than 4095 bytes, excluding a trailing NUL.
- Names must not be `""`, `"."` or `".."`.
- Names must not start or end with `"/"`, `"./"` or `"../"`.
- Names must not contain `"//"`, `"/./"` or `"/../"` as substrings.
For example, when converting from ZIP (with Japanese file names) to SUITAR, it
is the SUITAR producer's responsibility, not the SUITAR consumer's, to detect
and transform Shift-JIS encoded names to equivalent and valid UTF-8.
## File Structure
SUITAR files are a sequence of entries, followed by a 1024-byte "End Of File"
marker. The EOF marker's bytes are all NUL. Each entry occupies an integer
number of 512-byte blocks:
- 1 `GNUTYPE_LONGNAME` header block.
- 1 or more payload blocks containing the file or directory name.
- 1 `REGTYPE`, `GNUTYPE_SPARSE` or `DIRTYPE` header block.
- If `REGTYPE`, 0 or more payload blocks containing the file contents.
- If not `REGTYPE`, no further blocks.
### Header Blocks
Like all blocks, each header block is 512 bytes long. Each header block also
starts with a 12-byte magic signature (that is not valid UTF-8), identifying
SUITAR version 1. There are no other versions at this time.
The first 384 out of 512 bytes must match this template (arranged as 24 rows of
16 bytes per row, plus commentary):
@@@@@@@@@@@@.... @@@@@@@@@@@@ = "\x13sUItAR\x00\xFE\xFDv1".
................
................
................
................
................
....0000???.0177 ??? = mode.
776.0177776.$...
????????$...???? ???????? = physical size, ???????? = modTime.
??????????. ?... ?????? = checksum, ? = type.
................
................
................
................
................
................
.ustar .nobody.
................
.........nobody.
................
................
................
................
................
In this template, `@` indicates the magic signature, `.` indicates a `0x00` NUL
byte, `$` indicates a `0x80` byte and `?` indicates parts of the template that
are variable, not hard-coded.
These `?` bytes are the 3-byte mode (`"644"` or `"755"`), physical size or
modTime as an 8-byte big-endian `uint64`, 6-byte checksum (see below) or 1-byte
type, which must be one of:
- `'0'` for `REGTYPE`.
- `'5'` for `DIRTYPE`, in which case mode must be `"755"` and physical size
must be all zeroes.
- `'L'` for `GNUTYPE_LONGNAME`, in which case mode must be `"644"` and modTime
must be all zeroes.
- `'S'` for `GNUTYPE_SPARSE`, in which case physical size must be all zeroes
and offset and logical size (see below) must be the same number and, again,
within the half-open range `0 .. (1 << 53)`.
The last 128 out of 512 bytes (8 rows of 16 bytes per row) must be all NUL
bytes unless the type is `GNUTYPE_SPARSE`, in which case it must match this
template (and `?` again indicates an 8-byte big-endian `uint64`):
..$...????????$. ???????? = sparse offset.
................
................
................
................
................
...$...????????. ???????? = sparse logical size.
................
### Header Checksum
A 512-byte header block's checksum value is simply the sum of each byte (after
converting from `uint8` to `uint32`, to avoid overflow) in the block, at
offsets in the two half-open ranges `0 .. 148` and `156 .. 512`, which excludes
the 8 bytes for the 6-byte checksum itself plus another two hard-coded bytes
`"\x00\x20"`.
That checksum value is written as a 6-byte ASCII octal number in the header.
For example, `4853` (decimal) would be encoded as `"011365"` (octal).
### Payload Blocks
Each entry has one or more payload blocks, between its two header blocks,
containing the file or directory name. The name length (including a trailing
NUL byte) is the first header block's physical size value, and must be within
the half-open range `2 .. 4096`, and so the excluding-a-trailing-NUL length
must range within `1 .. 4095`. Rounding up that including-a-trailing-NUL length
to a multiple of 512 gives the number of 512-byte payload blocks that contain
the name. All padding bytes in the name's final payload block must be NUL.
For `REGTYPE` entries, the second header block's physical size value gives the
reconstructed file's size and rounding that up to a multiple of 512 gives the
number of 512-byte payload blocks that contain the file contents. Again, all
padding bytes in the contents' final payload block must be NUL.
For other entries (`DIRTYPE` or `GNUTYPE_SPARSE`), there are no further payload
blocks after the second header block.
For `GNUTYPE_SPARSE` entries, the second header block's logical size value
gives the reconstructed file's size and its contents are all NUL bytes.
# Reference Implementation
The [google/wuffs](https://github.com/google/wuffs) repository, which holds
this specification document, also holds a
[suitar](https://godoc.org/github.com/google/wuffs/lib/suitar) Go package and
some `test/data/*.suitar` example files, readable by that Go package but also
by `/usr/bin/tar`.
---
Updated on September 2026.