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
|
|
}
|