github.com/paketoio/libpak@v1.3.1/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  	"compress/gzip"
    23  	"fmt"
    24  	"io"
    25  	"os"
    26  	"path/filepath"
    27  	"strings"
    28  
    29  	"github.com/xi2/xz"
    30  )
    31  
    32  type Crush struct{}
    33  
    34  // CreateTar writes a TAR to the destination io.Writer containing the directories and files in the source folder.
    35  func (c *Crush) CreateTar(destination io.Writer, source string) error {
    36  	t := tar.NewWriter(destination)
    37  	defer t.Close()
    38  
    39  	if err := filepath.Walk(source, func(path string, info os.FileInfo, err error) error {
    40  		if err != nil {
    41  			return err
    42  		}
    43  
    44  		rel, err := filepath.Rel(source, path)
    45  		if err != nil {
    46  			return fmt.Errorf("uanble to calculate relative path %s -> %s: %w", source, path, err)
    47  		}
    48  		if info.IsDir() {
    49  			rel = fmt.Sprintf("%s/", rel)
    50  		}
    51  
    52  		if rel == "./" {
    53  			return nil
    54  		}
    55  
    56  		h, err := tar.FileInfoHeader(info, info.Name())
    57  		if err != nil {
    58  			return fmt.Errorf("unable to create TAR header from %+v: %w", info, err)
    59  		}
    60  		h.Name = rel
    61  
    62  		if err := t.WriteHeader(h); err != nil {
    63  			return fmt.Errorf("unable to write header %+v: %w", h, err)
    64  		}
    65  
    66  		if info.IsDir() {
    67  			return nil
    68  		}
    69  
    70  		in, err := os.Open(path)
    71  		if err != nil {
    72  			return fmt.Errorf("unable to open %s: %w", path, err)
    73  		}
    74  		defer in.Close()
    75  
    76  		if _, err := io.Copy(t, in); err != nil {
    77  			return fmt.Errorf("unable to copy %s to %s: %w", path, h.Name, err)
    78  		}
    79  
    80  		return nil
    81  	}); err != nil {
    82  		return fmt.Errorf("unable to create tar from %s: %w", source, err)
    83  	}
    84  
    85  	return nil
    86  }
    87  
    88  // CreateTarGz writes a GZIP'd TAR to the destination io.Writer containing the directories and files in the source
    89  // folder.
    90  func (c *Crush) CreateTarGz(destination io.Writer, source string) error {
    91  	gz := gzip.NewWriter(destination)
    92  	defer gz.Close()
    93  
    94  	return c.CreateTar(gz, source)
    95  }
    96  
    97  // ExtractTar extracts source TAR file to a destination directory.  An arbitrary number of top-level directory
    98  // components can be stripped from each path.
    99  func (c *Crush) ExtractTar(source io.Reader, destination string, stripComponents int) error {
   100  	t := tar.NewReader(source)
   101  
   102  	for {
   103  		f, err := t.Next()
   104  		if err != nil && err == io.EOF {
   105  			break
   106  		} else if err != nil {
   107  			return fmt.Errorf("unable to read TAR file: %w", err)
   108  		}
   109  
   110  		target := c.strippedPath(f.Name, destination, stripComponents)
   111  		if target == "" {
   112  			continue
   113  		}
   114  
   115  		info := f.FileInfo()
   116  		if info.IsDir() {
   117  			if err := os.MkdirAll(target, 0755); err != nil {
   118  				return fmt.Errorf("unable to make directory %s: %w", target, err)
   119  			}
   120  		} else if info.Mode()&os.ModeSymlink != 0 {
   121  			if err := c.writeSymlink(f.Linkname, target); err != nil {
   122  				return err
   123  			}
   124  		} else {
   125  			if err := c.writeFile(t, target, info.Mode()); err != nil {
   126  				return err
   127  			}
   128  		}
   129  	}
   130  
   131  	return nil
   132  }
   133  
   134  // ExtractTarGz extracts source GZIP'd TAR file to a destination directory.  An arbitrary number of top-level directory
   135  // components can be stripped from each path.
   136  func (c *Crush) ExtractTarGz(source io.Reader, destination string, stripComponents int) error {
   137  	gz, err := gzip.NewReader(source)
   138  	if err != nil {
   139  		return fmt.Errorf("unable to create GZIP reader: %w", err)
   140  	}
   141  	defer gz.Close()
   142  
   143  	return c.ExtractTar(gz, destination, stripComponents)
   144  }
   145  
   146  // ExtractTarXz extracts source XZ'd TAR file to a destination directory.  An arbitrary number of top-level directory
   147  // components can be stripped from each path.
   148  func (c *Crush) ExtractTarXz(source io.Reader, destination string, stripComponents int) error {
   149  	xz, err := xz.NewReader(source, 0)
   150  	if err != nil {
   151  		return fmt.Errorf("unable to create XZ reader: %w", err)
   152  	}
   153  
   154  	return c.ExtractTar(xz, destination, stripComponents)
   155  }
   156  
   157  // ExtractZip extracts source ZIP file to a destination directory.  An arbitrary number of top-level directory
   158  // components can be stripped from each path.
   159  func (c *Crush) ExtractZip(source *os.File, destination string, stripComponents int) error {
   160  	stat, err := source.Stat()
   161  	if err != nil {
   162  		return fmt.Errorf("unable to stat %s: %w", source.Name(), err)
   163  	}
   164  
   165  	z, err := zip.NewReader(source, stat.Size())
   166  	if err != nil {
   167  		return err
   168  	}
   169  
   170  	for _, f := range z.File {
   171  		target := c.strippedPath(f.Name, destination, stripComponents)
   172  		if target == "" {
   173  			continue
   174  		}
   175  
   176  		if f.FileInfo().IsDir() {
   177  			if err := os.MkdirAll(target, 0755); err != nil {
   178  				return err
   179  			}
   180  		} else {
   181  			if err := c.writeZipEntry(f, target); err != nil {
   182  				return err
   183  			}
   184  		}
   185  	}
   186  
   187  	return nil
   188  }
   189  
   190  func (Crush) strippedPath(source string, destination string, stripComponents int) string {
   191  	components := strings.Split(source, string(filepath.Separator))
   192  
   193  	if len(components) <= stripComponents {
   194  		return ""
   195  	}
   196  
   197  	return filepath.Join(append([]string{destination}, components[stripComponents:]...)...)
   198  }
   199  
   200  func (Crush) writeFile(source io.Reader, path string, perm os.FileMode) error {
   201  	file := filepath.Dir(path)
   202  	if err := os.MkdirAll(file, 0755); err != nil {
   203  		return fmt.Errorf("unable to create directory %s: %w", file, err)
   204  	}
   205  
   206  	out, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, perm)
   207  	if err != nil {
   208  		return fmt.Errorf("unable to open file %s: %w", path, err)
   209  	}
   210  	defer out.Close()
   211  
   212  	if _, err := io.Copy(out, source); err != nil {
   213  		return fmt.Errorf("unable to write data to %s: %w", path, err)
   214  	}
   215  
   216  	return nil
   217  }
   218  
   219  func (c Crush) writeZipEntry(file *zip.File, path string) error {
   220  	in, err := file.Open()
   221  	if err != nil {
   222  		return fmt.Errorf("unable to open %s: %w", file.Name, err)
   223  	}
   224  	defer in.Close()
   225  
   226  	return c.writeFile(in, path, file.Mode())
   227  }
   228  
   229  func (Crush) writeSymlink(oldName string, newName string) error {
   230  	file := filepath.Dir(newName)
   231  	if err := os.MkdirAll(file, 0755); err != nil {
   232  		return fmt.Errorf("unable to create directory %s: %w", file, err)
   233  	}
   234  
   235  	if err := os.Symlink(oldName, newName); err != nil {
   236  		return fmt.Errorf("unable to create '%s' as symlink to '%s': %v", newName, oldName, err)
   237  	}
   238  
   239  	return nil
   240  }