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

     1  package writer
     2  
     3  import (
     4  	"archive/tar"
     5  	"io"
     6  	"os"
     7  	"path/filepath"
     8  	"syscall"
     9  	"time"
    10  
    11  	"github.com/mgoltzsche/ctnr/pkg/fs"
    12  	"github.com/pkg/errors"
    13  )
    14  
    15  var _ fs.Writer = &TarWriter{}
    16  
    17  // A mapping file system writer that secures root directory boundaries.
    18  // Derived from umoci's tar_extract.go to allow separate source/dest interfaces
    19  // and filter archive contents on extraction
    20  type TarWriter struct {
    21  	writer  *tar.Writer
    22  	written map[string]*fs.FileAttrs
    23  }
    24  
    25  func NewTarWriter(writer io.Writer) (w *TarWriter) {
    26  	return &TarWriter{tar.NewWriter(writer), map[string]*fs.FileAttrs{}}
    27  }
    28  
    29  func (w *TarWriter) Close() error {
    30  	return errors.Wrap(w.writer.Close(), "close tar writer")
    31  }
    32  
    33  func (w *TarWriter) Parent() error                                        { return nil }
    34  func (w *TarWriter) LowerNode(path, name string, a *fs.NodeAttrs) error   { return nil }
    35  func (w *TarWriter) LowerLink(path, target string, a *fs.NodeAttrs) error { return nil }
    36  
    37  func (w *TarWriter) Lazy(path, name string, src fs.LazySource, written map[fs.Source]string) (err error) {
    38  	return errors.Errorf("refused to write lazy source %s into tar writer directly at %s since resulting tar could contain overridden entries. lazy sources must be resolved first.", src, path)
    39  }
    40  
    41  func (w *TarWriter) File(path string, src fs.FileSource) (r fs.Source, err error) {
    42  	a := src.Attrs()
    43  	if path, err = normalize(path); err != nil {
    44  		return
    45  	}
    46  	if err = w.writeTarHeader(path, a.FileAttrs); err != nil {
    47  		return
    48  	}
    49  
    50  	if a.NodeType != fs.TypeFile {
    51  		return src, nil
    52  	}
    53  
    54  	// Copy file
    55  	f, err := src.Reader()
    56  	if err != nil {
    57  		return
    58  	}
    59  	defer func() {
    60  		if e := f.Close(); e != nil && err == nil {
    61  			err = errors.Wrap(e, "write tar")
    62  		}
    63  	}()
    64  	n, err := io.Copy(w.writer, f)
    65  	if err != nil {
    66  		return nil, errors.Wrap(err, "write tar: file entry")
    67  	}
    68  	if n != a.Size {
    69  		return nil, errors.Wrap(io.ErrShortWrite, "write tar: file entry")
    70  	}
    71  	return src, nil
    72  }
    73  
    74  func (w *TarWriter) writeTarHeader(path string, a fs.FileAttrs) (err error) {
    75  	hdr, err := w.toTarHeader(path, a)
    76  	if err != nil {
    77  		return
    78  	}
    79  	err = w.writer.WriteHeader(hdr)
    80  	return errors.Wrapf(err, "write tar header for %q", path)
    81  }
    82  
    83  func (w *TarWriter) toTarHeader(path string, a fs.FileAttrs) (hdr *tar.Header, err error) {
    84  	a.Mtime = time.Unix(a.Mtime.Unix(), 0) // use floor(mtime) to preserve mtime which otherwise is not guaranteed due to rounding to seconds within tar
    85  	hdr, err = tar.FileInfoHeader(fs.NewFileInfo(path, &a), a.Symlink)
    86  	if err != nil {
    87  		return nil, errors.Wrapf(err, "to tar header: %s", path)
    88  	}
    89  	hdr.Uid = int(a.Uid)
    90  	hdr.Gid = int(a.Gid)
    91  	hdr.AccessTime = a.Atime
    92  	hdr.Xattrs = a.Xattrs
    93  	w.addWritten(path, &a)
    94  	return
    95  }
    96  
    97  // Taken from umoci
    98  func normalize(path string) (string, error) {
    99  	path = filepath.Clean(string(os.PathSeparator) + path)
   100  	path, _ = filepath.Rel(string(os.PathSeparator), path)
   101  	path = filepath.Clean(path)
   102  	if !isValidPath(path) {
   103  		return "", errors.Errorf("tar writer: path outside tar root: %s", path)
   104  	}
   105  	return path, nil
   106  }
   107  
   108  func isValidPath(path string) bool {
   109  	prfx := string(os.PathSeparator) + "___"
   110  	return filepath.HasPrefix(filepath.Join(prfx, path), prfx)
   111  }
   112  
   113  func (w *TarWriter) addWritten(path string, a *fs.FileAttrs) {
   114  	w.written[path] = a
   115  }
   116  
   117  func (w *TarWriter) Link(path, target string) (err error) {
   118  	if path, err = normalize(path); err != nil {
   119  		return
   120  	}
   121  	if !filepath.IsAbs(target) {
   122  		target = filepath.Join(filepath.Dir(path), target)
   123  	}
   124  	if target, err = normalize(target); err != nil {
   125  		return errors.Wrap(err, "link")
   126  	}
   127  
   128  	a := w.written[target]
   129  	if a == nil {
   130  		return errors.Errorf("write tar: link entry %s: target %s does not exist", path, target)
   131  	}
   132  	hdr, err := w.toTarHeader(path, *a)
   133  	if err != nil {
   134  		return
   135  	}
   136  	hdr.Typeflag = tar.TypeLink
   137  	hdr.Linkname = target
   138  	hdr.Size = 0
   139  
   140  	err = w.writer.WriteHeader(hdr)
   141  	return errors.Wrap(err, "tar writer: write link")
   142  }
   143  
   144  func (w *TarWriter) Symlink(path string, a fs.FileAttrs) (err error) {
   145  	if path, err = normalize(path); err != nil {
   146  		return
   147  	}
   148  
   149  	a.Mode |= os.ModeSymlink | 0777
   150  	a.Symlink, err = normalizeLinkDest(path, a.Symlink)
   151  	if err != nil {
   152  		return
   153  	}
   154  	return w.writeTarHeader(path, a)
   155  }
   156  
   157  func (w *TarWriter) Fifo(path string, a fs.DeviceAttrs) (err error) {
   158  	a.Mode |= syscall.S_IFIFO
   159  	return w.device(path, &a)
   160  }
   161  
   162  func (w *TarWriter) device(path string, a *fs.DeviceAttrs) (err error) {
   163  	if path, err = normalize(path); err != nil {
   164  		return
   165  	}
   166  	hdr, err := w.toTarHeader(path, a.FileAttrs)
   167  	if err != nil {
   168  		return
   169  	}
   170  	hdr.Size = 0
   171  	hdr.Devmajor = a.Devmajor
   172  	hdr.Devminor = a.Devminor
   173  	err = w.writer.WriteHeader(hdr)
   174  	return errors.Wrap(err, "tar writer: write device")
   175  }
   176  
   177  func (w *TarWriter) Device(path string, a fs.DeviceAttrs) (err error) {
   178  	return w.device(path, &a)
   179  }
   180  
   181  func (w *TarWriter) Mkdir(path string) (err error) {
   182  	if path, err = normalize(path); err != nil {
   183  		return
   184  	}
   185  	return w.writeTarHeader(path+string(os.PathSeparator), fs.FileAttrs{Mode: os.ModeDir | 0755})
   186  }
   187  
   188  func (w *TarWriter) Dir(path, base string, a fs.FileAttrs) (err error) {
   189  	if path, err = normalize(path); err != nil {
   190  		return
   191  	}
   192  	a.Mode |= os.ModeDir
   193  	return w.writeTarHeader(path+string(os.PathSeparator), a)
   194  }
   195  
   196  func (w *TarWriter) Remove(path string) (err error) {
   197  	if path, err = normalize(path); err != nil {
   198  		return
   199  	}
   200  	delete(w.written, path)
   201  	dir, file := filepath.Split(path)
   202  	file = fs.WhiteoutPrefix + file
   203  	now := time.Now()
   204  	// Using current time for header values which leads to unreproducable layer
   205  	// TODO: maybe change to fixed time instead of now()
   206  	return w.writeTarHeader(filepath.Join(dir, file), fs.FileAttrs{FileTimes: fs.FileTimes{Atime: now, Mtime: now}})
   207  }