diff --git a/binary.go b/binary.go new file mode 100644 index 0000000..983f108 --- /dev/null +++ b/binary.go @@ -0,0 +1,90 @@ +package cpio + +import ( + "encoding/binary" + "io" + "time" +) + +const ( + binaryHeaderLength = 26 +) + +func readInt16(b []byte) int { + return int(binary.LittleEndian.Uint16(b)) +} + +func int64FromInt32(b []byte) int64 { + // BigEndian order is called out in the cpio spec for the 16 bit words. + // This hard-codes LittleEndian Machine within the 16-bit words which + // should actually be parameterized by machine/archive + t := int64(binary.BigEndian.Uint32( + []byte{ + b[1], + b[0], + b[3], + b[2], + }, + )) + return t +} + +func readBinaryHeader(r io.Reader) (*Header, error) { + // TODO: support binary-be + + var buf [binaryHeaderLength - 2]byte // -2 for magic already read + if _, err := io.ReadFull(r, buf[:]); err != nil { + return nil, err + } + hdr := &Header{} + + hdr.DeviceID = readInt16(buf[:2]) + hdr.Inode = int64(readInt16(buf[2:4])) + hdr.Mode = FileMode(readInt16(buf[4:6])) + hdr.UID = readInt16(buf[6:8]) + hdr.GID = readInt16(buf[8:10]) + hdr.Links = readInt16(buf[10:12]) + // skips rdev at buf[12:14] + hdr.ModTime = time.Unix(int64FromInt32(buf[14:18]), 0) + nameSize := readInt16(buf[18:20]) + if nameSize < 1 { + return nil, ErrHeader + } + + hdr.Size = int64(int64FromInt32(buf[20:])) // :24 is the end + + name := make([]byte, nameSize) + if _, err := io.ReadFull(r, name); err != nil { + return nil, err + } + hdr.Name = string(name[:nameSize-1]) + if hdr.Name == headerEOF { + return nil, io.EOF + } + + // store padding between end of file and next header + hdr.pad = (2 - (hdr.Size % 2)) % 2 + + // skip to end of header/start of file + // +2 for magic bytes already read + pad := (2 - (2+len(buf)+len(name))%2) % 2 + if pad > 0 { + if _, err := io.ReadFull(r, buf[:pad]); err != nil { + return nil, err + } + } + + // read link name + if hdr.Mode&^ModePerm == ModeSymlink { + if hdr.Size < 1 { + return nil, ErrHeader + } + b := make([]byte, hdr.Size) + if _, err := io.ReadFull(r, b); err != nil { + return nil, err + } + hdr.Linkname = string(b) + hdr.Size = 0 + } + return hdr, nil +} diff --git a/binary_test.go b/binary_test.go new file mode 100644 index 0000000..7bca07c --- /dev/null +++ b/binary_test.go @@ -0,0 +1,28 @@ +package cpio + +import ( + "io" + "os" + "testing" +) + +func TestReadBinary(t *testing.T) { + f, err := os.Open("testdata/test_binary.cpio") + if err != nil { + t.Fatalf("error opening test file: %v", err) + } + defer f.Close() + + r := NewReader(f) + for { + _, err := r.Next() + if err == io.EOF { + return + } + if err != nil { + t.Errorf("error moving to the next header: %v", err) + return + } + // TODO: validate header fields + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..71b628a --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module github.com/jcrowgey/go-cpio + +go 1.12 + +require github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..4bbd455 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e h1:hHg27A0RSSp2Om9lubZpiMgVbvn39bsUmW9U5h0twqc= +github.com/cavaliercoder/go-cpio v0.0.0-20180626203310-925f9528c45e/go.mod h1:oDpT4efm8tSYHXV5tHSdRvBet/b/QzxZ+XyyPehvm3A= diff --git a/reader.go b/reader.go index 81912b6..0f2df5a 100644 --- a/reader.go +++ b/reader.go @@ -1,10 +1,17 @@ package cpio import ( + "bytes" "io" "io/ioutil" ) +var magic map[string][]byte = map[string][]byte{ + "binary-le": []byte{0xc7, 0x71}, // 070707 + "svr4": []byte{0x30, 0x37, 0x30, 0x37, 0x30, 0x31}, // "070701" + "svr4-crc": []byte{0x30, 0x37, 0x30, 0x37, 0x30, 0x32}, // "070702" +} + // A Reader provides sequential access to the contents of a CPIO archive. A CPIO // archive consists of a sequence of files. The Next method advances to the next // file in the archive (including the first), and then it can be treated as an @@ -67,6 +74,24 @@ func (r *Reader) next() (*Header, error) { // ReadHeader creates a new Header, reading from r. func readHeader(r io.Reader) (*Header, error) { - // currently only SVR4 format is supported - return readSVR4Header(r) + var binMagic [2]byte + if _, err := io.ReadFull(r, binMagic[:]); err != nil { + return nil, err + } + if bytes.Equal(binMagic[:], magic["binary-le"]) { + return readBinaryHeader(r) + } else { + var ascMagic [6]byte + copy(ascMagic[:], binMagic[:]) + if _, err := io.ReadFull(r, ascMagic[2:]); err != nil { + return nil, err + } + if bytes.Equal(ascMagic[:], magic["svr4"]) { + return readSVR4Header(r, false) + } else if bytes.Equal(ascMagic[:], magic["svr4-crc"]) { + return readSVR4Header(r, true) + } else { + return nil, ErrHeader + } + } } diff --git a/svr4.go b/svr4.go index f965554..a2bf62f 100644 --- a/svr4.go +++ b/svr4.go @@ -1,7 +1,6 @@ package cpio import ( - "bytes" "fmt" "io" "strconv" @@ -9,12 +8,11 @@ import ( ) const ( - svr4MaxNameSize = 4096 // MAX_PATH - svr4MaxFileSize = 4294967295 + svr4MaxNameSize = 4096 // MAX_PATH + svr4MaxFileSize = 4294967295 + svr4HeaderLength = 110 ) -var svr4Magic = []byte{0x30, 0x37, 0x30, 0x37, 0x30, 0x31} // 070701 - func readHex(s string) int64 { // errors are ignored and 0 returned i, _ := strconv.ParseInt(s, 16, 64) @@ -26,43 +24,32 @@ func writeHex(b []byte, i int64) { copy(b, fmt.Sprintf("%08X", i)) } -func readSVR4Header(r io.Reader) (*Header, error) { - var buf [110]byte - if _, err := io.ReadFull(r, buf[:]); err != nil { - return nil, err - } - +func readSVR4Header(r io.Reader, hasCRC bool) (*Header, error) { // TODO: check endianness - // check magic - hasCRC := false - if !bytes.HasPrefix(buf[:], svr4Magic[:5]) { - return nil, ErrHeader - } - if buf[5] == 0x32 { // '2' - hasCRC = true - } else if buf[5] != 0x31 { // '1' - return nil, ErrHeader + var buf [svr4HeaderLength - 6]byte // -6 for magic already read + if _, err := io.ReadFull(r, buf[:]); err != nil { + return nil, err } asc := string(buf[:]) hdr := &Header{} - hdr.Inode = readHex(asc[6:14]) - hdr.Mode = FileMode(readHex(asc[14:22])) - hdr.UID = int(readHex(asc[22:30])) - hdr.GID = int(readHex(asc[30:38])) - hdr.Links = int(readHex(asc[38:46])) - hdr.ModTime = time.Unix(readHex(asc[46:54]), 0) - hdr.Size = readHex(asc[54:62]) + hdr.Inode = readHex(asc[:8]) + hdr.Mode = FileMode(readHex(asc[8:16])) + hdr.UID = int(readHex(asc[16:24])) + hdr.GID = int(readHex(asc[24:32])) + hdr.Links = int(readHex(asc[32:40])) + hdr.ModTime = time.Unix(readHex(asc[40:48]), 0) + hdr.Size = readHex(asc[48:56]) if hdr.Size > svr4MaxFileSize { return nil, ErrHeader } - nameSize := readHex(asc[94:102]) + nameSize := readHex(asc[88:96]) if nameSize < 1 || nameSize > svr4MaxNameSize { return nil, ErrHeader } - hdr.Checksum = Checksum(readHex(asc[102:110])) + hdr.Checksum = Checksum(readHex(asc[96:104])) if !hasCRC && hdr.Checksum != 0 { return nil, ErrHeader } @@ -80,7 +67,8 @@ func readSVR4Header(r io.Reader) (*Header, error) { hdr.pad = (4 - (hdr.Size % 4)) % 4 // skip to end of header/start of file - pad := (4 - (len(buf)+len(name))%4) % 4 + // +6 for magic bytes already read + pad := (4 - (6+len(buf)+len(name))%4) % 4 if pad > 0 { if _, err := io.ReadFull(r, buf[:pad]); err != nil { return nil, err @@ -104,15 +92,17 @@ func readSVR4Header(r io.Reader) (*Header, error) { } func writeSVR4Header(w io.Writer, hdr *Header) (pad int64, err error) { - var hdrBuf [110]byte + var hdrBuf [svr4HeaderLength]byte for i := 0; i < len(hdrBuf); i++ { hdrBuf[i] = '0' } - magic := svr4Magic + var hMagic [6]byte if hdr.Checksum != 0 { - magic[5] = 0x32 + copy(hMagic[:], magic["svr4-crc"][:]) + } else { + copy(hMagic[:], magic["svr4"][:]) } - copy(hdrBuf[:], magic) + copy(hdrBuf[:], hMagic[:]) writeHex(hdrBuf[6:14], hdr.Inode) writeHex(hdrBuf[14:22], int64(hdr.Mode)) writeHex(hdrBuf[22:30], int64(hdr.UID)) diff --git a/svr4_test.go b/svr4_test.go index 4dafcec..de99e48 100644 --- a/svr4_test.go +++ b/svr4_test.go @@ -16,7 +16,7 @@ var files = []struct { {"./todo.txt", "Get animal handling license."}, } -func TestRead(t *testing.T) { +func TestReadSVR4(t *testing.T) { f, err := os.Open("testdata/test_svr4_crc.cpio") if err != nil { t.Fatalf("error opening test file: %v", err) diff --git a/testdata/test_binary.cpio b/testdata/test_binary.cpio new file mode 100644 index 0000000..1aa64c1 Binary files /dev/null and b/testdata/test_binary.cpio differ