github.com/goplus/llgo@v0.8.3/xtool/ar/reader.go (about) 1 /* 2 * Copyright (c) 2024 The GoPlus Authors (goplus.org). All rights reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package ar 18 19 import ( 20 "io" 21 "strconv" 22 "strings" 23 "unsafe" 24 ) 25 26 // Provides read access to an ar archive. 27 // Call next to skip files 28 // 29 // Example: 30 // reader := NewReader(f) 31 // var buf bytes.Buffer 32 // for { 33 // _, err := reader.Next() 34 // if err == io.EOF { 35 // break 36 // } 37 // if err != nil { 38 // t.Errorf(err.Error()) 39 // } 40 // io.Copy(&buf, reader) 41 // } 42 43 type Reader struct { 44 r io.Reader 45 nb int64 46 pad int64 47 } 48 49 // Copies read data to r. Strips the global ar header. 50 func NewReader(r io.Reader) (*Reader, error) { 51 buf := make([]byte, globalHeaderLen) 52 if _, err := io.ReadFull(r, buf); err != nil { 53 return nil, err 54 } 55 if string(buf) != globalHeader { 56 return nil, errInvalidHeader 57 } 58 59 return &Reader{r: r}, nil 60 } 61 62 func stringVal(b []byte) string { 63 return strings.TrimRight(string(b), " ") 64 } 65 66 func intVal(b []byte) (int64, error) { 67 return strconv.ParseInt(stringVal(b), 10, 64) 68 } 69 70 func (rd *Reader) skipUnread() error { 71 skip := rd.nb + rd.pad 72 rd.nb, rd.pad = 0, 0 73 if seeker, ok := rd.r.(io.Seeker); ok { 74 _, err := seeker.Seek(skip, io.SeekCurrent) 75 return err 76 } 77 78 _, err := io.CopyN(io.Discard, rd.r, skip) 79 return err 80 } 81 82 func (rd *Reader) readHeader() (header *Header, err error) { 83 var rec recHeader 84 var buf = (*[headerByteSize]byte)(unsafe.Pointer(&rec))[:] 85 if _, err = io.ReadFull(rd.r, buf); err != nil { 86 return 87 } 88 89 header = new(Header) 90 header.Name = stringVal(rec.name[:]) 91 if header.Size, err = intVal(rec.size[:]); err != nil { 92 return 93 } 94 95 if header.Size%2 == 1 { 96 rd.pad = 1 97 } else { 98 rd.pad = 0 99 } 100 101 if rec.name[0] == '#' { 102 if n, e := strconv.ParseInt(strings.TrimPrefix(header.Name[3:], "#1/"), 10, 64); e == nil { 103 name := make([]byte, n) 104 if _, err = io.ReadFull(rd.r, name); err != nil { 105 return 106 } 107 header.Name = string(name) 108 header.Size -= n 109 } 110 } 111 112 rd.nb = int64(header.Size) 113 return 114 } 115 116 // Call Next() to skip to the next file in the archive file. 117 // Returns a Header which contains the metadata about the 118 // file in the archive. 119 func (rd *Reader) Next() (*Header, error) { 120 err := rd.skipUnread() 121 if err != nil { 122 return nil, err 123 } 124 125 return rd.readHeader() 126 } 127 128 // Read data from the current entry in the archive. 129 func (rd *Reader) Read(b []byte) (n int, err error) { 130 if rd.nb == 0 { 131 return 0, io.EOF 132 } 133 if int64(len(b)) > rd.nb { 134 b = b[0:rd.nb] 135 } 136 n, err = rd.r.Read(b) 137 rd.nb -= int64(n) 138 139 return 140 }