| OLD | NEW |
| (Empty) |
| 1 // Copyright 2014 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 package cipd | |
| 6 | |
| 7 import ( | |
| 8 "archive/zip" | |
| 9 "bytes" | |
| 10 "fmt" | |
| 11 "io" | |
| 12 "io/ioutil" | |
| 13 "os" | |
| 14 "strings" | |
| 15 "time" | |
| 16 ) | |
| 17 | |
| 18 // BuildInstanceOptions defines options for BuildInstance function. | |
| 19 type BuildInstanceOptions struct { | |
| 20 // List of files to add to the package. | |
| 21 Input []File | |
| 22 // Where to write the package file to. | |
| 23 Output io.Writer | |
| 24 // Package name, e.g. 'infra/tools/cipd'. | |
| 25 PackageName string | |
| 26 } | |
| 27 | |
| 28 // BuildInstance builds a new package instance for package named opts.PackageNam
e | |
| 29 // by archiving input files (passed via opts.Input). The final binary is written | |
| 30 // to opts.Output. Some output may be written even if BuildInstance eventually | |
| 31 // returns an error. | |
| 32 func BuildInstance(opts BuildInstanceOptions) error { | |
| 33 err := ValidatePackageName(opts.PackageName) | |
| 34 if err != nil { | |
| 35 return err | |
| 36 } | |
| 37 | |
| 38 // Make sure no files are written to package service directory. | |
| 39 for _, f := range opts.Input { | |
| 40 if strings.HasPrefix(f.Name(), packageServiceDir+"/") { | |
| 41 return fmt.Errorf("Can't write to %s: %s", packageServic
eDir, f.Name()) | |
| 42 } | |
| 43 } | |
| 44 | |
| 45 // Generate the manifest file, add to the list of input files. | |
| 46 manifestFile, err := makeManifestFile(opts) | |
| 47 if err != nil { | |
| 48 return err | |
| 49 } | |
| 50 files := append(opts.Input, manifestFile) | |
| 51 | |
| 52 // Make sure filenames are unique. | |
| 53 seenNames := make(map[string]struct{}, len(files)) | |
| 54 for _, f := range files { | |
| 55 _, seen := seenNames[f.Name()] | |
| 56 if seen { | |
| 57 return fmt.Errorf("File %s is provided twice", f.Name()) | |
| 58 } | |
| 59 seenNames[f.Name()] = struct{}{} | |
| 60 } | |
| 61 | |
| 62 // Write the final zip file. | |
| 63 return zipInputFiles(files, opts.Output) | |
| 64 } | |
| 65 | |
| 66 // zipInputFiles deterministically builds a zip archive out of input files and | |
| 67 // writes it to the writer. Files are written in the order given. | |
| 68 func zipInputFiles(files []File, w io.Writer) error { | |
| 69 writer := zip.NewWriter(w) | |
| 70 defer writer.Close() | |
| 71 | |
| 72 // Reports zipping progress to the log each second. | |
| 73 lastReport := time.Time{} | |
| 74 progress := func(count int) { | |
| 75 if time.Since(lastReport) > time.Second { | |
| 76 lastReport = time.Now() | |
| 77 log.Infof("Zipping files: %d files left", len(files)-cou
nt) | |
| 78 } | |
| 79 } | |
| 80 | |
| 81 for i, in := range files { | |
| 82 progress(i) | |
| 83 | |
| 84 // Intentionally do not add timestamp or file mode to make zip a
rchive | |
| 85 // deterministic. See also zip.FileInfoHeader() implementation. | |
| 86 fh := zip.FileHeader{ | |
| 87 Name: in.Name(), | |
| 88 Method: zip.Deflate, | |
| 89 } | |
| 90 | |
| 91 mode := os.FileMode(0600) | |
| 92 if in.Executable() { | |
| 93 mode |= 0100 | |
| 94 } | |
| 95 if in.Symlink() { | |
| 96 mode |= os.ModeSymlink | |
| 97 } | |
| 98 fh.SetMode(mode) | |
| 99 | |
| 100 dst, err := writer.CreateHeader(&fh) | |
| 101 if err != nil { | |
| 102 return err | |
| 103 } | |
| 104 if in.Symlink() { | |
| 105 err = zipSymlinkFile(dst, in) | |
| 106 } else { | |
| 107 err = zipRegularFile(dst, in) | |
| 108 } | |
| 109 if err != nil { | |
| 110 return err | |
| 111 } | |
| 112 } | |
| 113 | |
| 114 return nil | |
| 115 } | |
| 116 | |
| 117 func zipRegularFile(dst io.Writer, f File) error { | |
| 118 src, err := f.Open() | |
| 119 if err != nil { | |
| 120 return err | |
| 121 } | |
| 122 defer src.Close() | |
| 123 written, err := io.Copy(dst, src) | |
| 124 if err != nil { | |
| 125 return err | |
| 126 } | |
| 127 if uint64(written) != f.Size() { | |
| 128 return fmt.Errorf("File %s changed midway", f.Name()) | |
| 129 } | |
| 130 return nil | |
| 131 } | |
| 132 | |
| 133 func zipSymlinkFile(dst io.Writer, f File) error { | |
| 134 target, err := f.SymlinkTarget() | |
| 135 if err != nil { | |
| 136 return err | |
| 137 } | |
| 138 // Symlinks are zipped as text files with target path. os.ModeSymlink bi
t in | |
| 139 // the header distinguishes them from regular files. | |
| 140 _, err = dst.Write([]byte(target)) | |
| 141 return err | |
| 142 } | |
| 143 | |
| 144 //////////////////////////////////////////////////////////////////////////////// | |
| 145 | |
| 146 type manifestFile []byte | |
| 147 | |
| 148 func (m *manifestFile) Name() string { return manifestName } | |
| 149 func (m *manifestFile) Size() uint64 { return uint64(len(*m)) } | |
| 150 func (m *manifestFile) Executable() bool { return false } | |
| 151 func (m *manifestFile) Symlink() bool { return false } | |
| 152 | |
| 153 func (m *manifestFile) SymlinkTarget() (string, error) { | |
| 154 return "", fmt.Errorf("Not a symlink: %s", m.Name()) | |
| 155 } | |
| 156 | |
| 157 func (m *manifestFile) Open() (io.ReadCloser, error) { | |
| 158 return ioutil.NopCloser(bytes.NewReader(*m)), nil | |
| 159 } | |
| 160 | |
| 161 // makeManifestFile generates a package manifest file and returns it as | |
| 162 // File interface. | |
| 163 func makeManifestFile(opts BuildInstanceOptions) (File, error) { | |
| 164 buf := &bytes.Buffer{} | |
| 165 err := writeManifest(&Manifest{ | |
| 166 FormatVersion: manifestFormatVersion, | |
| 167 PackageName: opts.PackageName, | |
| 168 }, buf) | |
| 169 if err != nil { | |
| 170 return nil, err | |
| 171 } | |
| 172 out := manifestFile(buf.Bytes()) | |
| 173 return &out, nil | |
| 174 } | |
| OLD | NEW |