github.com/containerd/Containerd@v1.4.13/archive/tartest/tar.go (about)

     1  /*
     2     Copyright The containerd 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         http://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 tartest
    18  
    19  import (
    20  	"archive/tar"
    21  	"errors"
    22  	"io"
    23  	"os"
    24  	"time"
    25  )
    26  
    27  // WriterToTar is an type which writes to a tar writer
    28  type WriterToTar interface {
    29  	WriteTo(*tar.Writer) error
    30  }
    31  
    32  type writerToFn func(*tar.Writer) error
    33  
    34  func (w writerToFn) WriteTo(tw *tar.Writer) error {
    35  	return w(tw)
    36  }
    37  
    38  // TarAll creates a WriterToTar which calls all the provided writers
    39  // in the order in which they are provided.
    40  func TarAll(wt ...WriterToTar) WriterToTar {
    41  	return writerToFn(func(tw *tar.Writer) error {
    42  		for _, w := range wt {
    43  			if err := w.WriteTo(tw); err != nil {
    44  				return err
    45  			}
    46  		}
    47  		return nil
    48  	})
    49  }
    50  
    51  // TarFromWriterTo is used to create a tar stream from a tar record
    52  // creator. This can be used to manufacture more specific tar records
    53  // which allow testing specific tar cases which may be encountered
    54  // by the untar process.
    55  func TarFromWriterTo(wt WriterToTar) io.ReadCloser {
    56  	r, w := io.Pipe()
    57  	go func() {
    58  		tw := tar.NewWriter(w)
    59  		if err := wt.WriteTo(tw); err != nil {
    60  			w.CloseWithError(err)
    61  			return
    62  		}
    63  		w.CloseWithError(tw.Close())
    64  	}()
    65  
    66  	return r
    67  }
    68  
    69  // TarContext is used to create tar records
    70  type TarContext struct {
    71  	UID int
    72  	GID int
    73  
    74  	// ModTime sets the modtimes for all files, if nil the current time
    75  	// is used for each file when it was written
    76  	ModTime *time.Time
    77  
    78  	Xattrs map[string]string
    79  }
    80  
    81  func (tc TarContext) newHeader(mode os.FileMode, name, link string, size int64) *tar.Header {
    82  	ti := tarInfo{
    83  		name: name,
    84  		mode: mode,
    85  		size: size,
    86  		modt: tc.ModTime,
    87  		hdr: &tar.Header{
    88  			Uid:    tc.UID,
    89  			Gid:    tc.GID,
    90  			Xattrs: tc.Xattrs,
    91  		},
    92  	}
    93  
    94  	if mode&os.ModeSymlink == 0 && link != "" {
    95  		ti.hdr.Typeflag = tar.TypeLink
    96  		ti.hdr.Linkname = link
    97  	}
    98  
    99  	hdr, err := tar.FileInfoHeader(ti, link)
   100  	if err != nil {
   101  		// Only returns an error on bad input mode
   102  		panic(err)
   103  	}
   104  
   105  	return hdr
   106  }
   107  
   108  type tarInfo struct {
   109  	name string
   110  	mode os.FileMode
   111  	size int64
   112  	modt *time.Time
   113  	hdr  *tar.Header
   114  }
   115  
   116  func (ti tarInfo) Name() string {
   117  	return ti.name
   118  }
   119  
   120  func (ti tarInfo) Size() int64 {
   121  	return ti.size
   122  }
   123  func (ti tarInfo) Mode() os.FileMode {
   124  	return ti.mode
   125  }
   126  
   127  func (ti tarInfo) ModTime() time.Time {
   128  	if ti.modt != nil {
   129  		return *ti.modt
   130  	}
   131  	return time.Now().UTC()
   132  }
   133  
   134  func (ti tarInfo) IsDir() bool {
   135  	return (ti.mode & os.ModeDir) != 0
   136  }
   137  func (ti tarInfo) Sys() interface{} {
   138  	return ti.hdr
   139  }
   140  
   141  // WithUIDGID sets the UID and GID for tar entries
   142  func (tc TarContext) WithUIDGID(uid, gid int) TarContext {
   143  	ntc := tc
   144  	ntc.UID = uid
   145  	ntc.GID = gid
   146  	return ntc
   147  }
   148  
   149  // WithModTime sets the ModTime for tar entries
   150  func (tc TarContext) WithModTime(modtime time.Time) TarContext {
   151  	ntc := tc
   152  	ntc.ModTime = &modtime
   153  	return ntc
   154  }
   155  
   156  // WithXattrs adds these xattrs to all files, merges with any
   157  // previously added xattrs
   158  func (tc TarContext) WithXattrs(xattrs map[string]string) TarContext {
   159  	ntc := tc
   160  	if ntc.Xattrs == nil {
   161  		ntc.Xattrs = map[string]string{}
   162  	}
   163  	for k, v := range xattrs {
   164  		ntc.Xattrs[k] = v
   165  	}
   166  	return ntc
   167  }
   168  
   169  // File returns a regular file tar entry using the provided bytes
   170  func (tc TarContext) File(name string, content []byte, perm os.FileMode) WriterToTar {
   171  	return writerToFn(func(tw *tar.Writer) error {
   172  		return writeHeaderAndContent(tw, tc.newHeader(perm, name, "", int64(len(content))), content)
   173  	})
   174  }
   175  
   176  // Dir returns a directory tar entry
   177  func (tc TarContext) Dir(name string, perm os.FileMode) WriterToTar {
   178  	return writerToFn(func(tw *tar.Writer) error {
   179  		return writeHeaderAndContent(tw, tc.newHeader(perm|os.ModeDir, name, "", 0), nil)
   180  	})
   181  }
   182  
   183  // Symlink returns a symlink tar entry
   184  func (tc TarContext) Symlink(oldname, newname string) WriterToTar {
   185  	return writerToFn(func(tw *tar.Writer) error {
   186  		return writeHeaderAndContent(tw, tc.newHeader(0777|os.ModeSymlink, newname, oldname, 0), nil)
   187  	})
   188  }
   189  
   190  // Link returns a hard link tar entry
   191  func (tc TarContext) Link(oldname, newname string) WriterToTar {
   192  	return writerToFn(func(tw *tar.Writer) error {
   193  		return writeHeaderAndContent(tw, tc.newHeader(0777, newname, oldname, 0), nil)
   194  	})
   195  }
   196  
   197  func writeHeaderAndContent(tw *tar.Writer, h *tar.Header, b []byte) error {
   198  	if h.Size != int64(len(b)) {
   199  		return errors.New("bad content length")
   200  	}
   201  	if err := tw.WriteHeader(h); err != nil {
   202  		return err
   203  	}
   204  	if len(b) > 0 {
   205  		if _, err := tw.Write(b); err != nil {
   206  			return err
   207  		}
   208  	}
   209  	return nil
   210  }