diff --git a/chore/ardump/ardump.go b/chore/ardump/ardump.go new file mode 100644 index 00000000..eaf818b2 --- /dev/null +++ b/chore/ardump/ardump.go @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package main + +import ( + "fmt" + "log" + "os" + + "github.com/goplus/llgo/x/ar" +) + +func main() { + if len(os.Args) != 2 { + fmt.Fprintln(os.Stderr, "Usage: ardump xxx.a") + return + } + + f, err := os.Open(os.Args[1]) + check(err) + defer f.Close() + + r := ar.NewReader(f) + for { + hdr, err := r.Next() + if err != nil { + log.Println(err) + break + } + fmt.Println("==>", hdr.Name) + } +} + +func check(err error) { + if err != nil { + panic(err) + } +} diff --git a/go.mod b/go.mod index 9490031f..a32e2249 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,6 @@ go 1.18 require ( github.com/aykevl/go-wasm v0.0.1 - github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb github.com/goplus/gop v1.2.6 github.com/goplus/llvm v0.7.1-0.20240418160956-6233231cbcc9 github.com/qiniu/x v1.13.10 diff --git a/go.sum b/go.sum index 06d3bb2c..c8c84464 100644 --- a/go.sum +++ b/go.sum @@ -1,19 +1,9 @@ github.com/aykevl/go-wasm v0.0.1 h1:lPxy8l48P39W7I0tLrtCrLfZBOUq9IWZ7odGdyJP2AM= github.com/aykevl/go-wasm v0.0.1/go.mod h1:b4nggwg3lEkNKOU4wzhtLKz2q2sLxSHFnc98aGt6z/Y= -github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb h1:m935MPodAbYS46DG4pJSv7WO+VECIWUQ7OJYSoTrMh4= -github.com/blakesmith/ar v0.0.0-20190502131153-809d4375e1fb/go.mod h1:PkYb9DJNAwrSvRx5DYA+gUcOIgTGVMNkfSCbZM8cWpI= github.com/goplus/gogen v1.15.2 h1:Q6XaSx/Zi5tWnjfAziYsQI6Jv6MgODRpFtOYqNkiiqM= github.com/goplus/gogen v1.15.2/go.mod h1:92qEzVgv7y8JEFICWG9GvYI5IzfEkxYdsA1DbmnTkqk= github.com/goplus/gop v1.2.6 h1:kog3c5Js+8EopqmI4+CwueXsqibnBwYVt5q5N7juRVY= github.com/goplus/gop v1.2.6/go.mod h1:uREWbR1MrFaviZ4Mbx4ZCcAYDoqzO0iv1Qo6Np0Xx4E= -github.com/goplus/llvm v0.7.0 h1:b8XzmRA97U0V0BPSaYuZ2vw+lLO2JSpRLMtR6dAenIo= -github.com/goplus/llvm v0.7.0/go.mod h1:PeVK8GgzxwAYCiMiUAJb5wJR6xbhj989tu9oulKLLT4= -github.com/goplus/llvm v0.7.1-0.20240417171659-4fb15c5dc82a h1:pKOqI/f4lyPAlto2MT0JJsDCNMvRyF8jBT2saegwIK8= -github.com/goplus/llvm v0.7.1-0.20240417171659-4fb15c5dc82a/go.mod h1:PeVK8GgzxwAYCiMiUAJb5wJR6xbhj989tu9oulKLLT4= -github.com/goplus/llvm v0.7.1-0.20240418070213-5013f6a4299b h1:sRVmYXGgKjqgaoVQ1bshnw9Ar77stDtjHC4A/PDJ0fk= -github.com/goplus/llvm v0.7.1-0.20240418070213-5013f6a4299b/go.mod h1:PeVK8GgzxwAYCiMiUAJb5wJR6xbhj989tu9oulKLLT4= -github.com/goplus/llvm v0.7.1-0.20240418094455-718cc880283b h1:vSEAK8m49NUX0YKyF+7m/wAkrRe8pf94PfFmJ/5+xKo= -github.com/goplus/llvm v0.7.1-0.20240418094455-718cc880283b/go.mod h1:PeVK8GgzxwAYCiMiUAJb5wJR6xbhj989tu9oulKLLT4= github.com/goplus/llvm v0.7.1-0.20240418160956-6233231cbcc9 h1:E/NBN5tDh6COcJmygdBb9RAJhE4uIHfT51VBlP3tglU= github.com/goplus/llvm v0.7.1-0.20240418160956-6233231cbcc9/go.mod h1:PeVK8GgzxwAYCiMiUAJb5wJR6xbhj989tu9oulKLLT4= github.com/goplus/mod v0.13.10 h1:5Om6KOvo31daN7N30kWU1vC5zhsJPM+uPbcEN/FnlzE= diff --git a/x/ar/ar.go b/internal/ar/ar.go similarity index 99% rename from x/ar/ar.go rename to internal/ar/ar.go index d1540836..255a7fab 100644 --- a/x/ar/ar.go +++ b/internal/ar/ar.go @@ -29,7 +29,7 @@ import ( "time" wasm "github.com/aykevl/go-wasm" - "github.com/blakesmith/ar" + "github.com/goplus/llgo/x/ar" ) // Create creates an arcive for static linking from a list of object files diff --git a/x/ar/common.go b/x/ar/common.go new file mode 100644 index 00000000..848ea078 --- /dev/null +++ b/x/ar/common.go @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ar + +import ( + "time" +) + +const ( + headerByteSize = 60 + globalHeader = "!\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/x/ar/reader.go b/x/ar/reader.go new file mode 100644 index 00000000..2cfb5419 --- /dev/null +++ b/x/ar/reader.go @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package ar + +import ( + "io" + "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(io.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, io.SeekCurrent) + return err + } + + _, err := io.CopyN(io.Discard, rd.r, skip) + return err +} + +func (rd *Reader) readHeader() (*Header, error) { + headerBuf := make([]byte, headerByteSize) + 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/x/ar/writer.go b/x/ar/writer.go new file mode 100644 index 00000000..8a9c8568 --- /dev/null +++ b/x/ar/writer.go @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +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(globalHeader)) + 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, headerByteSize) + 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/x/llexportdata/llexportdata.go b/x/llexportdata/llexportdata.go new file mode 100644 index 00000000..59bc05bf --- /dev/null +++ b/x/llexportdata/llexportdata.go @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package llexportdata + +import ( + "go/token" + "go/types" + "io" +) + +// Read reads export data from in, decodes it, and returns type information for the package. +// +// The package path (effectively its linker symbol prefix) is specified by path, since unlike +// the package name, this information may not be recorded in the export data. +// +// File position information is added to fset. +// +// Read may inspect and add to the imports map to ensure that references within the export data +// to other packages are consistent. The caller must ensure that imports[path] does not exist, +// or exists but is incomplete (see types.Package.Complete), and Read inserts the resulting package +// into this map entry. +// +// On return, the state of the reader is undefined. +func Read(in io.Reader, fset *token.FileSet, imports map[string]*types.Package, path string) (*types.Package, error) { + panic("todo") +}