Documentation
¶
Overview ¶
Package lzip decodes the lzip (.lz) compressed file format.
lzip is LZMA with its own framing. A .lz file is a sequence of one or more members concatenated; each member is a six-byte header, a raw LZMA stream with no .lzma header of its own, and a twenty-byte little-endian trailer carrying the CRC32 of the uncompressed data, the uncompressed size and the size of the whole member. The format is specified in the GNU lzip manual, "File format".
NewReader returns a reader over the concatenation of every member's uncompressed data. The CRC32 and both sizes in each member's trailer are verified as the member is finished, so a truncated or corrupted file is reported as an error rather than returned as short data.
There is no writer ¶
This package decodes and does not encode, on purpose. xz and zstd are read everywhere a .lz file might go and compress as well or better, so a new .lz file written today serves nobody: it would be a file fewer tools can open, for no gain in ratio. What a program does need is the ability to read the .lz files that already exist -- Linux distribution archives, GNU project tarballs, and anything a plzip pipeline produced. That is what this package is for. The LZMA coding itself comes from github.com/go-compressions/xz/lzma.
Memory ¶
The size coded in a member header is the only statement a member makes of its memory, and nothing in the framing contradicts it until the trailer, which is at the far end. lzip permits up to 512 MiB. The LZMA layer allocates its window lazily, growing with the decoded data, so an eleven-byte file that codes 512 MiB costs kilobytes (an earlier LZMA layer allocated the whole dictionary: 536 913 640 bytes).
NewReader still refuses, as a ceiling, with ErrTooLarge and before allocating, a member whose coded dictionary is above DefaultMaxDictSize (64 MiB, twice the 32 MiB of lzip -9). NewReaderLimit takes the ceiling from the caller: pass 512 MiB to read everything lzip can write, or something small for files that are known to be small. The limit applies to every member of the stream. It bounds memory per member; it does not bound output, which a streaming reader leaves to its consumer (use io.LimitReader).
Test corpus ¶
Every .lz fixture in this repository was produced by the real GNU lzip 1.26 and plzip 1.13, not hand-assembled from the specification, and testdata/generate.sh asserts that premise: lzip -dc must read each file back before it is allowed to be a fixture. Fixtures for the error paths -- a bad magic, version 0, a wrong trailer, a truncated member -- are derived in the tests by mutating those real files, so even the negative cases start from bytes a real compressor wrote.
witness_test.go goes further where it can: when lzip(1) and plzip(1) are on PATH it has them compress inputs that are not in the repository, at every level and at several member sizes, and checks this package against them. Those tests skip on CI, which has no lzip, so what CI checks is the committed corpus and what a developer's machine can add is a judge that shares no code with us.
Index ¶
Examples ¶
Constants ¶
const DefaultMaxDictSize = 64 << 20
DefaultMaxDictSize is the dictionary size NewReader accepts: 64 MiB, twice what lzip -9 and plzip -9 choose (32 MiB; GNU lzip manual, "Invoking lzip", level table). A file written with a larger -s needs NewReaderLimit.
Variables ¶
var ( // ErrMagic reports that the four bytes where a member header was // expected are not "LZIP". ErrMagic = errors.New("lzip: not an lzip stream") // ErrVersion0 reports the obsolete version 0 of the format by name. ErrVersion0 = errors.New("lzip: format version 0 is obsolete and not supported") // ErrVersion reports a format version this package does not know. ErrVersion = errors.New("lzip: unsupported format version") // ErrDictSize reports a coded dictionary size outside the range the // format permits. ErrDictSize = errors.New("lzip: dictionary size out of range") // ErrCRC reports that a member's data does not match the CRC32 in its // trailer. ErrCRC = errors.New("lzip: CRC mismatch") // ErrSize reports that a member's uncompressed size does not match its // trailer. ErrSize = errors.New("lzip: uncompressed size mismatch") // ErrMemberSize reports that the bytes consumed by a member do not // match the member size in its trailer. ErrMemberSize = errors.New("lzip: member size mismatch") // ErrTooLarge reports a member whose coded dictionary size is valid but // above the limit the reader was given. It is a ceiling on what a member // may declare, checked before the LZMA layer starts; nothing is allocated. ErrTooLarge = errors.New("lzip: dictionary size exceeds the limit") )
Errors reported for a malformed or corrupt stream. Each is wrapped with detail, so test with errors.Is.
Functions ¶
func NewReader ¶
NewReader returns a reader over the uncompressed data of every member of the lzip stream r, in order. The first member's header is read and checked immediately, so a stream that is not lzip at all is reported here.
Each member's trailer is verified when that member ends: the CRC32 and the uncompressed size must match the data produced, and the member size must match the bytes consumed. Read reports the first disagreement as an error and every later call returns the same one.
A member whose header codes a dictionary above DefaultMaxDictSize is refused with ErrTooLarge before the dictionary is allocated; use NewReaderLimit to choose another ceiling.
Example ¶
r, err := NewReader(bytes.NewReader([]byte{
// `printf 'hi' | lzip -9`, byte for byte.
0x4c, 0x5a, 0x49, 0x50, 0x01, 0x0c, 0x00, 0x34, 0x1a, 0x5c, 0xff, 0xff, 0xff,
0xff, 0xf0, 0x00, 0x00, 0x00, 0xac, 0x2a, 0x93, 0xd8, 0x02, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
}))
if err != nil {
fmt.Println(err)
return
}
out, err := io.ReadAll(r)
fmt.Printf("%q %v\n", out, err)
Output: "hi" <nil>
func NewReaderLimit ¶ added in v0.3.0
NewReaderLimit is NewReader with a caller-chosen ceiling on the dictionary size a member may declare. The LZMA layer allocates the whole dictionary when it starts a member, so this is the memory a stream can make the reader spend per member at most, and only for a member whose data really reaches back that far: the window grows with the decoded data, so an eleven-byte file that codes 512 MiB costs kilobytes. lzip's own range is 4 KiB to 512 MiB (ErrDictSize outside it); a limit above 512 MiB is the same as 512 MiB.
Types ¶
This section is empty.
