Documentation
¶
Overview ¶
Package cpio reads the records of a cpio archive, in all four variants, and stops there.
It returns records rather than a filesystem, and that is the whole design. Two packages needed this parser -- go-filesystems/unarchive for an initramfs and a .deb's data member, go-filesystems/rpm for a payload -- and they need DIFFERENT filesystems over it: unarchive indexes into an io.ReaderAt it keeps open, rpm has the whole payload decompressed in memory. Sharing the filesystem would have meant one of them accepting the other's shape; sharing the parser costs neither anything.
And the parser is where the defects live. Every trap below was measured, and each one had been got wrong at least once:
- newc pads the name AND the data to four bytes, from the start of the archive. Forgetting either puts every later header one to three bytes out.
- odc pads nothing at all.
- the old binary variant's magic is 0o070707 as a 16-bit WORD, so the same two bytes mean it on the machine that wrote them and 0o143561 on one of the other endianness. Both orders exist and the order is DETECTED, not assumed.
- that variant's 32-bit fields are two words, HIGH WORD FIRST, whatever the byte order of each word -- and it pads to an even length, not to four.
- a reader that returns the next offset as a computed value rather than a parameter returned zero once, and the walk re-read record one for ever. The suite reported a ten-minute timeout, not a failure.
CGO is not used, so this builds for every target Go builds for.
Index ¶
Constants ¶
const ( MagicNewc = magicNewc MagicCRC = magicCRC MagicODC = magicODC // TrailerName is the name of the record that ends an archive. It is the NAME, // not a magic: the trailer is an ordinary header whose name says stop. TrailerName = trailerName )
binaryMagic is 0o070707 as a 16-bit WORD, which is what makes the old binary format byte-order dependent: the same two bytes are 0o070707 on the machine that wrote them and 0o143561 on one of the other endianness.
So the order is DETECTED from the magic rather than assumed, and both are read. Guessing silently misreads every field of an archive written on the other kind of machine. The magics and the trailer name, exported because a WRITER needs them and they are facts about the format rather than about this reader.
go-filesystems/unarchive writes newc, and had its own copies of these three until the parser moved here -- which is the duplication this package exists to end, one string constant included.
Variables ¶
var ErrNotCpio = errors.New("cpio: not a cpio archive")
⛔ EVERY numeric field is checked, not the three the parser happens to use. A header field that is not a number means the header is not a header, and reading it as zero gives a record that looks ordinary and is not -- an mtime of 1970 and an inode of 0 are both perfectly representable.
Two readers disagreed about this before they were merged: go-filesystems/rpm checked all of them, go-filesystems/unarchive ignored the errors on inode and mtime. The strict one is right, and it was the one tested against real packages.
ErrNotCpio is returned when the bytes do not begin with any cpio magic.
var ErrTruncated = errors.New("cpio: the archive ends inside a record")
ErrTruncated is returned when a header or a name runs past the end.
Functions ¶
func FileMode ¶
FileMode turns a POSIX st_mode into an iofs.FileMode.
⛔ The type is in the HIGH bits of the octal value, not in iofs.FileMode's own places: S_IFDIR is 0o040000 while iofs.ModeDir is 1<<31. Narrowing one into the other without this switch leaves every directory looking like a file with peculiar permissions.
Types ¶
type Record ¶
type Record struct {
// Name is the name AS RECORDED, which is not the same as a clean path: GNU
// cpio writes "./real.txt" where other writers write "real.txt", and an
// archive made on Windows can carry backslashes. Normalising is left to the
// caller because both callers already do it, for lookups as well as for
// listing, and a parser that silently rewrote names would make their two
// spellings disagree with this one.
Name string
// Mode carries the permissions AND the type bits, already translated from
// POSIX st_mode -- see FileMode for why that translation is not a cast.
Mode iofs.FileMode
Size int64
// Offset is where this record's data begins in the reader.
Offset int64
// Link is the target of a symbolic link, read from the record's data. A cpio
// stores it there rather than in the header, which is why it is read here and
// not left to the caller.
Link string
MTime time.Time
// Inode is the number the header records. It is kept because a consumer reads
// it -- go-filesystems/rpm reports it in Stat -- and because a parser that
// dropped it would force that consumer to invent one, which is a different
// number wearing the same name.
//
// It is NOT unique in general: a cpio records hard links by repeating an inode,
// and an archive concatenated from two others can repeat one by accident.
//
// ⛔ And it is NOT portable across the variants. odc's field is six octal digits
// and the old binary variant's is a 16-bit word, so neither can hold a real
// inode -- cpio(1) RENUMBERS them 1..n when it writes those. Measured: one tree
// came out as 257757054 in newc and as 1 in both of the others. A caller using
// it as an identity across two archives of the same files will be wrong.
Inode uint64
}
Record is one entry, with its data left where it lies.
Offset points into the reader Records was given, so a caller can hand out a section of it rather than copying anything.
func Records ¶
Records reads every record in the archive.
The variant is decided per record from its own magic, because an archive concatenated from two others is a thing that exists. Trailing padding after the trailer is normal -- an initramfs is padded to a block -- so what follows the trailer is ignored rather than refused.
func RecordsExact ¶ added in v0.5.0
RecordsExact is Records for an archive whose length is known to be exact, and refuses what Records tolerates.
⛔ The difference is not strictness for its own sake: the two consumers need different answers and each is right about its own case.
- An RPM payload is a cpio of a known length, from the end of the header to the end of the file. Anything in it that does not parse is damage, and a reader that stops early and reports success hands back a package with files missing.
- An initramfs is padded to a block. The bytes after the trailer are zeros nobody wrote, and refusing them would refuse every initramfs there is.
So this requires a TRAILER record, and requires the archive to end there. Records requires neither.
