github.com/paketo-buildpacks/libpak/v2@v2.0.0-alpha.3.0.20231023030503-8365f81de65a/crush/crush.go (about) 1 /* 2 * Copyright 2018-2020 the original author or authors. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * https://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package crush 18 19 import ( 20 "archive/tar" 21 "archive/zip" 22 "bytes" 23 "compress/bzip2" 24 "compress/gzip" 25 "fmt" 26 "io" 27 "os" 28 "path/filepath" 29 "strings" 30 31 "github.com/h2non/filetype" 32 "github.com/xi2/xz" 33 ) 34 35 // CreateTar writes a TAR to the destination io.Writer containing the directories and files in the source folder. 36 func CreateTar(destination io.Writer, source string) error { 37 t := tar.NewWriter(destination) 38 defer t.Close() 39 40 if err := filepath.Walk(source, func(path string, info os.FileInfo, err error) error { 41 if err != nil { 42 return err 43 } 44 45 rel, err := filepath.Rel(source, path) 46 if err != nil { 47 return fmt.Errorf("unable to calculate relative path %s -> %s\n%w", source, path, err) 48 } 49 if info.IsDir() { 50 rel = fmt.Sprintf("%s/", rel) 51 } 52 53 if rel == "./" { 54 return nil 55 } 56 57 name := info.Name() 58 if info.Mode()&os.ModeSymlink == os.ModeSymlink { 59 name, err = os.Readlink(path) 60 if err != nil { 61 return fmt.Errorf("unable to read link from %s\n%w", info.Name(), err) 62 } 63 } 64 65 h, err := tar.FileInfoHeader(info, name) 66 if err != nil { 67 return fmt.Errorf("unable to create TAR header from %+v\n%w", info, err) 68 } 69 h.Name = rel 70 71 if err := t.WriteHeader(h); err != nil { 72 return fmt.Errorf("unable to write header %+v\n%w", h, err) 73 } 74 75 if !info.Mode().IsRegular() { 76 return nil 77 } 78 79 in, err := os.Open(path) 80 if err != nil { 81 return fmt.Errorf("unable to open %s\n%w", path, err) 82 } 83 defer in.Close() 84 85 if _, err := io.Copy(t, in); err != nil { 86 return fmt.Errorf("unable to copy %s to %s\n%w", path, h.Name, err) 87 } 88 89 return nil 90 }); err != nil { 91 return fmt.Errorf("unable to create tar from %s\n%w", source, err) 92 } 93 94 return nil 95 } 96 97 // CreateTarGz writes a GZIP'd TAR to the destination io.Writer containing the directories and files in the source 98 // folder. 99 func CreateTarGz(destination io.Writer, source string) error { 100 gz := gzip.NewWriter(destination) 101 defer gz.Close() 102 103 return CreateTar(gz, source) 104 } 105 106 // Extract decompresses and extract source files to a destination directory or path. For archives, an arbitrary number of top-level directory 107 // components can be stripped from each path. 108 func Extract(source io.Reader, destination string, stripComponents int) error { 109 buf := &bytes.Buffer{} 110 111 kind, err := filetype.MatchReader(io.TeeReader(source, buf)) 112 if err != nil { 113 return err 114 } 115 116 source = io.MultiReader(buf, source) 117 118 switch kind.MIME.Value { 119 case "application/x-tar": 120 return extractTar(source, destination, stripComponents) 121 case "application/zip": 122 return extractZip(source, destination, stripComponents) 123 case "application/x-bzip2": 124 return Extract(bzip2.NewReader(source), destination, stripComponents) 125 case "application/gzip": 126 gz, err := gzip.NewReader(source) 127 if err != nil { 128 return fmt.Errorf("unable to create GZIP reader\n%w", err) 129 } 130 defer gz.Close() 131 return Extract(gz, destination, stripComponents) 132 case "application/x-xz": 133 xz, err := xz.NewReader(source, 0) 134 if err != nil { 135 return fmt.Errorf("unable to create XZ reader\n%w", err) 136 } 137 return Extract(xz, destination, stripComponents) 138 default: 139 // no archive, can happen with xz/gzip/bz2 if compressed file is not an archive 140 in, err := os.Create(destination) 141 if err != nil { 142 return fmt.Errorf("unable to open %s\n%w", destination, err) 143 } 144 defer in.Close() 145 146 if _, err := io.Copy(in, source); err != nil { 147 return fmt.Errorf("unable to copy to %s\n%w", destination, err) 148 } 149 } 150 151 return nil 152 } 153 154 // ExtractTar extracts source TAR file to a destination directory. An arbitrary number of top-level directory 155 // components can be stripped from each path. 156 // 157 // Deprecated: use Extract instead 158 func ExtractTar(source io.Reader, destination string, stripComponents int) error { 159 return extractTar(source, destination, stripComponents) 160 } 161 162 func extractTar(source io.Reader, destination string, stripComponents int) error { 163 t := tar.NewReader(source) 164 165 for { 166 f, err := t.Next() 167 if err != nil && err == io.EOF { 168 break 169 } else if err != nil { 170 return fmt.Errorf("unable to read TAR file\n%w", err) 171 } 172 173 target := strippedPath(f.Name, destination, stripComponents) 174 if target == "" { 175 continue 176 } 177 178 info := f.FileInfo() 179 if info.IsDir() { 180 if err := os.MkdirAll(target, 0755); err != nil { 181 return fmt.Errorf("unable to make directory %s\n%w", target, err) 182 } 183 } else if info.Mode()&os.ModeSymlink != 0 { 184 if err := writeSymlink(f.Linkname, target); err != nil { 185 return err 186 } 187 } else { 188 if err := writeFile(t, target, info.Mode()); err != nil { 189 return err 190 } 191 } 192 } 193 194 return nil 195 } 196 197 // ExtractTarBz2 extracts source BZIP2'd TAR file to a destination directory. An arbitrary number of top-level 198 // directory components can be stripped from each path. 199 // 200 // Deprecated: use Extract instead 201 func ExtractTarBz2(source io.Reader, destination string, stripComponents int) error { 202 return ExtractTar(bzip2.NewReader(source), destination, stripComponents) 203 } 204 205 // ExtractTarGz extracts source GZIP'd TAR file to a destination directory. An arbitrary number of top-level directory 206 // components can be stripped from each path. 207 // 208 // Deprecated: use Extract instead 209 func ExtractTarGz(source io.Reader, destination string, stripComponents int) error { 210 gz, err := gzip.NewReader(source) 211 if err != nil { 212 return fmt.Errorf("unable to create GZIP reader\n%w", err) 213 } 214 defer gz.Close() 215 216 return ExtractTar(gz, destination, stripComponents) 217 } 218 219 // ExtractTarXz extracts source XZ'd TAR file to a destination directory. An arbitrary number of top-level directory 220 // components can be stripped from each path. 221 // 222 // Deprecated: use Extract instead 223 func ExtractTarXz(source io.Reader, destination string, stripComponents int) error { 224 xz, err := xz.NewReader(source, 0) 225 if err != nil { 226 return fmt.Errorf("unable to create XZ reader\n%w", err) 227 } 228 229 return ExtractTar(xz, destination, stripComponents) 230 } 231 232 // ExtractZip extracts source ZIP file to a destination directory. An arbitrary number of top-level directory 233 // components can be stripped from each path. 234 // 235 // Deprecated: use Extract instead 236 func ExtractZip(source io.Reader, destination string, stripComponents int) error { 237 return extractZip(source, destination, stripComponents) 238 } 239 240 func extractZip(source io.Reader, destination string, stripComponents int) error { 241 buffer, err := os.CreateTemp("", "") 242 if err != nil { 243 return err 244 } 245 defer os.Remove(buffer.Name()) 246 247 size, err := io.Copy(buffer, source) 248 if err != nil { 249 return err 250 } 251 252 z, err := zip.NewReader(buffer, size) 253 if err != nil { 254 return err 255 } 256 257 for _, f := range z.File { 258 target := strippedPath(f.Name, destination, stripComponents) 259 if target == "" { 260 continue 261 } 262 263 if f.FileInfo().IsDir() { 264 if err := os.MkdirAll(target, 0755); err != nil { 265 return err 266 } 267 } else { 268 if err := writeZipEntry(f, target); err != nil { 269 return err 270 } 271 } 272 } 273 274 return nil 275 } 276 277 func strippedPath(source string, destination string, stripComponents int) string { 278 components := strings.Split(source, string(filepath.Separator)) 279 280 if len(components) <= stripComponents { 281 return "" 282 } 283 284 return filepath.Join(append([]string{destination}, components[stripComponents:]...)...) 285 } 286 287 func writeFile(source io.Reader, path string, perm os.FileMode) error { 288 file := filepath.Dir(path) 289 if err := os.MkdirAll(file, 0755); err != nil { 290 return fmt.Errorf("unable to create directory %s\n%w", file, err) 291 } 292 293 out, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, perm) 294 if err != nil { 295 return fmt.Errorf("unable to open file %s\n%w", path, err) 296 } 297 defer out.Close() 298 299 if _, err := io.Copy(out, source); err != nil { 300 return fmt.Errorf("unable to write data to %s\n%w", path, err) 301 } 302 303 return nil 304 } 305 306 func writeZipEntry(file *zip.File, path string) error { 307 in, err := file.Open() 308 if err != nil { 309 return fmt.Errorf("unable to open %s\n%w", file.Name, err) 310 } 311 defer in.Close() 312 313 return writeFile(in, path, file.Mode()) 314 } 315 316 func writeSymlink(oldName string, newName string) error { 317 file := filepath.Dir(newName) 318 if err := os.MkdirAll(file, 0755); err != nil { 319 return fmt.Errorf("unable to create directory %s\n%w", file, err) 320 } 321 322 if err := os.Symlink(oldName, newName); err != nil { 323 return fmt.Errorf("unable to create '%s' as symlink to '%s': %v", newName, oldName, err) 324 } 325 326 return nil 327 }