github.com/u-root/u-root@v7.0.1-0.20200915234505-ad7babab0a8e+incompatible/pkg/tarutil/tar.go (about)

     1  // Copyright 2019 the u-root Authors. All rights reserved
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package tarutil
     6  
     7  import (
     8  	"archive/tar"
     9  	"fmt"
    10  	"io"
    11  	"log"
    12  	"os"
    13  	"path/filepath"
    14  	"strings"
    15  )
    16  
    17  // passesFilters returns true if the given file passes all filters, false otherwise.
    18  func passesFilters(hdr *tar.Header, filters []Filter) bool {
    19  	for _, filter := range filters {
    20  		if !filter(hdr) {
    21  			return false
    22  		}
    23  	}
    24  	return true
    25  }
    26  
    27  // applyToArchive applies function f to all files in the given archive
    28  func applyToArchive(
    29  	tarFile io.Reader, f func(tr *tar.Reader, hdr *tar.Header) error) error {
    30  	tr := tar.NewReader(tarFile)
    31  	for {
    32  		hdr, err := tr.Next()
    33  		if err == io.EOF {
    34  			break
    35  		}
    36  		if err != nil {
    37  			return err
    38  		}
    39  		if err := f(tr, hdr); err != nil {
    40  			return err
    41  		}
    42  	}
    43  	return nil
    44  }
    45  
    46  // ListArchive lists the contents of the given tar archive.
    47  func ListArchive(tarFile io.Reader) error {
    48  	return applyToArchive(tarFile, func(tr *tar.Reader, hdr *tar.Header) error {
    49  		fmt.Println(hdr.Name)
    50  		return nil
    51  	})
    52  }
    53  
    54  // ExtractDir extracts all the contents of the tar file to the given directory.
    55  func ExtractDir(tarFile io.Reader, dir string) error {
    56  	return ExtractDirFilter(tarFile, dir, nil)
    57  }
    58  
    59  // ExtractDirFilter extracts a tar file with the given filter.
    60  func ExtractDirFilter(tarFile io.Reader, dir string, filters []Filter) error {
    61  	fi, err := os.Stat(dir)
    62  	if os.IsNotExist(err) {
    63  		if err := os.Mkdir(dir, os.ModePerm); err != nil {
    64  			return fmt.Errorf("could not create directory %s: %v", dir, err)
    65  		}
    66  	} else if err != nil || !fi.IsDir() {
    67  		return fmt.Errorf("could not stat directory %s: %v", dir, err)
    68  	}
    69  
    70  	return applyToArchive(tarFile, func(tr *tar.Reader, hdr *tar.Header) error {
    71  		if !passesFilters(hdr, filters) {
    72  			return nil
    73  		}
    74  		return createFileInRoot(hdr, tr, dir)
    75  	})
    76  }
    77  
    78  // CreateTar creates a new tar file with all the contents of a directory.
    79  func CreateTar(tarFile io.Writer, files []string) error {
    80  	return CreateTarFilter(tarFile, files, nil)
    81  }
    82  
    83  // CreateTarFilter creates a new tar file of the given files, with the given filter.
    84  func CreateTarFilter(tarFile io.Writer, files []string, filters []Filter) error {
    85  	tw := tar.NewWriter(tarFile)
    86  	for _, file := range files {
    87  		err := filepath.Walk(file, func(path string, info os.FileInfo, err error) error {
    88  			if err != nil {
    89  				return err
    90  			}
    91  			symlink := ""
    92  			if info.Mode()&os.ModeSymlink != 0 {
    93  				// TODO: symlinks
    94  				return fmt.Errorf("symlinks not yet supported: %q", path)
    95  			}
    96  			hdr, err := tar.FileInfoHeader(info, symlink)
    97  			if err != nil {
    98  				return err
    99  			}
   100  			hdr.Name = path
   101  			if !passesFilters(hdr, filters) {
   102  				return nil
   103  			}
   104  			if err := tw.WriteHeader(hdr); err != nil {
   105  				return err
   106  			}
   107  			switch hdr.Typeflag {
   108  			case tar.TypeLink, tar.TypeSymlink, tar.TypeChar, tar.TypeBlock, tar.TypeDir, tar.TypeFifo:
   109  			default:
   110  				f, err := os.Open(path)
   111  				if err != nil {
   112  					return err
   113  				}
   114  				if _, err := io.Copy(tw, f); err != nil {
   115  					f.Close()
   116  					return err
   117  				}
   118  				f.Close()
   119  			}
   120  			return nil
   121  		})
   122  		if err != nil {
   123  			return err
   124  		}
   125  	}
   126  	if err := tw.Close(); err != nil {
   127  		return err
   128  	}
   129  	return nil
   130  }
   131  
   132  func createFileInRoot(hdr *tar.Header, r io.Reader, rootDir string) error {
   133  	fi := hdr.FileInfo()
   134  	path := filepath.Clean(filepath.Join(rootDir, hdr.Name))
   135  	if !strings.HasPrefix(path, filepath.Clean(rootDir)) {
   136  		return fmt.Errorf("file outside root directory: %q", path)
   137  	}
   138  
   139  	switch fi.Mode() & os.ModeType {
   140  	case os.ModeSymlink:
   141  		// TODO: support symlinks
   142  		return fmt.Errorf("symlinks not yet supported: %q", path)
   143  
   144  	case os.FileMode(0):
   145  		f, err := os.Create(path)
   146  		if err != nil {
   147  			return err
   148  		}
   149  		if _, err := io.Copy(f, r); err != nil {
   150  			f.Close()
   151  			return err
   152  		}
   153  		if err := f.Close(); err != nil {
   154  			return err
   155  		}
   156  
   157  	case os.ModeDir:
   158  		if err := os.MkdirAll(path, fi.Mode()&os.ModePerm); err != nil {
   159  			return err
   160  		}
   161  
   162  	case os.ModeDevice:
   163  		// TODO: support block device
   164  		return fmt.Errorf("block device not yet supported: %q", path)
   165  
   166  	case os.ModeCharDevice:
   167  		// TODO: support char device
   168  		return fmt.Errorf("char device not yet supported: %q", path)
   169  
   170  	default:
   171  		return fmt.Errorf("%q: Unknown type %#o", path, fi.Mode()&os.ModeType)
   172  	}
   173  
   174  	if err := os.Chmod(path, fi.Mode()&os.ModePerm); err != nil {
   175  		return fmt.Errorf("error setting mode %#o on %q: %v",
   176  			fi.Mode()&os.ModePerm, path, err)
   177  	}
   178  	// TODO: also set ownership, etc...
   179  	return nil
   180  }
   181  
   182  // Filter is applied to each file while creating or extracting a tar archive.
   183  // The filter can modify the tar.Header struct. If the filter returns false,
   184  // the file is omitted.
   185  type Filter func(hdr *tar.Header) bool
   186  
   187  // NoFilter does not filter or modify any files.
   188  func NoFilter(hdr *tar.Header) bool {
   189  	return true
   190  }
   191  
   192  // VerboseFilter prints the name of every file.
   193  func VerboseFilter(hdr *tar.Header) bool {
   194  	fmt.Println(hdr.Name)
   195  	return true
   196  }
   197  
   198  // VerboseLogFilter logs the name of every file.
   199  func VerboseLogFilter(hdr *tar.Header) bool {
   200  	log.Println(hdr.Name)
   201  	return true
   202  }
   203  
   204  // SafeFilter filters out all files which are not regular and not directories.
   205  // It also sets appropriate permissions.
   206  func SafeFilter(hdr *tar.Header) bool {
   207  	if hdr.Typeflag == tar.TypeDir {
   208  		hdr.Mode = 0770
   209  		return true
   210  	}
   211  	if hdr.Typeflag == tar.TypeReg {
   212  		hdr.Mode = 0660
   213  		return true
   214  	}
   215  	return false
   216  }