data: split node and snapshot code from restic package

This commit is contained in:
Michael Eischer
2025-09-23 20:01:09 +02:00
parent c85b157e0e
commit 56ac8360c7
166 changed files with 1170 additions and 1107 deletions

View File

@@ -14,6 +14,7 @@ import (
"github.com/anacrolix/fuse"
"github.com/anacrolix/fuse/fs"
"github.com/restic/restic/internal/data"
"github.com/restic/restic/internal/debug"
"github.com/restic/restic/internal/restic"
)
@@ -28,10 +29,10 @@ var _ = fs.NodeStringLookuper(&dir{})
type dir struct {
root *Root
forget forgetFn
items map[string]*restic.Node
items map[string]*data.Node
inode uint64
parentInode uint64
node *restic.Node
node *data.Node
m sync.Mutex
cache treeCache
}
@@ -40,7 +41,7 @@ func cleanupNodeName(name string) string {
return filepath.Base(name)
}
func newDir(root *Root, forget forgetFn, inode, parentInode uint64, node *restic.Node) (*dir, error) {
func newDir(root *Root, forget forgetFn, inode, parentInode uint64, node *data.Node) (*dir, error) {
debug.Log("new dir for %v (%v)", node.Name, node.Subtree)
return &dir{
@@ -65,16 +66,16 @@ func unwrapCtxCanceled(err error) error {
// replaceSpecialNodes replaces nodes with name "." and "/" by their contents.
// Otherwise, the node is returned.
func replaceSpecialNodes(ctx context.Context, repo restic.BlobLoader, node *restic.Node) ([]*restic.Node, error) {
if node.Type != restic.NodeTypeDir || node.Subtree == nil {
return []*restic.Node{node}, nil
func replaceSpecialNodes(ctx context.Context, repo restic.BlobLoader, node *data.Node) ([]*data.Node, error) {
if node.Type != data.NodeTypeDir || node.Subtree == nil {
return []*data.Node{node}, nil
}
if node.Name != "." && node.Name != "/" {
return []*restic.Node{node}, nil
return []*data.Node{node}, nil
}
tree, err := restic.LoadTree(ctx, repo, *node.Subtree)
tree, err := data.LoadTree(ctx, repo, *node.Subtree)
if err != nil {
return nil, unwrapCtxCanceled(err)
}
@@ -82,12 +83,12 @@ func replaceSpecialNodes(ctx context.Context, repo restic.BlobLoader, node *rest
return tree.Nodes, nil
}
func newDirFromSnapshot(root *Root, forget forgetFn, inode uint64, snapshot *restic.Snapshot) (*dir, error) {
func newDirFromSnapshot(root *Root, forget forgetFn, inode uint64, snapshot *data.Snapshot) (*dir, error) {
debug.Log("new dir for snapshot %v (%v)", snapshot.ID(), snapshot.Tree)
return &dir{
root: root,
forget: forget,
node: &restic.Node{
node: &data.Node{
AccessTime: snapshot.Time,
ModTime: snapshot.Time,
ChangeTime: snapshot.Time,
@@ -109,12 +110,12 @@ func (d *dir) open(ctx context.Context) error {
debug.Log("open dir %v (%v)", d.node.Name, d.node.Subtree)
tree, err := restic.LoadTree(ctx, d.root.repo, *d.node.Subtree)
tree, err := data.LoadTree(ctx, d.root.repo, *d.node.Subtree)
if err != nil {
debug.Log(" error loading tree %v: %v", d.node.Subtree, err)
return unwrapCtxCanceled(err)
}
items := make(map[string]*restic.Node)
items := make(map[string]*data.Node)
for _, n := range tree.Nodes {
if ctx.Err() != nil {
return ctx.Err()
@@ -156,7 +157,7 @@ func (d *dir) calcNumberOfLinks() uint32 {
// of directories contained by d
count := uint32(2)
for _, node := range d.items {
if node.Type == restic.NodeTypeDir {
if node.Type == data.NodeTypeDir {
count++
}
}
@@ -191,11 +192,11 @@ func (d *dir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
name := cleanupNodeName(node.Name)
var typ fuse.DirentType
switch node.Type {
case restic.NodeTypeDir:
case data.NodeTypeDir:
typ = fuse.DT_Dir
case restic.NodeTypeFile:
case data.NodeTypeFile:
typ = fuse.DT_File
case restic.NodeTypeSymlink:
case data.NodeTypeSymlink:
typ = fuse.DT_Link
}
@@ -225,13 +226,13 @@ func (d *dir) Lookup(ctx context.Context, name string) (fs.Node, error) {
}
inode := inodeFromNode(d.inode, node)
switch node.Type {
case restic.NodeTypeDir:
case data.NodeTypeDir:
return newDir(d.root, forget, inode, d.inode, node)
case restic.NodeTypeFile:
case data.NodeTypeFile:
return newFile(d.root, forget, inode, node)
case restic.NodeTypeSymlink:
case data.NodeTypeSymlink:
return newLink(d.root, forget, inode, node)
case restic.NodeTypeDev, restic.NodeTypeCharDev, restic.NodeTypeFifo, restic.NodeTypeSocket:
case data.NodeTypeDev, data.NodeTypeCharDev, data.NodeTypeFifo, data.NodeTypeSocket:
return newOther(d.root, forget, inode, node)
default:
debug.Log(" node %v has unknown type %v", name, node.Type)

View File

@@ -7,6 +7,7 @@ import (
"context"
"sort"
"github.com/restic/restic/internal/data"
"github.com/restic/restic/internal/debug"
"github.com/restic/restic/internal/errors"
"github.com/restic/restic/internal/restic"
@@ -28,7 +29,7 @@ var _ = fs.NodeOpener(&file{})
type file struct {
root *Root
forget forgetFn
node *restic.Node
node *data.Node
inode uint64
}
@@ -38,7 +39,7 @@ type openFile struct {
cumsize []uint64
}
func newFile(root *Root, forget forgetFn, inode uint64, node *restic.Node) (fusefile *file, err error) {
func newFile(root *Root, forget forgetFn, inode uint64, node *data.Node) (fusefile *file, err error) {
debug.Log("create new file for %v with %d blobs", node.Name, len(node.Content))
return &file{
inode: inode,

View File

@@ -13,6 +13,7 @@ import (
"time"
"github.com/restic/restic/internal/bloblru"
"github.com/restic/restic/internal/data"
"github.com/restic/restic/internal/repository"
"github.com/restic/restic/internal/restic"
@@ -52,15 +53,15 @@ func firstSnapshotID(t testing.TB, repo restic.Lister) (first restic.ID) {
return first
}
func loadFirstSnapshot(t testing.TB, repo restic.ListerLoaderUnpacked) *restic.Snapshot {
func loadFirstSnapshot(t testing.TB, repo restic.ListerLoaderUnpacked) *data.Snapshot {
id := firstSnapshotID(t, repo)
sn, err := restic.LoadSnapshot(context.TODO(), repo, id)
sn, err := data.LoadSnapshot(context.TODO(), repo, id)
rtest.OK(t, err)
return sn
}
func loadTree(t testing.TB, repo restic.Loader, id restic.ID) *restic.Tree {
tree, err := restic.LoadTree(context.TODO(), repo, id)
func loadTree(t testing.TB, repo restic.Loader, id restic.ID) *data.Tree {
tree, err := data.LoadTree(context.TODO(), repo, id)
rtest.OK(t, err)
return tree
}
@@ -73,7 +74,7 @@ func TestFuseFile(t *testing.T) {
timestamp, err := time.Parse(time.RFC3339, "2017-01-24T10:42:56+01:00")
rtest.OK(t, err)
restic.TestCreateSnapshot(t, repo, timestamp, 2)
data.TestCreateSnapshot(t, repo, timestamp, 2)
sn := loadFirstSnapshot(t, repo)
tree := loadTree(t, repo, *sn.Tree)
@@ -109,7 +110,7 @@ func TestFuseFile(t *testing.T) {
t.Logf("filesize is %v, memfile has size %v", filesize, len(memfile))
node := &restic.Node{
node := &data.Node{
Name: "foo",
Inode: 23,
Mode: 0742,
@@ -152,7 +153,7 @@ func TestFuseDir(t *testing.T) {
root := &Root{repo: repo, blobCache: bloblru.New(blobCacheSize)}
node := &restic.Node{
node := &data.Node{
Mode: 0755,
UID: 42,
GID: 43,
@@ -180,7 +181,7 @@ func TestFuseDir(t *testing.T) {
// Test top-level directories for their UID and GID.
func TestTopUIDGID(t *testing.T) {
repo := repository.TestRepository(t)
restic.TestCreateSnapshot(t, repo, time.Unix(1460289341, 207401672), 0)
data.TestCreateSnapshot(t, repo, time.Unix(1460289341, 207401672), 0)
testTopUIDGID(t, Config{}, repo, uint32(os.Getuid()), uint32(os.Getgid()))
testTopUIDGID(t, Config{OwnerIsRoot: true}, repo, 0, 0)
@@ -210,7 +211,7 @@ func testTopUIDGID(t *testing.T, cfg Config, repo restic.Repository, uid, gid ui
snapshotdir, err := idsdir.(fs.NodeStringLookuper).Lookup(ctx, snapID)
rtest.OK(t, err)
// restic.TestCreateSnapshot does not set the UID/GID thus it must be always zero
// data.TestCreateSnapshot does not set the UID/GID thus it must be always zero
err = snapshotdir.Attr(ctx, &attr)
rtest.OK(t, err)
rtest.Equals(t, uint32(0), attr.Uid)
@@ -235,7 +236,7 @@ func testStableLookup(t *testing.T, node fs.Node, path string) fs.Node {
func TestStableNodeObjects(t *testing.T) {
repo := repository.TestRepository(t)
restic.TestCreateSnapshot(t, repo, time.Unix(1460289341, 207401672), 2)
data.TestCreateSnapshot(t, repo, time.Unix(1460289341, 207401672), 2)
root := NewRoot(repo, Config{})
idsdir := testStableLookup(t, root, "ids")
@@ -264,9 +265,9 @@ func TestBlocks(t *testing.T) {
target := strings.Repeat("x", int(c.size))
for _, n := range []fs.Node{
&file{root: root, node: &restic.Node{Size: uint64(c.size)}},
&link{root: root, node: &restic.Node{LinkTarget: target}},
&snapshotLink{root: root, snapshot: &restic.Snapshot{}, target: target},
&file{root: root, node: &data.Node{Size: uint64(c.size)}},
&link{root: root, node: &data.Node{LinkTarget: target}},
&snapshotLink{root: root, snapshot: &data.Snapshot{}, target: target},
} {
var a fuse.Attr
err := n.Attr(context.TODO(), &a)
@@ -277,7 +278,7 @@ func TestBlocks(t *testing.T) {
}
func TestInodeFromNode(t *testing.T) {
node := &restic.Node{Name: "foo.txt", Type: restic.NodeTypeCharDev, Links: 2}
node := &data.Node{Name: "foo.txt", Type: data.NodeTypeCharDev, Links: 2}
ino1 := inodeFromNode(1, node)
ino2 := inodeFromNode(2, node)
rtest.Assert(t, ino1 == ino2, "inodes %d, %d of hard links differ", ino1, ino2)
@@ -289,9 +290,9 @@ func TestInodeFromNode(t *testing.T) {
// Regression test: in a path a/b/b, the grandchild should not get the
// same inode as the grandparent.
a := &restic.Node{Name: "a", Type: restic.NodeTypeDir, Links: 2}
ab := &restic.Node{Name: "b", Type: restic.NodeTypeDir, Links: 2}
abb := &restic.Node{Name: "b", Type: restic.NodeTypeDir, Links: 2}
a := &data.Node{Name: "a", Type: data.NodeTypeDir, Links: 2}
ab := &data.Node{Name: "b", Type: data.NodeTypeDir, Links: 2}
abb := &data.Node{Name: "b", Type: data.NodeTypeDir, Links: 2}
inoA := inodeFromNode(1, a)
inoAb := inodeFromNode(inoA, ab)
inoAbb := inodeFromNode(inoAb, abb)
@@ -300,7 +301,7 @@ func TestInodeFromNode(t *testing.T) {
}
func TestLink(t *testing.T) {
node := &restic.Node{Name: "foo.txt", Type: restic.NodeTypeSymlink, Links: 1, LinkTarget: "dst", ExtendedAttributes: []restic.ExtendedAttribute{
node := &data.Node{Name: "foo.txt", Type: data.NodeTypeSymlink, Links: 1, LinkTarget: "dst", ExtendedAttributes: []data.ExtendedAttribute{
{Name: "foo", Value: []byte("bar")},
}}
@@ -329,15 +330,15 @@ var sink uint64
func BenchmarkInode(b *testing.B) {
for _, sub := range []struct {
name string
node restic.Node
node data.Node
}{
{
name: "no_hard_links",
node: restic.Node{Name: "a somewhat long-ish filename.svg.bz2", Type: restic.NodeTypeFifo},
node: data.Node{Name: "a somewhat long-ish filename.svg.bz2", Type: data.NodeTypeFifo},
},
{
name: "hard_link",
node: restic.Node{Name: "some other filename", Type: restic.NodeTypeFile, Links: 2},
node: data.Node{Name: "some other filename", Type: data.NodeTypeFile, Links: 2},
},
} {
b.Run(sub.name, func(b *testing.B) {

View File

@@ -7,7 +7,7 @@ import (
"encoding/binary"
"github.com/cespare/xxhash/v2"
"github.com/restic/restic/internal/restic"
"github.com/restic/restic/internal/data"
)
const prime = 11400714785074694791 // prime1 from xxhash.
@@ -24,8 +24,8 @@ func inodeFromName(parent uint64, name string) uint64 {
}
// inodeFromNode generates an inode number for a file within a snapshot.
func inodeFromNode(parent uint64, node *restic.Node) (inode uint64) {
if node.Links > 1 && node.Type != restic.NodeTypeDir {
func inodeFromNode(parent uint64, node *data.Node) (inode uint64) {
if node.Links > 1 && node.Type != data.NodeTypeDir {
// If node has hard links, give them all the same inode,
// irrespective of the parent.
var buf [16]byte

View File

@@ -8,7 +8,7 @@ import (
"github.com/anacrolix/fuse"
"github.com/anacrolix/fuse/fs"
"github.com/restic/restic/internal/restic"
"github.com/restic/restic/internal/data"
)
// Statically ensure that *link implements the given interface
@@ -20,11 +20,11 @@ var _ = fs.NodeReadlinker(&link{})
type link struct {
root *Root
forget forgetFn
node *restic.Node
node *data.Node
inode uint64
}
func newLink(root *Root, forget forgetFn, inode uint64, node *restic.Node) (*link, error) {
func newLink(root *Root, forget forgetFn, inode uint64, node *data.Node) (*link, error) {
return &link{root: root, forget: forget, inode: inode, node: node}, nil
}

View File

@@ -8,7 +8,7 @@ import (
"github.com/anacrolix/fuse"
"github.com/anacrolix/fuse/fs"
"github.com/restic/restic/internal/restic"
"github.com/restic/restic/internal/data"
)
// Statically ensure that *other implements the given interface
@@ -18,11 +18,11 @@ var _ = fs.NodeReadlinker(&other{})
type other struct {
root *Root
forget forgetFn
node *restic.Node
node *data.Node
inode uint64
}
func newOther(root *Root, forget forgetFn, inode uint64, node *restic.Node) (*other, error) {
func newOther(root *Root, forget forgetFn, inode uint64, node *data.Node) (*other, error) {
return &other{root: root, forget: forget, inode: inode, node: node}, nil
}

View File

@@ -7,6 +7,7 @@ import (
"os"
"github.com/restic/restic/internal/bloblru"
"github.com/restic/restic/internal/data"
"github.com/restic/restic/internal/debug"
"github.com/restic/restic/internal/restic"
@@ -16,7 +17,7 @@ import (
// Config holds settings for the fuse mount.
type Config struct {
OwnerIsRoot bool
Filter restic.SnapshotFilter
Filter data.SnapshotFilter
TimeTemplate string
PathTemplates []string
}

View File

@@ -8,8 +8,8 @@ import (
"os"
"syscall"
"github.com/restic/restic/internal/data"
"github.com/restic/restic/internal/debug"
"github.com/restic/restic/internal/restic"
"github.com/anacrolix/fuse"
"github.com/anacrolix/fuse/fs"
@@ -138,14 +138,14 @@ type snapshotLink struct {
forget forgetFn
inode uint64
target string
snapshot *restic.Snapshot
snapshot *data.Snapshot
}
var _ = fs.NodeForgetter(&snapshotLink{})
var _ = fs.NodeReadlinker(&snapshotLink{})
// newSnapshotLink
func newSnapshotLink(root *Root, forget forgetFn, inode uint64, target string, snapshot *restic.Snapshot) (*snapshotLink, error) {
func newSnapshotLink(root *Root, forget forgetFn, inode uint64, target string, snapshot *data.Snapshot) (*snapshotLink, error) {
return &snapshotLink{root: root, forget: forget, inode: inode, target: target, snapshot: snapshot}, nil
}

View File

@@ -14,14 +14,14 @@ import (
"sync"
"time"
"github.com/restic/restic/internal/data"
"github.com/restic/restic/internal/debug"
"github.com/restic/restic/internal/restic"
)
type MetaDirData struct {
// set if this is a symlink or a snapshot mount point
linkTarget string
snapshot *restic.Snapshot
snapshot *data.Snapshot
// names is set if this is a pseudo directory
names map[string]*MetaDirData
}
@@ -57,7 +57,7 @@ func NewSnapshotsDirStructure(root *Root, pathTemplates []string, timeTemplate s
// pathsFromSn generates the paths from pathTemplate and timeTemplate
// where the variables are replaced by the snapshot data.
// The time is given as suffix if the pathTemplate ends with "%T".
func pathsFromSn(pathTemplate string, timeTemplate string, sn *restic.Snapshot) (paths []string, timeSuffix string) {
func pathsFromSn(pathTemplate string, timeTemplate string, sn *data.Snapshot) (paths []string, timeSuffix string) {
timeformat := sn.Time.Format(timeTemplate)
inVerb := false
@@ -207,11 +207,11 @@ func uniqueName(entries map[string]*MetaDirData, prefix, name string) string {
// makeDirs inserts all paths generated from pathTemplates and
// TimeTemplate for all given snapshots into d.names.
// Also adds d.latest links if "%T" is at end of a path template
func (d *SnapshotsDirStructure) makeDirs(snapshots restic.Snapshots) {
func (d *SnapshotsDirStructure) makeDirs(snapshots data.Snapshots) {
entries := make(map[string]*MetaDirData)
type mountData struct {
sn *restic.Snapshot
sn *data.Snapshot
linkTarget string // if linkTarget!= "", this is a symlink
childFn string
child *MetaDirData
@@ -293,8 +293,8 @@ func (d *SnapshotsDirStructure) updateSnapshots(ctx context.Context) error {
return nil
}
var snapshots restic.Snapshots
err := d.root.cfg.Filter.FindAll(ctx, d.root.repo, d.root.repo, nil, func(_ string, sn *restic.Snapshot, _ error) error {
var snapshots data.Snapshots
err := d.root.cfg.Filter.FindAll(ctx, d.root.repo, d.root.repo, nil, func(_ string, sn *data.Snapshot, _ error) error {
if sn != nil {
snapshots = append(snapshots, sn)
}

View File

@@ -8,6 +8,7 @@ import (
"testing"
"time"
"github.com/restic/restic/internal/data"
"github.com/restic/restic/internal/restic"
"github.com/restic/restic/internal/test"
)
@@ -15,8 +16,8 @@ import (
func TestPathsFromSn(t *testing.T) {
id1, _ := restic.ParseID("1234567812345678123456781234567812345678123456781234567812345678")
time1, _ := time.Parse("2006-01-02T15:04:05", "2021-01-01T00:00:01")
sn1 := &restic.Snapshot{Hostname: "host", Username: "user", Tags: []string{"tag1", "tag2"}, Time: time1}
restic.TestSetSnapshotID(t, sn1, id1)
sn1 := &data.Snapshot{Hostname: "host", Username: "user", Tags: []string{"tag1", "tag2"}, Time: time1}
data.TestSetSnapshotID(t, sn1, id1)
var p []string
var s string
@@ -67,27 +68,27 @@ func TestMakeDirs(t *testing.T) {
id0, _ := restic.ParseID("0000000012345678123456781234567812345678123456781234567812345678")
time0, _ := time.Parse("2006-01-02T15:04:05", "2020-12-31T00:00:01")
sn0 := &restic.Snapshot{Hostname: "host", Username: "user", Tags: []string{"tag1", "tag2"}, Time: time0}
restic.TestSetSnapshotID(t, sn0, id0)
sn0 := &data.Snapshot{Hostname: "host", Username: "user", Tags: []string{"tag1", "tag2"}, Time: time0}
data.TestSetSnapshotID(t, sn0, id0)
id1, _ := restic.ParseID("1234567812345678123456781234567812345678123456781234567812345678")
time1, _ := time.Parse("2006-01-02T15:04:05", "2021-01-01T00:00:01")
sn1 := &restic.Snapshot{Hostname: "host", Username: "user", Tags: []string{"tag1", "tag2"}, Time: time1}
restic.TestSetSnapshotID(t, sn1, id1)
sn1 := &data.Snapshot{Hostname: "host", Username: "user", Tags: []string{"tag1", "tag2"}, Time: time1}
data.TestSetSnapshotID(t, sn1, id1)
id2, _ := restic.ParseID("8765432112345678123456781234567812345678123456781234567812345678")
time2, _ := time.Parse("2006-01-02T15:04:05", "2021-01-01T01:02:03")
sn2 := &restic.Snapshot{Hostname: "host2", Username: "user2", Tags: []string{"tag2", "tag3", "tag4"}, Time: time2}
restic.TestSetSnapshotID(t, sn2, id2)
sn2 := &data.Snapshot{Hostname: "host2", Username: "user2", Tags: []string{"tag2", "tag3", "tag4"}, Time: time2}
data.TestSetSnapshotID(t, sn2, id2)
id3, _ := restic.ParseID("aaaaaaaa12345678123456781234567812345678123456781234567812345678")
time3, _ := time.Parse("2006-01-02T15:04:05", "2021-01-01T01:02:03")
sn3 := &restic.Snapshot{Hostname: "host", Username: "user2", Tags: []string{}, Time: time3}
restic.TestSetSnapshotID(t, sn3, id3)
sn3 := &data.Snapshot{Hostname: "host", Username: "user2", Tags: []string{}, Time: time3}
data.TestSetSnapshotID(t, sn3, id3)
sds.makeDirs(restic.Snapshots{sn0, sn1, sn2, sn3})
sds.makeDirs(data.Snapshots{sn0, sn1, sn2, sn3})
expNames := make(map[string]*restic.Snapshot)
expNames := make(map[string]*data.Snapshot)
expLatest := make(map[string]string)
// entries for sn0
@@ -214,8 +215,8 @@ func TestMakeDirs(t *testing.T) {
verifyEntries(t, expNames, expLatest, sds.entries)
}
func verifyEntries(t *testing.T, expNames map[string]*restic.Snapshot, expLatest map[string]string, entries map[string]*MetaDirData) {
actNames := make(map[string]*restic.Snapshot)
func verifyEntries(t *testing.T, expNames map[string]*data.Snapshot, expLatest map[string]string, entries map[string]*MetaDirData) {
actNames := make(map[string]*data.Snapshot)
actLatest := make(map[string]string)
for path, entry := range entries {
actNames[path] = entry.snapshot
@@ -257,9 +258,9 @@ func TestMakeEmptyDirs(t *testing.T) {
pathTemplates: pathTemplates,
timeTemplate: timeTemplate,
}
sds.makeDirs(restic.Snapshots{})
sds.makeDirs(data.Snapshots{})
expNames := make(map[string]*restic.Snapshot)
expNames := make(map[string]*data.Snapshot)
expLatest := make(map[string]string)
// empty entries for dir structure

View File

@@ -5,18 +5,18 @@ package fuse
import (
"github.com/anacrolix/fuse"
"github.com/restic/restic/internal/data"
"github.com/restic/restic/internal/debug"
"github.com/restic/restic/internal/restic"
)
func nodeToXattrList(node *restic.Node, req *fuse.ListxattrRequest, resp *fuse.ListxattrResponse) {
func nodeToXattrList(node *data.Node, req *fuse.ListxattrRequest, resp *fuse.ListxattrResponse) {
debug.Log("Listxattr(%v, %v)", node.Name, req.Size)
for _, attr := range node.ExtendedAttributes {
resp.Append(attr.Name)
}
}
func nodeGetXattr(node *restic.Node, req *fuse.GetxattrRequest, resp *fuse.GetxattrResponse) error {
func nodeGetXattr(node *data.Node, req *fuse.GetxattrRequest, resp *fuse.GetxattrResponse) error {
debug.Log("Getxattr(%v, %v, %v)", node.Name, req.Name, req.Size)
attrval := node.GetExtendedAttribute(req.Name)
if attrval != nil {