Merge pull request #96 from cyphar/add-unpriv-walking
walk: implement "unprivileged manifest generation"
This commit is contained in:
commit
0dc720e861
10 changed files with 272 additions and 64 deletions
15
check.go
15
check.go
|
@ -5,22 +5,17 @@ package mtree
|
||||||
// If keywords is nil, the check all present in the DirectoryHierarchy
|
// If keywords is nil, the check all present in the DirectoryHierarchy
|
||||||
//
|
//
|
||||||
// This is equivalent to creating a new DirectoryHierarchy with Walk(root, nil,
|
// This is equivalent to creating a new DirectoryHierarchy with Walk(root, nil,
|
||||||
// keywords) and then doing a Compare(dh, newDh, keywords).
|
// keywords, fs) and then doing a Compare(dh, newDh, keywords).
|
||||||
func Check(root string, dh *DirectoryHierarchy, keywords []Keyword) ([]InodeDelta, error) {
|
func Check(root string, dh *DirectoryHierarchy, keywords []Keyword, fs FsEval) ([]InodeDelta, error) {
|
||||||
if keywords == nil {
|
if keywords == nil {
|
||||||
used := dh.UsedKeywords()
|
keywords = dh.UsedKeywords()
|
||||||
newDh, err := Walk(root, nil, used)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return Compare(dh, newDh, used)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
newDh, err := Walk(root, nil, keywords)
|
newDh, err := Walk(root, nil, keywords, fs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// TODO: Handle tar_time, if necessary.
|
|
||||||
return Compare(dh, newDh, keywords)
|
return Compare(dh, newDh, keywords)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,12 +12,12 @@ import (
|
||||||
// simple walk of current directory, and imediately check it.
|
// simple walk of current directory, and imediately check it.
|
||||||
// may not be parallelizable.
|
// may not be parallelizable.
|
||||||
func TestCheck(t *testing.T) {
|
func TestCheck(t *testing.T) {
|
||||||
dh, err := Walk(".", nil, append(DefaultKeywords, "sha1"))
|
dh, err := Walk(".", nil, append(DefaultKeywords, "sha1"), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := Check(".", dh, nil)
|
res, err := Check(".", dh, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -43,13 +43,13 @@ func TestCheckKeywords(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Walk this tempdir
|
// Walk this tempdir
|
||||||
dh, err := Walk(dir, nil, append(DefaultKeywords, "sha1"))
|
dh, err := Walk(dir, nil, append(DefaultKeywords, "sha1"), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for sanity. This ought to pass.
|
// Check for sanity. This ought to pass.
|
||||||
res, err := Check(dir, dh, nil)
|
res, err := Check(dir, dh, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -64,7 +64,7 @@ func TestCheckKeywords(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check again. This ought to fail.
|
// Check again. This ought to fail.
|
||||||
res, err = Check(dir, dh, nil)
|
res, err = Check(dir, dh, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -76,7 +76,7 @@ func TestCheckKeywords(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check again, but only sha1 and mode. This ought to pass.
|
// Check again, but only sha1 and mode. This ought to pass.
|
||||||
res, err = Check(dir, dh, []Keyword{"sha1", "mode"})
|
res, err = Check(dir, dh, []Keyword{"sha1", "mode"}, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -86,12 +86,12 @@ func TestCheckKeywords(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExampleCheck() {
|
func ExampleCheck() {
|
||||||
dh, err := Walk(".", nil, append(DefaultKeywords, "sha1"))
|
dh, err := Walk(".", nil, append(DefaultKeywords, "sha1"), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// handle error ...
|
// handle error ...
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := Check(".", dh, nil)
|
res, err := Check(".", dh, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// handle error ...
|
// handle error ...
|
||||||
}
|
}
|
||||||
|
@ -103,11 +103,11 @@ func ExampleCheck() {
|
||||||
// Tests default action for evaluating a symlink, which is just to compare the
|
// Tests default action for evaluating a symlink, which is just to compare the
|
||||||
// link itself, not to follow it
|
// link itself, not to follow it
|
||||||
func TestDefaultBrokenLink(t *testing.T) {
|
func TestDefaultBrokenLink(t *testing.T) {
|
||||||
dh, err := Walk("./testdata/dirwithbrokenlink", nil, append(DefaultKeywords, "sha1"))
|
dh, err := Walk("./testdata/dirwithbrokenlink", nil, append(DefaultKeywords, "sha1"), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
res, err := Check("./testdata/dirwithbrokenlink", dh, nil)
|
res, err := Check("./testdata/dirwithbrokenlink", dh, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -155,7 +155,7 @@ func TestTimeComparison(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := Check(dir, dh, nil)
|
res, err := Check(dir, dh, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
@ -203,13 +203,13 @@ func TestTarTime(t *testing.T) {
|
||||||
keywords := dh.UsedKeywords()
|
keywords := dh.UsedKeywords()
|
||||||
|
|
||||||
// make sure "time" keyword works
|
// make sure "time" keyword works
|
||||||
_, err = Check(dir, dh, keywords)
|
_, err = Check(dir, dh, keywords, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure tar_time wins
|
// make sure tar_time wins
|
||||||
res, err := Check(dir, dh, append(keywords, "tar_time"))
|
res, err := Check(dir, dh, append(keywords, "tar_time"), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
@ -254,7 +254,7 @@ func TestIgnoreComments(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := Check(dir, dh, nil)
|
res, err := Check(dir, dh, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
@ -274,7 +274,7 @@ func TestIgnoreComments(t *testing.T) {
|
||||||
`
|
`
|
||||||
dh, err = ParseSpec(bytes.NewBufferString(spec))
|
dh, err = ParseSpec(bytes.NewBufferString(spec))
|
||||||
|
|
||||||
res, err = Check(dir, dh, nil)
|
res, err = Check(dir, dh, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
@ -306,11 +306,11 @@ func TestCheckNeedsEncoding(t *testing.T) {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
dh, err := Walk(dir, nil, DefaultKeywords)
|
dh, err := Walk(dir, nil, DefaultKeywords, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
res, err := Check(dir, dh, nil)
|
res, err := Check(dir, dh, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -236,7 +236,7 @@ func app() error {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// with a root directory
|
// with a root directory
|
||||||
stateDh, err = mtree.Walk(rootPath, excludes, currentKeywords)
|
stateDh, err = mtree.Walk(rootPath, excludes, currentKeywords, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,12 +15,12 @@ import (
|
||||||
// simple walk of current directory, and imediately check it.
|
// simple walk of current directory, and imediately check it.
|
||||||
// may not be parallelizable.
|
// may not be parallelizable.
|
||||||
func TestCompare(t *testing.T) {
|
func TestCompare(t *testing.T) {
|
||||||
old, err := Walk(".", nil, append(DefaultKeywords, "sha1"))
|
old, err := Walk(".", nil, append(DefaultKeywords, "sha1"), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
new, err := Walk(".", nil, append(DefaultKeywords, "sha1"))
|
new, err := Walk(".", nil, append(DefaultKeywords, "sha1"), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -59,7 +59,7 @@ func TestCompareModified(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Walk the current state.
|
// Walk the current state.
|
||||||
old, err := Walk(dir, nil, append(DefaultKeywords, "sha1"))
|
old, err := Walk(dir, nil, append(DefaultKeywords, "sha1"), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -70,7 +70,7 @@ func TestCompareModified(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Walk the new state.
|
// Walk the new state.
|
||||||
new, err := Walk(dir, nil, append(DefaultKeywords, "sha1"))
|
new, err := Walk(dir, nil, append(DefaultKeywords, "sha1"), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -140,7 +140,7 @@ func TestCompareMissing(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Walk the current state.
|
// Walk the current state.
|
||||||
old, err := Walk(dir, nil, append(DefaultKeywords, "sha1"))
|
old, err := Walk(dir, nil, append(DefaultKeywords, "sha1"), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -159,7 +159,7 @@ func TestCompareMissing(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Walk the new state.
|
// Walk the new state.
|
||||||
new, err := Walk(dir, nil, append(DefaultKeywords, "sha1"))
|
new, err := Walk(dir, nil, append(DefaultKeywords, "sha1"), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -215,7 +215,7 @@ func TestCompareExtra(t *testing.T) {
|
||||||
defer os.RemoveAll(dir)
|
defer os.RemoveAll(dir)
|
||||||
|
|
||||||
// Walk the current state.
|
// Walk the current state.
|
||||||
old, err := Walk(dir, nil, append(DefaultKeywords, "sha1"))
|
old, err := Walk(dir, nil, append(DefaultKeywords, "sha1"), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -237,7 +237,7 @@ func TestCompareExtra(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Walk the new state.
|
// Walk the new state.
|
||||||
new, err := Walk(dir, nil, append(DefaultKeywords, "sha1"))
|
new, err := Walk(dir, nil, append(DefaultKeywords, "sha1"), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -309,7 +309,7 @@ func TestCompareKeys(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Walk the current state.
|
// Walk the current state.
|
||||||
old, err := Walk(dir, nil, append(DefaultKeywords, "sha1"))
|
old, err := Walk(dir, nil, append(DefaultKeywords, "sha1"), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -320,7 +320,7 @@ func TestCompareKeys(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Walk the new state.
|
// Walk the new state.
|
||||||
new, err := Walk(dir, nil, append(DefaultKeywords, "sha1"))
|
new, err := Walk(dir, nil, append(DefaultKeywords, "sha1"), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -382,7 +382,7 @@ func TestTarCompare(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Walk the current state.
|
// Walk the current state.
|
||||||
old, err := Walk(dir, nil, append(DefaultKeywords, "sha1"))
|
old, err := Walk(dir, nil, append(DefaultKeywords, "sha1"), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package mtree
|
||||||
// dhCreator is used in when building a DirectoryHierarchy
|
// dhCreator is used in when building a DirectoryHierarchy
|
||||||
type dhCreator struct {
|
type dhCreator struct {
|
||||||
DH *DirectoryHierarchy
|
DH *DirectoryHierarchy
|
||||||
|
fs FsEval
|
||||||
curSet *Entry
|
curSet *Entry
|
||||||
curDir *Entry
|
curDir *Entry
|
||||||
curEnt *Entry
|
curEnt *Entry
|
||||||
|
|
54
fseval.go
Normal file
54
fseval.go
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
package mtree
|
||||||
|
|
||||||
|
import "os"
|
||||||
|
|
||||||
|
// FsEval is a mock-friendly method of specifying to go-mtree how to carry out
|
||||||
|
// filesystem operations such as opening files and the like. The semantics of
|
||||||
|
// all of these wrappers MUST be identical to the semantics described here.
|
||||||
|
type FsEval interface {
|
||||||
|
// Open must have the same semantics as os.Open.
|
||||||
|
Open(path string) (*os.File, error)
|
||||||
|
|
||||||
|
// Lstat must have the same semantics as os.Lstat.
|
||||||
|
Lstat(path string) (os.FileInfo, error)
|
||||||
|
|
||||||
|
// Readdir must have the same semantics as calling os.Open on the given
|
||||||
|
// path and then returning the result of (*os.File).Readdir(-1).
|
||||||
|
Readdir(path string) ([]os.FileInfo, error)
|
||||||
|
|
||||||
|
// KeywordFunc must return a wrapper around the provided function (in other
|
||||||
|
// words, the returned function must refer to the same keyword).
|
||||||
|
KeywordFunc(fn KeywordFunc) KeywordFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultFsEval is the default implementation of FsEval (and is the default
|
||||||
|
// used if a nil interface is passed to any mtree function). It does not modify
|
||||||
|
// or wrap any of the methods (they all just call out to os.*).
|
||||||
|
type DefaultFsEval struct{}
|
||||||
|
|
||||||
|
// Open must have the same semantics as os.Open.
|
||||||
|
func (fs DefaultFsEval) Open(path string) (*os.File, error) {
|
||||||
|
return os.Open(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lstat must have the same semantics as os.Lstat.
|
||||||
|
func (fs DefaultFsEval) Lstat(path string) (os.FileInfo, error) {
|
||||||
|
return os.Lstat(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Readdir must have the same semantics as calling os.Open on the given
|
||||||
|
// path and then returning the result of (*os.File).Readdir(-1).
|
||||||
|
func (fs DefaultFsEval) Readdir(path string) ([]os.FileInfo, error) {
|
||||||
|
fh, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer fh.Close()
|
||||||
|
return fh.Readdir(-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeywordFunc must return a wrapper around the provided function (in other
|
||||||
|
// words, the returned function must refer to the same keyword).
|
||||||
|
func (fs DefaultFsEval) KeywordFunc(fn KeywordFunc) KeywordFunc {
|
||||||
|
return fn
|
||||||
|
}
|
160
fseval_test.go
Normal file
160
fseval_test.go
Normal file
|
@ -0,0 +1,160 @@
|
||||||
|
package mtree
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var mockTime = time.Unix(1337888823, 88288518233)
|
||||||
|
|
||||||
|
// Here be some dodgy testing. In particular, we have to mess around with some
|
||||||
|
// of the FsEval functions. In particular, we change all of the FileInfos to a
|
||||||
|
// different value.
|
||||||
|
|
||||||
|
type mockFileInfo struct {
|
||||||
|
os.FileInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fi mockFileInfo) Mode() os.FileMode {
|
||||||
|
return os.FileMode(fi.FileInfo.Mode() | 0777)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fi mockFileInfo) ModTime() time.Time {
|
||||||
|
return mockTime
|
||||||
|
}
|
||||||
|
|
||||||
|
type MockFsEval struct {
|
||||||
|
open, lstat, readdir, keywordFunc int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Open must have the same semantics as os.Open.
|
||||||
|
func (fs *MockFsEval) Open(path string) (*os.File, error) {
|
||||||
|
fs.open++
|
||||||
|
return os.Open(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lstat must have the same semantics as os.Lstat.
|
||||||
|
func (fs *MockFsEval) Lstat(path string) (os.FileInfo, error) {
|
||||||
|
fs.lstat++
|
||||||
|
fi, err := os.Lstat(path)
|
||||||
|
return mockFileInfo{fi}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Readdir must have the same semantics as calling os.Open on the given
|
||||||
|
// path and then returning the result of (*os.File).Readdir(-1).
|
||||||
|
func (fs *MockFsEval) Readdir(path string) ([]os.FileInfo, error) {
|
||||||
|
fs.readdir++
|
||||||
|
fh, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer fh.Close()
|
||||||
|
|
||||||
|
fis, err := fh.Readdir(-1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for idx := range fis {
|
||||||
|
fis[idx] = mockFileInfo{fis[idx]}
|
||||||
|
}
|
||||||
|
return fis, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// KeywordFunc must return a wrapper around the provided function (in other
|
||||||
|
// words, the returned function must refer to the same keyword).
|
||||||
|
func (fs *MockFsEval) KeywordFunc(fn KeywordFunc) KeywordFunc {
|
||||||
|
fs.keywordFunc++
|
||||||
|
return fn
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCheckFsEval(t *testing.T) {
|
||||||
|
dir, err := ioutil.TempDir("", "test-check-fs-eval")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(dir) // clean up
|
||||||
|
|
||||||
|
content := []byte("If you hide your ignorance, no one will hit you and you'll never learn.")
|
||||||
|
tmpfn := filepath.Join(dir, "tmpfile")
|
||||||
|
if err := ioutil.WriteFile(tmpfn, content, 0451); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walk this tempdir
|
||||||
|
mock := &MockFsEval{}
|
||||||
|
dh, err := Walk(dir, nil, append(DefaultKeywords, "sha1"), mock)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// Make sure that mock functions have been called.
|
||||||
|
if mock.open == 0 {
|
||||||
|
t.Errorf("mock.Open not called")
|
||||||
|
}
|
||||||
|
if mock.lstat == 0 {
|
||||||
|
t.Errorf("mock.Lstat not called")
|
||||||
|
}
|
||||||
|
if mock.readdir == 0 {
|
||||||
|
t.Errorf("mock.Readdir not called")
|
||||||
|
}
|
||||||
|
if mock.keywordFunc == 0 {
|
||||||
|
t.Errorf("mock.KeywordFunc not called")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for sanity. This ought to pass.
|
||||||
|
mock = &MockFsEval{}
|
||||||
|
res, err := Check(dir, dh, nil, mock)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(res) > 0 {
|
||||||
|
t.Errorf("%#v", res)
|
||||||
|
}
|
||||||
|
// Make sure that mock functions have been called.
|
||||||
|
if mock.open == 0 {
|
||||||
|
t.Errorf("mock.Open not called")
|
||||||
|
}
|
||||||
|
if mock.lstat == 0 {
|
||||||
|
t.Errorf("mock.Lstat not called")
|
||||||
|
}
|
||||||
|
if mock.readdir == 0 {
|
||||||
|
t.Errorf("mock.Readdir not called")
|
||||||
|
}
|
||||||
|
if mock.keywordFunc == 0 {
|
||||||
|
t.Errorf("mock.KeywordFunc not called")
|
||||||
|
}
|
||||||
|
|
||||||
|
// This should FAIL.
|
||||||
|
res, err = Check(dir, dh, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(res) == 0 {
|
||||||
|
t.Errorf("expected Check to fail")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modify the metadata so you can get the right output.
|
||||||
|
if err := os.Chmod(tmpfn, 0777); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := os.Chtimes(tmpfn, mockTime, mockTime); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := os.Chmod(dir, 0777); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := os.Chtimes(dir, mockTime, mockTime); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// It should now succeed.
|
||||||
|
res, err = Check(dir, dh, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(res) > 0 {
|
||||||
|
t.Errorf("%#v", res)
|
||||||
|
}
|
||||||
|
}
|
10
tar_test.go
10
tar_test.go
|
@ -26,7 +26,7 @@ func ExampleStreamer() {
|
||||||
// handle error ...
|
// handle error ...
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := Check("/tmp/dir/", dh, nil)
|
res, err := Check("/tmp/dir/", dh, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// handle error ...
|
// handle error ...
|
||||||
}
|
}
|
||||||
|
@ -145,7 +145,7 @@ func TestArchiveCreation(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test the tar manifest against the actual directory
|
// Test the tar manifest against the actual directory
|
||||||
res, err := Check("./testdata/collection", tdh, []Keyword{"sha1"})
|
res, err := Check("./testdata/collection", tdh, []Keyword{"sha1"}, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -170,7 +170,7 @@ func TestArchiveCreation(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate the directory manifest against the archive
|
// Validate the directory manifest against the archive
|
||||||
dh, err := Walk("./testdata/collection", nil, []Keyword{"sha1"})
|
dh, err := Walk("./testdata/collection", nil, []Keyword{"sha1"}, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -224,7 +224,7 @@ func TestTreeTraversal(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// top-level "." directory will contain contents of traversal.tar
|
// top-level "." directory will contain contents of traversal.tar
|
||||||
res, err = Check("./testdata/.", tdh, []Keyword{"sha1"})
|
res, err = Check("./testdata/.", tdh, []Keyword{"sha1"}, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -262,7 +262,7 @@ func TestTreeTraversal(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Implied top-level "." directory will contain the contents of singlefile.tar
|
// Implied top-level "." directory will contain the contents of singlefile.tar
|
||||||
res, err = Check("./testdata/.", tdh, []Keyword{"sha1"})
|
res, err = Check("./testdata/.", tdh, []Keyword{"sha1"}, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
34
walk.go
34
walk.go
|
@ -26,8 +26,11 @@ var defaultSetKeywords = []KeyVal{"type=file", "nlink=1", "flags=none", "mode=06
|
||||||
// Walk from root directory and assemble the DirectoryHierarchy. excludes
|
// Walk from root directory and assemble the DirectoryHierarchy. excludes
|
||||||
// provided are used to skip paths. keywords are the set to collect from the
|
// provided are used to skip paths. keywords are the set to collect from the
|
||||||
// walked paths. The recommended default list is DefaultKeywords.
|
// walked paths. The recommended default list is DefaultKeywords.
|
||||||
func Walk(root string, excludes []ExcludeFunc, keywords []Keyword) (*DirectoryHierarchy, error) {
|
func Walk(root string, excludes []ExcludeFunc, keywords []Keyword, fsEval FsEval) (*DirectoryHierarchy, error) {
|
||||||
creator := dhCreator{DH: &DirectoryHierarchy{}}
|
if fsEval == nil {
|
||||||
|
fsEval = DefaultFsEval{}
|
||||||
|
}
|
||||||
|
creator := dhCreator{DH: &DirectoryHierarchy{}, fs: fsEval}
|
||||||
// insert signature and metadata comments first (user, machine, tree, date)
|
// insert signature and metadata comments first (user, machine, tree, date)
|
||||||
for _, e := range signatureEntries(root) {
|
for _, e := range signatureEntries(root) {
|
||||||
e.Pos = len(creator.DH.Entries)
|
e.Pos = len(creator.DH.Entries)
|
||||||
|
@ -88,7 +91,7 @@ func Walk(root string, excludes []ExcludeFunc, keywords []Keyword) (*DirectoryHi
|
||||||
err := func() error {
|
err := func() error {
|
||||||
var r io.Reader
|
var r io.Reader
|
||||||
if info.Mode().IsRegular() {
|
if info.Mode().IsRegular() {
|
||||||
fh, err := os.Open(path)
|
fh, err := creator.fs.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -99,7 +102,7 @@ func Walk(root string, excludes []ExcludeFunc, keywords []Keyword) (*DirectoryHi
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("Unknown keyword %q for file %q", keyword, path)
|
return fmt.Errorf("Unknown keyword %q for file %q", keyword, path)
|
||||||
}
|
}
|
||||||
if str, err := keywordFunc(path, info, r); err == nil && str != "" {
|
if str, err := creator.fs.KeywordFunc(keywordFunc)(path, info, r); err == nil && str != "" {
|
||||||
e.Keywords = append(e.Keywords, str)
|
e.Keywords = append(e.Keywords, str)
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -119,7 +122,7 @@ func Walk(root string, excludes []ExcludeFunc, keywords []Keyword) (*DirectoryHi
|
||||||
err := func() error {
|
err := func() error {
|
||||||
var r io.Reader
|
var r io.Reader
|
||||||
if info.Mode().IsRegular() {
|
if info.Mode().IsRegular() {
|
||||||
fh, err := os.Open(path)
|
fh, err := creator.fs.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -130,7 +133,7 @@ func Walk(root string, excludes []ExcludeFunc, keywords []Keyword) (*DirectoryHi
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("Unknown keyword %q for file %q", keyword, path)
|
return fmt.Errorf("Unknown keyword %q for file %q", keyword, path)
|
||||||
}
|
}
|
||||||
str, err := keywordFunc(path, info, r)
|
str, err := creator.fs.KeywordFunc(keywordFunc)(path, info, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -177,7 +180,7 @@ func Walk(root string, excludes []ExcludeFunc, keywords []Keyword) (*DirectoryHi
|
||||||
err := func() error {
|
err := func() error {
|
||||||
var r io.Reader
|
var r io.Reader
|
||||||
if info.Mode().IsRegular() {
|
if info.Mode().IsRegular() {
|
||||||
fh, err := os.Open(path)
|
fh, err := creator.fs.Open(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -188,7 +191,7 @@ func Walk(root string, excludes []ExcludeFunc, keywords []Keyword) (*DirectoryHi
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("Unknown keyword %q for file %q", keyword, path)
|
return fmt.Errorf("Unknown keyword %q for file %q", keyword, path)
|
||||||
}
|
}
|
||||||
str, err := keywordFunc(path, info, r)
|
str, err := creator.fs.KeywordFunc(keywordFunc)(path, info, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -227,7 +230,7 @@ func Walk(root string, excludes []ExcludeFunc, keywords []Keyword) (*DirectoryHi
|
||||||
// large directories Walk can be inefficient.
|
// large directories Walk can be inefficient.
|
||||||
// Walk does not follow symbolic links.
|
// Walk does not follow symbolic links.
|
||||||
func startWalk(c *dhCreator, root string, walkFn filepath.WalkFunc) error {
|
func startWalk(c *dhCreator, root string, walkFn filepath.WalkFunc) error {
|
||||||
info, err := os.Lstat(root)
|
info, err := c.fs.Lstat(root)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return walkFn(root, nil, err)
|
return walkFn(root, nil, err)
|
||||||
}
|
}
|
||||||
|
@ -248,14 +251,14 @@ func walk(c *dhCreator, path string, info os.FileInfo, walkFn filepath.WalkFunc)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
names, err := readOrderedDirNames(path)
|
names, err := readOrderedDirNames(c, path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return walkFn(path, info, err)
|
return walkFn(path, info, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, name := range names {
|
for _, name := range names {
|
||||||
filename := filepath.Join(path, name)
|
filename := filepath.Join(path, name)
|
||||||
fileInfo, err := os.Lstat(filename)
|
fileInfo, err := c.fs.Lstat(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err := walkFn(filename, fileInfo, err); err != nil && err != filepath.SkipDir {
|
if err := walkFn(filename, fileInfo, err); err != nil && err != filepath.SkipDir {
|
||||||
return err
|
return err
|
||||||
|
@ -282,13 +285,8 @@ func walk(c *dhCreator, path string, info os.FileInfo, walkFn filepath.WalkFunc)
|
||||||
|
|
||||||
// readOrderedDirNames reads the directory and returns a sorted list of all
|
// readOrderedDirNames reads the directory and returns a sorted list of all
|
||||||
// entries with non-directories first, followed by directories.
|
// entries with non-directories first, followed by directories.
|
||||||
func readOrderedDirNames(dirname string) ([]string, error) {
|
func readOrderedDirNames(c *dhCreator, dirname string) ([]string, error) {
|
||||||
f, err := os.Open(dirname)
|
infos, err := c.fs.Readdir(dirname)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
infos, err := f.Readdir(-1)
|
|
||||||
f.Close()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestWalk(t *testing.T) {
|
func TestWalk(t *testing.T) {
|
||||||
dh, err := Walk(".", nil, append(DefaultKeywords, "sha1"))
|
dh, err := Walk(".", nil, append(DefaultKeywords, "sha1"), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,7 @@ func TestWalk(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWalkDirectory(t *testing.T) {
|
func TestWalkDirectory(t *testing.T) {
|
||||||
dh, err := Walk(".", []ExcludeFunc{ExcludeNonDirectories}, []Keyword{"type"})
|
dh, err := Walk(".", []ExcludeFunc{ExcludeNonDirectories}, []Keyword{"type"}, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue