From 289829de85eaee18234d0d6c346d2d056e87cb7b Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 18 May 2025 17:48:16 +0200 Subject: [PATCH] Adding upstream version 0.0~git20190502.809d437. Signed-off-by: Daniel Baumann --- .gitignore | 1 + COPYING | 20 +++++ README.md | 9 +++ common.go | 48 ++++++++++++ fixtures/hello.a | 4 + fixtures/hello.txt | 1 + fixtures/lamp.txt | 1 + fixtures/multi_archive.a | 7 ++ reader.go | 155 +++++++++++++++++++++++++++++++++++++++ reader_test.go | 113 ++++++++++++++++++++++++++++ writer.go | 124 +++++++++++++++++++++++++++++++ writer_test.go | 92 +++++++++++++++++++++++ 12 files changed, 575 insertions(+) create mode 100644 .gitignore create mode 100644 COPYING create mode 100644 README.md create mode 100644 common.go create mode 100644 fixtures/hello.a create mode 100644 fixtures/hello.txt create mode 100644 fixtures/lamp.txt create mode 100644 fixtures/multi_archive.a create mode 100644 reader.go create mode 100644 reader_test.go create mode 100644 writer.go create mode 100644 writer_test.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..99cf5ff --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.*~ diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..c99e812 --- /dev/null +++ b/COPYING @@ -0,0 +1,20 @@ +Copyright (c) 2013 Blake Smith + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/README.md b/README.md new file mode 100644 index 0000000..ff75d7f --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +# Golang ar (archive) file reader + +This is a simple library for reading and writing [ar](http://en.wikipedia.org/wiki/Ar_(Unix)) files in common format. It is influenced heavily in style and interface from the golang [tar](http://golang.org/pkg/archive/tar/) package. + +## Author + +Written by Blake Smith + +Licensed under the MIT license. \ No newline at end of file diff --git a/common.go b/common.go new file mode 100644 index 0000000..335bade --- /dev/null +++ b/common.go @@ -0,0 +1,48 @@ +/* +Copyright (c) 2013 Blake Smith + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +package ar + +import ( + "time" +) + +const ( + HEADER_BYTE_SIZE = 60 + GLOBAL_HEADER = "!\n" +) + +type Header struct { + Name string + ModTime time.Time + Uid int + Gid int + Mode int64 + Size int64 +} + +type slicer []byte + +func (sp *slicer) next(n int) (b []byte) { + s := *sp + b, *sp = s[0:n], s[n:] + return +} diff --git a/fixtures/hello.a b/fixtures/hello.a new file mode 100644 index 0000000..8097b59 --- /dev/null +++ b/fixtures/hello.a @@ -0,0 +1,4 @@ +! +hello.txt 1361157466 501 20 100644 13 ` +Hello world! + diff --git a/fixtures/hello.txt b/fixtures/hello.txt new file mode 100644 index 0000000..cd08755 --- /dev/null +++ b/fixtures/hello.txt @@ -0,0 +1 @@ +Hello world! diff --git a/fixtures/lamp.txt b/fixtures/lamp.txt new file mode 100644 index 0000000..cc4e576 --- /dev/null +++ b/fixtures/lamp.txt @@ -0,0 +1 @@ +I love lamp. diff --git a/fixtures/multi_archive.a b/fixtures/multi_archive.a new file mode 100644 index 0000000..2d8085b --- /dev/null +++ b/fixtures/multi_archive.a @@ -0,0 +1,7 @@ +! +hello.txt 1361157466 501 20 100644 13 ` +Hello world! + +lamp.txt 1361248906 501 20 100644 13 ` +I love lamp. + diff --git a/reader.go b/reader.go new file mode 100644 index 0000000..4b82493 --- /dev/null +++ b/reader.go @@ -0,0 +1,155 @@ +/* +Copyright (c) 2013 Blake Smith + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +package ar + +import ( + "io" + "io/ioutil" + "os" + "strconv" + "time" +) + +// Provides read access to an ar archive. +// Call next to skip files +// +// Example: +// reader := NewReader(f) +// var buf bytes.Buffer +// for { +// _, err := reader.Next() +// if err == io.EOF { +// break +// } +// if err != nil { +// t.Errorf(err.Error()) +// } +// io.Copy(&buf, reader) +// } + +type Reader struct { + r io.Reader + nb int64 + pad int64 +} + +// Copies read data to r. Strips the global ar header. +func NewReader(r io.Reader) *Reader { + io.CopyN(ioutil.Discard, r, 8) // Discard global header + + return &Reader{r: r} +} + +func (rd *Reader) string(b []byte) string { + i := len(b)-1 + for i > 0 && b[i] == 32 { + i-- + } + + return string(b[0:i+1]) +} + +func (rd *Reader) numeric(b []byte) int64 { + i := len(b)-1 + for i > 0 && b[i] == 32 { + i-- + } + + n, _ := strconv.ParseInt(string(b[0:i+1]), 10, 64) + + return n +} + +func (rd *Reader) octal(b []byte) int64 { + i := len(b)-1 + for i > 0 && b[i] == 32 { + i-- + } + + n, _ := strconv.ParseInt(string(b[3:i+1]), 8, 64) + + return n +} + +func (rd *Reader) skipUnread() error { + skip := rd.nb + rd.pad + rd.nb, rd.pad = 0, 0 + if seeker, ok := rd.r.(io.Seeker); ok { + _, err := seeker.Seek(skip, os.SEEK_CUR) + return err + } + + _, err := io.CopyN(ioutil.Discard, rd.r, skip) + return err +} + +func (rd *Reader) readHeader() (*Header, error) { + headerBuf := make([]byte, HEADER_BYTE_SIZE) + if _, err := io.ReadFull(rd.r, headerBuf); err != nil { + return nil, err + } + + header := new(Header) + s := slicer(headerBuf) + + header.Name = rd.string(s.next(16)) + header.ModTime = time.Unix(rd.numeric(s.next(12)), 0) + header.Uid = int(rd.numeric(s.next(6))) + header.Gid = int(rd.numeric(s.next(6))) + header.Mode = rd.octal(s.next(8)) + header.Size = rd.numeric(s.next(10)) + + rd.nb = int64(header.Size) + if header.Size%2 == 1 { + rd.pad = 1 + } else { + rd.pad = 0 + } + + return header, nil +} + +// Call Next() to skip to the next file in the archive file. +// Returns a Header which contains the metadata about the +// file in the archive. +func (rd *Reader) Next() (*Header, error) { + err := rd.skipUnread() + if err != nil { + return nil, err + } + + return rd.readHeader() +} + +// Read data from the current entry in the archive. +func (rd *Reader) Read(b []byte) (n int, err error) { + if rd.nb == 0 { + return 0, io.EOF + } + if int64(len(b)) > rd.nb { + b = b[0:rd.nb] + } + n, err = rd.r.Read(b) + rd.nb -= int64(n) + + return +} diff --git a/reader_test.go b/reader_test.go new file mode 100644 index 0000000..9fad1d0 --- /dev/null +++ b/reader_test.go @@ -0,0 +1,113 @@ +/* +Copyright (c) 2013 Blake Smith + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +package ar + +import ( + "bytes" + "io" + "os" + "testing" + "time" +) + +func TestReadHeader(t *testing.T) { + f, err := os.Open("./fixtures/hello.a") + defer f.Close() + + if err != nil { + t.Errorf(err.Error()) + } + reader := NewReader(f) + header, err := reader.Next() + if err != nil { + t.Errorf(err.Error()) + } + + expectedName := "hello.txt" + if header.Name != expectedName { + t.Errorf("Header name should be %s but is %s", expectedName, header.Name) + } + expectedModTime := time.Unix(1361157466, 0) + if header.ModTime != expectedModTime { + t.Errorf("ModTime should be %s but is %s", expectedModTime, header.ModTime) + } + expectedUid := 501 + if header.Uid != expectedUid { + t.Errorf("Uid should be %d but is %d", expectedUid, header.Uid) + } + expectedGid := 20 + if header.Gid != expectedGid { + t.Errorf("Gid should be %d but is %d", expectedGid, header.Gid) + } + expectedMode := int64(0644) + if header.Mode != expectedMode { + t.Errorf("Mode should be %d but is %d", expectedMode, header.Mode) + } +} + +func TestReadBody(t *testing.T) { + f, err := os.Open("./fixtures/hello.a") + defer f.Close() + + if err != nil { + t.Errorf(err.Error()) + } + reader := NewReader(f) + _, err = reader.Next() + if err != nil && err != io.EOF { + t.Errorf(err.Error()) + } + var buf bytes.Buffer + io.Copy(&buf, reader) + + expected := []byte("Hello world!\n") + actual := buf.Bytes() + if !bytes.Equal(actual, expected) { + t.Errorf("Data value should be %s but is %s", expected, actual) + } +} + +func TestReadMulti(t *testing.T) { + f, err := os.Open("./fixtures/multi_archive.a") + defer f.Close() + + if err != nil { + t.Errorf(err.Error()) + } + reader := NewReader(f) + var buf bytes.Buffer + for { + _, err := reader.Next() + if err == io.EOF { + break + } + if err != nil { + t.Errorf(err.Error()) + } + io.Copy(&buf, reader) + } + expected := []byte("Hello world!\nI love lamp.\n") + actual := buf.Bytes() + if !bytes.Equal(expected, actual) { + t.Errorf("Concatted byte buffer should be %s but is %s", expected, actual) + } +} diff --git a/writer.go b/writer.go new file mode 100644 index 0000000..680f5cb --- /dev/null +++ b/writer.go @@ -0,0 +1,124 @@ +/* +Copyright (c) 2013 Blake Smith + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +package ar + +import ( + "errors" + "io" + "strconv" +) + +var ( + ErrWriteTooLong = errors.New("ar: write too long") +) + +// Writer provides sequential writing of an ar archive. +// An ar archive is sequence of header file pairs +// Call WriteHeader to begin writing a new file, then call Write to supply the file's data +// +// Example: +// archive := ar.NewWriter(writer) +// archive.WriteGlobalHeader() +// header := new(ar.Header) +// header.Size = 15 // bytes +// if err := archive.WriteHeader(header); err != nil { +// return err +// } +// io.Copy(archive, data) +type Writer struct { + w io.Writer + nb int64 // number of unwritten bytes for the current file entry +} + +// Create a new ar writer that writes to w +func NewWriter(w io.Writer) *Writer { return &Writer{w: w} } + +func (aw *Writer) numeric(b []byte, x int64) { + s := strconv.FormatInt(x, 10) + for len(s) < len(b) { + s = s + " " + } + copy(b, []byte(s)) +} + +func (aw *Writer) octal(b []byte, x int64) { + s := "100" + strconv.FormatInt(x, 8) + for len(s) < len(b) { + s = s + " " + } + copy(b, []byte(s)) +} + +func (aw *Writer) string(b []byte, str string) { + s := str + for len(s) < len(b) { + s = s + " " + } + copy(b, []byte(s)) +} + +// Writes to the current entry in the ar archive +// Returns ErrWriteTooLong if more than header.Size +// bytes are written after a call to WriteHeader +func (aw *Writer) Write(b []byte) (n int, err error) { + if int64(len(b)) > aw.nb { + b = b[0:aw.nb] + err = ErrWriteTooLong + } + n, werr := aw.w.Write(b) + aw.nb -= int64(n) + if werr != nil { + return n, werr + } + + if len(b)%2 == 1 { // data size must be aligned to an even byte + n2, _ := aw.w.Write([]byte{'\n'}) + return n+n2, err + } + + return +} + +func (aw *Writer) WriteGlobalHeader() error { + _, err := aw.w.Write([]byte(GLOBAL_HEADER)) + return err +} + +// Writes the header to the underlying writer and prepares +// to receive the file payload +func (aw *Writer) WriteHeader(hdr *Header) error { + aw.nb = int64(hdr.Size) + header := make([]byte, HEADER_BYTE_SIZE) + s := slicer(header) + + aw.string(s.next(16), hdr.Name) + aw.numeric(s.next(12), hdr.ModTime.Unix()) + aw.numeric(s.next(6), int64(hdr.Uid)) + aw.numeric(s.next(6), int64(hdr.Gid)) + aw.octal(s.next(8), hdr.Mode) + aw.numeric(s.next(10), hdr.Size) + aw.string(s.next(2), "`\n") + + _, err := aw.w.Write(header) + + return err +} diff --git a/writer_test.go b/writer_test.go new file mode 100644 index 0000000..36b576a --- /dev/null +++ b/writer_test.go @@ -0,0 +1,92 @@ +/* +Copyright (c) 2013 Blake Smith + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +package ar + +import ( + "bytes" + "io/ioutil" + "os" + "testing" + "time" +) + +func TestGlobalHeaderWrite(t *testing.T) { + var buf bytes.Buffer + writer := NewWriter(&buf) + if err := writer.WriteGlobalHeader(); err != nil { + t.Errorf(err.Error()) + } + + globalHeader := buf.Bytes() + expectedHeader := []byte("!\n") + if !bytes.Equal(globalHeader, expectedHeader) { + t.Errorf("Global header should be %s but is %s", expectedHeader, globalHeader) + } +} + +func TestSimpleFile(t *testing.T) { + hdr := new(Header) + body := "Hello world!\n" + hdr.ModTime = time.Unix(1361157466, 0) + hdr.Name = "hello.txt" + hdr.Size = int64(len(body)) + hdr.Mode = 0644 + hdr.Uid = 501 + hdr.Gid = 20 + + var buf bytes.Buffer + writer := NewWriter(&buf) + writer.WriteGlobalHeader() + writer.WriteHeader(hdr) + _, err := writer.Write([]byte(body)) + if err != nil { + t.Errorf(err.Error()) + } + + f, _ := os.Open("./fixtures/hello.a") + defer f.Close() + + b, err := ioutil.ReadAll(f) + if err != nil { + t.Errorf(err.Error()) + } + + actual := buf.Bytes() + if !bytes.Equal(b, actual) { + t.Errorf("Expected %s to equal %s", actual, b) + } +} + +func TestWriteTooLong(t *testing.T) { + body := "Hello world!\n" + + hdr := new(Header) + hdr.Size = 1 + + var buf bytes.Buffer + writer := NewWriter(&buf) + writer.WriteHeader(hdr) + _, err := writer.Write([]byte(body)) + if err != ErrWriteTooLong { + t.Errorf("Error should have been: %s", ErrWriteTooLong) + } +}