Merge pull request #135 from vbatts/xattr-updates
*: xattr can Update()
This commit is contained in:
commit
0b5038d0bc
19 changed files with 398 additions and 258 deletions
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
26
debug.go
26
debug.go
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
80
keywords.go
80
keywords.go
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"))
|
||||||
|
|
|
@ -9,14 +9,20 @@ 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 = "xattr"
|
||||||
|
got = string(kv.Keyword().Prefix())
|
||||||
|
if got != expected {
|
||||||
|
t.Errorf("expected %q; got %q", expected, got)
|
||||||
|
}
|
||||||
|
|
||||||
expected = "security.selinux"
|
expected = "security.selinux"
|
||||||
got = kv.KeywordSuffix()
|
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)
|
||||||
}
|
}
|
||||||
|
@ -27,7 +33,6 @@ func TestKeyValRoundtrip(t *testing.T) {
|
||||||
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"
|
||||||
got = string(kv.NewValue("farts"))
|
got = string(kv.NewValue("farts"))
|
||||||
if got != expected {
|
if got != expected {
|
||||||
|
@ -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
38
tar.go
|
@ -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
|
||||||
|
|
23
update.go
23
update.go
|
@ -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
94
update_linux_test.go
Normal 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.
|
||||||
|
}
|
|
@ -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))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
21
updatefuncs_linux.go
Normal 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)
|
||||||
|
}
|
9
updatefuncs_unsupported.go
Normal file
9
updatefuncs_unsupported.go
Normal 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
38
walk.go
|
@ -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
|
||||||
}()
|
}()
|
||||||
|
|
Loading…
Reference in a new issue