211 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			211 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
/*
 | 
						|
   Copyright The containerd Authors.
 | 
						|
 | 
						|
   Licensed under the Apache License, Version 2.0 (the "License");
 | 
						|
   you may not use this file except in compliance with the License.
 | 
						|
   You may obtain a copy of the License at
 | 
						|
 | 
						|
       http://www.apache.org/licenses/LICENSE-2.0
 | 
						|
 | 
						|
   Unless required by applicable law or agreed to in writing, software
 | 
						|
   distributed under the License is distributed on an "AS IS" BASIS,
 | 
						|
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
						|
   See the License for the specific language governing permissions and
 | 
						|
   limitations under the License.
 | 
						|
*/
 | 
						|
 | 
						|
package tartest
 | 
						|
 | 
						|
import (
 | 
						|
	"archive/tar"
 | 
						|
	"errors"
 | 
						|
	"io"
 | 
						|
	"os"
 | 
						|
	"time"
 | 
						|
)
 | 
						|
 | 
						|
// WriterToTar is an type which writes to a tar writer
 | 
						|
type WriterToTar interface {
 | 
						|
	WriteTo(*tar.Writer) error
 | 
						|
}
 | 
						|
 | 
						|
type writerToFn func(*tar.Writer) error
 | 
						|
 | 
						|
func (w writerToFn) WriteTo(tw *tar.Writer) error {
 | 
						|
	return w(tw)
 | 
						|
}
 | 
						|
 | 
						|
// TarAll creates a WriterToTar which calls all the provided writers
 | 
						|
// in the order in which they are provided.
 | 
						|
