1*9bb1b549SSpandan Das// Copyright 2021 The Bazel Authors. All rights reserved. 2*9bb1b549SSpandan Das// 3*9bb1b549SSpandan Das// Licensed under the Apache License, Version 2.0 (the "License"); 4*9bb1b549SSpandan Das// you may not use this file except in compliance with the License. 5*9bb1b549SSpandan Das// You may obtain a copy of the License at 6*9bb1b549SSpandan Das// 7*9bb1b549SSpandan Das// http://www.apache.org/licenses/LICENSE-2.0 8*9bb1b549SSpandan Das// 9*9bb1b549SSpandan Das// Unless required by applicable law or agreed to in writing, software 10*9bb1b549SSpandan Das// distributed under the License is distributed on an "AS IS" BASIS, 11*9bb1b549SSpandan Das// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12*9bb1b549SSpandan Das// See the License for the specific language governing permissions and 13*9bb1b549SSpandan Das// limitations under the License. 14*9bb1b549SSpandan Das 15*9bb1b549SSpandan Daspackage main 16*9bb1b549SSpandan Das 17*9bb1b549SSpandan Dasimport ( 18*9bb1b549SSpandan Das "archive/tar" 19*9bb1b549SSpandan Das "archive/zip" 20*9bb1b549SSpandan Das "compress/gzip" 21*9bb1b549SSpandan Das "context" 22*9bb1b549SSpandan Das "crypto/sha256" 23*9bb1b549SSpandan Das "encoding/hex" 24*9bb1b549SSpandan Das "errors" 25*9bb1b549SSpandan Das "fmt" 26*9bb1b549SSpandan Das "io" 27*9bb1b549SSpandan Das "os" 28*9bb1b549SSpandan Das "path" 29*9bb1b549SSpandan Das "path/filepath" 30*9bb1b549SSpandan Das "strings" 31*9bb1b549SSpandan Das "sync" 32*9bb1b549SSpandan Das) 33*9bb1b549SSpandan Das 34*9bb1b549SSpandan Dasvar repoRootState = struct { 35*9bb1b549SSpandan Das once sync.Once 36*9bb1b549SSpandan Das dir string 37*9bb1b549SSpandan Das err error 38*9bb1b549SSpandan Das}{} 39*9bb1b549SSpandan Das 40*9bb1b549SSpandan Das// repoRoot returns the workspace root directory. If this program was invoked 41*9bb1b549SSpandan Das// with 'bazel run', repoRoot returns the BUILD_WORKSPACE_DIRECTORY environment 42*9bb1b549SSpandan Das// variable. Otherwise, repoRoot walks up the directory tree and finds a 43*9bb1b549SSpandan Das// WORKSPACE file. 44*9bb1b549SSpandan Dasfunc repoRoot() (string, error) { 45*9bb1b549SSpandan Das repoRootState.once.Do(func() { 46*9bb1b549SSpandan Das if wsDir := os.Getenv("BUILD_WORKSPACE_DIRECTORY"); wsDir != "" { 47*9bb1b549SSpandan Das repoRootState.dir = wsDir 48*9bb1b549SSpandan Das return 49*9bb1b549SSpandan Das } 50*9bb1b549SSpandan Das dir, err := os.Getwd() 51*9bb1b549SSpandan Das if err != nil { 52*9bb1b549SSpandan Das repoRootState.err = err 53*9bb1b549SSpandan Das return 54*9bb1b549SSpandan Das } 55*9bb1b549SSpandan Das for { 56*9bb1b549SSpandan Das _, err := os.Stat(filepath.Join(dir, "WORKSPACE")) 57*9bb1b549SSpandan Das if err == nil { 58*9bb1b549SSpandan Das repoRootState.dir = dir 59*9bb1b549SSpandan Das return 60*9bb1b549SSpandan Das } 61*9bb1b549SSpandan Das if err != os.ErrNotExist { 62*9bb1b549SSpandan Das repoRootState.err = err 63*9bb1b549SSpandan Das return 64*9bb1b549SSpandan Das } 65*9bb1b549SSpandan Das parent := filepath.Dir(dir) 66*9bb1b549SSpandan Das if parent == dir { 67*9bb1b549SSpandan Das repoRootState.err = errors.New("could not find workspace directory") 68*9bb1b549SSpandan Das return 69*9bb1b549SSpandan Das } 70*9bb1b549SSpandan Das dir = parent 71*9bb1b549SSpandan Das } 72*9bb1b549SSpandan Das }) 73*9bb1b549SSpandan Das return repoRootState.dir, repoRootState.err 74*9bb1b549SSpandan Das} 75*9bb1b549SSpandan Das 76*9bb1b549SSpandan Das// extractArchive extracts a zip or tar.gz archive opened in f, into the 77*9bb1b549SSpandan Das// directory dir, stripping stripPrefix from each entry before extraction. 78*9bb1b549SSpandan Das// name is the name of the archive, used for error reporting. 79*9bb1b549SSpandan Dasfunc extractArchive(f *os.File, name, dir, stripPrefix string) (err error) { 80*9bb1b549SSpandan Das if strings.HasSuffix(name, ".zip") { 81*9bb1b549SSpandan Das return extractZip(f, name, dir, stripPrefix) 82*9bb1b549SSpandan Das } 83*9bb1b549SSpandan Das if strings.HasSuffix(name, ".tar.gz") { 84*9bb1b549SSpandan Das zr, err := gzip.NewReader(f) 85*9bb1b549SSpandan Das if err != nil { 86*9bb1b549SSpandan Das return fmt.Errorf("extracting %s: %w", name, err) 87*9bb1b549SSpandan Das } 88*9bb1b549SSpandan Das defer func() { 89*9bb1b549SSpandan Das if cerr := zr.Close(); err == nil && cerr != nil { 90*9bb1b549SSpandan Das err = cerr 91*9bb1b549SSpandan Das } 92*9bb1b549SSpandan Das }() 93*9bb1b549SSpandan Das return extractTar(zr, name, dir, stripPrefix) 94*9bb1b549SSpandan Das } 95*9bb1b549SSpandan Das return fmt.Errorf("could not determine archive format from extension: %s", name) 96*9bb1b549SSpandan Das} 97*9bb1b549SSpandan Das 98*9bb1b549SSpandan Dasfunc extractZip(zf *os.File, name, dir, stripPrefix string) (err error) { 99*9bb1b549SSpandan Das stripPrefix += "/" 100*9bb1b549SSpandan Das fi, err := zf.Stat() 101*9bb1b549SSpandan Das if err != nil { 102*9bb1b549SSpandan Das return err 103*9bb1b549SSpandan Das } 104*9bb1b549SSpandan Das defer func() { 105*9bb1b549SSpandan Das if err != nil { 106*9bb1b549SSpandan Das err = fmt.Errorf("extracting zip %s: %w", name, err) 107*9bb1b549SSpandan Das } 108*9bb1b549SSpandan Das }() 109*9bb1b549SSpandan Das 110*9bb1b549SSpandan Das zr, err := zip.NewReader(zf, fi.Size()) 111*9bb1b549SSpandan Das if err != nil { 112*9bb1b549SSpandan Das return err 113*9bb1b549SSpandan Das } 114*9bb1b549SSpandan Das 115*9bb1b549SSpandan Das extractFile := func(f *zip.File) (err error) { 116*9bb1b549SSpandan Das defer func() { 117*9bb1b549SSpandan Das if err != nil { 118*9bb1b549SSpandan Das err = fmt.Errorf("extracting %s: %w", f.Name, err) 119*9bb1b549SSpandan Das } 120*9bb1b549SSpandan Das }() 121*9bb1b549SSpandan Das outPath, err := extractedPath(dir, stripPrefix, f.Name) 122*9bb1b549SSpandan Das if err != nil { 123*9bb1b549SSpandan Das return err 124*9bb1b549SSpandan Das } 125*9bb1b549SSpandan Das if strings.HasSuffix(f.Name, "/") { 126*9bb1b549SSpandan Das return os.MkdirAll(outPath, 0777) 127*9bb1b549SSpandan Das } 128*9bb1b549SSpandan Das r, err := f.Open() 129*9bb1b549SSpandan Das if err != nil { 130*9bb1b549SSpandan Das return err 131*9bb1b549SSpandan Das } 132*9bb1b549SSpandan Das defer r.Close() 133*9bb1b549SSpandan Das parent := filepath.Dir(outPath) 134*9bb1b549SSpandan Das if err := os.MkdirAll(parent, 0777); err != nil { 135*9bb1b549SSpandan Das return err 136*9bb1b549SSpandan Das } 137*9bb1b549SSpandan Das w, err := os.Create(outPath) 138*9bb1b549SSpandan Das if err != nil { 139*9bb1b549SSpandan Das return err 140*9bb1b549SSpandan Das } 141*9bb1b549SSpandan Das defer func() { 142*9bb1b549SSpandan Das if cerr := w.Close(); err == nil && cerr != nil { 143*9bb1b549SSpandan Das err = cerr 144*9bb1b549SSpandan Das } 145*9bb1b549SSpandan Das }() 146*9bb1b549SSpandan Das _, err = io.Copy(w, r) 147*9bb1b549SSpandan Das return err 148*9bb1b549SSpandan Das } 149*9bb1b549SSpandan Das 150*9bb1b549SSpandan Das for _, f := range zr.File { 151*9bb1b549SSpandan Das if err := extractFile(f); err != nil { 152*9bb1b549SSpandan Das return err 153*9bb1b549SSpandan Das } 154*9bb1b549SSpandan Das } 155*9bb1b549SSpandan Das 156*9bb1b549SSpandan Das return nil 157*9bb1b549SSpandan Das} 158*9bb1b549SSpandan Das 159*9bb1b549SSpandan Dasfunc extractTar(r io.Reader, name, dir, stripPrefix string) (err error) { 160*9bb1b549SSpandan Das defer func() { 161*9bb1b549SSpandan Das if err != nil { 162*9bb1b549SSpandan Das err = fmt.Errorf("extracting tar %s: %w", name, err) 163*9bb1b549SSpandan Das } 164*9bb1b549SSpandan Das }() 165*9bb1b549SSpandan Das 166*9bb1b549SSpandan Das tr := tar.NewReader(r) 167*9bb1b549SSpandan Das extractFile := func(hdr *tar.Header) (err error) { 168*9bb1b549SSpandan Das outPath, err := extractedPath(dir, stripPrefix, hdr.Name) 169*9bb1b549SSpandan Das if err != nil { 170*9bb1b549SSpandan Das return err 171*9bb1b549SSpandan Das } 172*9bb1b549SSpandan Das switch hdr.Typeflag { 173*9bb1b549SSpandan Das case tar.TypeDir: 174*9bb1b549SSpandan Das return os.MkdirAll(outPath, 0777) 175*9bb1b549SSpandan Das case tar.TypeReg: 176*9bb1b549SSpandan Das w, err := os.Create(outPath) 177*9bb1b549SSpandan Das if err != nil { 178*9bb1b549SSpandan Das return err 179*9bb1b549SSpandan Das } 180*9bb1b549SSpandan Das defer func() { 181*9bb1b549SSpandan Das if cerr := w.Close(); err == nil && cerr != nil { 182*9bb1b549SSpandan Das err = cerr 183*9bb1b549SSpandan Das } 184*9bb1b549SSpandan Das }() 185*9bb1b549SSpandan Das _, err = io.Copy(w, tr) 186*9bb1b549SSpandan Das return err 187*9bb1b549SSpandan Das default: 188*9bb1b549SSpandan Das return fmt.Errorf("unsupported file type %x: %q", hdr.Typeflag, hdr.Name) 189*9bb1b549SSpandan Das } 190*9bb1b549SSpandan Das } 191*9bb1b549SSpandan Das 192*9bb1b549SSpandan Das stripPrefix += "/" 193*9bb1b549SSpandan Das for { 194*9bb1b549SSpandan Das hdr, err := tr.Next() 195*9bb1b549SSpandan Das if err == io.EOF { 196*9bb1b549SSpandan Das break 197*9bb1b549SSpandan Das } else if err != nil { 198*9bb1b549SSpandan Das return err 199*9bb1b549SSpandan Das } 200*9bb1b549SSpandan Das if err := extractFile(hdr); err != nil { 201*9bb1b549SSpandan Das return err 202*9bb1b549SSpandan Das } 203*9bb1b549SSpandan Das } 204*9bb1b549SSpandan Das return nil 205*9bb1b549SSpandan Das} 206*9bb1b549SSpandan Das 207*9bb1b549SSpandan Das// extractedPath returns the file path that a file in an archive should be 208*9bb1b549SSpandan Das// extracted to. It verifies that entryName starts with stripPrefix and does not 209*9bb1b549SSpandan Das// point outside dir. 210*9bb1b549SSpandan Dasfunc extractedPath(dir, stripPrefix, entryName string) (string, error) { 211*9bb1b549SSpandan Das if !strings.HasPrefix(entryName, stripPrefix) { 212*9bb1b549SSpandan Das return "", fmt.Errorf("entry does not start with prefix %s: %q", stripPrefix, entryName) 213*9bb1b549SSpandan Das } 214*9bb1b549SSpandan Das entryName = entryName[len(stripPrefix):] 215*9bb1b549SSpandan Das if entryName == "" { 216*9bb1b549SSpandan Das return dir, nil 217*9bb1b549SSpandan Das } 218*9bb1b549SSpandan Das if path.IsAbs(entryName) { 219*9bb1b549SSpandan Das return "", fmt.Errorf("entry has an absolute path: %q", entryName) 220*9bb1b549SSpandan Das } 221*9bb1b549SSpandan Das if strings.HasPrefix(entryName, "../") { 222*9bb1b549SSpandan Das return "", fmt.Errorf("entry refers to something outside the archive: %q", entryName) 223*9bb1b549SSpandan Das } 224*9bb1b549SSpandan Das entryName = strings.TrimSuffix(entryName, "/") 225*9bb1b549SSpandan Das if path.Clean(entryName) != entryName { 226*9bb1b549SSpandan Das return "", fmt.Errorf("entry does not have a clean path: %q", entryName) 227*9bb1b549SSpandan Das } 228*9bb1b549SSpandan Das return filepath.Join(dir, entryName), nil 229*9bb1b549SSpandan Das} 230*9bb1b549SSpandan Das 231*9bb1b549SSpandan Das// copyDir recursively copies a directory tree. 232*9bb1b549SSpandan Dasfunc copyDir(toDir, fromDir string) error { 233*9bb1b549SSpandan Das if err := os.MkdirAll(toDir, 0777); err != nil { 234*9bb1b549SSpandan Das return err 235*9bb1b549SSpandan Das } 236*9bb1b549SSpandan Das return filepath.Walk(fromDir, func(path string, fi os.FileInfo, err error) error { 237*9bb1b549SSpandan Das if err != nil { 238*9bb1b549SSpandan Das return err 239*9bb1b549SSpandan Das } 240*9bb1b549SSpandan Das rel, _ := filepath.Rel(fromDir, path) 241*9bb1b549SSpandan Das if rel == "." { 242*9bb1b549SSpandan Das return nil 243*9bb1b549SSpandan Das } 244*9bb1b549SSpandan Das outPath := filepath.Join(toDir, rel) 245*9bb1b549SSpandan Das if fi.IsDir() { 246*9bb1b549SSpandan Das return os.Mkdir(outPath, 0777) 247*9bb1b549SSpandan Das } else { 248*9bb1b549SSpandan Das return copyFile(outPath, path) 249*9bb1b549SSpandan Das } 250*9bb1b549SSpandan Das }) 251*9bb1b549SSpandan Das} 252*9bb1b549SSpandan Das 253*9bb1b549SSpandan Dasfunc copyFile(toFile, fromFile string) (err error) { 254*9bb1b549SSpandan Das r, err := os.Open(fromFile) 255*9bb1b549SSpandan Das if err != nil { 256*9bb1b549SSpandan Das return err 257*9bb1b549SSpandan Das } 258*9bb1b549SSpandan Das defer r.Close() 259*9bb1b549SSpandan Das w, err := os.Create(toFile) 260*9bb1b549SSpandan Das if err != nil { 261*9bb1b549SSpandan Das return err 262*9bb1b549SSpandan Das } 263*9bb1b549SSpandan Das defer func() { 264*9bb1b549SSpandan Das if cerr := w.Close(); err == nil && cerr != nil { 265*9bb1b549SSpandan Das err = cerr 266*9bb1b549SSpandan Das } 267*9bb1b549SSpandan Das }() 268*9bb1b549SSpandan Das _, err = io.Copy(w, r) 269*9bb1b549SSpandan Das return err 270*9bb1b549SSpandan Das} 271*9bb1b549SSpandan Das 272*9bb1b549SSpandan Dasfunc sha256SumFile(name string) (string, error) { 273*9bb1b549SSpandan Das r, err := os.Open(name) 274*9bb1b549SSpandan Das if err != nil { 275*9bb1b549SSpandan Das return "", err 276*9bb1b549SSpandan Das } 277*9bb1b549SSpandan Das defer r.Close() 278*9bb1b549SSpandan Das h := sha256.New() 279*9bb1b549SSpandan Das if _, err := io.Copy(h, r); err != nil { 280*9bb1b549SSpandan Das return "", err 281*9bb1b549SSpandan Das } 282*9bb1b549SSpandan Das sum := h.Sum(nil) 283*9bb1b549SSpandan Das return hex.EncodeToString(sum), nil 284*9bb1b549SSpandan Das} 285*9bb1b549SSpandan Das 286*9bb1b549SSpandan Das// copyFileToMirror uploads a file to the GCS bucket backing mirror.bazel.build. 287*9bb1b549SSpandan Das// gsutil must be installed, and the user must be authenticated with 288*9bb1b549SSpandan Das// 'gcloud auth login' and be allowed to write files to the bucket. 289*9bb1b549SSpandan Dasfunc copyFileToMirror(ctx context.Context, path, fileName string) (err error) { 290*9bb1b549SSpandan Das dest := "gs://bazel-mirror/" + path 291*9bb1b549SSpandan Das defer func() { 292*9bb1b549SSpandan Das if err != nil { 293*9bb1b549SSpandan Das err = fmt.Errorf("copying file %s to %s: %w", fileName, dest, err) 294*9bb1b549SSpandan Das } 295*9bb1b549SSpandan Das }() 296*9bb1b549SSpandan Das 297*9bb1b549SSpandan Das // This function shells out to gsutil instead of using 298*9bb1b549SSpandan Das // cloud.google.com/go/storage because that package has a million 299*9bb1b549SSpandan Das // dependencies. 300*9bb1b549SSpandan Das return runForError(ctx, ".", "gsutil", "cp", "-n", fileName, dest) 301*9bb1b549SSpandan Das} 302