fs: Refactor UtimesNano replacements

Previously, nodeRestoreTimestamps would do something like

	if node.Type == restic.NodeTypeSymlink {
	    return nodeRestoreSymlinkTimestamps(...)
	}
	return syscall.UtimesNano(...)

where nodeRestoreSymlinkTimestamps was either a no-op or a
reimplementation of syscall.UtimesNano that handles symlinks, with some
repeated converting between timestamp types. The Linux implementation
was a bit clumsy, requiring three syscalls to set the timestamps.

In this new setup, there is a function utimesNano that has three
implementations:

* on Linux, it's a modified syscall.UtimesNano that uses
  AT_SYMLINK_NOFOLLOW and AT_FDCWD so it can handle any type in a single
  call;
* on other Unix platforms, it just calls the syscall function after
  skipping symlinks;
* on Windows, it's the modified UtimesNano that was previously called
  nodeRestoreSymlinkTimestamps, except with different arguments.
This commit is contained in:
greatroar 2024-10-04 10:06:18 +02:00
parent f967a33ccc
commit 8f20d5dcd5
10 changed files with 57 additions and 65 deletions

View file

@ -292,18 +292,11 @@ func nodeRestoreMetadata(node *restic.Node, path string, warn func(msg string))
} }
func nodeRestoreTimestamps(node *restic.Node, path string) error { func nodeRestoreTimestamps(node *restic.Node, path string) error {
var utimes = [...]syscall.Timespec{ atime := node.AccessTime.UnixNano()
syscall.NsecToTimespec(node.AccessTime.UnixNano()), mtime := node.ModTime.UnixNano()
syscall.NsecToTimespec(node.ModTime.UnixNano()),
}
if node.Type == restic.NodeTypeSymlink { if err := utimesNano(fixpath(path), atime, mtime, node.Type); err != nil {
return nodeRestoreSymlinkTimestamps(path, utimes) return &os.PathError{Op: "UtimesNano", Path: path, Err: err}
} }
if err := syscall.UtimesNano(fixpath(path), utimes[:]); err != nil {
return errors.Wrap(err, "UtimesNano")
}
return nil return nil
} }

View file

