blob: a95004ff4e36372f435bc8848fddd3c815143865 [file] [edit]
//go:build windows
package windows
import (
"archive/tar"
"bytes"
"io"
"testing"
"github.com/Microsoft/go-winio"
"golang.org/x/sys/windows"
)
// fakeEntry describes one file returned by fakeLayerReader.
type fakeEntry struct {
name string
data []byte
nlinks uint32
fileID [16]byte
}
// fakeLayerReader implements hcsshim.LayerReader over a scripted set of files
// so that writeTarFromLayer can be exercised without a real layer.
type fakeLayerReader struct {
entries []fakeEntry
idx int
stream *bytes.Reader
}
func (r *fakeLayerReader) Next() (string, int64, *winio.FileBasicInfo, error) {
if r.idx >= len(r.entries) {
return "", 0, nil, io.EOF
}
e := r.entries[r.idx]
r.idx++
stream, err := backupStream(e.data)
if err != nil {
return "", 0, nil, err
}
r.stream = bytes.NewReader(stream)
fileInfo := &winio.FileBasicInfo{FileAttributes: windows.FILE_ATTRIBUTE_NORMAL}
return e.name, int64(len(e.data)), fileInfo, nil
}
func (r *fakeLayerReader) LinkInfo() (uint32, *winio.FileIDInfo, error) {
e := r.entries[r.idx-1]
return e.nlinks, &winio.FileIDInfo{VolumeSerialNumber: 1, FileID: e.fileID}, nil
}
func (r *fakeLayerReader) Read(b []byte) (int, error) {
if r.stream == nil {
return 0, io.EOF
}
return r.stream.Read(b)
}
func (r *fakeLayerReader) Close() error { return nil }
// backupStream wraps raw file contents in a Win32 backup stream, which is what
// WriteTarFileFromBackupStream expects to read.
func backupStream(data []byte) ([]byte, error) {
var buf bytes.Buffer
w := winio.NewBackupStreamWriter(&buf)
if err := w.WriteHeader(&winio.BackupHeader{Id: winio.BackupData, Size: int64(len(data))}); err != nil {
return nil, err
}
if _, err := w.Write(data); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
type tarEntry struct {
name string
typeflag byte
linkname string
size int64
}
func readTar(t *testing.T, b []byte) []tarEntry {
t.Helper()
var entries []tarEntry
tr := tar.NewReader(bytes.NewReader(b))
for {
hdr, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
t.Fatalf("reading tar: %v", err)
}
entries = append(entries, tarEntry{
name: hdr.Name,
typeflag: hdr.Typeflag,
linkname: hdr.Linkname,
size: hdr.Size,
})
}
return entries
}
func TestWriteTarFromLayerHardLinks(t *testing.T) {
fileIDA := [16]byte{1}
fileIDB := [16]byte{2}
for _, tc := range []struct {
name string
entries []fakeEntry
expected []tarEntry
}{
{
// Every later name for a file ID points at the first name, never at
// the name immediately before it, so links are never chained.
name: "repeated names link to the first name",
entries: []fakeEntry{
{name: "Files/a.txt", data: []byte("payload"), nlinks: 3, fileID: fileIDA},
{name: "Files/b.txt", data: []byte("payload"), nlinks: 3, fileID: fileIDA},
{name: "Files/c.txt", data: []byte("payload"), nlinks: 3, fileID: fileIDA},
},
expected: []tarEntry{
{name: "Files/a.txt", typeflag: tar.TypeReg, size: 7},
{name: "Files/b.txt", typeflag: tar.TypeLink, linkname: "Files/a.txt"},
{name: "Files/c.txt", typeflag: tar.TypeLink, linkname: "Files/a.txt"},
},
},
{
name: "distinct file IDs are never linked",
entries: []fakeEntry{
{name: "Files/a.txt", data: []byte("payload"), nlinks: 2, fileID: fileIDA},
{name: "Files/b.txt", data: []byte("payload"), nlinks: 2, fileID: fileIDB},
},
expected: []tarEntry{
{name: "Files/a.txt", typeflag: tar.TypeReg, size: 7},
{name: "Files/b.txt", typeflag: tar.TypeReg, size: 7},
},
},
{
name: "a single link count is never linked",
entries: []fakeEntry{
{name: "Files/a.txt", data: []byte("payload"), nlinks: 1, fileID: fileIDA},
{name: "Files/b.txt", data: []byte("payload"), nlinks: 1, fileID: fileIDA},
},
expected: []tarEntry{
{name: "Files/a.txt", typeflag: tar.TypeReg, size: 7},
{name: "Files/b.txt", typeflag: tar.TypeReg, size: 7},
},
},
} {
t.Run(tc.name, func(t *testing.T) {
var buf bytes.Buffer
r := &fakeLayerReader{entries: tc.entries}
if err := writeTarFromLayer(r, &buf); err != nil {
t.Fatalf("writeTarFromLayer: %v", err)
}
got := readTar(t, buf.Bytes())
if len(got) != len(tc.expected) {
t.Fatalf("got %d tar entries, want %d: %+v", len(got), len(tc.expected), got)
}
for i, want := range tc.expected {
if got[i].name != want.name {
t.Errorf("entry %d: name = %q, want %q", i, got[i].name, want.name)
}
if got[i].typeflag != want.typeflag {
t.Errorf("entry %d (%s): typeflag = %q, want %q", i, want.name, got[i].typeflag, want.typeflag)
}
if got[i].linkname != want.linkname {
t.Errorf("entry %d (%s): linkname = %q, want %q", i, want.name, got[i].linkname, want.linkname)
}
// A hard link entry must not carry a second copy of the payload.
if want.typeflag == tar.TypeLink && got[i].size != 0 {
t.Errorf("entry %d (%s): size = %d, want 0 for a hard link", i, want.name, got[i].size)
}
}
})
}
}