2016-04-15 22:39:18 +00:00
|
|
|
package mtree
|
|
|
|
|
|
|
|
import (
|
|
|
|
"archive/tar"
|
|
|
|
"bytes"
|
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
2017-04-13 18:35:01 +00:00
|
|
|
"path/filepath"
|
2016-10-30 16:39:13 +00:00
|
|
|
"syscall"
|
2016-04-15 22:39:18 +00:00
|
|
|
"testing"
|
2016-10-30 16:39:13 +00:00
|
|
|
"time"
|
2016-04-15 22:39:18 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func ExampleStreamer() {
|
|
|
|
fh, err := os.Open("./testdata/test.tar")
|
|
|
|
if err != nil {
|
|
|
|
// handle error ...
|
|
|
|
}
|
2016-11-18 03:43:02 +00:00
|
|
|
str := NewTarStreamer(fh, nil, nil)
|
2016-04-15 22:39:18 +00:00
|
|
|
if err := extractTar("/tmp/dir", str); err != nil {
|
|
|
|
// handle error ...
|
|
|
|
}
|
|
|
|
|
|
|
|
dh, err := str.Hierarchy()
|
|
|
|
if err != nil {
|
|
|
|
// handle error ...
|
|
|
|
}
|
|
|
|
|
2016-11-18 07:53:26 +00:00
|
|
|
res, err := Check("/tmp/dir/", dh, nil, nil)
|
2016-04-15 22:39:18 +00:00
|
|
|
if err != nil {
|
|
|
|
// handle error ...
|
|
|
|
}
|
2016-10-31 10:42:53 +00:00
|
|
|
if len(res) > 0 {
|
2016-04-15 22:39:18 +00:00
|
|
|
// handle validation issue ...
|
|
|
|
}
|
|
|
|
}
|
|
|
|
func extractTar(root string, tr io.Reader) error {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestTar(t *testing.T) {
|
|
|
|
/*
|
|
|
|
data, err := makeTarStream()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
buf := bytes.NewBuffer(data)
|
|
|
|
str := NewTarStreamer(buf, append(DefaultKeywords, "sha1"))
|
|
|
|
*/
|
2016-06-28 20:40:35 +00:00
|
|
|
/*
|
|
|
|
// open empty folder and check size.
|
|
|
|
fh, err := os.Open("./testdata/empty")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
log.Println(fh.Stat())
|
|
|
|
fh.Close() */
|
2016-04-15 22:39:18 +00:00
|
|
|
fh, err := os.Open("./testdata/test.tar")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2016-11-18 03:43:02 +00:00
|
|
|
str := NewTarStreamer(fh, nil, append(DefaultKeywords, "sha1"))
|
2016-04-15 22:39:18 +00:00
|
|
|
|
|
|
|
if _, err := io.Copy(ioutil.Discard, str); err != nil && err != io.EOF {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if err := str.Close(); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
defer fh.Close()
|
|
|
|
|
2016-06-28 20:40:35 +00:00
|
|
|
// get DirectoryHierarcy struct from walking the tar archive
|
|
|
|
tdh, err := str.Hierarchy()
|
2016-04-15 22:39:18 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2016-06-28 20:40:35 +00:00
|
|
|
if tdh == nil {
|
2016-04-15 22:39:18 +00:00
|
|
|
t.Fatal("expected a DirectoryHierarchy struct, but got nil")
|
|
|
|
}
|
2016-06-28 20:40:35 +00:00
|
|
|
|
2017-04-13 18:35:01 +00:00
|
|
|
testDir, present := os.LookupEnv("MTREE_TESTDIR")
|
|
|
|
if present == false {
|
|
|
|
testDir = "."
|
|
|
|
}
|
|
|
|
testPath := filepath.Join(testDir, "test.mtree")
|
|
|
|
fh, err = os.Create(testPath)
|
2016-06-28 20:40:35 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2017-04-13 18:35:01 +00:00
|
|
|
defer os.Remove(testPath)
|
2016-06-28 20:40:35 +00:00
|
|
|
|
|
|
|
// put output of tar walk into test.mtree
|
|
|
|
_, err = tdh.WriteTo(fh)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
fh.Close()
|
|
|
|
|
|
|
|
// now simulate gomtree -T testdata/test.tar -f testdata/test.mtree
|
2017-04-13 18:35:01 +00:00
|
|
|
fh, err = os.Open(testPath)
|
2016-06-28 20:40:35 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
defer fh.Close()
|
|
|
|
|
|
|
|
dh, err := ParseSpec(fh)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2018-12-10 16:26:12 +00:00
|
|
|
res, err := Compare(tdh, dh, append(DefaultKeywords, "sha1"))
|
2016-06-28 20:40:35 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// print any failures, and then call t.Fatal once all failures/extra/missing
|
|
|
|
// are outputted
|
2016-10-31 10:42:53 +00:00
|
|
|
if len(res) > 0 {
|
|
|
|
for _, delta := range res {
|
|
|
|
t.Error(delta)
|
2016-06-28 20:40:35 +00:00
|
|
|
}
|
2016-10-31 10:42:53 +00:00
|
|
|
t.Fatal("unexpected errors")
|
2016-06-28 20:40:35 +00:00
|
|
|
}
|
2016-04-15 22:39:18 +00:00
|
|
|
}
|
|
|
|
|
2016-08-02 19:30:45 +00:00
|
|
|
// This test checks how gomtree handles archives that were created
|
|
|
|
// with multiple directories, i.e, archives created with something like:
|
|
|
|
// `tar -cvf some.tar dir1 dir2 dir3 dir4/dir5 dir6` ... etc.
|
|
|
|
// The testdata of collection.tar resemble such an archive. the `collection` folder
|
|
|
|
// is the contents of `collection.tar` extracted
|
|
|
|
func TestArchiveCreation(t *testing.T) {
|
|
|
|
fh, err := os.Open("./testdata/collection.tar")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2016-11-18 03:43:02 +00:00
|
|
|
str := NewTarStreamer(fh, nil, []Keyword{"sha1"})
|
2016-08-02 19:30:45 +00:00
|
|
|
|
|
|
|
if _, err := io.Copy(ioutil.Discard, str); err != nil && err != io.EOF {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if err := str.Close(); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
defer fh.Close()
|
|
|
|
|
|
|
|
// get DirectoryHierarcy struct from walking the tar archive
|
|
|
|
tdh, err := str.Hierarchy()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Test the tar manifest against the actual directory
|
2016-11-18 07:53:26 +00:00
|
|
|
res, err := Check("./testdata/collection", tdh, []Keyword{"sha1"}, nil)
|
2016-08-02 19:30:45 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2016-10-31 10:42:53 +00:00
|
|
|
if len(res) > 0 {
|
|
|
|
for _, delta := range res {
|
|
|
|
t.Error(delta)
|
2016-08-02 19:30:45 +00:00
|
|
|
}
|
2016-10-31 10:42:53 +00:00
|
|
|
t.Fatal("unexpected errors")
|
2016-08-02 19:30:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Test the tar manifest against itself
|
2018-12-10 16:26:12 +00:00
|
|
|
res, err = Compare(tdh, tdh, []Keyword{"sha1"})
|
2016-08-02 19:30:45 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2016-10-31 10:42:53 +00:00
|
|
|
if len(res) > 0 {
|
|
|
|
for _, delta := range res {
|
|
|
|
t.Error(delta)
|
2016-08-02 19:30:45 +00:00
|
|
|
}
|
2016-10-31 10:42:53 +00:00
|
|
|
t.Fatal("unexpected errors")
|
2016-08-02 19:30:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Validate the directory manifest against the archive
|
2016-11-18 07:53:26 +00:00
|
|
|
dh, err := Walk("./testdata/collection", nil, []Keyword{"sha1"}, nil)
|
2016-08-02 19:30:45 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2018-12-10 16:26:12 +00:00
|
|
|
res, err = Compare(tdh, dh, []Keyword{"sha1"})
|
2016-08-02 19:30:45 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2016-10-31 10:42:53 +00:00
|
|
|
if len(res) > 0 {
|
|
|
|
for _, delta := range res {
|
|
|
|
t.Error(delta)
|
2016-08-02 19:30:45 +00:00
|
|
|
}
|
2016-10-31 10:42:53 +00:00
|
|
|
t.Fatal("unexpected errors")
|
2016-08-02 19:30:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now test a tar file that was created with just the path to a file. In this
|
|
|
|
// test case, the traversal and creation of "placeholder" directories are
|
|
|
|
// evaluated. Also, The fact that this archive contains a single entry, yet the
|
|
|
|
// entry is associated with a file that has parent directories, means that the
|
|
|
|
// "." directory should be the lowest sub-directory under which `file` is contained.
|
|
|
|
func TestTreeTraversal(t *testing.T) {
|
|
|
|
fh, err := os.Open("./testdata/traversal.tar")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2016-11-18 03:43:02 +00:00
|
|
|
str := NewTarStreamer(fh, nil, DefaultTarKeywords)
|
2016-08-02 19:30:45 +00:00
|
|
|
|
|
|
|
if _, err = io.Copy(ioutil.Discard, str); err != nil && err != io.EOF {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if err = str.Close(); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
fh.Close()
|
|
|
|
tdh, err := str.Hierarchy()
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
2018-12-10 16:26:12 +00:00
|
|
|
res, err := Compare(tdh, tdh, []Keyword{"sha1"})
|
2016-08-02 19:30:45 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2016-10-31 10:42:53 +00:00
|
|
|
if len(res) > 0 {
|
|
|
|
for _, delta := range res {
|
|
|
|
t.Error(delta)
|
2016-08-02 19:30:45 +00:00
|
|
|
}
|
2016-10-31 10:42:53 +00:00
|
|
|
t.Fatal("unexpected errors")
|
2016-08-02 19:30:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// top-level "." directory will contain contents of traversal.tar
|
2016-11-18 07:53:26 +00:00
|
|
|
res, err = Check("./testdata/.", tdh, []Keyword{"sha1"}, nil)
|
2016-08-02 19:30:45 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2016-10-31 10:42:53 +00:00
|
|
|
if len(res) > 0 {
|
|
|
|
var failed bool
|
|
|
|
for _, delta := range res {
|
|
|
|
// We only care about missing or modified files.
|
|
|
|
// The original test was written using the old check code.
|
|
|
|
if delta.Type() != Extra {
|
|
|
|
failed = true
|
|
|
|
t.Error(delta)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if failed {
|
|
|
|
t.Fatal("unexpected errors")
|
2016-08-02 19:30:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now test an archive that requires placeholder directories, i.e, there are
|
|
|
|
// no headers in the archive that are associated with the actual directory name
|
|
|
|
fh, err = os.Open("./testdata/singlefile.tar")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2016-11-18 03:43:02 +00:00
|
|
|
str = NewTarStreamer(fh, nil, DefaultTarKeywords)
|
2016-08-02 19:30:45 +00:00
|
|
|
if _, err = io.Copy(ioutil.Discard, str); err != nil && err != io.EOF {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if err = str.Close(); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
tdh, err = str.Hierarchy()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Implied top-level "." directory will contain the contents of singlefile.tar
|
2016-11-18 07:53:26 +00:00
|
|
|
res, err = Check("./testdata/.", tdh, []Keyword{"sha1"}, nil)
|
2016-08-02 19:30:45 +00:00
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2016-10-31 10:42:53 +00:00
|
|
|
if len(res) > 0 {
|
|
|
|
var failed bool
|
|
|
|
for _, delta := range res {
|
|
|
|
// We only care about missing or modified files.
|
|
|
|
// The original test was written using the old check code.
|
|
|
|
if delta.Type() != Extra {
|
|
|
|
failed = true
|
|
|
|
t.Error(delta)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if failed {
|
|
|
|
t.Fatal("unexpected errors")
|
2016-08-02 19:30:45 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-08-10 15:40:47 +00:00
|
|
|
func TestHardlinks(t *testing.T) {
|
|
|
|
fh, err := os.Open("./testdata/hardlinks.tar")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
2016-11-18 03:43:02 +00:00
|
|
|
str := NewTarStreamer(fh, nil, append(DefaultTarKeywords, "nlink"))
|
2016-08-10 15:40:47 +00:00
|
|
|
|
|
|
|
if _, err = io.Copy(ioutil.Discard, str); err != nil && err != io.EOF {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if err = str.Close(); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
fh.Close()
|
|
|
|
tdh, err := str.Hierarchy()
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
foundnlink := false
|
|
|
|
for _, e := range tdh.Entries {
|
|
|
|
if e.Type == RelativeType {
|
|
|
|
for _, kv := range e.Keywords {
|
|
|
|
if KeyVal(kv).Keyword() == "nlink" {
|
|
|
|
foundnlink = true
|
|
|
|
if KeyVal(kv).Value() != "3" {
|
|
|
|
t.Errorf("expected to have 3 hardlinks for %s", e.Name)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !foundnlink {
|
|
|
|
t.Errorf("nlink expected to be evaluated")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-30 16:39:13 +00:00
|
|
|
type fakeFile struct {
|
|
|
|
Name, Body string
|
|
|
|
Mode int64
|
|
|
|
Type byte
|
|
|
|
Sec, Nsec int64
|
|
|
|
Xattrs map[string]string
|
|
|
|
}
|
|
|
|
|
|
|
|
// minimal tar archive that mimics what is in ./testdata/test.tar
|
|
|
|
var minimalFiles = []fakeFile{
|
|
|
|
{"x/", "", 0755, '5', 0, 0, nil},
|
|
|
|
{"x/files", "howdy\n", 0644, '0', 0, 0, nil},
|
|
|
|
}
|
|
|
|
|
|
|
|
func makeTarStream(ff []fakeFile) ([]byte, error) {
|
2016-04-15 22:39:18 +00:00
|
|
|
buf := new(bytes.Buffer)
|
|
|
|
|
|
|
|
// Create a new tar archive.
|
|
|
|
tw := tar.NewWriter(buf)
|
|
|
|
|
|
|
|
// Add some files to the archive.
|
2016-10-30 16:39:13 +00:00
|
|
|
for _, file := range ff {
|
2016-04-15 22:39:18 +00:00
|
|
|
hdr := &tar.Header{
|
2016-10-30 16:39:13 +00:00
|
|
|
Name: file.Name,
|
|
|
|
Uid: syscall.Getuid(),
|
|
|
|
Gid: syscall.Getgid(),
|
|
|
|
Mode: file.Mode,
|
|
|
|
Typeflag: file.Type,
|
|
|
|
Size: int64(len(file.Body)),
|
|
|
|
ModTime: time.Unix(file.Sec, file.Nsec),
|
|
|
|
AccessTime: time.Unix(file.Sec, file.Nsec),
|
|
|
|
ChangeTime: time.Unix(file.Sec, file.Nsec),
|
|
|
|
Xattrs: file.Xattrs,
|
2016-04-15 22:39:18 +00:00
|
|
|
}
|
|
|
|
if err := tw.WriteHeader(hdr); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if len(file.Body) > 0 {
|
|
|
|
if _, err := tw.Write([]byte(file.Body)); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// Make sure to check the error on Close.
|
|
|
|
if err := tw.Close(); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return buf.Bytes(), nil
|
|
|
|
}
|
2016-11-18 03:43:02 +00:00
|
|
|
|
|
|
|
func TestArchiveExcludeNonDirectory(t *testing.T) {
|
|
|
|
fh, err := os.Open("./testdata/collection.tar")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
str := NewTarStreamer(fh, []ExcludeFunc{ExcludeNonDirectories}, []Keyword{"type"})
|
|
|
|
|
|
|
|
if _, err := io.Copy(ioutil.Discard, str); err != nil && err != io.EOF {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
if err := str.Close(); err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
fh.Close()
|
|
|
|
// get DirectoryHierarcy struct from walking the tar archive
|
|
|
|
tdh, err := str.Hierarchy()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatal(err)
|
|
|
|
}
|
|
|
|
for i := range tdh.Entries {
|
|
|
|
for _, keyval := range tdh.Entries[i].AllKeys() {
|
|
|
|
if tdh.Entries[i].Type == FullType || tdh.Entries[i].Type == RelativeType {
|
|
|
|
if keyval.Keyword() == "type" && keyval.Value() != "dir" {
|
|
|
|
t.Errorf("expected only directories, but %q is a %q", tdh.Entries[i].Name, keyval.Value())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|