Skip to content

Commit

Permalink
*: update -u behavior
Browse files Browse the repository at this point in the history
Fixes #16

In attempt to close #16 I've
uncovered that the update was missing a function for symlink.
Additionally the update was not even opperating on the correct directory
hierarchy.

I've uncovered that os.Chtimes follows the symlink, and presumably only
Linux has an obscure way to set the mtime/atime on a symlink itself. So
I've made a custom lchtimes().

Also Mode follows through the symlink, and symlinks only ever have a
mode of 0777, so don't set them.

Lastly, directories need to have their mtime/atime set in a reverse
order after all other updates have been done. This is going to require
something like a `container/heap` to be unwound.

Also, each updateFunc will _only_ perform the update if it is needed. Much less
invasive this way.

Signed-off-by: Vincent Batts <vbatts@hashbangbash.com>
  • Loading branch information
vbatts committed Jun 30, 2017
1 parent 593cfb6 commit 54fb6b3
Show file tree
Hide file tree
Showing 9 changed files with 303 additions and 17 deletions.
31 changes: 24 additions & 7 deletions cmd/gomtree/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,18 +254,36 @@ func app() error {
if *flUpdateAttributes && stateDh != nil {
// -u
// this comes before the next case, intentionally.
result, err := mtree.Update(rootPath, specDh, mtree.DefaultUpdateKeywords, nil)
if err != nil {
return err
}
if result != nil && len(result) > 0 {
fmt.Printf("%#v\n", result)
}

// TODO brainstorm where to allow setting of xattrs. Maybe a new flag that allows a comma delimited list of keywords to update?
updateKeywords := []mtree.Keyword{"uid", "gid", "mode"}

result, err := mtree.Update(rootPath, stateDh, updateKeywords, nil)
var res []mtree.InodeDelta
// only check the keywords that we just updated
res, err = mtree.Check(rootPath, specDh, mtree.DefaultUpdateKeywords, nil)
if err != nil {
return err
}
if res != nil {
out := formatFunc(res)
if _, err := os.Stdout.Write([]byte(out)); err != nil {
return err
}

if result != nil {
fmt.Printf("%#v\n", result)
// TODO: This should be a flag. Allowing files to be added and
// removed and still returning "it's all good" is simply
// unsafe IMO.
for _, diff := range res {
if diff.Type() == mtree.Modified {
return fmt.Errorf("mainfest validation failed")
}
}
}

return nil
}

Expand Down Expand Up @@ -300,7 +318,6 @@ func app() error {
// This is a validation.
if specDh != nil && stateDh != nil {
var res []mtree.InodeDelta

res, err = mtree.Compare(specDh, stateDh, currentKeywords)
if err != nil {
return err
Expand Down
10 changes: 10 additions & 0 deletions entry.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,16 @@ func (e Entry) AllKeys() []KeyVal {
return e.Keywords
}

// IsDir checks the type= value for this entry on whether it is a directory
func (e Entry) IsDir() bool {
for _, kv := range e.AllKeys() {
if kv.Keyword().Prefix() == "type" {
return kv.Value() == "dir"
}
}
return false
}

// EntryType are the formats of lines in an mtree spec file
type EntryType int

Expand Down
18 changes: 18 additions & 0 deletions stat_unix.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// +build !windows

package mtree

import (
"os"
"syscall"
)

func statIsUid(stat os.FileInfo, uid int) bool {
statT := stat.Sys().(*syscall.Stat_t)
return statT.Uid == uint32(uid)
}

func statIsGid(stat os.FileInfo, gid int) bool {
statT := stat.Sys().(*syscall.Stat_t)
return statT.Gid == uint32(gid)
}
12 changes: 12 additions & 0 deletions stat_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// +build windows

package mtree

import "os"

func statIsUid(stat os.FileInfo, uid int) bool {
return false
}
func statIsGid(stat os.FileInfo, uid int) bool {
return false
}
76 changes: 70 additions & 6 deletions update.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package mtree

import (
"container/heap"
"os"
"sort"

Expand All @@ -12,8 +13,9 @@ var DefaultUpdateKeywords = []Keyword{
"uid",
"gid",
"mode",
"time",
"xattr",
"link",
"time",
}

// Update attempts to set the attributes of root directory path, given the values of `keywords` in dh DirectoryHierarchy.
Expand All @@ -29,6 +31,11 @@ func Update(root string, dh *DirectoryHierarchy, keywords []Keyword, fs FsEval)
}
sort.Sort(byPos(creator.DH.Entries))

// This is for deferring the update of mtimes of directories, to unwind them
// in a most specific path first
h := &pathUpdateHeap{}
heap.Init(h)

results := []InodeDelta{}
for i, e := range creator.DH.Entries {
switch e.Type {
Expand Down Expand Up @@ -62,6 +69,19 @@ func Update(root string, dh *DirectoryHierarchy, keywords []Keyword, fs FsEval)
logrus.Debugf("no UpdateKeywordFunc for %s; skipping", kv.Keyword())
continue
}

// TODO check for the type=dir of the entry as well
if kv.Keyword().Prefix() == "time" && e.IsDir() {
heap.Push(h, pathUpdate{
Path: pathname,
E: e,
KV: kv,
Func: ukFunc,
})

continue
}

if _, err := ukFunc(pathname, kv); err != nil {
results = append(results, InodeDelta{
diff: ErrorDifference,
Expand All @@ -75,16 +95,60 @@ func Update(root string, dh *DirectoryHierarchy, keywords []Keyword, fs FsEval)
},
}})
}
// XXX really would be great to have a Check() or Compare() right here,
// to compare each entry as it is encountered, rather than just running
// Check() on this path after the whole update is finished.
}
}
}

for h.Len() > 0 {
pu := heap.Pop(h).(pathUpdate)
if _, err := pu.Func(pu.Path, pu.KV); err != nil {
results = append(results, InodeDelta{
diff: ErrorDifference,
path: pu.Path,
old: pu.E,
keys: []KeyDelta{
{
diff: ErrorDifference,
name: pu.KV.Keyword(),
err: err,
},
}})
}
}
return results, nil
}

// Result of an "update" returns the produced results
type Result struct {
Path string
Keyword Keyword
Got string
type pathUpdateHeap []pathUpdate

func (h pathUpdateHeap) Len() int { return len(h) }
func (h pathUpdateHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }

// This may end up looking backwards, but for container/heap, Less evaluates
// the negative priority. So when popping members of the array, it will be
// sorted by least. For this use-case, we want the most-qualified-name popped
// first (the longest path name), such that "." is the last entry popped.
func (h pathUpdateHeap) Less(i, j int) bool {
return len(h[i].Path) > len(h[j].Path)
}

func (h *pathUpdateHeap) Push(x interface{}) {
*h = append(*h, x.(pathUpdate))
}

func (h *pathUpdateHeap) Pop() interface{} {
old := *h
n := len(old)
x := old[n-1]
*h = old[0 : n-1]
return x
}

type pathUpdate struct {
Path string
E Entry
KV KeyVal
Func UpdateKeywordFunc
}
31 changes: 31 additions & 0 deletions update_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
package mtree

import (
"container/heap"
"encoding/json"
"io/ioutil"
"os"
Expand All @@ -11,8 +12,14 @@ import (
"strconv"
"testing"
"time"

"github.com/Sirupsen/logrus"
)

func init() {
logrus.SetLevel(logrus.DebugLevel)
}

func TestUpdate(t *testing.T) {
content := []byte("I know half of you half as well as I ought to")
dir, err := ioutil.TempDir("", "test-check-keywords")
Expand Down Expand Up @@ -103,3 +110,27 @@ func TestUpdate(t *testing.T) {
}

}

func TestPathUpdateHeap(t *testing.T) {
h := &pathUpdateHeap{
pathUpdate{Path: "not/the/longest"},
pathUpdate{Path: "almost/the/longest"},
pathUpdate{Path: "."},
pathUpdate{Path: "short"},
}
heap.Init(h)
v := "this/is/one/is/def/the/longest"
heap.Push(h, pathUpdate{Path: v})

longest := len(v)
var p string
for h.Len() > 0 {
p = heap.Pop(h).(pathUpdate).Path
if len(p) > longest {
t.Errorf("expected next path to be shorter, but it was not %q is longer than %d", p, longest)
}
}
if p != "." {
t.Errorf("expected \".\" to be the last, but got %q", p)
}
}
Loading

0 comments on commit 54fb6b3

Please sign in to comment.