*: xattr can Update()

This is a gnarly patchset that has been mashed together.
It uncovered that some aspects of Check were never really working
correctly for `xattr` keywords, but also the `Update()` had been left
undone for a while.

This includes some API changes around the `Keyword` and `KeyVal` types.

Also I would like to update the signature for the `UpdateKeywordFunc` to
just accept a `KeyVal` as an argugment, rather than a keyword AND the
value. with this context there would be no need to guess on the value of
what's passed to the xattr update function of whether it needs or
already is base64 encoded.

Signed-off-by: Vincent Batts <vbatts@hashbangbash.com>
This commit is contained in:
Vincent Batts 2017-06-15 14:54:58 -04:00
parent fb4ec19981
commit ed464af779
Signed by: vbatts
GPG Key ID: 10937E57733F1362
19 changed files with 398 additions and 258 deletions

View File

@ -12,7 +12,7 @@ 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"), nil) dh, err := Walk(".", nil, append(DefaultKeywords, []Keyword{"sha1", "xattr"}...), nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@ -7,10 +7,10 @@ import (
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"log"
"os" "os"
"strings" "strings"
"github.com/Sirupsen/logrus"
"github.com/vbatts/go-mtree" "github.com/vbatts/go-mtree"
) )
@ -37,7 +37,7 @@ var (
func main() { func main() {
// so that defers cleanly exec // so that defers cleanly exec
if err := app(); err != nil { if err := app(); err != nil {
log.Fatal(err) logrus.Fatal(err)
} }
} }
@ -46,6 +46,7 @@ func app() error {
if *flDebug { if *flDebug {
os.Setenv("DEBUG", "1") os.Setenv("DEBUG", "1")
logrus.SetLevel(logrus.DebugLevel)
} }
if *flVersion { if *flVersion {

View File

@ -197,7 +197,7 @@ func compareEntry(oldEntry, newEntry Entry) ([]KeyDelta, error) {
for _, kv := range oldKeys { for _, kv := range oldKeys {
key := kv.Keyword() key := kv.Keyword()
// only add this diff if the new keys has this keyword // only add this diff if the new keys has this keyword
if key != "tar_time" && key != "time" && key != "xattr" && HasKeyword(newKeys, key) == emptyKV { if key != "tar_time" && key != "time" && key != "xattr" && len(HasKeyword(newKeys, key)) == 0 {
continue continue
} }
@ -216,7 +216,7 @@ func compareEntry(oldEntry, newEntry Entry) ([]KeyDelta, error) {
for _, kv := range newKeys { for _, kv := range newKeys {
key := kv.Keyword() key := kv.Keyword()
// only add this diff if the old keys has this keyword // only add this diff if the old keys has this keyword
if key != "tar_time" && key != "time" && key != "xattr" && HasKeyword(oldKeys, key) == emptyKV { if key != "tar_time" && key != "time" && key != "xattr" && len(HasKeyword(oldKeys, key)) == 0 {
continue continue
} }

View File

@ -1,26 +0,0 @@
package mtree
import (
"fmt"
"os"
"time"
)
// DebugOutput is the where DEBUG output is written
var DebugOutput = os.Stderr
// Debugln writes output to DebugOutput, only if DEBUG environment variable is set
func Debugln(a ...interface{}) (n int, err error) {
if os.Getenv("DEBUG") != "" {
return fmt.Fprintf(DebugOutput, "[%d] [DEBUG] %s\n", time.Now().UnixNano(), a)
}
return 0, nil
}
// Debugf writes formatted output to DebugOutput, only if DEBUG environment variable is set
func Debugf(format string, a ...interface{}) (n int, err error) {
if os.Getenv("DEBUG") != "" {
return fmt.Fprintf(DebugOutput, "[%d] [DEBUG] %s\n", time.Now().UnixNano(), fmt.Sprintf(format, a...))
}
return 0, nil
}

View File

@ -21,7 +21,7 @@ import (
// io.Reader `r` is to the file stream for the file payload. While this // io.Reader `r` is to the file stream for the file payload. While this
// function takes an io.Reader, the caller needs to reset it to the beginning // function takes an io.Reader, the caller needs to reset it to the beginning
// for each new KeywordFunc // for each new KeywordFunc
type KeywordFunc func(path string, info os.FileInfo, r io.Reader) (KeyVal, error) type KeywordFunc func(path string, info os.FileInfo, r io.Reader) ([]KeyVal, error)
var ( var (
// KeywordFuncs is the map of all keywords (and the functions to produce them) // KeywordFuncs is the map of all keywords (and the functions to produce them)
@ -67,7 +67,7 @@ var (
} }
) )
var ( var (
modeKeywordFunc = func(path string, info os.FileInfo, r io.Reader) (KeyVal, error) { modeKeywordFunc = func(path string, info os.FileInfo, r io.Reader) ([]KeyVal, error) {
permissions := info.Mode().Perm() permissions := info.Mode().Perm()
if os.ModeSetuid&info.Mode() > 0 { if os.ModeSetuid&info.Mode() > 0 {
permissions |= (1 << 11) permissions |= (1 << 11)
@ -78,93 +78,93 @@ var (
if os.ModeSticky&info.Mode() > 0 { if os.ModeSticky&info.Mode() > 0 {
permissions |= (1 << 9) permissions |= (1 << 9)
} }
return KeyVal(fmt.Sprintf("mode=%#o", permissions)), nil return []KeyVal{KeyVal(fmt.Sprintf("mode=%#o", permissions))}, nil
} }
sizeKeywordFunc = func(path string, info os.FileInfo, r io.Reader) (KeyVal, error) { sizeKeywordFunc = func(path string, info os.FileInfo, r io.Reader) ([]KeyVal, error) {
if sys, ok := info.Sys().(*tar.Header); ok { if sys, ok := info.Sys().(*tar.Header); ok {
if sys.Typeflag == tar.TypeSymlink { if sys.Typeflag == tar.TypeSymlink {
return KeyVal(fmt.Sprintf("size=%d", len(sys.Linkname))), nil return []KeyVal{KeyVal(fmt.Sprintf("size=%d", len(sys.Linkname)))}, nil
} }
} }
return KeyVal(fmt.Sprintf("size=%d", info.Size())), nil return []KeyVal{KeyVal(fmt.Sprintf("size=%d", info.Size()))}, nil
} }
cksumKeywordFunc = func(path string, info os.FileInfo, r io.Reader) (KeyVal, error) { cksumKeywordFunc = func(path string, info os.FileInfo, r io.Reader) ([]KeyVal, error) {
if !info.Mode().IsRegular() { if !info.Mode().IsRegular() {
return emptyKV, nil return nil, nil
} }
sum, _, err := cksum(r) sum, _, err := cksum(r)
if err != nil { if err != nil {
return emptyKV, err return nil, err
} }
return KeyVal(fmt.Sprintf("cksum=%d", sum)), nil return []KeyVal{KeyVal(fmt.Sprintf("cksum=%d", sum))}, nil
} }
hasherKeywordFunc = func(name string, newHash func() hash.Hash) KeywordFunc { hasherKeywordFunc = func(name string, newHash func() hash.Hash) KeywordFunc {
return func(path string, info os.FileInfo, r io.Reader) (KeyVal, error) { return func(path string, info os.FileInfo, r io.Reader) ([]KeyVal, error) {
if !info.Mode().IsRegular() { if !info.Mode().IsRegular() {
return emptyKV, nil return nil, nil
} }
h := newHash() h := newHash()
if _, err := io.Copy(h, r); err != nil { if _, err := io.Copy(h, r); err != nil {
return emptyKV, err return nil, err
} }
return KeyVal(fmt.Sprintf("%s=%x", KeywordSynonym(name), h.Sum(nil))), nil return []KeyVal{KeyVal(fmt.Sprintf("%s=%x", KeywordSynonym(name), h.Sum(nil)))}, nil
} }
} }
tartimeKeywordFunc = func(path string, info os.FileInfo, r io.Reader) (KeyVal, error) { tartimeKeywordFunc = func(path string, info os.FileInfo, r io.Reader) ([]KeyVal, error) {
return KeyVal(fmt.Sprintf("tar_time=%d.%9.9d", info.ModTime().Unix(), 0)), nil return []KeyVal{KeyVal(fmt.Sprintf("tar_time=%d.%9.9d", info.ModTime().Unix(), 0))}, nil
} }
timeKeywordFunc = func(path string, info os.FileInfo, r io.Reader) (KeyVal, error) { timeKeywordFunc = func(path string, info os.FileInfo, r io.Reader) ([]KeyVal, error) {
tSec := info.ModTime().Unix() tSec := info.ModTime().Unix()
tNano := info.ModTime().Nanosecond() tNano := info.ModTime().Nanosecond()
return KeyVal(fmt.Sprintf("time=%d.%9.9d", tSec, tNano)), nil return []KeyVal{KeyVal(fmt.Sprintf("time=%d.%9.9d", tSec, tNano))}, nil
} }
linkKeywordFunc = func(path string, info os.FileInfo, r io.Reader) (KeyVal, error) { linkKeywordFunc = func(path string, info os.FileInfo, r io.Reader) ([]KeyVal, error) {
if sys, ok := info.Sys().(*tar.Header); ok { if sys, ok := info.Sys().(*tar.Header); ok {
if sys.Linkname != "" { if sys.Linkname != "" {
linkname, err := govis.Vis(sys.Linkname, DefaultVisFlags) linkname, err := govis.Vis(sys.Linkname, DefaultVisFlags)
if err != nil { if err != nil {
return emptyKV, err return nil, nil
} }
return KeyVal(fmt.Sprintf("link=%s", linkname)), nil return []KeyVal{KeyVal(fmt.Sprintf("link=%s", linkname))}, nil
} }
return emptyKV, nil return nil, nil
} }
if info.Mode()&os.ModeSymlink != 0 { if info.Mode()&os.ModeSymlink != 0 {
str, err := os.Readlink(path) str, err := os.Readlink(path)
if err != nil { if err != nil {
return emptyKV, err return nil, nil
} }
linkname, err := govis.Vis(str, DefaultVisFlags) linkname, err := govis.Vis(str, DefaultVisFlags)
if err != nil { if err != nil {
return emptyKV, err return nil, nil
} }
return KeyVal(fmt.Sprintf("link=%s", linkname)), nil return []KeyVal{KeyVal(fmt.Sprintf("link=%s", linkname))}, nil
} }
return emptyKV, nil return nil, nil
} }
typeKeywordFunc = func(path string, info os.FileInfo, r io.Reader) (KeyVal, error) { typeKeywordFunc = func(path string, info os.FileInfo, r io.Reader) ([]KeyVal, error) {
if info.Mode().IsDir() { if info.Mode().IsDir() {
return "type=dir", nil return []KeyVal{"type=dir"}, nil
} }
if info.Mode().IsRegular() { if info.Mode().IsRegular() {
return "type=file", nil return []KeyVal{"type=file"}, nil
} }
if info.Mode()&os.ModeSocket != 0 { if info.Mode()&os.ModeSocket != 0 {
return "type=socket", nil return []KeyVal{"type=socket"}, nil
} }
if info.Mode()&os.ModeSymlink != 0 { if info.Mode()&os.ModeSymlink != 0 {
return "type=link", nil return []KeyVal{"type=link"}, nil
} }
if info.Mode()&os.ModeNamedPipe != 0 { if info.Mode()&os.ModeNamedPipe != 0 {
return "type=fifo", nil return []KeyVal{"type=fifo"}, nil
} }
if info.Mode()&os.ModeDevice != 0 { if info.Mode()&os.ModeDevice != 0 {
if info.Mode()&os.ModeCharDevice != 0 { if info.Mode()&os.ModeCharDevice != 0 {
return "type=char", nil return []KeyVal{"type=char"}, nil
} }
return "type=block", nil return []KeyVal{"type=block"}, nil
} }
return emptyKV, nil return nil, nil
} }
) )

View File

@ -12,58 +12,58 @@ import (
) )
var ( var (
flagsKeywordFunc = func(path string, info os.FileInfo, r io.Reader) (KeyVal, error) { flagsKeywordFunc = func(path string, info os.FileInfo, r io.Reader) ([]KeyVal, error) {
// ideally this will pull in from here https://www.freebsd.org/cgi/man.cgi?query=chflags&sektion=2 // ideally this will pull in from here https://www.freebsd.org/cgi/man.cgi?query=chflags&sektion=2
return emptyKV, nil return nil, nil
} }
unameKeywordFunc = func(path string, info os.FileInfo, r io.Reader) (KeyVal, error) { unameKeywordFunc = func(path string, info os.FileInfo, r io.Reader) ([]KeyVal, error) {
if hdr, ok := info.Sys().(*tar.Header); ok { if hdr, ok := info.Sys().(*tar.Header); ok {
return KeyVal(fmt.Sprintf("uname=%s", hdr.Uname)), nil return []KeyVal{KeyVal(fmt.Sprintf("uname=%s", hdr.Uname))}, nil
} }
stat := info.Sys().(*syscall.Stat_t) stat := info.Sys().(*syscall.Stat_t)
u, err := user.LookupId(fmt.Sprintf("%d", stat.Uid)) u, err := user.LookupId(fmt.Sprintf("%d", stat.Uid))
if err != nil { if err != nil {
return emptyKV, err return nil, err
} }
return KeyVal(fmt.Sprintf("uname=%s", u.Username)), nil return []KeyVal{KeyVal(fmt.Sprintf("uname=%s", u.Username))}, nil
} }
gnameKeywordFunc = func(path string, info os.FileInfo, r io.Reader) (KeyVal, error) { gnameKeywordFunc = func(path string, info os.FileInfo, r io.Reader) ([]KeyVal, error) {
if hdr, ok := info.Sys().(*tar.Header); ok { if hdr, ok := info.Sys().(*tar.Header); ok {
return KeyVal(fmt.Sprintf("gname=%s", hdr.Gname)), nil return []KeyVal{KeyVal(fmt.Sprintf("gname=%s", hdr.Gname))}, nil
} }
stat := info.Sys().(*syscall.Stat_t) stat := info.Sys().(*syscall.Stat_t)
g, err := lookupGroupID(fmt.Sprintf("%d", stat.Gid)) g, err := lookupGroupID(fmt.Sprintf("%d", stat.Gid))
if err != nil { if err != nil {
return emptyKV, err return nil, err
} }
return KeyVal(fmt.Sprintf("gname=%s", g.Name)), nil return []KeyVal{KeyVal(fmt.Sprintf("gname=%s", g.Name))}, nil
} }
uidKeywordFunc = func(path string, info os.FileInfo, r io.Reader) (KeyVal, error) { uidKeywordFunc = func(path string, info os.FileInfo, r io.Reader) ([]KeyVal, error) {
if hdr, ok := info.Sys().(*tar.Header); ok { if hdr, ok := info.Sys().(*tar.Header); ok {
return KeyVal(fmt.Sprintf("uid=%d", hdr.Uid)), nil return []KeyVal{KeyVal(fmt.Sprintf("uid=%d", hdr.Uid))}, nil
} }
stat := info.Sys().(*syscall.Stat_t) stat := info.Sys().(*syscall.Stat_t)
return KeyVal(fmt.Sprintf("uid=%d", stat.Uid)), nil return []KeyVal{KeyVal(fmt.Sprintf("uid=%d", stat.Uid))}, nil
} }
gidKeywordFunc = func(path string, info os.FileInfo, r io.Reader) (KeyVal, error) { gidKeywordFunc = func(path string, info os.FileInfo, r io.Reader) ([]KeyVal, error) {
if hdr, ok := info.Sys().(*tar.Header); ok { if hdr, ok := info.Sys().(*tar.Header); ok {
return KeyVal(fmt.Sprintf("gid=%d", hdr.Gid)), nil return []KeyVal{KeyVal(fmt.Sprintf("gid=%d", hdr.Gid))}, nil
} }
if stat, ok := info.Sys().(*syscall.Stat_t); ok { if stat, ok := info.Sys().(*syscall.Stat_t); ok {
return KeyVal(fmt.Sprintf("gid=%d", stat.Gid)), nil return []KeyVal{KeyVal(fmt.Sprintf("gid=%d", stat.Gid))}, nil
} }
return emptyKV, nil return nil, nil
} }
nlinkKeywordFunc = func(path string, info os.FileInfo, r io.Reader) (KeyVal, error) { nlinkKeywordFunc = func(path string, info os.FileInfo, r io.Reader) ([]KeyVal, error) {
if stat, ok := info.Sys().(*syscall.Stat_t); ok { if stat, ok := info.Sys().(*syscall.Stat_t); ok {
return KeyVal(fmt.Sprintf("nlink=%d", stat.Nlink)), nil return []KeyVal{KeyVal(fmt.Sprintf("nlink=%d", stat.Nlink))}, nil
} }
return emptyKV, nil return nil, nil
} }
xattrKeywordFunc = func(path string, info os.FileInfo, r io.Reader) (KeyVal, error) { xattrKeywordFunc = func(path string, info os.FileInfo, r io.Reader) ([]KeyVal, error) {
return emptyKV, nil return nil, nil
} }
) )

View File

@ -9,7 +9,6 @@ import (
"io" "io"
"os" "os"
"os/user" "os/user"
"strings"
"syscall" "syscall"
"github.com/vbatts/go-mtree/pkg/govis" "github.com/vbatts/go-mtree/pkg/govis"
@ -18,91 +17,91 @@ import (
var ( var (
// this is bsd specific https://www.freebsd.org/cgi/man.cgi?query=chflags&sektion=2 // this is bsd specific https://www.freebsd.org/cgi/man.cgi?query=chflags&sektion=2
flagsKeywordFunc = func(path string, info os.FileInfo, r io.Reader) (KeyVal, error) { flagsKeywordFunc = func(path string, info os.FileInfo, r io.Reader) ([]KeyVal, error) {
return emptyKV, nil return nil, nil
} }
unameKeywordFunc = func(path string, info os.FileInfo, r io.Reader) (KeyVal, error) { unameKeywordFunc = func(path string, info os.FileInfo, r io.Reader) ([]KeyVal, error) {
if hdr, ok := info.Sys().(*tar.Header); ok { if hdr, ok := info.Sys().(*tar.Header); ok {
return KeyVal(fmt.Sprintf("uname=%s", hdr.Uname)), nil return []KeyVal{KeyVal(fmt.Sprintf("uname=%s", hdr.Uname))}, nil
} }
stat := info.Sys().(*syscall.Stat_t) stat := info.Sys().(*syscall.Stat_t)
u, err := user.LookupId(fmt.Sprintf("%d", stat.Uid)) u, err := user.LookupId(fmt.Sprintf("%d", stat.Uid))
if err != nil { if err != nil {
return emptyKV, err return nil, nil
} }
return KeyVal(fmt.Sprintf("uname=%s", u.Username)), nil return []KeyVal{KeyVal(fmt.Sprintf("uname=%s", u.Username))}, nil
} }
gnameKeywordFunc = func(path string, info os.FileInfo, r io.Reader) (KeyVal, error) { gnameKeywordFunc = func(path string, info os.FileInfo, r io.Reader) ([]KeyVal, error) {
if hdr, ok := info.Sys().(*tar.Header); ok { if hdr, ok := info.Sys().(*tar.Header); ok {
return KeyVal(fmt.Sprintf("gname=%s", hdr.Gname)), nil return []KeyVal{KeyVal(fmt.Sprintf("gname=%s", hdr.Gname))}, nil
} }
stat := info.Sys().(*syscall.Stat_t) stat := info.Sys().(*syscall.Stat_t)
g, err := lookupGroupID(fmt.Sprintf("%d", stat.Gid)) g, err := lookupGroupID(fmt.Sprintf("%d", stat.Gid))
if err != nil { if err != nil {
return emptyKV, err return nil, nil
} }
return KeyVal(fmt.Sprintf("gname=%s", g.Name)), nil return []KeyVal{KeyVal(fmt.Sprintf("gname=%s", g.Name))}, nil
} }
uidKeywordFunc = func(path string, info os.FileInfo, r io.Reader) (KeyVal, error) { uidKeywordFunc = func(path string, info os.FileInfo, r io.Reader) ([]KeyVal, error) {
if hdr, ok := info.Sys().(*tar.Header); ok { if hdr, ok := info.Sys().(*tar.Header); ok {
return KeyVal(fmt.Sprintf("uid=%d", hdr.Uid)), nil return []KeyVal{KeyVal(fmt.Sprintf("uid=%d", hdr.Uid))}, nil
} }
stat := info.Sys().(*syscall.Stat_t) stat := info.Sys().(*syscall.Stat_t)
return KeyVal(fmt.Sprintf("uid=%d", stat.Uid)), nil return []KeyVal{KeyVal(fmt.Sprintf("uid=%d", stat.Uid))}, nil
} }
gidKeywordFunc = func(path string, info os.FileInfo, r io.Reader) (KeyVal, error) { gidKeywordFunc = func(path string, info os.FileInfo, r io.Reader) ([]KeyVal, error) {
if hdr, ok := info.Sys().(*tar.Header); ok { if hdr, ok := info.Sys().(*tar.Header); ok {
return KeyVal(fmt.Sprintf("gid=%d", hdr.Gid)), nil return []KeyVal{KeyVal(fmt.Sprintf("gid=%d", hdr.Gid))}, nil
} }
if stat, ok := info.Sys().(*syscall.Stat_t); ok { if stat, ok := info.Sys().(*syscall.Stat_t); ok {
return KeyVal(fmt.Sprintf("gid=%d", stat.Gid)), nil return []KeyVal{KeyVal(fmt.Sprintf("gid=%d", stat.Gid))}, nil
} }
return emptyKV, nil return nil, nil
} }
nlinkKeywordFunc = func(path string, info os.FileInfo, r io.Reader) (KeyVal, error) { nlinkKeywordFunc = func(path string, info os.FileInfo, r io.Reader) ([]KeyVal, error) {
if stat, ok := info.Sys().(*syscall.Stat_t); ok { if stat, ok := info.Sys().(*syscall.Stat_t); ok {
return KeyVal(fmt.Sprintf("nlink=%d", stat.Nlink)), nil return []KeyVal{KeyVal(fmt.Sprintf("nlink=%d", stat.Nlink))}, nil
} }
return emptyKV, nil return nil, nil
} }
xattrKeywordFunc = func(path string, info os.FileInfo, r io.Reader) (KeyVal, error) { xattrKeywordFunc = func(path string, info os.FileInfo, r io.Reader) ([]KeyVal, error) {
if hdr, ok := info.Sys().(*tar.Header); ok { if hdr, ok := info.Sys().(*tar.Header); ok {
if len(hdr.Xattrs) == 0 { if len(hdr.Xattrs) == 0 {
return emptyKV, nil return nil, nil
} }
klist := []KeyVal{} klist := []KeyVal{}
for k, v := range hdr.Xattrs { for k, v := range hdr.Xattrs {
encKey, err := govis.Vis(k, DefaultVisFlags) encKey, err := govis.Vis(k, DefaultVisFlags)
if err != nil { if err != nil {
return emptyKV, err return nil, nil
} }
klist = append(klist, KeyVal(fmt.Sprintf("xattr.%s=%s", encKey, base64.StdEncoding.EncodeToString([]byte(v))))) klist = append(klist, KeyVal(fmt.Sprintf("xattr.%s=%s", encKey, base64.StdEncoding.EncodeToString([]byte(v)))))
} }
return KeyVal(strings.Join(KeyValToString(klist), " ")), nil return klist, nil
} }
if !info.Mode().IsRegular() && !info.Mode().IsDir() { if !info.Mode().IsRegular() && !info.Mode().IsDir() {
return emptyKV, nil return nil, nil
} }
xlist, err := xattr.List(path) xlist, err := xattr.List(path)
if err != nil { if err != nil {
return emptyKV, err return nil, nil
} }
klist := make([]KeyVal, len(xlist)) klist := make([]KeyVal, len(xlist))
for i := range xlist { for i := range xlist {
data, err := xattr.Get(path, xlist[i]) data, err := xattr.Get(path, xlist[i])
if err != nil { if err != nil {
return emptyKV, err return nil, nil
} }
encKey, err := govis.Vis(xlist[i], DefaultVisFlags) encKey, err := govis.Vis(xlist[i], DefaultVisFlags)
if err != nil { if err != nil {
return emptyKV, err return nil, nil
} }
klist[i] = KeyVal(fmt.Sprintf("xattr.%s=%s", encKey, base64.StdEncoding.EncodeToString(data))) klist[i] = KeyVal(fmt.Sprintf("xattr.%s=%s", encKey, base64.StdEncoding.EncodeToString(data)))
} }
return KeyVal(strings.Join(KeyValToString(klist), " ")), nil return klist, nil
} }
) )

View File

@ -11,37 +11,37 @@ import (
var ( var (
// this is bsd specific https://www.freebsd.org/cgi/man.cgi?query=chflags&sektion=2 // this is bsd specific https://www.freebsd.org/cgi/man.cgi?query=chflags&sektion=2
flagsKeywordFunc = func(path string, info os.FileInfo, r io.Reader) (KeyVal, error) { flagsKeywordFunc = func(path string, info os.FileInfo, r io.Reader) ([]KeyVal, error) {
return emptyKV, nil return nil, nil
} }
unameKeywordFunc = func(path string, info os.FileInfo, r io.Reader) (KeyVal, error) { unameKeywordFunc = func(path string, info os.FileInfo, r io.Reader) ([]KeyVal, error) {
if hdr, ok := info.Sys().(*tar.Header); ok { if hdr, ok := info.Sys().(*tar.Header); ok {
return KeyVal(fmt.Sprintf("uname=%s", hdr.Uname)), nil return []KeyVal{KeyVal(fmt.Sprintf("uname=%s", hdr.Uname))}, nil
} }
return emptyKV, nil return nil, nil
} }
gnameKeywordFunc = func(path string, info os.FileInfo, r io.Reader) (KeyVal, error) { gnameKeywordFunc = func(path string, info os.FileInfo, r io.Reader) ([]KeyVal, error) {
if hdr, ok := info.Sys().(*tar.Header); ok { if hdr, ok := info.Sys().(*tar.Header); ok {
return KeyVal(fmt.Sprintf("gname=%s", hdr.Gname)), nil return []KeyVal{KeyVal(fmt.Sprintf("gname=%s", hdr.Gname))}, nil
} }
return emptyKV, nil return nil, nil
} }
uidKeywordFunc = func(path string, info os.FileInfo, r io.Reader) (KeyVal, error) { uidKeywordFunc = func(path string, info os.FileInfo, r io.Reader) ([]KeyVal, error) {
if hdr, ok := info.Sys().(*tar.Header); ok { if hdr, ok := info.Sys().(*tar.Header); ok {
return KeyVal(fmt.Sprintf("uid=%d", hdr.Uid)), nil return []KeyVal{KeyVal(fmt.Sprintf("uid=%d", hdr.Uid))}, nil
} }
return emptyKV, nil return nil, nil
} }
gidKeywordFunc = func(path string, info os.FileInfo, r io.Reader) (KeyVal, error) { gidKeywordFunc = func(path string, info os.FileInfo, r io.Reader) ([]KeyVal, error) {
if hdr, ok := info.Sys().(*tar.Header); ok { if hdr, ok := info.Sys().(*tar.Header); ok {
return KeyVal(fmt.Sprintf("gid=%d", hdr.Gid)), nil return []KeyVal{KeyVal(fmt.Sprintf("gid=%d", hdr.Gid))}, nil
} }
return emptyKV, nil return nil, nil
} }
nlinkKeywordFunc = func(path string, info os.FileInfo, r io.Reader) (KeyVal, error) { nlinkKeywordFunc = func(path string, info os.FileInfo, r io.Reader) ([]KeyVal, error) {
return emptyKV, nil return nil, nil
} }
xattrKeywordFunc = func(path string, info os.FileInfo, r io.Reader) (KeyVal, error) { xattrKeywordFunc = func(path string, info os.FileInfo, r io.Reader) ([]KeyVal, error) {
return emptyKV, nil return nil, nil
} }
) )

View File

@ -13,8 +13,30 @@ const DefaultVisFlags govis.VisFlag = govis.VisWhite | govis.VisOctal | govis.Vi
// Keyword is the string name of a keyword, with some convenience functions for // Keyword is the string name of a keyword, with some convenience functions for
// determining whether it is a default or bsd standard keyword. // determining whether it is a default or bsd standard keyword.
// It first portion before the "="
type Keyword string type Keyword string
// Prefix is the portion of the keyword before a first "." (if present).
//
// Primarly for the xattr use-case, where the keyword `xattr.security.selinux` would have a Suffix of `security.selinux`.
func (k Keyword) Prefix() Keyword {
if strings.Contains(string(k), ".") {
return Keyword(strings.SplitN(string(k), ".", 2)[0])
}
return k
}
// Suffix is the portion of the keyword after a first ".".
// This is an option feature.
//
// Primarly for the xattr use-case, where the keyword `xattr.security.selinux` would have a Suffix of `security.selinux`.
func (k Keyword) Suffix() string {
if strings.Contains(string(k), ".") {
return strings.SplitN(string(k), ".", 2)[1]
}
return string(k)
}
// Default returns whether this keyword is in the default set of keywords // Default returns whether this keyword is in the default set of keywords
func (k Keyword) Default() bool { func (k Keyword) Default() bool {
return InKeywordSlice(k, DefaultKeywords) return InKeywordSlice(k, DefaultKeywords)
@ -93,24 +115,7 @@ func (kv KeyVal) Keyword() Keyword {
if !strings.Contains(string(kv), "=") { if !strings.Contains(string(kv), "=") {
return Keyword("") return Keyword("")
} }
chunks := strings.SplitN(strings.TrimSpace(string(kv)), "=", 2)[0] return Keyword(strings.SplitN(strings.TrimSpace(string(kv)), "=", 2)[0])
if !strings.Contains(chunks, ".") {
return Keyword(chunks)
}
return Keyword(strings.SplitN(chunks, ".", 2)[0])
}
// KeywordSuffix is really only used for xattr, as the keyword is a prefix to
// the xattr "namespace.key"
func (kv KeyVal) KeywordSuffix() string {
if !strings.Contains(string(kv), "=") {
return ""
}
chunks := strings.SplitN(strings.TrimSpace(string(kv)), "=", 2)[0]
if !strings.Contains(chunks, ".") {
return ""
}
return strings.SplitN(chunks, ".", 2)[1]
} }
// Value is the data/value portion of "keyword=value" // Value is the data/value portion of "keyword=value"
@ -123,9 +128,6 @@ func (kv KeyVal) Value() string {
// NewValue returns a new KeyVal with the newval // NewValue returns a new KeyVal with the newval
func (kv KeyVal) NewValue(newval string) KeyVal { func (kv KeyVal) NewValue(newval string) KeyVal {
if suff := kv.KeywordSuffix(); suff != "" {
return KeyVal(fmt.Sprintf("%s.%s=%s", kv.Keyword(), suff, newval))
}
return KeyVal(fmt.Sprintf("%s=%s", kv.Keyword(), newval)) return KeyVal(fmt.Sprintf("%s=%s", kv.Keyword(), newval))
} }
@ -135,14 +137,23 @@ func (kv KeyVal) NewValue(newval string) KeyVal {
// doing. // doing.
func (kv KeyVal) Equal(b KeyVal) bool { func (kv KeyVal) Equal(b KeyVal) bool {
// TODO: Implement handling of tar_mtime. // TODO: Implement handling of tar_mtime.
return kv.Keyword() == b.Keyword() && kv.KeywordSuffix() == b.KeywordSuffix() && kv.Value() == b.Value() return kv.Keyword() == b.Keyword() && kv.Value() == b.Value()
} }
// keyvalSelector takes an array of KeyVal ("keyword=value") and filters out that only the set of keywords func keywordPrefixes(kvset []Keyword) []Keyword {
kvs := []Keyword{}
for _, kv := range kvset {
kvs = append(kvs, kv.Prefix())
}
return kvs
}
// keyvalSelector takes an array of KeyVal ("keyword=value") and filters out
// that only the set of keywords
func keyvalSelector(keyval []KeyVal, keyset []Keyword) []KeyVal { func keyvalSelector(keyval []KeyVal, keyset []Keyword) []KeyVal {
retList := []KeyVal{} retList := []KeyVal{}
for _, kv := range keyval { for _, kv := range keyval {
if InKeywordSlice(kv.Keyword(), keyset) { if InKeywordSlice(kv.Keyword().Prefix(), keywordPrefixes(keyset)) {
retList = append(retList, kv) retList = append(retList, kv)
} }
} }
@ -171,23 +182,23 @@ func keyValCopy(set []KeyVal) []KeyVal {
// Has the "keyword" present in the list of KeyVal, and returns the // Has the "keyword" present in the list of KeyVal, and returns the
// corresponding KeyVal, else an empty string. // corresponding KeyVal, else an empty string.
func Has(keyvals []KeyVal, keyword string) KeyVal { func Has(keyvals []KeyVal, keyword string) []KeyVal {
return HasKeyword(keyvals, Keyword(keyword)) return HasKeyword(keyvals, Keyword(keyword))
} }
// HasKeyword the "keyword" present in the list of KeyVal, and returns the // HasKeyword the "keyword" present in the list of KeyVal, and returns the
// corresponding KeyVal, else an empty string. // corresponding KeyVal, else an empty string.
func HasKeyword(keyvals []KeyVal, keyword Keyword) KeyVal { // This match is done on the Prefix of the keyword only.
func HasKeyword(keyvals []KeyVal, keyword Keyword) []KeyVal {
kvs := []KeyVal{}
for i := range keyvals { for i := range keyvals {
if keyvals[i].Keyword() == keyword { if keyvals[i].Keyword().Prefix() == keyword.Prefix() {
return keyvals[i] kvs = append(kvs, keyvals[i])
} }
} }
return emptyKV return kvs
} }
var emptyKV = KeyVal("")
// MergeSet takes the current setKeyVals, and then applies the entryKeyVals // MergeSet takes the current setKeyVals, and then applies the entryKeyVals
// such that the entry's values win. The union is returned. // such that the entry's values win. The union is returned.
func MergeSet(setKeyVals, entryKeyVals []string) []KeyVal { func MergeSet(setKeyVals, entryKeyVals []string) []KeyVal {
@ -203,8 +214,11 @@ func MergeKeyValSet(setKeyVals, entryKeyVals []KeyVal) []KeyVal {
seenKeywords := []Keyword{} seenKeywords := []Keyword{}
for i := range retList { for i := range retList {
word := retList[i].Keyword() word := retList[i].Keyword()
if ekv := HasKeyword(entryKeyVals, word); ekv != emptyKV { for _, kv := range HasKeyword(entryKeyVals, word) {
retList[i] = ekv // match on the keyword prefix and suffix here
if kv.Keyword() == word {
retList[i] = kv
}
} }
seenKeywords = append(seenKeywords, word) seenKeywords = append(seenKeywords, word)
} }

View File

@ -51,12 +51,12 @@ func TestXattr(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
// Check the directory // Check the directory
str, err := xattrKeywordFunc(dir, dirstat, nil) kvs, err := xattrKeywordFunc(dir, dirstat, nil)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
if str == "" { if len(kvs) == 0 {
t.Errorf("expected a keyval; got %q", str) t.Errorf("expected a keyval; got none")
} }
filestat, err := fh.Stat() filestat, err := fh.Stat()
@ -64,12 +64,12 @@ func TestXattr(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
// Check the regular file // Check the regular file
str, err = xattrKeywordFunc(filepath.Join(dir, "file"), filestat, fh) kvs, err = xattrKeywordFunc(filepath.Join(dir, "file"), filestat, fh)
if err != nil { if err != nil {
t.Error(err) t.Error(err)
} }
if str == "" { if len(kvs) == 0 {
t.Errorf("expected a keyval; got %q", str) t.Errorf("expected a keyval; got none")
} }
linkstat, err := os.Lstat(filepath.Join(dir, "symlink")) linkstat, err := os.Lstat(filepath.Join(dir, "symlink"))

View File

@ -8,38 +8,43 @@ import (
) )
func TestKeyValRoundtrip(t *testing.T) { func TestKeyValRoundtrip(t *testing.T) {
kv := KeyVal("xattr.security.selinux=dW5jb25maW5lZF91Om9iamVjdF9yOnVzZXJfaG9tZV90OnMwAA==") kv := KeyVal("xattr.security.selinux=dW5jb25maW5lZF91Om9iamVjdF9yOnVzZXJfaG9tZV90OnMwAA==")
expected := "xattr" expected := "xattr.security.selinux"
got := string(kv.Keyword()) got := string(kv.Keyword())
if got != expected { if got != expected {
t.Errorf("expected %q; got %q", expected, got) t.Errorf("expected %q; got %q", expected, got)
} }
expected = "security.selinux" expected = "xattr"
got = kv.KeywordSuffix() got = string(kv.Keyword().Prefix())
if got != expected { if got != expected {
t.Errorf("expected %q; got %q", expected, got) t.Errorf("expected %q; got %q", expected, got)
} }
expected = "dW5jb25maW5lZF91Om9iamVjdF9yOnVzZXJfaG9tZV90OnMwAA==" expected = "security.selinux"
got = kv.Value() got = kv.Keyword().Suffix()
if got != expected { if got != expected {
t.Errorf("expected %q; got %q", expected, got) t.Errorf("expected %q; got %q", expected, got)
} }
expected = "dW5jb25maW5lZF91Om9iamVjdF9yOnVzZXJfaG9tZV90OnMwAA=="
got = kv.Value()
if got != expected {
t.Errorf("expected %q; got %q", expected, got)
}
expected = "xattr.security.selinux=farts" expected = "xattr.security.selinux=farts"
got = string(kv.NewValue("farts")) got = string(kv.NewValue("farts"))
if got != expected { if got != expected {
t.Errorf("expected %q; got %q", expected, got) t.Errorf("expected %q; got %q", expected, got)
} }
expected = "xattr.security.selinux=farts" expected = "xattr.security.selinux=farts"
kv1 := KeyVal(got) kv1 := KeyVal(got)
kv2 := kv.NewValue("farts") kv2 := kv.NewValue("farts")
if !kv2.Equal(kv1) { if !kv2.Equal(kv1) {
t.Errorf("expected equality of %q and %q", kv1, kv2) t.Errorf("expected equality of %q and %q", kv1, kv2)
} }
} }
@ -97,8 +102,11 @@ func TestKeywordsTimeNano(t *testing.T) {
if err != nil { if err != nil {
t.Errorf("unexpected error while parsing '%q': %q", mtime, err) t.Errorf("unexpected error while parsing '%q': %q", mtime, err)
} }
if expected != got { if len(got) != 1 {
t.Errorf("keyword didn't match, expected '%s' got '%s'", expected, got) t.Errorf("expected 1 KeyVal, but got %d", len(got))
}
if expected != got[0] {
t.Errorf("keyword didn't match, expected '%s' got '%s'", expected, got[0])
} }
} }
} }
@ -124,8 +132,11 @@ func TestKeywordsTimeTar(t *testing.T) {
if err != nil { if err != nil {
t.Errorf("unexpected error while parsing '%q': %q", mtime, err) t.Errorf("unexpected error while parsing '%q': %q", mtime, err)
} }
if expected != got { if len(got) != 1 {
t.Errorf("keyword didn't match, expected '%s' got '%s'", expected, got) t.Errorf("expected 1 KeyVal, but got %d", len(got))
}
if expected != got[0] {
t.Errorf("keyword didn't match, expected '%s' got '%s'", expected, got[0])
} }
} }
} }

38
tar.go
View File

@ -5,11 +5,11 @@ import (
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"log"
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/Sirupsen/logrus"
"github.com/vbatts/go-mtree/pkg/govis" "github.com/vbatts/go-mtree/pkg/govis"
) )
@ -144,16 +144,16 @@ hdrloop:
// Keep track of which files are hardlinks so we can resolve them later // Keep track of which files are hardlinks so we can resolve them later
if hdr.Typeflag == tar.TypeLink { if hdr.Typeflag == tar.TypeLink {
linkFunc := KeywordFuncs["link"] keyFunc := KeywordFuncs["link"]
kv, err := linkFunc(hdr.Name, hdr.FileInfo(), nil) kvs, err := keyFunc(hdr.Name, hdr.FileInfo(), nil)
if err != nil { if err != nil {
log.Println(err) logrus.Warn(err)
break break // XXX is breaking an okay thing to do here?
} }
linkname, err := govis.Unvis(KeyVal(kv).Value(), DefaultVisFlags) linkname, err := govis.Unvis(KeyVal(kvs[0]).Value(), DefaultVisFlags)
if err != nil { if err != nil {
log.Println(err) logrus.Warn(err)
break break // XXX is breaking an okay thing to do here?
} }
if _, ok := ts.hardlinks[linkname]; !ok { if _, ok := ts.hardlinks[linkname]; !ok {
ts.hardlinks[linkname] = []string{hdr.Name} ts.hardlinks[linkname] = []string{hdr.Name}
@ -164,19 +164,19 @@ hdrloop:
// now collect keywords on the file // now collect keywords on the file
for _, keyword := range ts.keywords { for _, keyword := range ts.keywords {
if keyFunc, ok := KeywordFuncs[keyword]; ok { if keyFunc, ok := KeywordFuncs[keyword.Prefix()]; ok {
// We can't extract directories on to disk, so "size" keyword // We can't extract directories on to disk, so "size" keyword
// is irrelevant for now // is irrelevant for now
if hdr.FileInfo().IsDir() && keyword == "size" { if hdr.FileInfo().IsDir() && keyword == "size" {
continue continue
} }
val, err := keyFunc(hdr.Name, hdr.FileInfo(), tmpFile) kvs, err := keyFunc(hdr.Name, hdr.FileInfo(), tmpFile)
if err != nil { if err != nil {
ts.setErr(err) ts.setErr(err)
} }
// for good measure, check that we actually get a value for a keyword // for good measure, check that we actually get a value for a keyword
if val != "" { if len(kvs) > 0 && kvs[0] != "" {
e.Keywords = append(e.Keywords, val) e.Keywords = append(e.Keywords, kvs[0])
} }
// don't forget to reset the reader // don't forget to reset the reader
@ -196,13 +196,15 @@ hdrloop:
Type: SpecialType, Type: SpecialType,
} }
for _, setKW := range SetKeywords { for _, setKW := range SetKeywords {
if keyFunc, ok := KeywordFuncs[setKW]; ok { if keyFunc, ok := KeywordFuncs[setKW.Prefix()]; ok {
val, err := keyFunc(hdr.Name, hdr.FileInfo(), tmpFile) kvs, err := keyFunc(hdr.Name, hdr.FileInfo(), tmpFile)
if err != nil { if err != nil {
ts.setErr(err) ts.setErr(err)
} }
if val != "" { for _, kv := range kvs {
s.Keywords = append(s.Keywords, val) if kv != "" {
s.Keywords = append(s.Keywords, kv)
}
} }
if _, err := tmpFile.Seek(0, 0); err != nil { if _, err := tmpFile.Seek(0, 0); err != nil {
tmpFile.Close() tmpFile.Close()
@ -383,7 +385,7 @@ func resolveHardlinks(root *Entry, hardlinks map[string][]string, countlinks boo
if seen, ok := originals[base]; !ok { if seen, ok := originals[base]; !ok {
basefile = root.Find(base) basefile = root.Find(base)
if basefile == nil { if basefile == nil {
log.Printf("%s does not exist in this tree\n", base) logrus.Printf("%s does not exist in this tree\n", base)
continue continue
} }
originals[base] = basefile originals[base] = basefile
@ -393,7 +395,7 @@ func resolveHardlinks(root *Entry, hardlinks map[string][]string, countlinks boo
for _, link := range links { for _, link := range links {
linkfile := root.Find(link) linkfile := root.Find(link)
if linkfile == nil { if linkfile == nil {
log.Printf("%s does not exist in this tree\n", link) logrus.Printf("%s does not exist in this tree\n", link)
continue continue
} }
linkfile.Keywords = basefile.Keywords linkfile.Keywords = basefile.Keywords

View File

@ -3,6 +3,8 @@ package mtree
import ( import (
"os" "os"
"sort" "sort"
"github.com/Sirupsen/logrus"
) )
// DefaultUpdateKeywords is the default set of keywords that can take updates to the files on disk // DefaultUpdateKeywords is the default set of keywords that can take updates to the files on disk
@ -11,7 +13,7 @@ var DefaultUpdateKeywords = []Keyword{
"gid", "gid",
"mode", "mode",
"time", "time",
// TODO xattr "xattr",
} }
// Update attempts to set the attributes of root directory path, given the values of `keywords` in dh DirectoryHierarchy. // Update attempts to set the attributes of root directory path, given the values of `keywords` in dh DirectoryHierarchy.
@ -36,7 +38,7 @@ func Update(root string, dh *DirectoryHierarchy, keywords []Keyword, fs FsEval)
} else if e.Name == "/unset" { } else if e.Name == "/unset" {
creator.curSet = nil creator.curSet = nil
} }
Debugf("%#v", e) logrus.Debugf("%#v", e)
continue continue
case RelativeType, FullType: case RelativeType, FullType:
e.Set = creator.curSet e.Set = creator.curSet
@ -46,20 +48,21 @@ func Update(root string, dh *DirectoryHierarchy, keywords []Keyword, fs FsEval)
} }
// filter the keywords to update on the file, from the keywords available for this entry: // filter the keywords to update on the file, from the keywords available for this entry:
var toCheck []KeyVal var kvToUpdate []KeyVal
toCheck = keyvalSelector(e.AllKeys(), keywords) kvToUpdate = keyvalSelector(e.AllKeys(), keywords)
Debugf("toCheck(%q): %v", pathname, toCheck) logrus.Debugf("kvToUpdate(%q): %#v", pathname, kvToUpdate)
for _, kv := range toCheck { for _, kv := range kvToUpdate {
if !InKeywordSlice(kv.Keyword(), keywords) { if !InKeywordSlice(kv.Keyword().Prefix(), keywordPrefixes(keywords)) {
continue continue
} }
ukFunc, ok := UpdateKeywordFuncs[kv.Keyword()] logrus.Debugf("finding function for %q (%q)", kv.Keyword(), kv.Keyword().Prefix())
ukFunc, ok := UpdateKeywordFuncs[kv.Keyword().Prefix()]
if !ok { if !ok {
Debugf("no UpdateKeywordFunc for %s; skipping", kv.Keyword()) logrus.Debugf("no UpdateKeywordFunc for %s; skipping", kv.Keyword())
continue continue
} }
if _, err := ukFunc(pathname, kv.Value()); err != nil { if _, err := ukFunc(kv.Keyword(), pathname, kv.Value()); err != nil {
results = append(results, InodeDelta{ results = append(results, InodeDelta{
diff: ErrorDifference, diff: ErrorDifference,
path: pathname, path: pathname,

94
update_linux_test.go Normal file
View File

@ -0,0 +1,94 @@
package mtree
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"testing"
"github.com/vbatts/go-mtree/xattr"
)
func init() {
//logrus.SetLevel(logrus.DebugLevel)
}
func TestXattrUpdate(t *testing.T) {
content := []byte("I know half of you half as well as I ought to")
// a bit dirty to create/destory a directory in cwd, but often /tmp is
// mounted tmpfs and doesn't support xattrs
dir, err := ioutil.TempDir(".", "test.xattr.restore.")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir) // clean up
tmpfn := filepath.Join(dir, "tmpfile")
if err := ioutil.WriteFile(tmpfn, content, 0666); err != nil {
t.Fatal(err)
}
if err := xattr.Set(dir, "user.test", []byte("directory")); err != nil {
t.Skip(fmt.Sprintf("skipping: %q does not support xattrs", dir))
}
if err := xattr.Set(tmpfn, "user.test", []byte("regular file")); err != nil {
t.Fatal(err)
}
// Walk this tempdir
dh, err := Walk(dir, nil, append(DefaultKeywords, []Keyword{"xattr", "sha1"}...), nil)
if err != nil {
t.Fatal(err)
}
// Now check that we're sane
res, err := Check(dir, dh, nil, nil)
if err != nil {
t.Fatal(err)
}
if len(res) != 0 {
t.Errorf("expecting no failures, but got %q", res)
}
if err := xattr.Set(tmpfn, "user.test", []byte("let it fly")); err != nil {
t.Fatal(err)
}
// Now check that we fail the check
res, err = Check(dir, dh, nil, nil)
if err != nil {
t.Fatal(err)
}
if len(res) == 0 {
t.Error("expected failures (like xattrs), but got none")
}
// restore the xattrs to original
res, err = Update(dir, dh, append(DefaultUpdateKeywords, "xattr"), nil)
if err != nil {
t.Error(err)
}
if len(res) != 0 {
t.Errorf("expecting no failures, but got %q", res)
}
// Now check that we're sane again
res, err = Check(dir, dh, nil, nil)
if err != nil {
t.Fatal(err)
}
if len(res) != 0 {
// pretty this shit up
buf, err := json.MarshalIndent(res, "", " ")
if err != nil {
t.Errorf("expecting no failures, but got %q", res)
} else {
t.Errorf("expecting no failures, but got %s", string(buf))
}
}
// TODO make a test for xattr here. Likely in the user space for privileges. Even still this may be prone to error for some tmpfs don't act right with xattrs. :-\
// I'd hate to have to t.Skip() a test rather than fail alltogether.
}

View File

@ -97,8 +97,9 @@ func TestUpdate(t *testing.T) {
buf, err := json.MarshalIndent(res, "", " ") buf, err := json.MarshalIndent(res, "", " ")
if err != nil { if err != nil {
t.Errorf("%#v", res) t.Errorf("%#v", res)
} else {
t.Error(string(buf))
} }
t.Error(string(buf))
} }
} }

View File

@ -6,12 +6,14 @@ import (
"strconv" "strconv"
"strings" "strings"
"time" "time"
"github.com/Sirupsen/logrus"
) )
// UpdateKeywordFunc is the signature for a function that will restore a file's // UpdateKeywordFunc is the signature for a function that will restore a file's
// attributes. Where path is relative path to the file, and value to be // attributes. Where path is relative path to the file, and value to be
// restored to. // restored to.
type UpdateKeywordFunc func(path string, value string) (os.FileInfo, error) type UpdateKeywordFunc func(keyword Keyword, path string, value string) (os.FileInfo, error)
// UpdateKeywordFuncs is the registered list of functions to update file attributes. // UpdateKeywordFuncs is the registered list of functions to update file attributes.
// Keyed by the keyword as it would show up in the manifest // Keyed by the keyword as it would show up in the manifest
@ -21,9 +23,10 @@ var UpdateKeywordFuncs = map[Keyword]UpdateKeywordFunc{
"tar_time": tartimeUpdateKeywordFunc, "tar_time": tartimeUpdateKeywordFunc,
"uid": uidUpdateKeywordFunc, "uid": uidUpdateKeywordFunc,
"gid": gidUpdateKeywordFunc, "gid": gidUpdateKeywordFunc,
"xattr": xattrUpdateKeywordFunc,
} }
func uidUpdateKeywordFunc(path, value string) (os.FileInfo, error) { func uidUpdateKeywordFunc(keyword Keyword, path, value string) (os.FileInfo, error) {
uid, err := strconv.Atoi(value) uid, err := strconv.Atoi(value)
if err != nil { if err != nil {
return nil, err return nil, err
@ -34,7 +37,7 @@ func uidUpdateKeywordFunc(path, value string) (os.FileInfo, error) {
return os.Lstat(path) return os.Lstat(path)
} }
func gidUpdateKeywordFunc(path, value string) (os.FileInfo, error) { func gidUpdateKeywordFunc(keyword Keyword, path, value string) (os.FileInfo, error) {
gid, err := strconv.Atoi(value) gid, err := strconv.Atoi(value)
if err != nil { if err != nil {
return nil, err return nil, err
@ -45,12 +48,12 @@ func gidUpdateKeywordFunc(path, value string) (os.FileInfo, error) {
return os.Lstat(path) return os.Lstat(path)
} }
func modeUpdateKeywordFunc(path, value string) (os.FileInfo, error) { func modeUpdateKeywordFunc(keyword Keyword, path, value string) (os.FileInfo, error) {
vmode, err := strconv.ParseInt(value, 8, 32) vmode, err := strconv.ParseInt(value, 8, 32)
if err != nil { if err != nil {
return nil, err return nil, err
} }
Debugf("path: %q, value: %q, vmode: %o", path, value, vmode) logrus.Debugf("path: %q, value: %q, vmode: %o", path, value, vmode)
if err := os.Chmod(path, os.FileMode(vmode)); err != nil { if err := os.Chmod(path, os.FileMode(vmode)); err != nil {
return nil, err return nil, err
} }
@ -60,7 +63,7 @@ func modeUpdateKeywordFunc(path, value string) (os.FileInfo, error) {
// since tar_time will only be second level precision, then when restoring the // since tar_time will only be second level precision, then when restoring the
// filepath from a tar_time, then compare the seconds first and only Chtimes if // filepath from a tar_time, then compare the seconds first and only Chtimes if
// the seconds value is different. // the seconds value is different.
func tartimeUpdateKeywordFunc(path, value string) (os.FileInfo, error) { func tartimeUpdateKeywordFunc(keyword Keyword, path, value string) (os.FileInfo, error) {
info, err := os.Lstat(path) info, err := os.Lstat(path)
if err != nil { if err != nil {
return nil, err return nil, err
@ -89,7 +92,7 @@ func tartimeUpdateKeywordFunc(path, value string) (os.FileInfo, error) {
} }
// this is nano second precision // this is nano second precision
func timeUpdateKeywordFunc(path, value string) (os.FileInfo, error) { func timeUpdateKeywordFunc(keyword Keyword, path, value string) (os.FileInfo, error) {
v := strings.SplitN(value, ".", 2) v := strings.SplitN(value, ".", 2)
if len(v) != 2 { if len(v) != 2 {
return nil, fmt.Errorf("expected a number like 1469104727.871937272") return nil, fmt.Errorf("expected a number like 1469104727.871937272")
@ -98,7 +101,7 @@ func timeUpdateKeywordFunc(path, value string) (os.FileInfo, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("expected nano seconds, but got %q", v[0]+v[1]) return nil, fmt.Errorf("expected nano seconds, but got %q", v[0]+v[1])
} }
Debugf("arg: %q; nsec: %q", v[0]+v[1], nsec) logrus.Debugf("arg: %q; nsec: %q", v[0]+v[1], nsec)
vtime := time.Unix(0, nsec) vtime := time.Unix(0, nsec)
if err := os.Chtimes(path, vtime, vtime); err != nil { if err := os.Chtimes(path, vtime, vtime); err != nil {

21
updatefuncs_linux.go Normal file
View File

@ -0,0 +1,21 @@
// +build linux
package mtree
import (
"encoding/base64"
"os"
"github.com/vbatts/go-mtree/xattr"
)
func xattrUpdateKeywordFunc(keyword Keyword, path, value string) (os.FileInfo, error) {
buf, err := base64.StdEncoding.DecodeString(value)
if err != nil {
return nil, err
}
if err := xattr.Set(path, keyword.Suffix(), buf); err != nil {
return nil, err
}
return os.Lstat(path)
}

View File

@ -0,0 +1,9 @@
// +build !linux
package mtree
import "os"
func xattrUpdateKeywordFunc(keyword Keyword, path, value string) (os.FileInfo, error) {
return os.Lstat(path)
}

38
walk.go
View File

@ -101,15 +101,19 @@ func Walk(root string, excludes []ExcludeFunc, keywords []Keyword, fsEval FsEval
defer fh.Close() defer fh.Close()
r = fh r = fh
} }
keywordFunc, ok := KeywordFuncs[keyword] keyFunc, ok := KeywordFuncs[keyword.Prefix()]
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.Prefix(), path)
} }
if str, err := creator.fs.KeywordFunc(keywordFunc)(path, info, r); err == nil && str != "" { kvs, err := creator.fs.KeywordFunc(keyFunc)(path, info, r)
e.Keywords = append(e.Keywords, str) if err != nil {
} else if err != nil {
return err return err
} }
for _, kv := range kvs {
if kv != "" {
e.Keywords = append(e.Keywords, kv)
}
}
return nil return nil
}() }()
if err != nil { if err != nil {
@ -132,16 +136,18 @@ func Walk(root string, excludes []ExcludeFunc, keywords []Keyword, fsEval FsEval
defer fh.Close() defer fh.Close()
r = fh r = fh
} }
keywordFunc, ok := KeywordFuncs[keyword] keyFunc, ok := KeywordFuncs[keyword.Prefix()]
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.Prefix(), path)
} }
str, err := creator.fs.KeywordFunc(keywordFunc)(path, info, r) kvs, err := creator.fs.KeywordFunc(keyFunc)(path, info, r)
if err != nil { if err != nil {
return err return err
} }
if str != "" { for _, kv := range kvs {
klist = append(klist, str) if kv != "" {
klist = append(klist, kv)
}
} }
return nil return nil
}() }()
@ -190,16 +196,18 @@ func Walk(root string, excludes []ExcludeFunc, keywords []Keyword, fsEval FsEval
defer fh.Close() defer fh.Close()
r = fh r = fh
} }
keywordFunc, ok := KeywordFuncs[keyword] keyFunc, ok := KeywordFuncs[keyword.Prefix()]
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.Prefix(), path)
} }
str, err := creator.fs.KeywordFunc(keywordFunc)(path, info, r) kvs, err := creator.fs.KeywordFunc(keyFunc)(path, info, r)
if err != nil { if err != nil {
return err return err
} }
if str != "" && !inKeyValSlice(str, creator.curSet.Keywords) { for _, kv := range kvs {
e.Keywords = append(e.Keywords, str) if kv != "" && !inKeyValSlice(kv, creator.curSet.Keywords) {
e.Keywords = append(e.Keywords, kv)
}
} }
return nil return nil
}() }()