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 }