forked from TrueCloudLab/restic
48dbefc37e
The actual implementation still relies on file paths, but with the abstraction layer in place, an FS implementation can ensure atomic file accesses in the future.
146 lines
3.7 KiB
Go
146 lines
3.7 KiB
Go
//go:build !windows
|
|
// +build !windows
|
|
|
|
package fs
|
|
|
|
import (
|
|
"io/fs"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strings"
|
|
"syscall"
|
|
"testing"
|
|
|
|
"github.com/restic/restic/internal/errors"
|
|
"github.com/restic/restic/internal/restic"
|
|
rtest "github.com/restic/restic/internal/test"
|
|
)
|
|
|
|
func stat(t testing.TB, filename string) (fi os.FileInfo, ok bool) {
|
|
fi, err := os.Lstat(filename)
|
|
if err != nil && os.IsNotExist(err) {
|
|
return fi, false
|
|
}
|
|
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
return fi, true
|
|
}
|
|
|
|
func checkFile(t testing.TB, fi fs.FileInfo, node *restic.Node) {
|
|
t.Helper()
|
|
|
|
stat := fi.Sys().(*syscall.Stat_t)
|
|
|
|
if uint32(node.Mode.Perm()) != uint32(stat.Mode&0777) {
|
|
t.Errorf("Mode does not match, want %v, got %v", stat.Mode&0777, node.Mode)
|
|
}
|
|
|
|
if node.Inode != uint64(stat.Ino) {
|
|
t.Errorf("Inode does not match, want %v, got %v", stat.Ino, node.Inode)
|
|
}
|
|
|
|
if node.DeviceID != uint64(stat.Dev) {
|
|
t.Errorf("Dev does not match, want %v, got %v", stat.Dev, node.DeviceID)
|
|
}
|
|
|
|
if node.Size != uint64(stat.Size) && node.Type != restic.NodeTypeSymlink {
|
|
t.Errorf("Size does not match, want %v, got %v", stat.Size, node.Size)
|
|
}
|
|
|
|
if node.Links != uint64(stat.Nlink) {
|
|
t.Errorf("Links does not match, want %v, got %v", stat.Nlink, node.Links)
|
|
}
|
|
|
|
if node.UID != stat.Uid {
|
|
t.Errorf("UID does not match, want %v, got %v", stat.Uid, node.UID)
|
|
}
|
|
|
|
if node.GID != stat.Gid {
|
|
t.Errorf("UID does not match, want %v, got %v", stat.Gid, node.GID)
|
|
}
|
|
|
|
// use the os dependent function to compare the timestamps
|
|
s := ExtendedStat(fi)
|
|
if node.ModTime != s.ModTime {
|
|
t.Errorf("ModTime does not match, want %v, got %v", s.ModTime, node.ModTime)
|
|
}
|
|
if node.ChangeTime != s.ChangeTime {
|
|
t.Errorf("ChangeTime does not match, want %v, got %v", s.ChangeTime, node.ChangeTime)
|
|
}
|
|
if node.AccessTime != s.AccessTime {
|
|
t.Errorf("AccessTime does not match, want %v, got %v", s.AccessTime, node.AccessTime)
|
|
}
|
|
}
|
|
|
|
func checkDevice(t testing.TB, fi fs.FileInfo, node *restic.Node) {
|
|
stat := fi.Sys().(*syscall.Stat_t)
|
|
if node.Device != uint64(stat.Rdev) {
|
|
t.Errorf("Rdev does not match, want %v, got %v", stat.Rdev, node.Device)
|
|
}
|
|
}
|
|
|
|
func TestNodeFromFileInfo(t *testing.T) {
|
|
tmp := t.TempDir()
|
|
symlink := filepath.Join(tmp, "symlink")
|
|
rtest.OK(t, os.Symlink("target", symlink))
|
|
|
|
type Test struct {
|
|
filename string
|
|
canSkip bool
|
|
}
|
|
var tests = []Test{
|
|
{"node_test.go", false},
|
|
{"/dev/sda", true},
|
|
{symlink, false},
|
|
}
|
|
|
|
// on darwin, users are not permitted to list the extended attributes of
|
|
// /dev/null, therefore skip it.
|
|
// on solaris, /dev/null is a symlink to a device node in /devices
|
|
// which does not support extended attributes, therefore skip it.
|
|
if runtime.GOOS != "darwin" && runtime.GOOS != "solaris" {
|
|
tests = append(tests, Test{"/dev/null", true})
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run("", func(t *testing.T) {
|
|
fi, found := stat(t, test.filename)
|
|
if !found && test.canSkip {
|
|
t.Skipf("%v not found in filesystem", test.filename)
|
|
return
|
|
}
|
|
|
|
fs := &Local{}
|
|
meta, err := fs.OpenFile(test.filename, O_NOFOLLOW, true)
|
|
rtest.OK(t, err)
|
|
node, err := meta.ToNode(false)
|
|
rtest.OK(t, err)
|
|
rtest.OK(t, meta.Close())
|
|
|
|
rtest.OK(t, err)
|
|
|
|
switch node.Type {
|
|
case restic.NodeTypeFile, restic.NodeTypeSymlink:
|
|
checkFile(t, fi, node)
|
|
case restic.NodeTypeDev, restic.NodeTypeCharDev:
|
|
checkFile(t, fi, node)
|
|
checkDevice(t, fi, node)
|
|
default:
|
|
t.Fatalf("invalid node type %q", node.Type)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMknodError(t *testing.T) {
|
|
d := t.TempDir()
|
|
// Call mkfifo, which calls mknod, as mknod may give
|
|
// "operation not permitted" on Mac.
|
|
err := mkfifo(d, 0)
|
|
rtest.Assert(t, errors.Is(err, os.ErrExist), "want ErrExist, got %q", err)
|
|
rtest.Assert(t, strings.Contains(err.Error(), d), "filename not in %q", err)
|
|
}
|