commit
81e5c5c8f0
7 changed files with 120 additions and 15 deletions
|
@ -15,11 +15,15 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
flCreate = flag.Bool("c", false, "create a directory hierarchy spec")
|
// Flags common with mtree(8)
|
||||||
flFile = flag.String("f", "", "directory hierarchy spec to validate")
|
flCreate = flag.Bool("c", false, "create a directory hierarchy spec")
|
||||||
flPath = flag.String("p", "", "root path that the hierarchy spec is relative to")
|
flFile = flag.String("f", "", "directory hierarchy spec to validate")
|
||||||
flAddKeywords = flag.String("K", "", "Add the specified (delimited by comma or space) keywords to the current set of keywords")
|
flPath = flag.String("p", "", "root path that the hierarchy spec is relative to")
|
||||||
flUseKeywords = flag.String("k", "", "Use the specified (delimited by comma or space) keywords as the current set of keywords")
|
flAddKeywords = flag.String("K", "", "Add the specified (delimited by comma or space) keywords to the current set of keywords")
|
||||||
|
flUseKeywords = flag.String("k", "", "Use the specified (delimited by comma or space) keywords as the current set of keywords")
|
||||||
|
flDirectoryOnly = flag.Bool("d", false, "Ignore everything except directory type files")
|
||||||
|
|
||||||
|
// Flags unique to gomtree
|
||||||
flListKeywords = flag.Bool("list-keywords", false, "List the keywords available")
|
flListKeywords = flag.Bool("list-keywords", false, "List the keywords available")
|
||||||
flResultFormat = flag.String("result-format", "bsd", "output the validation results using the given format (bsd, json, path)")
|
flResultFormat = flag.String("result-format", "bsd", "output the validation results using the given format (bsd, json, path)")
|
||||||
flTar = flag.String("T", "", "use tar archive to create or validate a directory hierarchy spec (\"-\" indicates stdin)")
|
flTar = flag.String("T", "", "use tar archive to create or validate a directory hierarchy spec (\"-\" indicates stdin)")
|
||||||
|
@ -198,6 +202,12 @@ func app() error {
|
||||||
rootPath = *flPath
|
rootPath = *flPath
|
||||||
}
|
}
|
||||||
|
|
||||||
|
excludes := []mtree.ExcludeFunc{}
|
||||||
|
// -d
|
||||||
|
if *flDirectoryOnly {
|
||||||
|
excludes = append(excludes, mtree.ExcludeNonDirectories)
|
||||||
|
}
|
||||||
|
|
||||||
// -T <tar file>
|
// -T <tar file>
|
||||||
if *flTar != "" {
|
if *flTar != "" {
|
||||||
var input io.Reader
|
var input io.Reader
|
||||||
|
@ -211,7 +221,7 @@ func app() error {
|
||||||
defer fh.Close()
|
defer fh.Close()
|
||||||
input = fh
|
input = fh
|
||||||
}
|
}
|
||||||
ts := mtree.NewTarStreamer(input, currentKeywords)
|
ts := mtree.NewTarStreamer(input, excludes, currentKeywords)
|
||||||
|
|
||||||
if _, err := io.Copy(ioutil.Discard, ts); err != nil && err != io.EOF {
|
if _, err := io.Copy(ioutil.Discard, ts); err != nil && err != io.EOF {
|
||||||
return err
|
return err
|
||||||
|
@ -226,7 +236,7 @@ func app() error {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// with a root directory
|
// with a root directory
|
||||||
stateDh, err = mtree.Walk(rootPath, nil, currentKeywords)
|
stateDh, err = mtree.Walk(rootPath, excludes, currentKeywords)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -391,7 +391,7 @@ func TestTarCompare(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
str := NewTarStreamer(bytes.NewBuffer(ts), append(DefaultTarKeywords, "sha1"))
|
str := NewTarStreamer(bytes.NewBuffer(ts), nil, append(DefaultTarKeywords, "sha1"))
|
||||||
if _, err = io.Copy(ioutil.Discard, str); err != nil && err != io.EOF {
|
if _, err = io.Copy(ioutil.Discard, str); err != nil && err != io.EOF {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
12
tar.go
12
tar.go
|
@ -25,7 +25,7 @@ var tarDefaultSetKeywords = []KeyVal{
|
||||||
|
|
||||||
// NewTarStreamer streams a tar archive and creates a file hierarchy based off
|
// NewTarStreamer streams a tar archive and creates a file hierarchy based off
|
||||||
// of the tar metadata headers
|
// of the tar metadata headers
|
||||||
func NewTarStreamer(r io.Reader, keywords []Keyword) Streamer {
|
func NewTarStreamer(r io.Reader, excludes []ExcludeFunc, keywords []Keyword) Streamer {
|
||||||
pR, pW := io.Pipe()
|
pR, pW := io.Pipe()
|
||||||
ts := &tarStream{
|
ts := &tarStream{
|
||||||
pipeReader: pR,
|
pipeReader: pR,
|
||||||
|
@ -35,6 +35,7 @@ func NewTarStreamer(r io.Reader, keywords []Keyword) Streamer {
|
||||||
tarReader: tar.NewReader(pR),
|
tarReader: tar.NewReader(pR),
|
||||||
keywords: keywords,
|
keywords: keywords,
|
||||||
hardlinks: map[string][]string{},
|
hardlinks: map[string][]string{},
|
||||||
|
excludes: excludes,
|
||||||
}
|
}
|
||||||
|
|
||||||
go ts.readHeaders()
|
go ts.readHeaders()
|
||||||
|
@ -50,6 +51,7 @@ type tarStream struct {
|
||||||
teeReader io.Reader
|
teeReader io.Reader
|
||||||
tarReader *tar.Reader
|
tarReader *tar.Reader
|
||||||
keywords []Keyword
|
keywords []Keyword
|
||||||
|
excludes []ExcludeFunc
|
||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,12 +87,20 @@ func (ts *tarStream) readHeaders() {
|
||||||
e.Pos = len(ts.creator.DH.Entries)
|
e.Pos = len(ts.creator.DH.Entries)
|
||||||
ts.creator.DH.Entries = append(ts.creator.DH.Entries, e)
|
ts.creator.DH.Entries = append(ts.creator.DH.Entries, e)
|
||||||
}
|
}
|
||||||
|
hdrloop:
|
||||||
for {
|
for {
|
||||||
hdr, err := ts.tarReader.Next()
|
hdr, err := ts.tarReader.Next()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ts.pipeReader.CloseWithError(err)
|
ts.pipeReader.CloseWithError(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, ex := range ts.excludes {
|
||||||
|
if ex(hdr.Name, hdr.FileInfo()) {
|
||||||
|
continue hdrloop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Because the content of the file may need to be read by several
|
// Because the content of the file may need to be read by several
|
||||||
// KeywordFuncs, it needs to be an io.Seeker as well. So, just reading from
|
// KeywordFuncs, it needs to be an io.Seeker as well. So, just reading from
|
||||||
// ts.tarReader is not enough.
|
// ts.tarReader is not enough.
|
||||||
|
|
42
tar_test.go
42
tar_test.go
|
@ -16,7 +16,7 @@ func ExampleStreamer() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// handle error ...
|
// handle error ...
|
||||||
}
|
}
|
||||||
str := NewTarStreamer(fh, nil)
|
str := NewTarStreamer(fh, nil, nil)
|
||||||
if err := extractTar("/tmp/dir", str); err != nil {
|
if err := extractTar("/tmp/dir", str); err != nil {
|
||||||
// handle error ...
|
// handle error ...
|
||||||
}
|
}
|
||||||
|
@ -59,7 +59,7 @@ func TestTar(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
str := NewTarStreamer(fh, append(DefaultKeywords, "sha1"))
|
str := NewTarStreamer(fh, nil, append(DefaultKeywords, "sha1"))
|
||||||
|
|
||||||
if _, err := io.Copy(ioutil.Discard, str); err != nil && err != io.EOF {
|
if _, err := io.Copy(ioutil.Discard, str); err != nil && err != io.EOF {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
@ -128,7 +128,7 @@ func TestArchiveCreation(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
str := NewTarStreamer(fh, []Keyword{"sha1"})
|
str := NewTarStreamer(fh, nil, []Keyword{"sha1"})
|
||||||
|
|
||||||
if _, err := io.Copy(ioutil.Discard, str); err != nil && err != io.EOF {
|
if _, err := io.Copy(ioutil.Discard, str); err != nil && err != io.EOF {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
@ -196,7 +196,7 @@ func TestTreeTraversal(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
str := NewTarStreamer(fh, DefaultTarKeywords)
|
str := NewTarStreamer(fh, nil, DefaultTarKeywords)
|
||||||
|
|
||||||
if _, err = io.Copy(ioutil.Discard, str); err != nil && err != io.EOF {
|
if _, err = io.Copy(ioutil.Discard, str); err != nil && err != io.EOF {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
@ -249,7 +249,7 @@ func TestTreeTraversal(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
str = NewTarStreamer(fh, DefaultTarKeywords)
|
str = NewTarStreamer(fh, nil, DefaultTarKeywords)
|
||||||
if _, err = io.Copy(ioutil.Discard, str); err != nil && err != io.EOF {
|
if _, err = io.Copy(ioutil.Discard, str); err != nil && err != io.EOF {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -287,7 +287,7 @@ func TestHardlinks(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
str := NewTarStreamer(fh, append(DefaultTarKeywords, "nlink"))
|
str := NewTarStreamer(fh, nil, append(DefaultTarKeywords, "nlink"))
|
||||||
|
|
||||||
if _, err = io.Copy(ioutil.Discard, str); err != nil && err != io.EOF {
|
if _, err = io.Copy(ioutil.Discard, str); err != nil && err != io.EOF {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
@ -369,3 +369,33 @@ func makeTarStream(ff []fakeFile) ([]byte, error) {
|
||||||
}
|
}
|
||||||
return buf.Bytes(), nil
|
return buf.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
33
test/cli/0006.sh
Normal file
33
test/cli/0006.sh
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
name=$(basename $0)
|
||||||
|
root=$1
|
||||||
|
gomtree=$(readlink -f ${root}/gomtree)
|
||||||
|
t=$(mktemp -d /tmp/go-mtree.XXXXXX)
|
||||||
|
|
||||||
|
echo "[${name}] Running in ${t}"
|
||||||
|
# This test is for basic running check of manifest, and check against tar and file system
|
||||||
|
#
|
||||||
|
|
||||||
|
pushd ${root}
|
||||||
|
|
||||||
|
git archive --format=tar HEAD^{tree} . > ${t}/${name}.tar
|
||||||
|
|
||||||
|
prev_umask=$(umask)
|
||||||
|
umask 0 # this is so the tar command can set the mode's properly
|
||||||
|
mkdir -p ${t}/extract
|
||||||
|
tar -C ${t}/extract/ -xf ${t}/${name}.tar
|
||||||
|
umask ${prev_umask}
|
||||||
|
|
||||||
|
# create manifest from tar, ignoring non directories
|
||||||
|
${gomtree} -d -c -k type -T ${t}/${name}.tar > ${t}/${name}.mtree
|
||||||
|
|
||||||
|
# check tar-manifest against the tar
|
||||||
|
${gomtree} -d -f ${t}/${name}.mtree -T ${t}/${name}.tar
|
||||||
|
|
||||||
|
# check filesystem-manifest against the filesystem
|
||||||
|
${gomtree} -f ${t}/${name}.mtree -p ${t}/extract/
|
||||||
|
|
||||||
|
popd
|
||||||
|
rm -rf ${t}
|
5
walk.go
5
walk.go
|
@ -15,6 +15,11 @@ import (
|
||||||
// returns true, then the path is not included in the spec.
|
// returns true, then the path is not included in the spec.
|
||||||
type ExcludeFunc func(path string, info os.FileInfo) bool
|
type ExcludeFunc func(path string, info os.FileInfo) bool
|
||||||
|
|
||||||
|
// ExcludeNonDirectories is an ExcludeFunc for excluding all paths that are not directories
|
||||||
|
var ExcludeNonDirectories = func(path string, info os.FileInfo) bool {
|
||||||
|
return !info.IsDir()
|
||||||
|
}
|
||||||
|
|
||||||
var defaultSetKeywords = []KeyVal{"type=file", "nlink=1", "flags=none", "mode=0664"}
|
var defaultSetKeywords = []KeyVal{"type=file", "nlink=1", "flags=none", "mode=0664"}
|
||||||
|
|
||||||
// Walk from root directory and assemble the DirectoryHierarchy. excludes
|
// Walk from root directory and assemble the DirectoryHierarchy. excludes
|
||||||
|
|
17
walk_test.go
17
walk_test.go
|
@ -35,3 +35,20 @@ func TestWalk(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestWalkDirectory(t *testing.T) {
|
||||||
|
dh, err := Walk(".", []ExcludeFunc{ExcludeNonDirectories}, []Keyword{"type"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := range dh.Entries {
|
||||||
|
for _, keyval := range dh.Entries[i].AllKeys() {
|
||||||
|
if dh.Entries[i].Type == FullType || dh.Entries[i].Type == RelativeType {
|
||||||
|
if keyval.Keyword() == "type" && keyval.Value() != "dir" {
|
||||||
|
t.Errorf("expected only directories, but %q is a %q", dh.Entries[i].Name, keyval.Value())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue