github.com/distbuild/reclient@v0.0.0-20240401075343-3de72e395564/internal/pkg/inputprocessor/action/clanglink/ar_reader.go (about)

     1  // Copyright 2023 Google LLC
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package clanglink
    16  
    17  import (
    18  	"fmt"
    19  	"io"
    20  	"os"
    21  	"path/filepath"
    22  	"strconv"
    23  	"strings"
    24  )
    25  
    26  const (
    27  	arFileSignature     = "!<arch>\n"
    28  	bsdFilenamePrefix   = "#1/"
    29  	fileSignatureLen    = int64(len(arFileSignature))
    30  	gnuExtendedFilename = "//"
    31  	headerSize          = 60
    32  	thinFileSignature   = "!<thin>\n"
    33  )
    34  
    35  type header struct {
    36  	Name     string
    37  	FileSize int64
    38  }
    39  
    40  // This arReader is an implementation of unix's ar used to read archive files.
    41  // For more information on ar file format, see https://en.wikipedia.org/wiki/Ar_(Unix)#File_format_details
    42  type arReader struct {
    43  	thinFilePrefix string
    44  	thin           bool
    45  	path           string
    46  	r              io.Reader
    47  }
    48  
    49  // arPath is the absolute path of the archive file.
    50  // thinFilePrefix is the path of the archive file relative to the wd which will be pepended to thin archive file entries.
    51  func newArReader(arPath string, thinFilePrefix string) (*arReader, error) {
    52  	f, err := os.Open(arPath)
    53  	if err != nil {
    54  		return nil, err
    55  	}
    56  
    57  	var r io.Reader = f
    58  	// Check if the file signature is valid.
    59  	fs := make([]byte, fileSignatureLen)
    60  	if _, err := io.ReadFull(r, fs); err != nil {
    61  		f.Close()
    62  		return nil, err
    63  	}
    64  	if string(fs) != arFileSignature && string(fs) != thinFileSignature {
    65  		f.Close()
    66  		return nil, fmt.Errorf("%v: file format not recognized", arPath)
    67  	}
    68  
    69  	return &arReader{
    70  		thinFilePrefix: thinFilePrefix,
    71  		path:           arPath,
    72  		r:              r,
    73  		thin:           string(fs) == thinFileSignature}, nil
    74  }
    75  
    76  // !<arch> files will output the file names as written in the archive.
    77  // !<thin> files will output the files relative to the current working directory.
    78  func (ar *arReader) ReadFileNames() ([]string, error) {
    79  	var files []string
    80  	var gnuFilenames []byte
    81  	var thinFiles []string
    82  	var next int
    83  
    84  	for {
    85  		h, err := ar.readHeader()
    86  
    87  		if err == io.EOF {
    88  			break
    89  		} else if err != nil {
    90  			return nil, err
    91  		}
    92  
    93  		if h.Name == gnuExtendedFilename {
    94  			d, err := ar.readFullBytes(h.FileSize)
    95  			if err != nil {
    96  				return nil, fmt.Errorf("%v: malformed archive: %w", ar.path, err)
    97  			}
    98  			gnuFilenames = d
    99  			thinFiles = strings.Split(string(gnuFilenames), "\n")
   100  			next = 0
   101  		} else if h.Name == "/" {
   102  			err = ar.skipBytes(h.FileSize)
   103  			if err != nil {
   104  				return nil, fmt.Errorf("%v: malformed archive: %w", ar.path, err)
   105  			}
   106  		} else if strings.HasPrefix(h.Name, "/") {
   107  			if ar.thin {
   108  				file := filepath.Join(ar.thinFilePrefix, thinFiles[next])
   109  				files = append(files, file)
   110  				next++
   111  			} else {
   112  				offset, err := strconv.ParseInt(strings.TrimLeft(h.Name, "/"), 10, 64)
   113  				if err != nil {
   114  					return nil, err
   115  				}
   116  
   117  				end := offset
   118  				// "/" Denotes the end of a file name.
   119  				for i := offset; gnuFilenames[i] != '/'; i++ {
   120  					end++
   121  				}
   122  				fn := gnuFilenames[offset:end]
   123  				files = append(files, string(fn))
   124  				// Skip the data section.
   125  				err = ar.skipBytes(h.FileSize)
   126  				if err != nil {
   127  					return nil, fmt.Errorf("%v: malformed archive: %w", ar.path, err)
   128  				}
   129  			}
   130  		} else if strings.HasPrefix(h.Name, bsdFilenamePrefix) {
   131  			d, err := ar.readFullBytes(h.FileSize)
   132  			if err != nil {
   133  				return nil, fmt.Errorf("%v: malformed archive: %w", ar.path, err)
   134  			}
   135  
   136  			size, err := strconv.ParseInt(strings.TrimLeft(h.Name, bsdFilenamePrefix), 10, 64)
   137  			if err != nil {
   138  				return nil, err
   139  			}
   140  			files = append(files, string(d[:size]))
   141  		} else {
   142  			files = append(files, h.Name)
   143  			// Skip the data section.
   144  			err = ar.skipBytes(h.FileSize)
   145  			if err != nil {
   146  				return nil, fmt.Errorf("%v: malformed archive: %w", ar.path, err)
   147  			}
   148  		}
   149  	}
   150  
   151  	if err := ar.resetOffsetToContentStart(); err != nil {
   152  		return nil, err
   153  	}
   154  	return files, nil
   155  }
   156  
   157  func (ar *arReader) readHeader() (*header, error) {
   158  	headerBuf, err := ar.readFullBytes(headerSize)
   159  	if err != nil {
   160  		return nil, err
   161  	}
   162  
   163  	fs, err := strconv.ParseInt((strings.TrimSpace(string(headerBuf[48:58]))), 10, 64)
   164  	if err != nil {
   165  		return nil, err
   166  	}
   167  	h := header{
   168  		Name:     strings.TrimSpace(string(headerBuf[0:16])),
   169  		FileSize: fs,
   170  	}
   171  
   172  	return &h, nil
   173  }
   174  
   175  func (ar *arReader) readFullBytes(length int64) ([]byte, error) {
   176  	data := make([]byte, length)
   177  	if _, err := io.ReadFull(ar.r, data); err != nil {
   178  		return nil, err
   179  	}
   180  	return data, nil
   181  }
   182  
   183  func (ar *arReader) skipBytes(length int64) error {
   184  	if seeker, ok := ar.r.(io.Seeker); ok {
   185  		_, err := seeker.Seek(length, io.SeekCurrent)
   186  		return err
   187  	}
   188  	return nil
   189  }
   190  
   191  func (ar *arReader) resetOffsetToContentStart() error {
   192  	if seeker, ok := ar.r.(io.Seeker); ok {
   193  		_, err := seeker.Seek(fileSignatureLen, io.SeekStart)
   194  		return err
   195  	}
   196  	return nil
   197  }
   198  
   199  func (ar *arReader) Close() error {
   200  	if closer, ok := ar.r.(io.Closer); ok {
   201  		err := closer.Close()
   202  		return err
   203  	}
   204  	return nil
   205  }