func TarAll(wt ...WriterToTar) WriterToTar {
 | 
						|
	return writerToFn(func(tw *tar.Writer) error {
 | 
						|
		for _, w := range wt {
 | 
						|
			if err := w.WriteTo(tw); err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
		}
 | 
						|
		return nil
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
// TarFromWriterTo is used to create a tar stream from a tar record
 | 
						|
// creator. This can be used to manufacture more specific tar records
 | 
						|
// which allow testing specific tar cases which may be encountered
 | 
						|
// by the untar process.
 | 
						|
func TarFromWriterTo(wt WriterToTar) io.ReadCloser {
 | 
						|
	r, w := io.Pipe()
 | 
						|
	go func() {
 | 
						|
		tw := tar.NewWriter(w)
 | 
						|
		if err := wt.WriteTo(tw); err != nil {
 | 
						|
			w.CloseWithError(err)
 | 
						|
			return
 | 
						|
		}
 | 
						|
		w.CloseWithError(tw.Close())
 | 
						|
	}()
 | 
						|
 | 
						|
	return r
 | 
						|
}
 | 
						|
 | 
						|
// TarContext is used to create tar records
 | 
						|
type TarContext struct {
 | 
						|
	UID int
 | 
						|
	GID int
 | 
						|
 | 
						|
	// ModTime sets the modtimes for all files, if nil the current time
 | 
						|
	// is used for each file when it was written
 | 
						|
	ModTime *time.Time
 | 
						|
 | 
						|
	Xattrs map[string]string
 | 
						|
}
 | 
						|
 | 
						|
func (tc TarContext) newHeader(mode os.FileMode, name, link string, size int64) *tar.Header {
 | 
						|
	ti := tarInfo{
 | 
						|
		name: name,
 | 
						|
		mode: mode,
 | 
						|
		size: size,
 | 
						|
		modt: tc.ModTime,
 | 
						|
		hdr: &tar.Header{
 | 
						|
			Uid:    tc.UID,
 | 
						|
			Gid:    tc.GID,
 | 
						|
			Xattrs: tc.Xattrs,
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	if mode&os.ModeSymlink == 0 && link != "" {
 | 
						|
		ti.hdr.Typeflag = tar.TypeLink
 | 
						|
		ti.hdr.Linkname = link
 | 
						|
	}
 | 
						|
 | 
						|
	hdr, err := tar.FileInfoHeader(ti, link)
 | 
						|
	if err != nil {
 | 
						|
		// Only returns an error on bad input mode
 | 
						|
		panic(err)
 | 
						|
	}
 | 
						|
 | 
						|
	return hdr
 | 
						|
}
 | 
						|
 | 
						|
type tarInfo struct {
 | 
						|
	name string
 | 
						|
	mode os.FileMode
 | 
						|
	size int64
 | 
						|
	modt *time.Time
 | 
						|
	hdr  *tar.Header
 | 
						|
}
 | 
						|
 | 
						|
func (ti tarInfo) Name() string {
 | 
						|
	return ti.name
 | 
						|
}
 | 
						|
 | 
						|
func (ti tarInfo) Size() int64 {
 | 
						|
	return ti.size
 | 
						|
}
 | 
						|
func (ti tarInfo) Mode() os.FileMode {
 | 
						|
	return ti.mode
 | 
						|
}
 | 
						|
 | 
						|
func (ti tarInfo) ModTime() time.Time {
 | 
						|
	if ti.modt != nil {
 | 
						|
		return *ti.modt
 | 
						|
	}
 | 
						|
	return time.Now().UTC()
 | 
						|
}
 | 
						|
 | 
						|
func (ti tarInfo) IsDir() bool {
 | 
						|
	return (ti.mode & os.ModeDir) != 0
 | 
						|
}
 | 
						|
func (ti tarInfo) Sys() interface{} {
 | 
						|
	return ti.hdr
 | 
						|
}
 | 
						|
 | 
						|
// WithUIDGID sets the UID and GID for tar entries
 | 
						|
func (tc TarContext) WithUIDGID(uid, gid int) TarContext {
 | 
						|
	ntc := tc
 | 
						|
	ntc.UID = uid
 | 
						|
	ntc.GID = gid
 | 
						|
	return ntc
 | 
						|
}
 | 
						|
 | 
						|
// WithModTime sets the ModTime for tar entries
 | 
						|
func (tc TarContext) WithModTime(modtime time.Time) TarContext {
 | 
						|
	ntc := tc
 | 
						|
	ntc.ModTime = &modtime
 | 
						|
	return ntc
 | 
						|
}
 | 
						|
 | 
						|
// WithXattrs adds these xattrs to all files, merges with any
 | 
						|
// previously added xattrs
 | 
						|
func (tc TarContext) WithXattrs(xattrs map[string]string) TarContext {
 | 
						|
	ntc := tc
 | 
						|
	if ntc.Xattrs == nil {
 | 
						|
		ntc.Xattrs = map[string]string{}
 | 
						|
	}
 | 
						|
	for k, v := range xattrs {
 | 
						|
		ntc.Xattrs[k] = v
 | 
						|
	}
 | 
						|
	return ntc
 | 
						|
}
 | 
						|
 | 
						|
// File returns a regular file tar entry using the provided bytes
 | 
						|
func (tc TarContext) File(name string, content []byte, perm os.FileMode) WriterToTar {
 | 
						|
	return writerToFn(func(tw *tar.Writer) error {
 | 
						|
		return writeHeaderAndContent(tw, tc.newHeader(perm, name, "", int64(len(content))), content)
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
// Dir returns a directory tar entry
 | 
						|
func (tc TarContext) Dir(name string, perm os.FileMode) WriterToTar {
 | 
						|
	return writerToFn(func(tw *tar.Writer) error {
 | 
						|
		return writeHeaderAndContent(tw, tc.newHeader(perm|os.ModeDir, name, "", 0), nil)
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
// Symlink returns a symlink tar entry
 | 
						|
func (tc TarContext) Symlink(oldname, newname string) WriterToTar {
 | 
						|
	return writerToFn(func(tw *tar.Writer) error {
 | 
						|
		return writeHeaderAndContent(tw, tc.newHeader(0777|os.ModeSymlink, newname, oldname, 0), nil)
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
// Link returns a hard link tar entry
 | 
						|
func (tc TarContext) Link(oldname, newname string) WriterToTar {
 | 
						|
	return writerToFn(func(tw *tar.Writer) error {
 | 
						|
		return writeHeaderAndContent(tw, tc.newHeader(0777, newname, oldname, 0), nil)
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
func writeHeaderAndContent(tw *tar.Writer, h *tar.Header, b []byte) error {
 | 
						|
	if h.Size != int64(len(b)) {
 | 
						|
		return errors.New("bad content length")
 | 
						|
	}
 | 
						|
	if err := tw.WriteHeader(h); err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	if len(b) > 0 {
 | 
						|
		if _, err := tw.Write(b); err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 |