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