1// Copyright 2017 The Bazel Authors. All rights reserved. 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 15package main 16 17import ( 18 "bufio" 19 "bytes" 20 "errors" 21 "fmt" 22 "io" 23 "io/ioutil" 24 "os" 25 "path/filepath" 26 "runtime" 27 "strconv" 28 "strings" 29) 30 31func copyFile(inPath, outPath string) error { 32 inFile, err := os.Open(inPath) 33 if err != nil { 34 return err 35 } 36 defer inFile.Close() 37 outFile, err := os.OpenFile(outPath, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0666) 38 if err != nil { 39 return err 40 } 41 defer outFile.Close() 42 _, err = io.Copy(outFile, inFile) 43 return err 44} 45 46func linkFile(inPath, outPath string) error { 47 inPath, err := filepath.Abs(inPath) 48 if err != nil { 49 return err 50 } 51 return os.Symlink(inPath, outPath) 52} 53 54func copyOrLinkFile(inPath, outPath string) error { 55 if runtime.GOOS == "windows" { 56 return copyFile(inPath, outPath) 57 } else { 58 return linkFile(inPath, outPath) 59 } 60} 61 62const ( 63 // arHeader appears at the beginning of archives created by "ar" and 64 // "go tool pack" on all platforms. 65 arHeader = "!<arch>\n" 66 67 // entryLength is the size in bytes of the metadata preceding each file 68 // in an archive. 69 entryLength = 60 70 71 // pkgDef is the name of the export data file within an archive 72 pkgDef = "__.PKGDEF" 73 74 // nogoFact is the name of the nogo fact file 75 nogoFact = "nogo.out" 76) 77 78var zeroBytes = []byte("0 ") 79 80type bufioReaderWithCloser struct { 81 // bufio.Reader is needed to skip bytes in archives 82 *bufio.Reader 83 io.Closer 84} 85 86func extractFiles(archive, dir string, names map[string]struct{}) (files []string, err error) { 87 rc, err := openArchive(archive) 88 if err != nil { 89 return nil, err 90 } 91 defer rc.Close() 92 93 var nameData []byte 94 bufReader := rc.Reader 95 for { 96 name, size, err := readMetadata(bufReader, &nameData) 97 if err == io.EOF { 98 return files, nil 99 } 100 if err != nil { 101 return nil, err 102 } 103 if !isObjectFile(name) { 104 if err := skipFile(bufReader, size); err != nil { 105 return nil, err 106 } 107 continue 108 } 109 name, err = simpleName(name, names) 110 if err != nil { 111 return nil, err 112 } 113 name = filepath.Join(dir, name) 114 if err := extractFile(bufReader, name, size); err != nil { 115 return nil, err 116 } 117 files = append(files, name) 118 } 119} 120 121func openArchive(archive string) (bufioReaderWithCloser, error) { 122 f, err := os.Open(archive) 123 if err != nil { 124 return bufioReaderWithCloser{}, err 125 } 126 r := bufio.NewReader(f) 127 header := make([]byte, len(arHeader)) 128 if _, err := io.ReadFull(r, header); err != nil || string(header) != arHeader { 129 f.Close() 130 return bufioReaderWithCloser{}, fmt.Errorf("%s: bad header", archive) 131 } 132 return bufioReaderWithCloser{r, f}, nil 133} 134 135// readMetadata reads the relevant fields of an entry. Before calling, 136// r must be positioned at the beginning of an entry. Afterward, r will 137// be positioned at the beginning of the file data. io.EOF is returned if 138// there are no more files in the archive. 139// 140// Both BSD and GNU / SysV naming conventions are supported. 141func readMetadata(r *bufio.Reader, nameData *[]byte) (name string, size int64, err error) { 142retry: 143 // Each file is preceded by a 60-byte header that contains its metadata. 144 // We only care about two fields, name and size. Other fields (mtime, 145 // owner, group, mode) are ignored because they don't affect compilation. 146 var entry [entryLength]byte 147 if _, err := io.ReadFull(r, entry[:]); err != nil { 148 return "", 0, err 149 } 150 151 sizeField := strings.TrimSpace(string(entry[48:58])) 152 size, err = strconv.ParseInt(sizeField, 10, 64) 153 if err != nil { 154 return "", 0, err 155 } 156 157 nameField := strings.TrimRight(string(entry[:16]), " ") 158 switch { 159 case strings.HasPrefix(nameField, "#1/"): 160 // BSD-style name. The number of bytes in the name is written here in 161 // ASCII, right-padded with spaces. The actual name is stored at the 162 // beginning of the file data, left-padded with NUL bytes. 163 nameField = nameField[len("#1/"):] 164 nameLen, err := strconv.ParseInt(nameField, 10, 64) 165 if err != nil { 166 return "", 0, err 167 } 168 nameBuf := make([]byte, nameLen) 169 if _, err := io.ReadFull(r, nameBuf); err != nil { 170 return "", 0, err 171 } 172 name = strings.TrimRight(string(nameBuf), "\x00") 173 size -= nameLen 174 175 case nameField == "//": 176 // GNU / SysV-style name data. This is a fake file that contains names 177 // for files with long names. We read this into nameData, then read 178 // the next entry. 179 *nameData = make([]byte, size) 180 if _, err := io.ReadFull(r, *nameData); err != nil { 181 return "", 0, err 182 } 183 if size%2 != 0 { 184 // Files are aligned at 2-byte offsets. Discard the padding byte if the 185 // size was odd. 186 if _, err := r.ReadByte(); err != nil { 187 return "", 0, err 188 } 189 } 190 goto retry 191 192 case nameField == "/": 193 // GNU / SysV-style symbol lookup table. Skip. 194 if err := skipFile(r, size); err != nil { 195 return "", 0, err 196 } 197 goto retry 198 199 case strings.HasPrefix(nameField, "/"): 200 // GNU / SysV-style long file name. The number that follows the slash is 201 // an offset into the name data that should have been read earlier. 202 // The file name ends with a slash. 203 nameField = nameField[1:] 204 nameOffset, err := strconv.Atoi(nameField) 205 if err != nil { 206 return "", 0, err 207 } 208 if nameData == nil || nameOffset < 0 || nameOffset >= len(*nameData) { 209 return "", 0, fmt.Errorf("invalid name length: %d", nameOffset) 210 } 211 i := bytes.IndexByte((*nameData)[nameOffset:], '/') 212 if i < 0 { 213 return "", 0, errors.New("file name does not end with '/'") 214 } 215 name = string((*nameData)[nameOffset : nameOffset+i]) 216 217 case strings.HasSuffix(nameField, "/"): 218 // GNU / SysV-style short file name. 219 name = nameField[:len(nameField)-1] 220 221 default: 222 // Common format name. 223 name = nameField 224 } 225 226 return name, size, err 227} 228 229// extractFile reads size bytes from r and writes them to a new file, name. 230func extractFile(r *bufio.Reader, name string, size int64) error { 231 w, err := os.Create(name) 232 if err != nil { 233 return err 234 } 235 defer w.Close() 236 _, err = io.CopyN(w, r, size) 237 if err != nil { 238 return err 239 } 240 if size%2 != 0 { 241 // Files are aligned at 2-byte offsets. Discard the padding byte if the 242 // size was odd. 243 if _, err := r.ReadByte(); err != nil { 244 return err 245 } 246 } 247 return nil 248} 249 250func skipFile(r *bufio.Reader, size int64) error { 251 if size%2 != 0 { 252 // Files are aligned at 2-byte offsets. Discard the padding byte if the 253 // size was odd. 254 size += 1 255 } 256 _, err := r.Discard(int(size)) 257 return err 258} 259 260func isObjectFile(name string) bool { 261 return strings.HasSuffix(name, ".o") 262} 263 264// simpleName returns a file name which is at most 15 characters 265// and doesn't conflict with other names. If it is not possible to choose 266// such a name, simpleName will truncate the given name to 15 characters. 267// The original file extension will be preserved. 268func simpleName(name string, names map[string]struct{}) (string, error) { 269 if _, ok := names[name]; !ok && len(name) < 16 { 270 names[name] = struct{}{} 271 return name, nil 272 } 273 var stem, ext string 274 if i := strings.LastIndexByte(name, '.'); i < 0 { 275 stem = name 276 } else { 277 stem = strings.Replace(name[:i], ".", "_", -1) 278 ext = name[i:] 279 } 280 for n := 0; n < len(names)+1; n++ { 281 ns := strconv.Itoa(n) 282 stemLen := 15 - len(ext) - len(ns) 283 if stemLen < 0 { 284 break 285 } 286 if stemLen > len(stem) { 287 stemLen = len(stem) 288 } 289 candidate := stem[:stemLen] + ns + ext 290 if _, ok := names[candidate]; !ok { 291 names[candidate] = struct{}{} 292 return candidate, nil 293 } 294 } 295 return "", fmt.Errorf("cannot shorten file name: %q", name) 296} 297 298func appendFiles(goenv *env, archive string, files []string) error { 299 archive = abs(archive) // required for long filenames on Windows. 300 301 // Create an empty archive if one doesn't already exist. 302 // In Go 1.16, 'go tool pack r' reports an error if the archive doesn't exist. 303 // 'go tool pack c' copies export data in addition to creating the archive, 304 // so we don't want to use that directly. 305 _, err := os.Stat(archive) 306 if err != nil && !os.IsNotExist(err) { 307 return err 308 } 309 if os.IsNotExist(err) { 310 if err := ioutil.WriteFile(archive, []byte(arHeader), 0666); err != nil { 311 return err 312 } 313 } 314 315 // Append files to the archive. 316 // TODO(jayconrod): copy cmd/internal/archive and use that instead of 317 // shelling out to cmd/pack. 318 args := goenv.goTool("pack", "r", archive) 319 args = append(args, files...) 320 return goenv.runCommand(args) 321} 322 323type readWithCloser struct { 324 io.Reader 325 io.Closer 326} 327 328func readFileInArchive(fileName, archive string) (io.ReadCloser, error) { 329 rc, err := openArchive(archive) 330 if err != nil { 331 return nil, err 332 } 333 var nameData []byte 334 bufReader := rc.Reader 335 for err == nil { 336 // avoid shadowing err in the loop it can be returned correctly in the end 337 var ( 338 name string 339 size int64 340 ) 341 name, size, err = readMetadata(bufReader, &nameData) 342 if err != nil { 343 break 344 } 345 if name == fileName { 346 return readWithCloser{ 347 Reader: io.LimitReader(rc, size), 348 Closer: rc, 349 }, nil 350 } 351 err = skipFile(bufReader, size) 352 } 353 if err == io.EOF { 354 err = os.ErrNotExist 355 } 356 rc.Close() 357 return nil, err 358} 359 360func extractFileFromArchive(archive, dir, name string) (err error) { 361 archiveReader, err := readFileInArchive(name, archive) 362 if err != nil { 363 return fmt.Errorf("error reading %s from %s: %v", name, archive, err) 364 } 365 defer func() { 366 e := archiveReader.Close() 367 if e != nil && err == nil { 368 err = fmt.Errorf("error closing %q: %v", archive, e) 369 } 370 }() 371 outPath := filepath.Join(dir, pkgDef) 372 outFile, err := os.Create(outPath) 373 if err != nil { 374 return fmt.Errorf("error creating %s: %v", outPath, err) 375 } 376 defer func() { 377 e := outFile.Close() 378 if e != nil && err == nil { 379 err = fmt.Errorf("error closing %q: %v", outPath, e) 380 } 381 }() 382 if size, err := io.Copy(outFile, archiveReader); err != nil { 383 return fmt.Errorf("error writing %s: %v", outPath, err) 384 } else if size == 0 { 385 return fmt.Errorf("%s is empty in %s", name, archive) 386 } 387 return err 388} 389