1// +build darwin freebsd linux
2
3package fuse
4
5import (
6	"context"
7	"os"
8	"path/filepath"
9	"sync"
10
11	"bazil.org/fuse"
12	"bazil.org/fuse/fs"
13
14	"github.com/restic/restic/internal/debug"
15	"github.com/restic/restic/internal/restic"
16)
17
18// Statically ensure that *dir implement those interface
19var _ = fs.HandleReadDirAller(&dir{})
20var _ = fs.NodeStringLookuper(&dir{})
21
22type dir struct {
23	root        *Root
24	items       map[string]*restic.Node
25	inode       uint64
26	parentInode uint64
27	node        *restic.Node
28	m           sync.Mutex
29}
30
31func cleanupNodeName(name string) string {
32	return filepath.Base(name)
33}
34
35func newDir(ctx context.Context, root *Root, inode, parentInode uint64, node *restic.Node) (*dir, error) {
36	debug.Log("new dir for %v (%v)", node.Name, node.Subtree)
37
38	return &dir{
39		root:        root,
40		node:        node,
41		inode:       inode,
42		parentInode: parentInode,
43	}, nil
44}
45
46// replaceSpecialNodes replaces nodes with name "." and "/" by their contents.
47// Otherwise, the node is returned.
48func replaceSpecialNodes(ctx context.Context, repo restic.Repository, node *restic.Node) ([]*restic.Node, error) {
49	if node.Type != "dir" || node.Subtree == nil {
50		return []*restic.Node{node}, nil
51	}
52
53	if node.Name != "." && node.Name != "/" {
54		return []*restic.Node{node}, nil
55	}
56
57	tree, err := repo.LoadTree(ctx, *node.Subtree)
58	if err != nil {
59		return nil, err
60	}
61
62	return tree.Nodes, nil
63}
64
65func newDirFromSnapshot(ctx context.Context, root *Root, inode uint64, snapshot *restic.Snapshot) (*dir, error) {
66	debug.Log("new dir for snapshot %v (%v)", snapshot.ID(), snapshot.Tree)
67	return &dir{
68		root: root,
69		node: &restic.Node{
70			AccessTime: snapshot.Time,
71			ModTime:    snapshot.Time,
72			ChangeTime: snapshot.Time,
73			Mode:       os.ModeDir | 0555,
74			Subtree:    snapshot.Tree,
75		},
76		inode: inode,
77	}, nil
78}
79
80func (d *dir) open(ctx context.Context) error {
81	d.m.Lock()
82	defer d.m.Unlock()
83
84	if d.items != nil {
85		return nil
86	}
87
88	debug.Log("open dir %v (%v)", d.node.Name, d.node.Subtree)
89
90	tree, err := d.root.repo.LoadTree(ctx, *d.node.Subtree)
91	if err != nil {
92		debug.Log("  error loading tree %v: %v", d.node.Subtree, err)
93		return err
94	}
95	items := make(map[string]*restic.Node)
96	for _, n := range tree.Nodes {
97		nodes, err := replaceSpecialNodes(ctx, d.root.repo, n)
98		if err != nil {
99			debug.Log("  replaceSpecialNodes(%v) failed: %v", n, err)
100			return err
101		}
102		for _, node := range nodes {
103			items[cleanupNodeName(node.Name)] = node
104		}
105	}
106	d.items = items
107	return nil
108}
109
110func (d *dir) Attr(ctx context.Context, a *fuse.Attr) error {
111	debug.Log("Attr()")
112	a.Inode = d.inode
113	a.Mode = os.ModeDir | d.node.Mode
114
115	if !d.root.cfg.OwnerIsRoot {
116		a.Uid = d.node.UID
117		a.Gid = d.node.GID
118	}
119	a.Atime = d.node.AccessTime
120	a.Ctime = d.node.ChangeTime
121	a.Mtime = d.node.ModTime
122
123	a.Nlink = d.calcNumberOfLinks()
124
125	return nil
126}
127
128func (d *dir) calcNumberOfLinks() uint32 {
129	// a directory d has 2 hardlinks + the number
130	// of directories contained by d
131	count := uint32(2)
132	for _, node := range d.items {
133		if node.Type == "dir" {
134			count++
135		}
136	}
137	return count
138}
139
140func (d *dir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
141	debug.Log("ReadDirAll()")
142	err := d.open(ctx)
143	if err != nil {
144		return nil, err
145	}
146	ret := make([]fuse.Dirent, 0, len(d.items)+2)
147
148	ret = append(ret, fuse.Dirent{
149		Inode: d.inode,
150		Name:  ".",
151		Type:  fuse.DT_Dir,
152	})
153
154	ret = append(ret, fuse.Dirent{
155		Inode: d.parentInode,
156		Name:  "..",
157		Type:  fuse.DT_Dir,
158	})
159
160	for _, node := range d.items {
161		name := cleanupNodeName(node.Name)
162		var typ fuse.DirentType
163		switch node.Type {
164		case "dir":
165			typ = fuse.DT_Dir
166		case "file":
167			typ = fuse.DT_File
168		case "symlink":
169			typ = fuse.DT_Link
170		}
171
172		ret = append(ret, fuse.Dirent{
173			Inode: fs.GenerateDynamicInode(d.inode, name),
174			Type:  typ,
175			Name:  name,
176		})
177	}
178
179	return ret, nil
180}
181
182func (d *dir) Lookup(ctx context.Context, name string) (fs.Node, error) {
183	debug.Log("Lookup(%v)", name)
184
185	err := d.open(ctx)
186	if err != nil {
187		return nil, err
188	}
189
190	node, ok := d.items[name]
191	if !ok {
192		debug.Log("  Lookup(%v) -> not found", name)
193		return nil, fuse.ENOENT
194	}
195	switch node.Type {
196	case "dir":
197		return newDir(ctx, d.root, fs.GenerateDynamicInode(d.inode, name), d.inode, node)
198	case "file":
199		return newFile(ctx, d.root, fs.GenerateDynamicInode(d.inode, name), node)
200	case "symlink":
201		return newLink(ctx, d.root, fs.GenerateDynamicInode(d.inode, name), node)
202	case "dev", "chardev", "fifo", "socket":
203		return newOther(ctx, d.root, fs.GenerateDynamicInode(d.inode, name), node)
204	default:
205		debug.Log("  node %v has unknown type %v", name, node.Type)
206		return nil, fuse.ENOENT
207	}
208}
209
210func (d *dir) Listxattr(ctx context.Context, req *fuse.ListxattrRequest, resp *fuse.ListxattrResponse) error {
211	debug.Log("Listxattr(%v, %v)", d.node.Name, req.Size)
212	for _, attr := range d.node.ExtendedAttributes {
213		resp.Append(attr.Name)
214	}
215	return nil
216}
217
218func (d *dir) Getxattr(ctx context.Context, req *fuse.GetxattrRequest, resp *fuse.GetxattrResponse) error {
219	debug.Log("Getxattr(%v, %v, %v)", d.node.Name, req.Name, req.Size)
220	attrval := d.node.GetExtendedAttribute(req.Name)
221	if attrval != nil {
222		resp.Xattr = attrval
223		return nil
224	}
225	return fuse.ErrNoXattr
226}
227