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  }