@ -3,15 +3,7 @@
package fs package fs
import ( import "github.com/restic/restic/internal/restic"
"syscall"
"github.com/restic/restic/internal/restic"
)
func nodeRestoreSymlinkTimestamps(_ string, _ [2]syscall.Timespec) error {
return nil
}
// nodeRestoreExtendedAttributes is a no-op on AIX. // nodeRestoreExtendedAttributes is a no-op on AIX.
func nodeRestoreExtendedAttributes(_ *restic.Node, _ string) error { func nodeRestoreExtendedAttributes(_ *restic.Node, _ string) error {

View file

@ -1,7 +0,0 @@
package fs
import "syscall"
func nodeRestoreSymlinkTimestamps(path string, utimes [2]syscall.Timespec) error {
return nil
}

View file

@ -1,18 +1,15 @@
package fs package fs
import ( import (
"syscall" "github.com/restic/restic/internal/restic"
"github.com/restic/restic/internal/errors"
"golang.org/x/sys/unix" "golang.org/x/sys/unix"
) )
func nodeRestoreSymlinkTimestamps(path string, utimes [2]syscall.Timespec) error { // utimesNano is like syscall.UtimesNano, except that it does not follow symlinks.
func utimesNano(path string, atime, mtime int64, _ restic.NodeType) error {
times := []unix.Timespec{ times := []unix.Timespec{
{Sec: utimes[0].Sec, Nsec: utimes[0].Nsec}, unix.NsecToTimespec(atime),
{Sec: utimes[1].Sec, Nsec: utimes[1].Nsec}, unix.NsecToTimespec(mtime),
} }
return unix.UtimesNanoAt(unix.AT_FDCWD, path, times, unix.AT_SYMLINK_NOFOLLOW)
err := unix.UtimesNanoAt(unix.AT_FDCWD, path, times, unix.AT_SYMLINK_NOFOLLOW)
return errors.Wrap(err, "UtimesNanoAt")
} }

View file

@ -0,0 +1,19 @@
package fs
import (
"io/fs"
"strings"
"testing"
"github.com/restic/restic/internal/errors"
"github.com/restic/restic/internal/restic"
rtest "github.com/restic/restic/internal/test"
)
func TestRestoreSymlinkTimestampsError(t *testing.T) {
d := t.TempDir()
node := restic.Node{Type: restic.NodeTypeSymlink}
err := nodeRestoreTimestamps(&node, d+"/nosuchfile")
rtest.Assert(t, errors.Is(err, fs.ErrNotExist), "want ErrNotExist, got %q", err)
rtest.Assert(t, strings.Contains(err.Error(), d), "filename not in %q", err)
}

View file

@ -1,14 +1,6 @@
package fs package fs
import ( import "github.com/restic/restic/internal/restic"
"syscall"
"github.com/restic/restic/internal/restic"
)
func nodeRestoreSymlinkTimestamps(_ string, _ [2]syscall.Timespec) error {
return nil
}
// nodeRestoreExtendedAttributes is a no-op on netbsd. // nodeRestoreExtendedAttributes is a no-op on netbsd.
func nodeRestoreExtendedAttributes(_ *restic.Node, _ string) error { func nodeRestoreExtendedAttributes(_ *restic.Node, _ string) error {

View file

@ -1,14 +1,6 @@
package fs package fs
import ( import "github.com/restic/restic/internal/restic"
"syscall"
"github.com/restic/restic/internal/restic"
)
func nodeRestoreSymlinkTimestamps(_ string, _ [2]syscall.Timespec) error {
return nil
}
// nodeRestoreExtendedAttributes is a no-op on openbsd. // nodeRestoreExtendedAttributes is a no-op on openbsd.
func nodeRestoreExtendedAttributes(_ *restic.Node, _ string) error { func nodeRestoreExtendedAttributes(_ *restic.Node, _ string) error {

View file

@ -1,7 +0,0 @@
package fs
import "syscall"
func nodeRestoreSymlinkTimestamps(path string, utimes [2]syscall.Timespec) error {
return nil
}

View file

@ -0,0 +1,21 @@
//go:build !linux && unix
package fs
import (
"syscall"
"github.com/restic/restic/internal/restic"
)
// utimesNano is like syscall.UtimesNano, except that it skips symlinks.
func utimesNano(path string, atime, mtime int64, typ restic.NodeType) error {
if typ == restic.NodeTypeSymlink {
return nil
}
return syscall.UtimesNano(path, []syscall.Timespec{
syscall.NsecToTimespec(atime),
syscall.NsecToTimespec(mtime),
})
}

View file

@ -42,8 +42,8 @@ func lchown(_ string, _ int, _ int) (err error) {
return nil return nil
} }
// restoreSymlinkTimestamps restores timestamps for symlinks // utimesNano is like syscall.UtimesNano, except that it sets FILE_FLAG_OPEN_REPARSE_POINT.
func nodeRestoreSymlinkTimestamps(path string, utimes [2]syscall.Timespec) error { func utimesNano(path string, atime, mtime int64, _ restic.NodeType) error {
// tweaked version of UtimesNano from go/src/syscall/syscall_windows.go // tweaked version of UtimesNano from go/src/syscall/syscall_windows.go
pathp, e := syscall.UTF16PtrFromString(fixpath(path)) pathp, e := syscall.UTF16PtrFromString(fixpath(path))
if e != nil { if e != nil {
@ -63,8 +63,8 @@ func nodeRestoreSymlinkTimestamps(path string, utimes [2]syscall.Timespec) error
} }
}() }()
a := syscall.NsecToFiletime(syscall.TimespecToNsec(utimes[0])) a := syscall.NsecToFiletime(atime)
w := syscall.NsecToFiletime(syscall.TimespecToNsec(utimes[1])) w := syscall.NsecToFiletime(mtime)
return syscall.SetFileTime(h, nil, &a, &w) return syscall.SetFileTime(h, nil, &a, &w)
} }