diff --git a/fs/copy_test.go b/fs/copy_test.go index 1c1f7c5e9..d63be66ac 100644 --- a/fs/copy_test.go +++ b/fs/copy_test.go @@ -16,14 +16,14 @@ import ( // setxattr fstest.SetXAttr("/home", "trusted.overlay.opaque", "y"), func TestCopyDirectory(t *testing.T) { - apply := fstest.MultiApply( - fstest.CreateDirectory("/etc/", 0755), - fstest.NewTestFile("/etc/hosts", []byte("localhost 127.0.0.1"), 0644), + apply := fstest.Apply( + fstest.CreateDir("/etc/", 0755), + fstest.CreateFile("/etc/hosts", []byte("localhost 127.0.0.1"), 0644), fstest.Link("/etc/hosts", "/etc/hosts.allow"), - fstest.CreateDirectory("/usr/local/lib", 0755), - fstest.NewTestFile("/usr/local/lib/libnothing.so", []byte{0x00, 0x00}, 0755), + fstest.CreateDir("/usr/local/lib", 0755), + fstest.CreateFile("/usr/local/lib/libnothing.so", []byte{0x00, 0x00}, 0755), fstest.Symlink("libnothing.so", "/usr/local/lib/libnothing.so.2"), - fstest.CreateDirectory("/home", 0755), + fstest.CreateDir("/home", 0755), ) if err := testCopy(apply); err != nil { @@ -41,7 +41,7 @@ func testCopy(apply fstest.Applier) error { return errors.Wrap(err, "failed to create temporary directory") } - if err := apply(t1); err != nil { + if err := apply.Apply(t1); err != nil { return errors.Wrap(err, "failed to apply changes") } diff --git a/fs/diff.go b/fs/diff.go index 0ec4c894e..52e2445bc 100644 --- a/fs/diff.go +++ b/fs/diff.go @@ -48,12 +48,17 @@ type Change struct { Path string } -// Changes computes changes between lower and upper calling the -// given change function for each computed change. Callbacks -// will be done serialially and order by path name. +// ChangeFunc is the type of function called for each change +// computed during a directory changes calculation. +type ChangeFunc func(ChangeKind, string, os.FileInfo, error) error + +// Changes computes changes between two directories calling the +// given change function for each computed change. The first +// directory is intended to the base directory and second +// directory the changed directory. // -// Changes are ordered by name and should be appliable in the -// order in which they received. +// The change callback is called by the order of path names and +// should be appliable in that order. // Due to this apply ordering, the following is true // - Removed directory trees only create a single change for the root // directory removed. Remaining changes are implied. @@ -62,7 +67,7 @@ type Change struct { // by the removal of the parent directory. // // Opaque directories will not be treated specially and each file -// removed from the lower will show up as a removal +// removed from the base directory will show up as a removal. // // File content comparisons will be done on files which have timestamps // which may have been truncated. If either of the files being compared @@ -71,22 +76,20 @@ type Change struct { // nanosecond values where one of those values is zero, the files will // be considered unchanged if the content is the same. This behavior // is to account for timestamp truncation during archiving. -func Changes(ctx context.Context, upper, lower string, ch func(Change, os.FileInfo) error) error { - if lower == "" { - logrus.Debugf("Using single walk diff for %s", upper) - return addDirChanges(ctx, ch, upper) - } else if diffOptions := detectDirDiff(upper, lower); diffOptions != nil { - logrus.Debugf("Using single walk diff for %s from %s", diffOptions.diffDir, lower) - return diffDirChanges(ctx, ch, lower, diffOptions) +func Changes(ctx context.Context, a, b string, changeFn ChangeFunc) error { + if a == "" { + logrus.Debugf("Using single walk diff for %s", b) + return addDirChanges(ctx, changeFn, b) + } else if diffOptions := detectDirDiff(b, a); diffOptions != nil { + logrus.Debugf("Using single walk diff for %s from %s", diffOptions.diffDir, a) + return diffDirChanges(ctx, changeFn, a, diffOptions) } - logrus.Debugf("Using double walk diff for %s from %s", upper, lower) - return doubleWalkDiff(ctx, ch, upper, lower) + logrus.Debugf("Using double walk diff for %s from %s", b, a) + return doubleWalkDiff(ctx, changeFn, a, b) } -type changeFn func(Change, os.FileInfo) error - -func addDirChanges(ctx context.Context, changes changeFn, root string) error { +func addDirChanges(ctx context.Context, changeFn ChangeFunc, root string) error { return filepath.Walk(root, func(path string, f os.FileInfo, err error) error { if err != nil { return err @@ -105,25 +108,20 @@ func addDirChanges(ctx context.Context, changes changeFn, root string) error { return nil } - change := Change{ - Path: path, - Kind: ChangeKindAdd, - } - - return changes(change, f) + return changeFn(ChangeKindAdd, path, f, nil) }) } // diffDirOptions is used when the diff can be directly calculated from -// a diff directory to its lower, without walking both trees. +// a diff directory to its base, without walking both trees. type diffDirOptions struct { diffDir string skipChange func(string) (bool, error) deleteChange func(string, string, os.FileInfo) (string, error) } -// diffDirChanges walks the diff directory and compares changes against the lower. -func diffDirChanges(ctx context.Context, changes changeFn, lower string, o *diffDirOptions) error { +// diffDirChanges walks the diff directory and compares changes against the base. +func diffDirChanges(ctx context.Context, changeFn ChangeFunc, base string, o *diffDirOptions) error { changedDirs := make(map[string]struct{}) return filepath.Walk(o.diffDir, func(path string, f os.FileInfo, err error) error { if err != nil { @@ -152,9 +150,7 @@ func diffDirChanges(ctx context.Context, changes changeFn, lower string, o *diff } } - change := Change{ - Path: path, - } + var kind ChangeKind deletedFile, err := o.deleteChange(o.diffDir, path, f) if err != nil { @@ -163,20 +159,20 @@ func diffDirChanges(ctx context.Context, changes changeFn, lower string, o *diff // Find out what kind of modification happened if deletedFile != "" { - change.Path = deletedFile - change.Kind = ChangeKindDelete + path = deletedFile + kind = ChangeKindDelete f = nil } else { // Otherwise, the file was added - change.Kind = ChangeKindAdd + kind = ChangeKindAdd - // ...Unless it already existed in a lower, in which case, it's a modification - stat, err := os.Stat(filepath.Join(lower, path)) + // ...Unless it already existed in a base, in which case, it's a modification + stat, err := os.Stat(filepath.Join(base, path)) if err != nil && !os.IsNotExist(err) { return err } if err == nil { - // The file existed in the lower, so that's a modification + // The file existed in the base, so that's a modification // However, if it's a directory, maybe it wasn't actually modified. // If you modify /foo/bar/baz, then /foo will be part of the changed files only because it's the parent of bar @@ -186,7 +182,7 @@ func diffDirChanges(ctx context.Context, changes changeFn, lower string, o *diff return nil } } - change.Kind = ChangeKindModify + kind = ChangeKindModify } } @@ -197,30 +193,23 @@ func diffDirChanges(ctx context.Context, changes changeFn, lower string, o *diff if f.IsDir() { changedDirs[path] = struct{}{} } - if change.Kind == ChangeKindAdd || change.Kind == ChangeKindDelete { + if kind == ChangeKindAdd || kind == ChangeKindDelete { parent := filepath.Dir(path) if _, ok := changedDirs[parent]; !ok && parent != "/" { pi, err := os.Stat(filepath.Join(o.diffDir, parent)) - if err != nil { - return err - } - dirChange := Change{ - Path: parent, - Kind: ChangeKindModify, - } - if err := changes(dirChange, pi); err != nil { + if err := changeFn(ChangeKindModify, parent, pi, err); err != nil { return err } changedDirs[parent] = struct{}{} } } - return changes(change, f) + return changeFn(kind, path, f, nil) }) } // doubleWalkDiff walks both directories to create a diff -func doubleWalkDiff(ctx context.Context, changes changeFn, upper, lower string) (err error) { +func doubleWalkDiff(ctx context.Context, changeFn ChangeFunc, a, b string) (err error) { g, ctx := errgroup.WithContext(ctx) var ( @@ -232,11 +221,11 @@ func doubleWalkDiff(ctx context.Context, changes changeFn, upper, lower string) ) g.Go(func() error { defer close(c1) - return pathWalk(ctx, lower, c1) + return pathWalk(ctx, a, c1) }) g.Go(func() error { defer close(c2) - return pathWalk(ctx, upper, c2) + return pathWalk(ctx, b, c2) }) g.Go(func() error { for c1 != nil || c2 != nil { @@ -264,8 +253,8 @@ func doubleWalkDiff(ctx context.Context, changes changeFn, upper, lower string) } var f os.FileInfo - c := pathChange(f1, f2) - switch c.Kind { + k, p := pathChange(f1, f2) + switch k { case ChangeKindAdd: if rmdir != "" { rmdir = "" @@ -301,7 +290,7 @@ func doubleWalkDiff(ctx context.Context, changes changeFn, upper, lower string) continue } } - if err := changes(c, f); err != nil { + if err := changeFn(k, p, f, nil); err != nil { return err } } diff --git a/fs/diff_test.go b/fs/diff_test.go index 334bceb5f..fb8f012e5 100644 --- a/fs/diff_test.go +++ b/fs/diff_test.go @@ -21,21 +21,21 @@ import ( // - hardlink test func TestSimpleDiff(t *testing.T) { - l1 := fstest.MultiApply( - fstest.CreateDirectory("/etc", 0755), - fstest.NewTestFile("/etc/hosts", []byte("mydomain 10.0.0.1"), 0644), - fstest.NewTestFile("/etc/profile", []byte("PATH=/usr/bin"), 0644), - fstest.NewTestFile("/etc/unchanged", []byte("PATH=/usr/bin"), 0644), - fstest.NewTestFile("/etc/unexpected", []byte("#!/bin/sh"), 0644), + l1 := fstest.Apply( + fstest.CreateDir("/etc", 0755), + fstest.CreateFile("/etc/hosts", []byte("mydomain 10.0.0.1"), 0644), + fstest.CreateFile("/etc/profile", []byte("PATH=/usr/bin"), 0644), + fstest.CreateFile("/etc/unchanged", []byte("PATH=/usr/bin"), 0644), + fstest.CreateFile("/etc/unexpected", []byte("#!/bin/sh"), 0644), ) - l2 := fstest.MultiApply( - fstest.NewTestFile("/etc/hosts", []byte("mydomain 10.0.0.120"), 0644), - fstest.NewTestFile("/etc/profile", []byte("PATH=/usr/bin"), 0666), - fstest.CreateDirectory("/root", 0700), - fstest.NewTestFile("/root/.bashrc", []byte("PATH=/usr/sbin:/usr/bin"), 0644), + l2 := fstest.Apply( + fstest.CreateFile("/etc/hosts", []byte("mydomain 10.0.0.120"), 0644), + fstest.CreateFile("/etc/profile", []byte("PATH=/usr/bin"), 0666), + fstest.CreateDir("/root", 0700), + fstest.CreateFile("/root/.bashrc", []byte("PATH=/usr/sbin:/usr/bin"), 0644), fstest.RemoveFile("/etc/unexpected"), ) - diff := []Change{ + diff := []testChange{ Modify("/etc/hosts"), Modify("/etc/profile"), Delete("/etc/unexpected"), @@ -49,18 +49,18 @@ func TestSimpleDiff(t *testing.T) { } func TestDirectoryReplace(t *testing.T) { - l1 := fstest.MultiApply( - fstest.CreateDirectory("/dir1", 0755), - fstest.NewTestFile("/dir1/f1", []byte("#####"), 0644), - fstest.CreateDirectory("/dir1/f2", 0755), - fstest.NewTestFile("/dir1/f2/f3", []byte("#!/bin/sh"), 0644), + l1 := fstest.Apply( + fstest.CreateDir("/dir1", 0755), + fstest.CreateFile("/dir1/f1", []byte("#####"), 0644), + fstest.CreateDir("/dir1/f2", 0755), + fstest.CreateFile("/dir1/f2/f3", []byte("#!/bin/sh"), 0644), ) - l2 := fstest.MultiApply( - fstest.NewTestFile("/dir1/f11", []byte("#New file here"), 0644), + l2 := fstest.Apply( + fstest.CreateFile("/dir1/f11", []byte("#New file here"), 0644), fstest.RemoveFile("/dir1/f2"), - fstest.NewTestFile("/dir1/f2", []byte("Now file"), 0666), + fstest.CreateFile("/dir1/f2", []byte("Now file"), 0666), ) - diff := []Change{ + diff := []testChange{ Add("/dir1/f11"), Modify("/dir1/f2"), } @@ -71,15 +71,15 @@ func TestDirectoryReplace(t *testing.T) { } func TestRemoveDirectoryTree(t *testing.T) { - l1 := fstest.MultiApply( - fstest.CreateDirectory("/dir1/dir2/dir3", 0755), - fstest.NewTestFile("/dir1/f1", []byte("f1"), 0644), - fstest.NewTestFile("/dir1/dir2/f2", []byte("f2"), 0644), + l1 := fstest.Apply( + fstest.CreateDir("/dir1/dir2/dir3", 0755), + fstest.CreateFile("/dir1/f1", []byte("f1"), 0644), + fstest.CreateFile("/dir1/dir2/f2", []byte("f2"), 0644), ) - l2 := fstest.MultiApply( + l2 := fstest.Apply( fstest.RemoveFile("/dir1"), ) - diff := []Change{ + diff := []testChange{ Delete("/dir1"), } @@ -89,15 +89,15 @@ func TestRemoveDirectoryTree(t *testing.T) { } func TestFileReplace(t *testing.T) { - l1 := fstest.MultiApply( - fstest.NewTestFile("/dir1", []byte("a file, not a directory"), 0644), + l1 := fstest.Apply( + fstest.CreateFile("/dir1", []byte("a file, not a directory"), 0644), ) - l2 := fstest.MultiApply( + l2 := fstest.Apply( fstest.RemoveFile("/dir1"), - fstest.CreateDirectory("/dir1/dir2", 0755), - fstest.NewTestFile("/dir1/dir2/f1", []byte("also a file"), 0644), + fstest.CreateDir("/dir1/dir2", 0755), + fstest.CreateFile("/dir1/dir2/f1", []byte("also a file"), 0644), ) - diff := []Change{ + diff := []testChange{ Modify("/dir1"), Add("/dir1/dir2"), Add("/dir1/dir2/f1"), @@ -112,31 +112,31 @@ func TestUpdateWithSameTime(t *testing.T) { tt := time.Now().Truncate(time.Second) t1 := tt.Add(5 * time.Nanosecond) t2 := tt.Add(6 * time.Nanosecond) - l1 := fstest.MultiApply( - fstest.NewTestFile("/file-modified-time", []byte("1"), 0644), + l1 := fstest.Apply( + fstest.CreateFile("/file-modified-time", []byte("1"), 0644), fstest.Chtime("/file-modified-time", t1), - fstest.NewTestFile("/file-no-change", []byte("1"), 0644), + fstest.CreateFile("/file-no-change", []byte("1"), 0644), fstest.Chtime("/file-no-change", t1), - fstest.NewTestFile("/file-same-time", []byte("1"), 0644), + fstest.CreateFile("/file-same-time", []byte("1"), 0644), fstest.Chtime("/file-same-time", t1), - fstest.NewTestFile("/file-truncated-time-1", []byte("1"), 0644), + fstest.CreateFile("/file-truncated-time-1", []byte("1"), 0644), fstest.Chtime("/file-truncated-time-1", t1), - fstest.NewTestFile("/file-truncated-time-2", []byte("1"), 0644), + fstest.CreateFile("/file-truncated-time-2", []byte("1"), 0644), fstest.Chtime("/file-truncated-time-2", tt), ) - l2 := fstest.MultiApply( - fstest.NewTestFile("/file-modified-time", []byte("2"), 0644), + l2 := fstest.Apply( + fstest.CreateFile("/file-modified-time", []byte("2"), 0644), fstest.Chtime("/file-modified-time", t2), - fstest.NewTestFile("/file-no-change", []byte("1"), 0644), + fstest.CreateFile("/file-no-change", []byte("1"), 0644), fstest.Chtime("/file-no-change", tt), // use truncated time, should be regarded as no change - fstest.NewTestFile("/file-same-time", []byte("2"), 0644), + fstest.CreateFile("/file-same-time", []byte("2"), 0644), fstest.Chtime("/file-same-time", t1), - fstest.NewTestFile("/file-truncated-time-1", []byte("2"), 0644), + fstest.CreateFile("/file-truncated-time-1", []byte("2"), 0644), fstest.Chtime("/file-truncated-time-1", tt), - fstest.NewTestFile("/file-truncated-time-2", []byte("2"), 0644), + fstest.CreateFile("/file-truncated-time-2", []byte("2"), 0644), fstest.Chtime("/file-truncated-time-2", tt), ) - diff := []Change{ + diff := []testChange{ // "/file-same-time" excluded because matching non-zero nanosecond values Modify("/file-modified-time"), Modify("/file-truncated-time-1"), @@ -148,7 +148,7 @@ func TestUpdateWithSameTime(t *testing.T) { } } -func testDiffWithBase(base, diff fstest.Applier, expected []Change) error { +func testDiffWithBase(base, diff fstest.Applier, expected []testChange) error { t1, err := ioutil.TempDir("", "diff-with-base-lower-") if err != nil { return errors.Wrap(err, "failed to create temp dir") @@ -160,7 +160,7 @@ func testDiffWithBase(base, diff fstest.Applier, expected []Change) error { } defer os.RemoveAll(t2) - if err := base(t1); err != nil { + if err := base.Apply(t1); err != nil { return errors.Wrap(err, "failed to apply base filesytem") } @@ -168,11 +168,11 @@ func testDiffWithBase(base, diff fstest.Applier, expected []Change) error { return errors.Wrap(err, "failed to copy base directory") } - if err := diff(t2); err != nil { + if err := diff.Apply(t2); err != nil { return errors.Wrap(err, "failed to apply diff filesystem") } - changes, err := collectChanges(t2, t1) + changes, err := collectChanges(t1, t2) if err != nil { return errors.Wrap(err, "failed to collect changes") } @@ -181,14 +181,14 @@ func testDiffWithBase(base, diff fstest.Applier, expected []Change) error { } func TestBaseDirectoryChanges(t *testing.T) { - apply := fstest.MultiApply( - fstest.CreateDirectory("/etc", 0755), - fstest.NewTestFile("/etc/hosts", []byte("mydomain 10.0.0.1"), 0644), - fstest.NewTestFile("/etc/profile", []byte("PATH=/usr/bin"), 0644), - fstest.CreateDirectory("/root", 0700), - fstest.NewTestFile("/root/.bashrc", []byte("PATH=/usr/sbin:/usr/bin"), 0644), + apply := fstest.Apply( + fstest.CreateDir("/etc", 0755), + fstest.CreateFile("/etc/hosts", []byte("mydomain 10.0.0.1"), 0644), + fstest.CreateFile("/etc/profile", []byte("PATH=/usr/bin"), 0644), + fstest.CreateDir("/root", 0700), + fstest.CreateFile("/root/.bashrc", []byte("PATH=/usr/sbin:/usr/bin"), 0644), ) - changes := []Change{ + changes := []testChange{ Add("/etc"), Add("/etc/hosts"), Add("/etc/profile"), @@ -201,18 +201,18 @@ func TestBaseDirectoryChanges(t *testing.T) { } } -func testDiffWithoutBase(apply fstest.Applier, expected []Change) error { +func testDiffWithoutBase(apply fstest.Applier, expected []testChange) error { tmp, err := ioutil.TempDir("", "diff-without-base-") if err != nil { return errors.Wrap(err, "failed to create temp dir") } defer os.RemoveAll(tmp) - if err := apply(tmp); err != nil { + if err := apply.Apply(tmp); err != nil { return errors.Wrap(err, "failed to apply filesytem changes") } - changes, err := collectChanges(tmp, "") + changes, err := collectChanges("", tmp) if err != nil { return errors.Wrap(err, "failed to collect changes") } @@ -220,13 +220,13 @@ func testDiffWithoutBase(apply fstest.Applier, expected []Change) error { return checkChanges(tmp, changes, expected) } -func checkChanges(root string, changes []testChange, expected []Change) error { +func checkChanges(root string, changes, expected []testChange) error { if len(changes) != len(expected) { - return errors.Errorf("Unexpected number of changes:\n%s", diffString(convertTestChanges(changes), expected)) + return errors.Errorf("Unexpected number of changes:\n%s", diffString(changes, expected)) } for i := range changes { if changes[i].Path != expected[i].Path || changes[i].Kind != expected[i].Kind { - return errors.Errorf("Unexpected change at %d:\n%s", i, diffString(convertTestChanges(changes), expected)) + return errors.Errorf("Unexpected change at %d:\n%s", i, diffString(changes, expected)) } if changes[i].Kind != ChangeKindDelete { filename := filepath.Join(root, changes[i].Path) @@ -254,18 +254,23 @@ func checkChanges(root string, changes []testChange, expected []Change) error { } type testChange struct { - Change + Kind ChangeKind + Path string FileInfo os.FileInfo Source string } -func collectChanges(upper, lower string) ([]testChange, error) { +func collectChanges(a, b string) ([]testChange, error) { changes := []testChange{} - err := Changes(context.Background(), upper, lower, func(c Change, f os.FileInfo) error { + err := Changes(context.Background(), a, b, func(k ChangeKind, p string, f os.FileInfo, err error) error { + if err != nil { + return err + } changes = append(changes, testChange{ - Change: c, + Kind: k, + Path: p, FileInfo: f, - Source: filepath.Join(upper, c.Path), + Source: filepath.Join(b, p), }) return nil }) @@ -276,20 +281,12 @@ func collectChanges(upper, lower string) ([]testChange, error) { return changes, nil } -func convertTestChanges(c []testChange) []Change { - nc := make([]Change, len(c)) - for i := range c { - nc[i] = c[i].Change - } - return nc -} - -func diffString(c1, c2 []Change) string { +func diffString(c1, c2 []testChange) string { return fmt.Sprintf("got(%d):\n%s\nexpected(%d):\n%s", len(c1), changesString(c1), len(c2), changesString(c2)) } -func changesString(c []Change) string { +func changesString(c []testChange) string { strs := make([]string, len(c)) for i := range c { strs[i] = fmt.Sprintf("\t%s\t%s", c[i].Kind, c[i].Path) @@ -297,22 +294,22 @@ func changesString(c []Change) string { return strings.Join(strs, "\n") } -func Add(p string) Change { - return Change{ +func Add(p string) testChange { + return testChange{ Kind: ChangeKindAdd, Path: p, } } -func Delete(p string) Change { - return Change{ +func Delete(p string) testChange { + return testChange{ Kind: ChangeKindDelete, Path: p, } } -func Modify(p string) Change { - return Change{ +func Modify(p string) testChange { + return testChange{ Kind: ChangeKindModify, Path: p, } diff --git a/fs/fstest/file.go b/fs/fstest/file.go index 640fad6f1..fab4f6818 100644 --- a/fs/fstest/file.go +++ b/fs/fstest/file.go @@ -10,12 +10,20 @@ import ( ) // Applier applies single file changes -type Applier func(root string) error +type Applier interface { + Apply(root string) error +} -// NewTestFile returns a file applier which creates a file as the +type applyFn func(root string) error + +func (a applyFn) Apply(root string) error { + return a(root) +} + +// CreateFile returns a file applier which creates a file as the // provided name with the given content and permission. -func NewTestFile(name string, content []byte, perm os.FileMode) Applier { - return func(root string) error { +func CreateFile(name string, content []byte, perm os.FileMode) Applier { + return applyFn(func(root string) error { fullPath := filepath.Join(root, name) if err := ioutil.WriteFile(fullPath, content, perm); err != nil { return err @@ -26,67 +34,67 @@ func NewTestFile(name string, content []byte, perm os.FileMode) Applier { } return nil - } + }) } // RemoveFile returns a file applier which removes the provided file name func RemoveFile(name string) Applier { - return func(root string) error { + return applyFn(func(root string) error { return os.RemoveAll(filepath.Join(root, name)) - } + }) } -// CreateDirectory returns a file applier to create the directory with +// CreateDir returns a file applier to create the directory with // the provided name and permission -func CreateDirectory(name string, perm os.FileMode) Applier { - return func(root string) error { +func CreateDir(name string, perm os.FileMode) Applier { + return applyFn(func(root string) error { fullPath := filepath.Join(root, name) if err := os.MkdirAll(fullPath, perm); err != nil { return err } return nil - } + }) } // Rename returns a file applier which renames a file func Rename(old, new string) Applier { - return func(root string) error { + return applyFn(func(root string) error { return os.Rename(filepath.Join(root, old), filepath.Join(root, new)) - } + }) } // Chown returns a file applier which changes the ownership of a file func Chown(name string, uid, gid int) Applier { - return func(root string) error { + return applyFn(func(root string) error { return os.Chown(filepath.Join(root, name), uid, gid) - } + }) } // Chtime changes access and mod time of file func Chtime(name string, t time.Time) Applier { - return func(root string) error { + return applyFn(func(root string) error { return os.Chtimes(filepath.Join(root, name), t, t) - } + }) } // Symlink returns a file applier which creates a symbolic link func Symlink(oldname, newname string) Applier { - return func(root string) error { + return applyFn(func(root string) error { return os.Symlink(oldname, filepath.Join(root, newname)) - } + }) } // Link returns a file applier which creates a hard link func Link(oldname, newname string) Applier { - return func(root string) error { + return applyFn(func(root string) error { return os.Link(filepath.Join(root, oldname), filepath.Join(root, newname)) - } + }) } func SetXAttr(name, key, value string) Applier { - return func(root string) error { + return applyFn(func(root string) error { return sysx.LSetxattr(name, key, []byte(value), 0) - } + }) } // TODO: Make platform specific, windows applier is always no-op @@ -96,14 +104,14 @@ func SetXAttr(name, key, value string) Applier { // } //} -// MultiApply returns a new applier from the given appliers -func MultiApply(appliers ...Applier) Applier { - return func(root string) error { +// Apply returns a new applier from the given appliers +func Apply(appliers ...Applier) Applier { + return applyFn(func(root string) error { for _, a := range appliers { - if err := a(root); err != nil { + if err := a.Apply(root); err != nil { return err } } return nil - } + }) } diff --git a/fs/path.go b/fs/path.go index 342f392c0..a46d0fcbd 100644 --- a/fs/path.go +++ b/fs/path.go @@ -15,42 +15,27 @@ type currentPath struct { fullPath string } -func pathChange(lower, upper *currentPath) Change { +func pathChange(lower, upper *currentPath) (ChangeKind, string) { if lower == nil { if upper == nil { panic("cannot compare nil paths") } - return Change{ - Kind: ChangeKindAdd, - Path: upper.path, - } + return ChangeKindAdd, upper.path } if upper == nil { - return Change{ - Kind: ChangeKindDelete, - Path: lower.path, - } + return ChangeKindDelete, lower.path } // TODO: compare by directory switch i := strings.Compare(lower.path, upper.path); { case i < 0: // File in lower that is not in upper - return Change{ - Kind: ChangeKindDelete, - Path: lower.path, - } + return ChangeKindDelete, lower.path case i > 0: // File in upper that is not in lower - return Change{ - Kind: ChangeKindAdd, - Path: upper.path, - } + return ChangeKindAdd, upper.path default: - return Change{ - Kind: ChangeKindModify, - Path: upper.path, - } + return ChangeKindModify, upper.path } }