diff --git a/pkg/ar/ar.go b/pkg/ar/ar.go index 9245801..8d0da5f 100644 --- a/pkg/ar/ar.go +++ b/pkg/ar/ar.go @@ -6,14 +6,16 @@ import ( "fmt" "io" "io/fs" + "iter" "strconv" "strings" "time" ) const ( - headerSize = 60 - PrefixSymdef = "__.SYMDEF" + headerSize = 60 + PrefixSymdef = "__.SYMDEF" + bsdVariantMarker = "#1/" ) var ( @@ -37,38 +39,28 @@ type Header struct { nameSize int64 } -type Reader struct { - sr *io.SectionReader - cur int64 - next int64 +type Iter struct { + sr *io.SectionReader } -// NewArchive is a wrapper func NewArchive(ra io.ReaderAt) ([]*File, error) { - r, err := NewReader(ra) + iter, err := NewIter(ra) if err != nil { return nil, err } files := []*File{} - - for { - f, err := r.Next() - if errors.Is(err, io.EOF) { - break - } + for file, err := range iter.Next() { if err != nil { return nil, err } - - files = append(files, f) + files = append(files, file) } return files, nil } -func NewReader(r io.ReaderAt) (*Reader, error) { - mhLen := len(MagicHeader) - buf := make([]byte, mhLen) +func NewIter(r io.ReaderAt) (*Iter, error) { + buf := make([]byte, len(MagicHeader)) sr := io.NewSectionReader(r, 0, 1<<63-1) if _, err := io.ReadFull(sr, buf); err != nil { if errors.Is(err, io.EOF) { @@ -82,124 +74,121 @@ func NewReader(r io.ReaderAt) (*Reader, error) { string(MagicHeader), string(buf), ErrInvalidFormat) } - return &Reader{sr: sr, cur: int64(mhLen), next: int64(mhLen)}, nil + return &Iter{sr: sr}, nil } -// Next returns a file header and a reader of original data -func (r *Reader) Next() (*File, error) { - hdr, err := r.readHeader() - if err != nil { - return nil, err - } +func (r *Iter) Next() iter.Seq2[*File, error] { + return func(yield func(*File, error) bool) { + cur := int64(len(MagicHeader)) + for { + hdr, err := readHeader(r.sr, cur) + cur += headerSize + if errors.Is(err, io.EOF) { + return + } + if err != nil { + if !yield(nil, err) { + return + } + } - sr := io.NewSectionReader(r.sr, r.cur+hdr.nameSize, hdr.Size-hdr.nameSize) + sr := io.NewSectionReader(r.sr, + cur+hdr.nameSize, hdr.Size-hdr.nameSize) + f := &File{SectionReader: sr, Header: *hdr} + cur += hdr.Size - r.cur += hdr.Size - return &File{ - SectionReader: sr, - Header: *hdr, - }, nil + if !yield(f, nil) { + return + } + } + } } -func (r *Reader) readHeader() (*Header, error) { - if _, err := r.sr.Seek(r.next, io.SeekStart); err != nil { +func readHeader(sr *io.SectionReader, start int64) (*Header, error) { + var hdrBuf [headerSize]byte + hdrsr := io.NewSectionReader(sr, start, headerSize) + n, err := io.ReadFull(hdrsr, hdrBuf[:]) + if err != nil { return nil, err } - header := make([]byte, headerSize) - n, err := io.ReadFull(r.sr, header) + if n != headerSize { + return nil, fmt.Errorf("error reading header want: %d bytes, got: %d bytes", headerSize, n) + } + + hdr, err := parseHeader(hdrBuf) if err != nil { return nil, err } - if n != headerSize { - return nil, fmt.Errorf("error reading header want: %d bytes, got: %d bytes", headerSize, n) + // handle BSD variant + if strings.HasPrefix(hdr.Name, bsdVariantMarker) { + trimmedSize := strings.TrimPrefix(hdr.Name, bsdVariantMarker) + parsedSize, err := parseDecimal(trimmedSize) + if err != nil { + return nil, err + } + + namesr := io.NewSectionReader(sr, start+headerSize, parsedSize) + nameBuf := make([]byte, parsedSize) + if _, err := io.ReadFull(namesr, nameBuf); err != nil { + return nil, err + } + + // update + hdr.Name = strings.TrimRight(string(nameBuf), "\x00") + hdr.nameSize = int64(parsedSize) } - name := TrimTailSpace(header[0:16]) + return hdr, nil +} - parsedMTime, err := parseDecimal(TrimTailSpace(header[16:28])) +func parseHeader(buf [headerSize]byte) (*Header, error) { + name := TrimTailSpace(buf[0:16]) + + parsedMTime, err := parseDecimal(TrimTailSpace(buf[16:28])) if err != nil { return nil, fmt.Errorf("parse modtime: %w", err) } modTime := time.Unix(parsedMTime, 0) - parsedUID, err := parseDecimal(TrimTailSpace(header[28:34])) + parsedUID, err := parseDecimal(TrimTailSpace(buf[28:34])) if err != nil { return nil, fmt.Errorf("parse uid: %w", err) } - parsedGID, err := parseDecimal(TrimTailSpace(header[34:40])) + parsedGID, err := parseDecimal(TrimTailSpace(buf[34:40])) if err != nil { return nil, fmt.Errorf("parse gid: %w", err) } uid, gid := int(parsedUID), int(parsedGID) - parsedPerm, err := parseOctal(TrimTailSpace(header[40:48])) + parsedPerm, err := parseOctal(TrimTailSpace(buf[40:48])) if err != nil { return nil, fmt.Errorf("parse mode: %w", err) } perm := fs.FileMode(parsedPerm) - size, err := parseDecimal(TrimTailSpace(header[48:58])) + size, err := parseDecimal(TrimTailSpace(buf[48:58])) if err != nil { return nil, fmt.Errorf("parse size value of name: %w", err) } - endChars := header[58:60] + endChars := buf[58:60] if want := []byte{0x60, 0x0a}; !bytes.Equal(want, endChars) { return nil, fmt.Errorf("unexpected ending characters want: %x, got: %x", want, endChars) } - // update - r.cur += headerSize - - var nameSize int64 = 0 - // handle BSD variant - if strings.HasPrefix(name, "#1/") { - trimmedSize := strings.TrimPrefix(name, "#1/") - parsedSize, err := parseDecimal(trimmedSize) - if err != nil { - return nil, err - } - - nameBuf := make([]byte, parsedSize) - if _, err := io.ReadFull(r.sr, nameBuf); err != nil { - return nil, err - } - - // update - name = strings.TrimRight(string(nameBuf), "\x00") - // update - nameSize = int64(parsedSize) - } - - // align to read body - if size%2 != 0 { - if _, err := io.CopyN(io.Discard, r.sr, 1); err != nil { - if !errors.Is(err, io.EOF) { - return nil, err - } - } - // update - r.cur += 1 - } - - // next offset points to a next header - r.next = r.cur + size - - h := &Header{ - Size: size, - Name: name, - ModTime: modTime, - GID: gid, - UID: uid, - Mode: perm, - nameSize: nameSize, - } - return h, nil + return &Header{ + Size: size, + Name: name, + ModTime: modTime, + GID: gid, + UID: uid, + Mode: perm, + }, nil } func parseDecimal(s string) (int64, error) { diff --git a/pkg/ar/ar_test.go b/pkg/ar/ar_test.go index fc39f1f..6647343 100644 --- a/pkg/ar/ar_test.go +++ b/pkg/ar/ar_test.go @@ -5,7 +5,6 @@ import ( "crypto/sha256" "debug/macho" "encoding/hex" - "errors" "io" "io/fs" "os" @@ -53,17 +52,13 @@ func TestNew(t *testing.T) { } defer f.Close() - archiver, err := ar.NewReader(f) + archiver, err := ar.NewIter(f) if err != nil { t.Fatal(err) } got := []string{} - for { - file, err := archiver.Next() - if errors.Is(err, io.EOF) { - break - } + for file, err := range archiver.Next() { if err != nil { t.Fatal(err) } @@ -102,6 +97,10 @@ func TestNew(t *testing.T) { } } + if len(got) == 0 { + t.Error("got are empty") + } + if !reflect.DeepEqual(tt.want, got) { t.Errorf("want: %v, got: %v", tt.want, got) } diff --git a/pkg/lipo/operation.go b/pkg/lipo/operation.go index f956bf6..3a31792 100644 --- a/pkg/lipo/operation.go +++ b/pkg/lipo/operation.go @@ -137,7 +137,7 @@ func inspect(p string) (inspectType, error) { return inspectUnknown, errors.Join(baseErr, errors.New("cannot read first 40 bytes")) } - _, err = lmacho.NewFatReader(bytes.NewReader(buf)) + _, err = lmacho.NewFatIter(bytes.NewReader(buf)) if err == nil { return inspectFat, nil } @@ -147,7 +147,7 @@ func inspect(p string) (inspectType, error) { inspectedErrs = append(inspectedErrs, err) - _, err = ar.NewReader(bytes.NewReader(buf)) + _, err = ar.NewIter(bytes.NewReader(buf)) if err == nil { return inspectArchive, nil } diff --git a/pkg/lmacho/fat.go b/pkg/lmacho/fat.go index a496524..96c8b18 100644 --- a/pkg/lmacho/fat.go +++ b/pkg/lmacho/fat.go @@ -148,9 +148,9 @@ type FatFile struct { Arches []*FatArch } -// NewFatFile is wrapper for Fat Reader +// NewFatFile is wrapper for Fat NewFatIter func NewFatFile(ra io.ReaderAt) (*FatFile, error) { - r, err := NewFatReader(ra) + r, err := NewFatIter(ra) if err != nil { return nil, err } @@ -160,8 +160,7 @@ func NewFatFile(ra io.ReaderAt) (*FatFile, error) { FatHeader: r.FatHeader, } - for { - a, err := r.Next() + for a, err := range r.Next() { if err != nil { if errors.Is(err, io.EOF) { break diff --git a/pkg/lmacho/reader.go b/pkg/lmacho/fatiter.go similarity index 53% rename from pkg/lmacho/reader.go rename to pkg/lmacho/fatiter.go index 67e8ca7..d03f9d1 100644 --- a/pkg/lmacho/reader.go +++ b/pkg/lmacho/fatiter.go @@ -6,16 +6,15 @@ import ( "errors" "fmt" "io" + "iter" ) -type Reader struct { - r *io.SectionReader - FatHeader FatHeader - firstObjectOffset uint64 - nextNArch uint32 +type FatIter struct { + r *io.SectionReader + FatHeader FatHeader } -func NewFatReader(r io.ReaderAt) (*Reader, error) { +func NewFatIter(r io.ReaderAt) (*FatIter, error) { sr := io.NewSectionReader(r, 0, 1<<63-1) var ff FatHeader @@ -43,56 +42,80 @@ func NewFatReader(r io.ReaderAt) (*Reader, error) { return nil, &FormatError{errors.New("file contains no images")} } - return &Reader{r: sr, FatHeader: ff, nextNArch: 1}, nil + return &FatIter{r: sr, FatHeader: ff}, nil } -func (r *Reader) Next() (*FatArch, error) { - defer func() { - r.nextNArch++ - }() - magic := r.FatHeader.Magic - if r.nextNArch <= r.FatHeader.NArch { - faHdr, err := readFatArchHeader(r.r, magic) - if err != nil { - return nil, &FormatError{err} +func (r *FatIter) Next() iter.Seq2[*FatArch, error] { + return func(yield func(*FatArch, error) bool) { + nextNArch := uint32(1) + firstObjectOffset := uint64(0) + for { + fa, err := r.next(nextNArch, firstObjectOffset) + if errors.Is(err, io.EOF) { + return + } + + if nextNArch == 1 && err == nil { + firstObjectOffset = fa.Offset() + } + nextNArch++ + + if !yield(fa, err) { + return + } } + } +} - fa := &FatArch{ - sr: io.NewSectionReader(r.r, int64(faHdr.Offset), int64(faHdr.Size)), - faHdr: *faHdr, - Hidden: false, - } +func (r *FatIter) next(nextNArch uint32, firstObjectOffset uint64) (*FatArch, error) { + magic := r.FatHeader.Magic - if r.firstObjectOffset == 0 { - r.firstObjectOffset = faHdr.Offset + nextFatArchHdrOffset := FatHeaderSize() + + FatArchHeaderSize(magic)*uint64(nextNArch-1) + hr := io.NewSectionReader(r.r, + int64(nextFatArchHdrOffset), int64(FatArchHeaderSize(magic))) + if nextNArch <= r.FatHeader.NArch { + fa, err := load(magic, r.r, hr, false) + if err != nil { + return nil, err } return fa, nil } - // hidden arches - nextObjectOffset := FatHeaderSize() + FatArchHeaderSize(magic)*uint64(r.nextNArch-1) - // require to add fatArchHeaderSize, to read the header - if nextObjectOffset+FatArchHeaderSize(magic) > r.firstObjectOffset { + // for hidden + if nextFatArchHdrOffset+FatArchHeaderSize(magic) > firstObjectOffset { return nil, io.EOF } - hr := io.NewSectionReader(r.r, int64(nextObjectOffset), int64(FatArchHeaderSize(magic))) - faHdr, err := readFatArchHeader(hr, magic) + fa, err := load(magic, r.r, hr, true) if err != nil { - return nil, &FormatError{fmt.Errorf("hideARM64: %w", err)} + return nil, fmt.Errorf("hideARM64: %w", err) } - if faHdr.Cpu != TypeArm64 { + if fa.CPU() != TypeArm64 { // TODO handle error return nil, io.EOF } - return &FatArch{ - sr: io.NewSectionReader(r.r, int64(faHdr.Offset), int64(faHdr.Size)), - faHdr: *faHdr, - Hidden: true, - }, nil + return fa, nil +} + +func load(magic uint32, body io.ReaderAt, header io.Reader, hidden bool) (*FatArch, error) { + hdr, err := readFatArchHeader(header, magic) + if err != nil { + return nil, &FormatError{err} + } + + fa := &FatArch{ + sr: io.NewSectionReader(body, + int64(hdr.Offset), int64(hdr.Size)), + faHdr: *hdr, + Hidden: hidden, + } + + return fa, nil + } func readFatArchHeader(r io.Reader, magic uint32) (*FatArchHeader, error) { diff --git a/pkg/lmacho/reader_test.go b/pkg/lmacho/fatiter_test.go similarity index 85% rename from pkg/lmacho/reader_test.go rename to pkg/lmacho/fatiter_test.go index 8baa94b..d1eb5ba 100644 --- a/pkg/lmacho/reader_test.go +++ b/pkg/lmacho/fatiter_test.go @@ -2,7 +2,6 @@ package lmacho_test import ( "debug/macho" - "errors" "io" "os" "testing" @@ -14,7 +13,7 @@ import ( var bm = testlipo.NewBinManager(os.TempDir()) -func TestNewReader(t *testing.T) { +func TestNewFatIter(t *testing.T) { tests := []struct { name string setupper func(t *testing.T) (string, int) @@ -54,9 +53,9 @@ func TestNewReader(t *testing.T) { return "./../ar/testdata/fat-arm64-amd64-func1", 2 }, validator: func(t *testing.T, ra io.ReaderAt) { - _, err := ar.NewReader(ra) + _, err := ar.NewIter(ra) if err != nil { - t.Errorf("ar.NewReader failed: %v", err) + t.Errorf("ar.NewFatIter failed: %v", err) } }, }, @@ -71,19 +70,14 @@ func TestNewReader(t *testing.T) { } defer f.Close() - reader, err := lmacho.NewFatReader(f) + iter, err := lmacho.NewFatIter(f) if err != nil { - t.Errorf("NewReader() error = %v", err) + t.Errorf("NewFatITer() error = %v", err) return } narch := 0 - for { - obj, err := reader.Next() - if errors.Is(err, io.EOF) { - break - } - + for obj, err := range iter.Next() { if err != nil { t.Fatal(err) }