-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
53 changed files
with
1,958 additions
and
1,175 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
module github.com/konoui/lipo | ||
|
||
go 1.20 | ||
go 1.22 | ||
|
||
require github.com/konoui/go-qsort v0.1.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
create-archives: | ||
cd testdata/ &&\ | ||
libtool -c -static arm64-func1.o -o arm64-func1.a &&\ | ||
libtool -c -static arm64-func1.o arm64-func2.o -o arm64-func12.a &&\ | ||
libtool -c -static arm64-func1.o arm64-func2.o arm64-func3.o -o arm64-func123.a &&\ | ||
libtool -c -static arm64-func1.o amd64-func1.o -o fat-arm64-amd64-func1 &&\ | ||
ar -rc arm64-amd64-func12.a amd64-func1.o amd64-func2.o arm64-func1.o arm64-func2.o |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
package ar | ||
|
||
import ( | ||
"bytes" | ||
"errors" | ||
"fmt" | ||
"io" | ||
"strconv" | ||
"strings" | ||
) | ||
|
||
const ( | ||
headerSize = 60 | ||
PrefixSymdef = "__.SYMDEF" | ||
) | ||
|
||
var ( | ||
MagicHeader = []byte("!<arch>\n") | ||
ErrInvalidFormat = errors.New("not ar file format") | ||
) | ||
|
||
type File struct { | ||
*io.SectionReader | ||
Header | ||
} | ||
|
||
// https://en.wikipedia.org/wiki/Ar_(Unix) | ||
// TODO other fields | ||
type Header struct { | ||
Name string | ||
Size int64 | ||
nameSize int64 | ||
} | ||
|
||
type Reader struct { | ||
sr *io.SectionReader | ||
cur int64 | ||
next int64 | ||
} | ||
|
||
// NewArchive is a wrapper | ||
func NewArchive(ra io.ReaderAt) ([]*File, error) { | ||
r, err := NewReader(ra) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
files := []*File{} | ||
|
||
for { | ||
f, err := r.Next() | ||
if err == io.EOF { | ||
break | ||
} | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
files = append(files, f) | ||
} | ||
return files, nil | ||
} | ||
|
||
func NewReader(r io.ReaderAt) (*Reader, error) { | ||
mhLen := len(MagicHeader) | ||
buf := make([]byte, mhLen) | ||
sr := io.NewSectionReader(r, 0, 1<<63-1) | ||
if _, err := io.ReadFull(sr, buf); err != nil { | ||
if errors.Is(err, io.EOF) { | ||
return nil, ErrInvalidFormat | ||
} | ||
return nil, err | ||
} | ||
|
||
if !bytes.Equal(MagicHeader, buf) { | ||
return nil, fmt.Errorf("invalid magic header want: %s, got: %s: %w", | ||
string(MagicHeader), string(buf), ErrInvalidFormat) | ||
} | ||
|
||
return &Reader{sr: sr, cur: int64(mhLen), next: int64(mhLen)}, 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 | ||
} | ||
|
||
sr := io.NewSectionReader(r.sr, r.cur+hdr.nameSize, hdr.Size-hdr.nameSize) | ||
|
||
r.cur += hdr.Size | ||
return &File{ | ||
SectionReader: sr, | ||
Header: *hdr, | ||
}, nil | ||
} | ||
|
||
func (r *Reader) readHeader() (*Header, error) { | ||
if _, err := r.sr.Seek(r.next, io.SeekStart); err != nil { | ||
return nil, err | ||
} | ||
|
||
header := make([]byte, headerSize) | ||
n, err := io.ReadFull(r.sr, header) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
if n != headerSize { | ||
return nil, fmt.Errorf("error reading header want: %d bytes, got: %d bytes", headerSize, n) | ||
} | ||
|
||
name := TrimTailSpace(header[0:16]) | ||
// mTime := header[16:18] | ||
// uid, gid := header[28:34], buf[34:40] | ||
// perm := header[40:48] | ||
size, err := strconv.ParseInt(TrimTailSpace(header[48:58]), 10, 64) | ||
if err != nil { | ||
return nil, fmt.Errorf("parse size value of name: %v", err) | ||
} | ||
|
||
endChars := header[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 := strconv.ParseInt(trimmedSize, 10, 64) | ||
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 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, | ||
nameSize: nameSize, | ||
} | ||
return h, nil | ||
} | ||
|
||
func TrimTailSpace(b []byte) string { | ||
return strings.TrimRight(string(b), " ") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
package ar_test | ||
|
||
import ( | ||
"bytes" | ||
"crypto/sha256" | ||
"debug/macho" | ||
"encoding/hex" | ||
"io" | ||
"os" | ||
"path/filepath" | ||
"reflect" | ||
"strings" | ||
"testing" | ||
|
||
"github.com/konoui/lipo/pkg/ar" | ||
) | ||
|
||
func TestNew(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
filename string | ||
want []string | ||
}{ | ||
{ | ||
name: "arm64-func1.a", | ||
filename: "arm64-func1.a", | ||
want: []string{"arm64-func1.o"}, | ||
}, | ||
{ | ||
name: "arm64-func12.a", | ||
filename: "arm64-func12.a", | ||
want: []string{"arm64-func1.o", "arm64-func2.o"}, | ||
}, | ||
{ | ||
name: "arm64-func123.a", | ||
filename: "arm64-func123.a", | ||
want: []string{"arm64-func1.o", "arm64-func2.o", "arm64-func3.o"}, | ||
}, | ||
{ | ||
name: "arm64-amd64-func12.a", | ||
filename: "arm64-amd64-func12.a", | ||
want: []string{"amd64-func1.o", "amd64-func2.o", "arm64-func1.o", "arm64-func2.o"}, | ||
}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
p := filepath.Join("testdata", tt.filename) | ||
f, err := os.Open(p) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
defer f.Close() | ||
|
||
archiver, err := ar.NewReader(f) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
got := []string{} | ||
for { | ||
file, err := archiver.Next() | ||
if err == io.EOF { | ||
break | ||
} | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
if strings.HasPrefix(file.Name, ar.PrefixSymdef) { | ||
continue | ||
} | ||
|
||
got = append(got, file.Name) | ||
|
||
machoBuf := &bytes.Buffer{} | ||
tee := io.TeeReader(file, machoBuf) | ||
gotHash := sha256String(t, tee) | ||
wantHash := sha256StringFromFile(t, filepath.Join("testdata", file.Name)) | ||
if gotHash != wantHash { | ||
t.Errorf("%s: want: %s got: %s", file.Name, wantHash, gotHash) | ||
} | ||
|
||
// validate | ||
if _, err := macho.NewFile(bytes.NewReader(machoBuf.Bytes())); err != nil { | ||
t.Fatal(err) | ||
} | ||
} | ||
|
||
if !reflect.DeepEqual(tt.want, got) { | ||
t.Errorf("want: %v, got: %v", tt.want, got) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func sha256StringFromFile(t *testing.T, p string) string { | ||
f, err := os.Open(p) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
defer f.Close() | ||
return sha256String(t, f) | ||
} | ||
|
||
func sha256String(t *testing.T, r io.Reader) string { | ||
h := sha256.New() | ||
_, err := io.Copy(h, r) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
return hex.EncodeToString(h.Sum(nil)) | ||
} | ||
|
||
func Test_TrimTailSpace(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
input []byte | ||
want string | ||
}{ | ||
{ | ||
name: "space1", | ||
input: []byte(" test"), | ||
want: " test", | ||
}, | ||
{ | ||
name: "space2", | ||
input: []byte(" test "), | ||
want: " test", | ||
}, | ||
{ | ||
name: "space3", | ||
input: []byte("test test "), | ||
want: "test test", | ||
}, | ||
{ | ||
name: "space4", | ||
input: []byte(" test test "), | ||
want: " test test", | ||
}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
if got := ar.TrimTailSpace(tt.input); got != tt.want { | ||
t.Errorf("TrimTailSpace() = [%v:, want [%v]", got, tt.want) | ||
} | ||
}) | ||
} | ||
} |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
#include <stdio.h> | ||
|
||
int func1() { | ||
printf("func1"); | ||
return 0; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
#include <stdio.h> | ||
|
||
int func2() { | ||
printf("func2"); | ||
return 0; | ||
} |
Oops, something went wrong.