Vincent Batts
73be830998
Fixes #16 In attempt to close https://github.com/vbatts/go-mtree/issues/16 I've uncovered that the update was missing a function for symlink. Additionally the update was not even opperating on the correct directory hierarchy. I've uncovered that os.Chtimes follows the symlink, and presumably only Linux has an obscure way to set the mtime/atime on a symlink itself. So I've made a custom lchtimes(). Also Mode follows through the symlink, and symlinks only ever have a mode of 0777, so don't set them. Lastly, directories need to have their mtime/atime set in a reverse order after all other updates have been done. This is going to require something like a `container/heap` to be unwound. Also, each updateFunc will _only_ perform the update if it is needed. Much less invasive this way. Signed-off-by: Vincent Batts <vbatts@hashbangbash.com>
201 lines
4.9 KiB
Go
201 lines
4.9 KiB
Go
package mtree
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/Sirupsen/logrus"
|
|
"github.com/vbatts/go-mtree/pkg/govis"
|
|
)
|
|
|
|
// 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
|
|
// restored to.
|
|
type UpdateKeywordFunc func(path string, kv KeyVal) (os.FileInfo, error)
|
|
|
|
// UpdateKeywordFuncs is the registered list of functions to update file attributes.
|
|
// Keyed by the keyword as it would show up in the manifest
|
|
var UpdateKeywordFuncs = map[Keyword]UpdateKeywordFunc{
|
|
"mode": modeUpdateKeywordFunc,
|
|
"time": timeUpdateKeywordFunc,
|
|
"tar_time": tartimeUpdateKeywordFunc,
|
|
"uid": uidUpdateKeywordFunc,
|
|
"gid": gidUpdateKeywordFunc,
|
|
"xattr": xattrUpdateKeywordFunc,
|
|
"link": linkUpdateKeywordFunc,
|
|
}
|
|
|
|
func uidUpdateKeywordFunc(path string, kv KeyVal) (os.FileInfo, error) {
|
|
uid, err := strconv.Atoi(kv.Value())
|
|
if err != nil {
|
|
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 {
|
|
return nil, err
|
|
}
|
|
return os.Lstat(path)
|
|
}
|
|
|
|
func gidUpdateKeywordFunc(path string, kv KeyVal) (os.FileInfo, error) {
|
|
gid, err := strconv.Atoi(kv.Value())
|
|
if err != nil {
|
|
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 {
|
|
return nil, err
|
|
}
|
|
return os.Lstat(path)
|
|
}
|
|
|
|
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)
|
|
if err != nil {
|
|
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)
|
|
if err := os.Chmod(path, os.FileMode(vmode)); err != nil {
|
|
return nil, err
|
|
}
|
|
return os.Lstat(path)
|
|
}
|
|
|
|
// 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
|
|
// the seconds value is different.
|
|
func tartimeUpdateKeywordFunc(path string, kv KeyVal) (os.FileInfo, error) {
|
|
info, err := os.Lstat(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
v := strings.SplitN(kv.Value(), ".", 2)
|
|
if len(v) != 2 {
|
|
return nil, fmt.Errorf("expected a number like 1469104727.000000000")
|
|
}
|
|
sec, err := strconv.ParseInt(v[0], 10, 64)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("expected seconds, but got %q", v[0])
|
|
}
|
|
|
|
// if the seconds are the same, don't do anything, because the file might
|
|
// have nanosecond value, and if using tar_time it would zero it out.
|
|
if info.ModTime().Unix() == sec {
|
|
return info, nil
|
|
}
|
|
|
|
vtime := time.Unix(sec, 0)
|
|
|
|
// 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 os.Lstat(path)
|
|
}
|
|
|
|
// this is nano second precision
|
|
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)
|
|
if len(v) != 2 {
|
|
return nil, fmt.Errorf("expected a number like 1469104727.871937272")
|
|
}
|
|
nsec, err := strconv.ParseInt(v[0]+v[1], 10, 64)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("expected nano seconds, but got %q", v[0]+v[1])
|
|
}
|
|
logrus.Debugf("arg: %q; nsec: %d", v[0]+v[1], nsec)
|
|
|
|
vtime := time.Unix(0, nsec)
|
|
|
|
// 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 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)
|
|
}
|