github.com/mgoltzsche/ctnr@v0.7.1-alpha/pkg/fs/source/sourcetar.go (about)

     1  package source
     2  
     3  import (
     4  	"archive/tar"
     5  	"io"
     6  	"os"
     7  	"path/filepath"
     8  	"strings"
     9  
    10  	"github.com/mgoltzsche/ctnr/pkg/fs"
    11  	"github.com/mgoltzsche/ctnr/pkg/idutils"
    12  	"github.com/openSUSE/umoci/oci/layer"
    13  	"github.com/openSUSE/umoci/pkg/system"
    14  	"github.com/opencontainers/go-digest"
    15  	"github.com/pkg/errors"
    16  )
    17  
    18  var (
    19  	dirAttrs           = fs.FileAttrs{Mode: os.ModeDir | 0755}
    20  	_        fs.Source = &sourceTar{}
    21  )
    22  
    23  type sourceTar struct {
    24  	file string
    25  	hash string
    26  }
    27  
    28  func NewSourceTar(file string) *sourceTar {
    29  	return &sourceTar{file, ""}
    30  }
    31  
    32  func (s *sourceTar) Attrs() fs.NodeInfo {
    33  	return fs.NodeInfo{fs.TypeOverlay, fs.FileAttrs{Mode: os.ModeDir | 0755}}
    34  }
    35  
    36  func (s *sourceTar) HashIfAvailable() string {
    37  	return s.hash
    38  }
    39  
    40  func (s *sourceTar) Hash() (string, error) {
    41  	if s.hash == "" {
    42  		f, err := os.Open(s.file)
    43  		if err != nil {
    44  			return "", errors.Errorf("hash: %s", err)
    45  		}
    46  		defer f.Close()
    47  		d, err := digest.FromReader(f)
    48  		if err != nil {
    49  			return "", errors.Errorf("hash %s: %s", s.file, err)
    50  		}
    51  		s.hash = d.String()
    52  	}
    53  	return s.hash, nil
    54  }
    55  
    56  func (s *sourceTar) DeriveAttrs() (a fs.DerivedAttrs, err error) {
    57  	a.Hash, err = s.Hash()
    58  	return
    59  }
    60  
    61  func (s *sourceTar) Write(dest, name string, w fs.Writer, written map[fs.Source]string) error {
    62  	return w.Lazy(dest, name, s, written)
    63  }
    64  
    65  func (s *sourceTar) Expand(dest string, w fs.Writer, written map[fs.Source]string) (err error) {
    66  	f, err := os.Open(s.file)
    67  	if err != nil {
    68  		return errors.Wrap(err, "extract tar")
    69  	}
    70  	defer f.Close()
    71  	if err = unpackTar(f, dest, w); err != nil {
    72  		return errors.Wrap(err, "extract tar")
    73  	}
    74  	return
    75  }
    76  
    77  func unpackTar(r io.Reader, dest string, w fs.Writer) error {
    78  	tr := tar.NewReader(r)
    79  	for {
    80  		hdr, err := tr.Next()
    81  		if err == io.EOF {
    82  			break
    83  		}
    84  		if err != nil {
    85  			return errors.Wrap(err, "read next tar entry")
    86  		}
    87  		links := map[string]string{}
    88  		if err = unpackTarEntry(hdr, tr, dest, w, links); err != nil {
    89  			return errors.Wrapf(err, "unpack tar entry: %s", hdr.Name)
    90  		}
    91  		for path, target := range links {
    92  			if err = w.Link(path, target); err != nil {
    93  				return errors.Wrapf(err, "unpack tar entry link: %s", hdr.Name)
    94  			}
    95  		}
    96  	}
    97  	return nil
    98  }
    99  
   100  // Derived from umoci's tar_extract.go to allow separate source/dest interfaces
   101  // and filter archive contents on extraction
   102  func unpackTarEntry(hdr *tar.Header, r io.Reader, dest string, w fs.Writer, links map[string]string) (err error) {
   103  	path := layer.CleanPath(filepath.Join(dest, hdr.Name))
   104  	dir, file := filepath.Split(path)
   105  
   106  	// Remove file if whiteout
   107  	if strings.HasPrefix(file, fs.WhiteoutPrefix) {
   108  		file = strings.TrimPrefix(file, fs.WhiteoutPrefix)
   109  		return w.Remove(filepath.Join(dir, file))
   110  	}
   111  
   112  	// Convert attributes
   113  	fi := hdr.FileInfo()
   114  	a := fs.FileAttrs{
   115  		Mode:    fi.Mode() | os.FileMode(system.Tarmode(hdr.Typeflag)),
   116  		UserIds: idutils.UserIds{uint(hdr.Uid), uint(hdr.Gid)},
   117  		FileTimes: fs.FileTimes{
   118  			Atime: hdr.AccessTime,
   119  			Mtime: hdr.ModTime,
   120  		},
   121  		Xattrs: hdr.Xattrs,
   122  	}
   123  
   124  	// Write file
   125  	switch hdr.Typeflag {
   126  	// regular file
   127  	case tar.TypeReg, tar.TypeRegA:
   128  		delete(links, path)
   129  		a.Size = hdr.Size
   130  		_, err = w.File(path, NewSourceFile(fs.NewReadable(r), a))
   131  	// directory
   132  	case tar.TypeDir:
   133  		delete(links, path)
   134  		err = w.Dir(path, filepath.Base(path), a)
   135  	// hard link
   136  	case tar.TypeLink:
   137  		links[path] = filepath.Join(string(filepath.Separator)+dest, hdr.Linkname)
   138  	// symbolic link
   139  	case tar.TypeSymlink:
   140  		a.Symlink = hdr.Linkname
   141  		if filepath.IsAbs(a.Symlink) {
   142  			a.Symlink = filepath.Join(string(filepath.Separator)+dest, a.Symlink)
   143  		}
   144  		delete(links, path)
   145  		err = w.Symlink(path, a)
   146  	// character device node, block device node
   147  	case tar.TypeChar, tar.TypeBlock:
   148  		delete(links, path)
   149  		err = w.Device(path, fs.DeviceAttrs{a, hdr.Devmajor, hdr.Devminor})
   150  	// fifo node
   151  	case tar.TypeFifo:
   152  		delete(links, path)
   153  		err = w.Fifo(path, fs.DeviceAttrs{a, hdr.Devmajor, hdr.Devminor})
   154  	default:
   155  		err = errors.Errorf("unpack entry: %s: unknown typeflag '\\x%x'", hdr.Name, hdr.Typeflag)
   156  	}
   157  	return
   158  }