mirror of
https://github.com/vbatts/go-mtree.git
synced 2024-11-22 08:25:38 +00:00
Merge pull request #138 from vbatts/match_update_behavior
*: update `-u` behavior
This commit is contained in:
commit
2ede6ecf20
10 changed files with 305 additions and 21 deletions
|
@ -254,18 +254,36 @@ func app() error {
|
||||||
if *flUpdateAttributes && stateDh != nil {
|
if *flUpdateAttributes && stateDh != nil {
|
||||||
// -u
|
// -u
|
||||||
// this comes before the next case, intentionally.
|
// this comes before the next case, intentionally.
|
||||||
|
result, err := mtree.Update(rootPath, specDh, mtree.DefaultUpdateKeywords, nil)
|
||||||
// TODO brainstorm where to allow setting of xattrs. Maybe a new flag that allows a comma delimited list of keywords to update?
|
|
||||||
updateKeywords := []mtree.Keyword{"uid", "gid", "mode"}
|
|
||||||
|
|
||||||
result, err := mtree.Update(rootPath, stateDh, updateKeywords, nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if result != nil && len(result) > 0 {
|
||||||
if result != nil {
|
|
||||||
fmt.Printf("%#v\n", result)
|
fmt.Printf("%#v\n", result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var res []mtree.InodeDelta
|
||||||
|
// only check the keywords that we just updated
|
||||||
|
res, err = mtree.Check(rootPath, specDh, mtree.DefaultUpdateKeywords, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if res != nil {
|
||||||
|
out := formatFunc(res)
|
||||||
|
if _, err := os.Stdout.Write([]byte(out)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: This should be a flag. Allowing files to be added and
|
||||||
|
// removed and still returning "it's all good" is simply
|
||||||
|
// unsafe IMO.
|
||||||
|
for _, diff := range res {
|
||||||
|
if diff.Type() == mtree.Modified {
|
||||||
|
return fmt.Errorf("mainfest validation failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -300,7 +318,6 @@ func app() error {
|
||||||
// This is a validation.
|
// This is a validation.
|
||||||
if specDh != nil && stateDh != nil {
|
if specDh != nil && stateDh != nil {
|
||||||
var res []mtree.InodeDelta
|
var res []mtree.InodeDelta
|
||||||
|
|
||||||
res, err = mtree.Compare(specDh, stateDh, currentKeywords)
|
res, err = mtree.Compare(specDh, stateDh, currentKeywords)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
10
entry.go
10
entry.go
|
@ -112,6 +112,16 @@ func (e Entry) AllKeys() []KeyVal {
|
||||||
return e.Keywords
|
return e.Keywords
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsDir checks the type= value for this entry on whether it is a directory
|
||||||
|
func (e Entry) IsDir() bool {
|
||||||
|
for _, kv := range e.AllKeys() {
|
||||||
|
if kv.Keyword().Prefix() == "type" {
|
||||||
|
return kv.Value() == "dir"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// EntryType are the formats of lines in an mtree spec file
|
// EntryType are the formats of lines in an mtree spec file
|
||||||
type EntryType int
|
type EntryType int
|
||||||
|
|
||||||
|
|
18
stat_unix.go
Normal file
18
stat_unix.go
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package mtree
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
func statIsUID(stat os.FileInfo, uid int) bool {
|
||||||
|
statT := stat.Sys().(*syscall.Stat_t)
|
||||||
|
return statT.Uid == uint32(uid)
|
||||||
|
}
|
||||||
|
|
||||||
|
func statIsGID(stat os.FileInfo, gid int) bool {
|
||||||
|
statT := stat.Sys().(*syscall.Stat_t)
|
||||||
|
return statT.Gid == uint32(gid)
|
||||||
|
}
|
12
stat_windows.go
Normal file
12
stat_windows.go
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package mtree
|
||||||
|
|
||||||
|
import "os"
|
||||||
|
|
||||||
|
func statIsUID(stat os.FileInfo, uid int) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
func statIsGID(stat os.FileInfo, uid int) bool {
|
||||||
|
return false
|
||||||
|
}
|
|
@ -7,8 +7,6 @@ gomtree=$(readlink -f ${root}/gomtree)
|
||||||
t=$(mktemp -t -d go-mtree.XXXXXX)
|
t=$(mktemp -t -d go-mtree.XXXXXX)
|
||||||
|
|
||||||
echo "[${name}] Running in ${t}"
|
echo "[${name}] Running in ${t}"
|
||||||
# This test is for basic running check of manifest, and check against tar and file system
|
|
||||||
#
|
|
||||||
|
|
||||||
pushd ${root}
|
pushd ${root}
|
||||||
|
|
||||||
|
@ -17,7 +15,7 @@ mkdir ${t}/root
|
||||||
echo "some data" > "${t}/root/$(printf 'this file has \u042a some unicode !!')"
|
echo "some data" > "${t}/root/$(printf 'this file has \u042a some unicode !!')"
|
||||||
echo "more data" > "${t}/root/$(printf 'even more \x07 unicode \ua4ff characters \udead\ubeef\ucafe')"
|
echo "more data" > "${t}/root/$(printf 'even more \x07 unicode \ua4ff characters \udead\ubeef\ucafe')"
|
||||||
mkdir -p "${t}/root/$(printf '\024 <-- some more weird characters --> \u4f60\u597d\uff0c\u4e16\u754c')"
|
mkdir -p "${t}/root/$(printf '\024 <-- some more weird characters --> \u4f60\u597d\uff0c\u4e16\u754c')"
|
||||||
ln -s "$(printf '62_\u00c6\u00c62\u00ae\u00b7m\u00db\u00c3r^\u00bfp\u00c6u"q\u00fbc2\u00f0u\u00b8\u00dd\u00e8v\u00ff\u00b0\u00dc\u00c2\u00f53\u00db')" "${t}/root/$(printf '-k\u00f2sd4\\p\u00da\u00a6\u00d3\u00eea<\u00e6s{\u00a0p\u00f0\u00ffj\u00e0\u00e8\u00b8\u00b8\u00bc\u00fcb')"
|
ln -s "$(printf '62_\u00c6\u00c62\u00ae\u00b7m\u00db\u00c3r^\u00bfp\u00c6u"q\u00fbc2\u00f0u\u00b8\u00dd\u00e8v\u00ff\u00b0\u00dc\u00c2\u00f53\u00db')" "${t}/root/$(printf 'k\u00f2sd4\\p\u00da\u00a6\u00d3\u00eea<\u00e6s{\u00a0p\u00f0\u00ffj\u00e0\u00e8\u00b8\u00b8\u00bc\u00fcb')"
|
||||||
printf 'some lovely data 62_\u00c6\u00c62\u00ae\u00b7m\u00db\u00c3r' > "${t}/root/$(printf 'T\u00dcB\u0130TAK_UEKAE_K\u00f6k_Sertifika_Hizmet_Sa\u011flay\u0131c\u0131s\u0131_-_S\u00fcr\u00fcm_3.pem')"
|
printf 'some lovely data 62_\u00c6\u00c62\u00ae\u00b7m\u00db\u00c3r' > "${t}/root/$(printf 'T\u00dcB\u0130TAK_UEKAE_K\u00f6k_Sertifika_Hizmet_Sa\u011flay\u0131c\u0131s\u0131_-_S\u00fcr\u00fcm_3.pem')"
|
||||||
|
|
||||||
# Create manifest and check it against the same root.
|
# Create manifest and check it against the same root.
|
||||||
|
|
76
update.go
76
update.go
|
@ -1,6 +1,7 @@
|
||||||
package mtree
|
package mtree
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"container/heap"
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
|
@ -12,8 +13,9 @@ var DefaultUpdateKeywords = []Keyword{
|
||||||
"uid",
|
"uid",
|
||||||
"gid",
|
"gid",
|
||||||
"mode",
|
"mode",
|
||||||
"time",
|
|
||||||
"xattr",
|
"xattr",
|
||||||
|
"link",
|
||||||
|
"time",
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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.
|
||||||
|
@ -29,6 +31,11 @@ func Update(root string, dh *DirectoryHierarchy, keywords []Keyword, fs FsEval)
|
||||||
}
|
}
|
||||||
sort.Sort(byPos(creator.DH.Entries))
|
sort.Sort(byPos(creator.DH.Entries))
|
||||||
|
|
||||||
|
// This is for deferring the update of mtimes of directories, to unwind them
|
||||||
|
// in a most specific path first
|
||||||
|
h := &pathUpdateHeap{}
|
||||||
|
heap.Init(h)
|
||||||
|
|
||||||
results := []InodeDelta{}
|
results := []InodeDelta{}
|
||||||
for i, e := range creator.DH.Entries {
|
for i, e := range creator.DH.Entries {
|
||||||
switch e.Type {
|
switch e.Type {
|
||||||
|
@ -62,6 +69,19 @@ func Update(root string, dh *DirectoryHierarchy, keywords []Keyword, fs FsEval)
|
||||||
logrus.Debugf("no UpdateKeywordFunc for %s; skipping", kv.Keyword())
|
logrus.Debugf("no UpdateKeywordFunc for %s; skipping", kv.Keyword())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO check for the type=dir of the entry as well
|
||||||
|
if kv.Keyword().Prefix() == "time" && e.IsDir() {
|
||||||
|
heap.Push(h, pathUpdate{
|
||||||
|
Path: pathname,
|
||||||
|
E: e,
|
||||||
|
KV: kv,
|
||||||
|
Func: ukFunc,
|
||||||
|
})
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
if _, err := ukFunc(pathname, kv); err != nil {
|
if _, err := ukFunc(pathname, kv); err != nil {
|
||||||
results = append(results, InodeDelta{
|
results = append(results, InodeDelta{
|
||||||
diff: ErrorDifference,
|
diff: ErrorDifference,
|
||||||
|
@ -75,16 +95,60 @@ func Update(root string, dh *DirectoryHierarchy, keywords []Keyword, fs FsEval)
|
||||||
},
|
},
|
||||||
}})
|
}})
|
||||||
}
|
}
|
||||||
|
// XXX really would be great to have a Check() or Compare() right here,
|
||||||
|
// to compare each entry as it is encountered, rather than just running
|
||||||
|
// Check() on this path after the whole update is finished.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for h.Len() > 0 {
|
||||||
|
pu := heap.Pop(h).(pathUpdate)
|
||||||
|
if _, err := pu.Func(pu.Path, pu.KV); err != nil {
|
||||||
|
results = append(results, InodeDelta{
|
||||||
|
diff: ErrorDifference,
|
||||||
|
path: pu.Path,
|
||||||
|
old: pu.E,
|
||||||
|
keys: []KeyDelta{
|
||||||
|
{
|
||||||
|
diff: ErrorDifference,
|
||||||
|
name: pu.KV.Keyword(),
|
||||||
|
err: err,
|
||||||
|
},
|
||||||
|
}})
|
||||||
|
}
|
||||||
|
}
|
||||||
return results, nil
|
return results, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Result of an "update" returns the produced results
|
type pathUpdateHeap []pathUpdate
|
||||||
type Result struct {
|
|
||||||
Path string
|
func (h pathUpdateHeap) Len() int { return len(h) }
|
||||||
Keyword Keyword
|
func (h pathUpdateHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
|
||||||
Got string
|
|
||||||
|
// This may end up looking backwards, but for container/heap, Less evaluates
|
||||||
|
// the negative priority. So when popping members of the array, it will be
|
||||||
|
// sorted by least. For this use-case, we want the most-qualified-name popped
|
||||||
|
// first (the longest path name), such that "." is the last entry popped.
|
||||||
|
func (h pathUpdateHeap) Less(i, j int) bool {
|
||||||
|
return len(h[i].Path) > len(h[j].Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *pathUpdateHeap) Push(x interface{}) {
|
||||||
|
*h = append(*h, x.(pathUpdate))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *pathUpdateHeap) Pop() interface{} {
|
||||||
|
old := *h
|
||||||
|
n := len(old)
|
||||||
|
x := old[n-1]
|
||||||
|
*h = old[0 : n-1]
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
type pathUpdate struct {
|
||||||
|
Path string
|
||||||
|
E Entry
|
||||||
|
KV KeyVal
|
||||||
|
Func UpdateKeywordFunc
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
package mtree
|
package mtree
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"container/heap"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
@ -11,8 +12,14 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
logrus.SetLevel(logrus.DebugLevel)
|
||||||
|
}
|
||||||
|
|
||||||
func TestUpdate(t *testing.T) {
|
func TestUpdate(t *testing.T) {
|
||||||
content := []byte("I know half of you half as well as I ought to")
|
content := []byte("I know half of you half as well as I ought to")
|
||||||
dir, err := ioutil.TempDir("", "test-check-keywords")
|
dir, err := ioutil.TempDir("", "test-check-keywords")
|
||||||
|
@ -103,3 +110,27 @@ func TestUpdate(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPathUpdateHeap(t *testing.T) {
|
||||||
|
h := &pathUpdateHeap{
|
||||||
|
pathUpdate{Path: "not/the/longest"},
|
||||||
|
pathUpdate{Path: "almost/the/longest"},
|
||||||
|
pathUpdate{Path: "."},
|
||||||
|
pathUpdate{Path: "short"},
|
||||||
|
}
|
||||||
|
heap.Init(h)
|
||||||
|
v := "this/is/one/is/def/the/longest"
|
||||||
|
heap.Push(h, pathUpdate{Path: v})
|
||||||
|
|
||||||
|
longest := len(v)
|
||||||
|
var p string
|
||||||
|
for h.Len() > 0 {
|
||||||
|
p = heap.Pop(h).(pathUpdate).Path
|
||||||
|
if len(p) > longest {
|
||||||
|
t.Errorf("expected next path to be shorter, but it was not %q is longer than %d", p, longest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if p != "." {
|
||||||
|
t.Errorf("expected \".\" to be the last, but got %q", p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/vbatts/go-mtree/pkg/govis"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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
|
||||||
|
@ -24,6 +25,7 @@ var UpdateKeywordFuncs = map[Keyword]UpdateKeywordFunc{
|
||||||
"uid": uidUpdateKeywordFunc,
|
"uid": uidUpdateKeywordFunc,
|
||||||
"gid": gidUpdateKeywordFunc,
|
"gid": gidUpdateKeywordFunc,
|
||||||
"xattr": xattrUpdateKeywordFunc,
|
"xattr": xattrUpdateKeywordFunc,
|
||||||
|
"link": linkUpdateKeywordFunc,
|
||||||
}
|
}
|
||||||
|
|
||||||
func uidUpdateKeywordFunc(path string, kv KeyVal) (os.FileInfo, error) {
|
func uidUpdateKeywordFunc(path string, kv KeyVal) (os.FileInfo, error) {
|
||||||
|
@ -31,6 +33,15 @@ func uidUpdateKeywordFunc(path string, kv KeyVal) (os.FileInfo, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stat, err := os.Lstat(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if statIsUID(stat, uid) {
|
||||||
|
return stat, nil
|
||||||
|
}
|
||||||
|
|
||||||
if err := os.Lchown(path, uid, -1); err != nil {
|
if err := os.Lchown(path, uid, -1); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -42,6 +53,15 @@ func gidUpdateKeywordFunc(path string, kv KeyVal) (os.FileInfo, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stat, err := os.Lstat(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if statIsGID(stat, gid) {
|
||||||
|
return stat, nil
|
||||||
|
}
|
||||||
|
|
||||||
if err := os.Lchown(path, -1, gid); err != nil {
|
if err := os.Lchown(path, -1, gid); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -49,10 +69,28 @@ func gidUpdateKeywordFunc(path string, kv KeyVal) (os.FileInfo, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func modeUpdateKeywordFunc(path string, kv KeyVal) (os.FileInfo, error) {
|
func modeUpdateKeywordFunc(path string, kv KeyVal) (os.FileInfo, error) {
|
||||||
|
info, err := os.Lstat(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// don't set mode on symlinks, as it passes through to the backing file
|
||||||
|
if info.Mode()&os.ModeSymlink != 0 {
|
||||||
|
return info, nil
|
||||||
|
}
|
||||||
vmode, err := strconv.ParseInt(kv.Value(), 8, 32)
|
vmode, err := strconv.ParseInt(kv.Value(), 8, 32)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stat, err := os.Lstat(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if stat.Mode() == os.FileMode(vmode) {
|
||||||
|
return stat, nil
|
||||||
|
}
|
||||||
|
|
||||||
logrus.Debugf("path: %q, kv.Value(): %q, vmode: %o", path, kv.Value(), vmode)
|
logrus.Debugf("path: %q, kv.Value(): %q, vmode: %o", path, kv.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
|
||||||
|
@ -85,7 +123,19 @@ func tartimeUpdateKeywordFunc(path string, kv KeyVal) (os.FileInfo, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
vtime := time.Unix(sec, 0)
|
vtime := time.Unix(sec, 0)
|
||||||
if err := os.Chtimes(path, vtime, vtime); err != nil {
|
|
||||||
|
// if times are same then don't modify anything
|
||||||
|
// comparing Unix, since it does not include Nano seconds
|
||||||
|
if info.ModTime().Unix() == vtime.Unix() {
|
||||||
|
return info, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// symlinks are strange and most of the time passes through to the backing file
|
||||||
|
if info.Mode()&os.ModeSymlink != 0 {
|
||||||
|
if err := lchtimes(path, vtime, vtime); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else if err := os.Chtimes(path, vtime, vtime); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return os.Lstat(path)
|
return os.Lstat(path)
|
||||||
|
@ -93,6 +143,11 @@ func tartimeUpdateKeywordFunc(path string, kv KeyVal) (os.FileInfo, error) {
|
||||||
|
|
||||||
// this is nano second precision
|
// this is nano second precision
|
||||||
func timeUpdateKeywordFunc(path string, kv KeyVal) (os.FileInfo, error) {
|
func timeUpdateKeywordFunc(path string, kv KeyVal) (os.FileInfo, error) {
|
||||||
|
info, err := os.Lstat(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
v := strings.SplitN(kv.Value(), ".", 2)
|
v := strings.SplitN(kv.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")
|
||||||
|
@ -101,11 +156,46 @@ func timeUpdateKeywordFunc(path string, kv KeyVal) (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])
|
||||||
}
|
}
|
||||||
logrus.Debugf("arg: %q; nsec: %q", v[0]+v[1], nsec)
|
logrus.Debugf("arg: %q; nsec: %d", 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 times are same then don't modify anything
|
||||||
|
if info.ModTime().Equal(vtime) {
|
||||||
|
return info, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// symlinks are strange and most of the time passes through to the backing file
|
||||||
|
if info.Mode()&os.ModeSymlink != 0 {
|
||||||
|
if err := lchtimes(path, vtime, vtime); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else if err := os.Chtimes(path, vtime, vtime); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return os.Lstat(path)
|
return os.Lstat(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func linkUpdateKeywordFunc(path string, kv KeyVal) (os.FileInfo, error) {
|
||||||
|
linkname, err := govis.Unvis(kv.Value(), DefaultVisFlags)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
got, err := os.Readlink(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if got == linkname {
|
||||||
|
return os.Lstat(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
logrus.Debugf("linkUpdateKeywordFunc: removing %q to link to %q", path, linkname)
|
||||||
|
if err := os.Remove(path); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if err := os.Symlink(linkname, path); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return os.Lstat(path)
|
||||||
|
}
|
||||||
|
|
|
@ -5,6 +5,9 @@ package mtree
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"os"
|
"os"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
"github.com/vbatts/go-mtree/xattr"
|
"github.com/vbatts/go-mtree/xattr"
|
||||||
)
|
)
|
||||||
|
@ -19,3 +22,37 @@ func xattrUpdateKeywordFunc(path string, kv KeyVal) (os.FileInfo, error) {
|
||||||
}
|
}
|
||||||
return os.Lstat(path)
|
return os.Lstat(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func lchtimes(name string, atime time.Time, mtime time.Time) error {
|
||||||
|
var utimes [2]syscall.Timespec
|
||||||
|
utimes[0] = syscall.NsecToTimespec(atime.UnixNano())
|
||||||
|
utimes[1] = syscall.NsecToTimespec(mtime.UnixNano())
|
||||||
|
if e := utimensat(atFdCwd, name, (*[2]syscall.Timespec)(unsafe.Pointer(&utimes[0])), atSymlinkNofollow); e != nil {
|
||||||
|
return &os.PathError{Op: "chtimes", Path: name, Err: e}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// from uapi/linux/fcntl.h
|
||||||
|
// don't follow symlinks
|
||||||
|
const atSymlinkNofollow = 0x100
|
||||||
|
|
||||||
|
// special value for utimes as the FD for the current working directory
|
||||||
|
const atFdCwd = -0x64
|
||||||
|
|
||||||
|
func utimensat(dirfd int, path string, times *[2]syscall.Timespec, flags int) (err error) {
|
||||||
|
if len(times) != 2 {
|
||||||
|
return syscall.EINVAL
|
||||||
|
}
|
||||||
|
var _p0 *byte
|
||||||
|
_p0, err = syscall.BytePtrFromString(path)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_, _, e1 := syscall.Syscall6(syscall.SYS_UTIMENSAT, uintptr(dirfd), uintptr(unsafe.Pointer(_p0)), uintptr(unsafe.Pointer(times)), uintptr(flags), 0, 0)
|
||||||
|
if e1 != 0 {
|
||||||
|
err = syscall.Errno(e1)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
|
@ -2,8 +2,15 @@
|
||||||
|
|
||||||
package mtree
|
package mtree
|
||||||
|
|
||||||
import "os"
|
import (
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
func xattrUpdateKeywordFunc(path string, kv KeyVal) (os.FileInfo, error) {
|
func xattrUpdateKeywordFunc(path string, kv KeyVal) (os.FileInfo, error) {
|
||||||
return os.Lstat(path)
|
return os.Lstat(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func lchtimes(name string, atime time.Time, mtime time.Time) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue