forked from TrueCloudLab/restic
Merge pull request #903 from restic/fix-hardlinks
Add new field DeviceID and tests
This commit is contained in:
commit
a725e065d9
2 changed files with 144 additions and 5 deletions
|
@ -37,11 +37,12 @@ type Node struct {
|
|||
User string `json:"user,omitempty"`
|
||||
Group string `json:"group,omitempty"`
|
||||
Inode uint64 `json:"inode,omitempty"`
|
||||
DeviceID uint64 `json:"device_id,omitempty"` // device id of the file, stat.st_dev
|
||||
Size uint64 `json:"size,omitempty"`
|
||||
Links uint64 `json:"links,omitempty"`
|
||||
LinkTarget string `json:"linktarget,omitempty"`
|
||||
ExtendedAttributes []ExtendedAttribute `json:"extended_attributes,omitempty"`
|
||||
Device uint64 `json:"device,omitempty"`
|
||||
Device uint64 `json:"device,omitempty"` // in case of Type == "dev", stat.st_rdev
|
||||
Content IDs `json:"content"`
|
||||
Subtree *ID `json:"subtree,omitempty"`
|
||||
|
||||
|
@ -228,11 +229,11 @@ func (node Node) createDirAt(path string) error {
|
|||
}
|
||||
|
||||
func (node Node) createFileAt(path string, repo Repository, idx *HardlinkIndex) error {
|
||||
if node.Links > 1 && idx.Has(node.Inode, node.Device) {
|
||||
if node.Links > 1 && idx.Has(node.Inode, node.DeviceID) {
|
||||
if err := fs.Remove(path); !os.IsNotExist(err) {
|
||||
return errors.Wrap(err, "RemoveCreateHardlink")
|
||||
}
|
||||
err := fs.Link(idx.GetFilename(node.Inode, node.Device), path)
|
||||
err := fs.Link(idx.GetFilename(node.Inode, node.DeviceID), path)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "CreateHardlink")
|
||||
}
|
||||
|
@ -271,7 +272,7 @@ func (node Node) createFileAt(path string, repo Repository, idx *HardlinkIndex)
|
|||
}
|
||||
|
||||
if node.Links > 1 {
|
||||
idx.Add(node.Inode, node.Device, path)
|
||||
idx.Add(node.Inode, node.DeviceID, path)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -376,6 +377,9 @@ func (node Node) Equals(other Node) bool {
|
|||
if node.Inode != other.Inode {
|
||||
return false
|
||||
}
|
||||
if node.DeviceID != other.DeviceID {
|
||||
return false
|
||||
}
|
||||
if node.Size != other.Size {
|
||||
return false
|
||||
}
|
||||
|
@ -568,7 +572,7 @@ func (node *Node) fillExtra(path string, fi os.FileInfo) error {
|
|||
}
|
||||
|
||||
node.Inode = uint64(stat.ino())
|
||||
node.Device = uint64(stat.dev())
|
||||
node.DeviceID = uint64(stat.dev())
|
||||
|
||||
node.fillTimes(stat)
|
||||
|
||||
|
|
135
src/restic/node_unix_test.go
Normal file
135
src/restic/node_unix_test.go
Normal file
|
@ -0,0 +1,135 @@
|
|||
// +build !windows
|
||||
|
||||
package restic
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
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, stat *syscall.Stat_t, node *Node) {
|
||||
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) {
|
||||
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, ok := toStatT(stat)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
mtime := s.mtim()
|
||||
if node.ModTime != time.Unix(mtime.Unix()) {
|
||||
t.Errorf("ModTime does not match, want %v, got %v", time.Unix(mtime.Unix()), node.ModTime)
|
||||
}
|
||||
|
||||
ctime := s.ctim()
|
||||
if node.ChangeTime != time.Unix(ctime.Unix()) {
|
||||
t.Errorf("ChangeTime does not match, want %v, got %v", time.Unix(ctime.Unix()), node.ChangeTime)
|
||||
}
|
||||
|
||||
atime := s.atim()
|
||||
if node.AccessTime != time.Unix(atime.Unix()) {
|
||||
t.Errorf("AccessTime does not match, want %v, got %v", time.Unix(atime.Unix()), node.AccessTime)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func checkDevice(t testing.TB, stat *syscall.Stat_t, node *Node) {
|
||||
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) {
|
||||
type Test struct {
|
||||
filename string
|
||||
canSkip bool
|
||||
}
|
||||
var tests = []Test{
|
||||
{"node_test.go", false},
|
||||
{"/dev/sda", true},
|
||||
}
|
||||
|
||||
// on darwin, users are not permitted to list the extended attributes of
|
||||
// /dev/null, therefore skip it.
|
||||
if runtime.GOOS != "darwin" {
|
||||
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")
|
||||
return
|
||||
}
|
||||
|
||||
if fi.Sys() == nil {
|
||||
t.Skip("fi.Sys() is nil")
|
||||
return
|
||||
}
|
||||
|
||||
s, ok := fi.Sys().(*syscall.Stat_t)
|
||||
if !ok {
|
||||
t.Skip("fi type is %T, not stat_t", fi.Sys())
|
||||
return
|
||||
}
|
||||
|
||||
node, err := NodeFromFileInfo(test.filename, fi)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
switch node.Type {
|
||||
case "file":
|
||||
checkFile(t, s, node)
|
||||
case "dev", "chardev":
|
||||
checkFile(t, s, node)
|
||||
checkDevice(t, s, node)
|
||||
default:
|
||||
t.Fatalf("invalid node type %q", node.Type)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue