]> Sergey Matveev's repositories - btrtrc.git/blob - fs/torrentfs.go
torrentfs: fix a bug where ENOENT is returned when the node for an entry in the root...
[btrtrc.git] / fs / torrentfs.go
1 package torrentfs
2
3 import (
4         "context"
5         "expvar"
6         "os"
7         "strings"
8         "sync"
9
10         "bazil.org/fuse"
11         fusefs "bazil.org/fuse/fs"
12         "github.com/anacrolix/torrent"
13         "github.com/anacrolix/torrent/metainfo"
14 )
15
16 const (
17         defaultMode = 0555
18 )
19
20 var (
21         torrentfsReadRequests        = expvar.NewInt("torrentfsReadRequests")
22         torrentfsDelayedReadRequests = expvar.NewInt("torrentfsDelayedReadRequests")
23         interruptedReads             = expvar.NewInt("interruptedReads")
24 )
25
26 type TorrentFS struct {
27         Client       *torrent.Client
28         destroyed    chan struct{}
29         mu           sync.Mutex
30         blockedReads int
31         event        sync.Cond
32 }
33
34 var (
35         _ fusefs.FSDestroyer = &TorrentFS{}
36
37         _ fusefs.NodeForgetter      = rootNode{}
38         _ fusefs.HandleReadDirAller = rootNode{}
39         _ fusefs.HandleReadDirAller = dirNode{}
40 )
41
42 // Is a directory node that lists all torrents and handles destruction of the
43 // filesystem.
44 type rootNode struct {
45         fs *TorrentFS
46 }
47
48 type node struct {
49         path     string
50         metadata *metainfo.Info
51         FS       *TorrentFS
52         t        *torrent.Torrent
53 }
54
55 type dirNode struct {
56         node
57 }
58
59 var (
60         _ fusefs.HandleReadDirAller = dirNode{}
61 )
62
63 func isSubPath(parent, child string) bool {
64         if len(parent) == 0 {
65                 return len(child) > 0
66         }
67         if !strings.HasPrefix(child, parent) {
68                 return false
69         }
70         extra := child[len(parent):]
71         if len(extra) == 0 {
72                 return false
73         }
74         // Not just a file with more stuff on the end.
75         return extra[0] == '/'
76 }
77
78 func (dn dirNode) ReadDirAll(ctx context.Context) (des []fuse.Dirent, err error) {
79         names := map[string]bool{}
80         for _, fi := range dn.metadata.Files {
81                 if !isSubPath(dn.path, strings.Join(fi.Path, "/")) {
82                         continue
83                 }
84                 name := fi.Path[len(dn.path)]
85                 if names[name] {
86                         continue
87                 }
88                 names[name] = true
89                 de := fuse.Dirent{
90                         Name: name,
91                 }
92                 if len(fi.Path) == len(dn.path)+1 {
93                         de.Type = fuse.DT_File
94                 } else {
95                         de.Type = fuse.DT_Dir
96                 }
97                 des = append(des, de)
98         }
99         return
100 }
101
102 func (dn dirNode) Lookup(_ context.Context, name string) (fusefs.Node, error) {
103         dir := false
104         var file *torrent.File
105         var fullPath string
106         if dn.path != "" {
107                 fullPath = dn.path + "/" + name
108         } else {
109                 fullPath = name
110         }
111         for _, f := range dn.t.Files() {
112                 if f.DisplayPath() == fullPath {
113                         file = f
114                 }
115                 if isSubPath(fullPath, f.DisplayPath()) {
116                         dir = true
117                 }
118         }
119         n := dn.node
120         n.path = fullPath
121         if dir && file != nil {
122                 panic("both dir and file")
123         }
124         if file != nil {
125                 return fileNode{n, file}, nil
126         }
127         if dir {
128                 return dirNode{n}, nil
129         }
130         return nil, fuse.ENOENT
131 }
132
133 func (dn dirNode) Attr(ctx context.Context, attr *fuse.Attr) error {
134         attr.Mode = os.ModeDir | defaultMode
135         return nil
136 }
137
138 func (rn rootNode) Lookup(ctx context.Context, name string) (_node fusefs.Node, err error) {
139         for _, t := range rn.fs.Client.Torrents() {
140                 info := t.Info()
141                 if t.Name() != name || info == nil {
142                         continue
143                 }
144                 __node := node{
145                         metadata: info,
146                         FS:       rn.fs,
147                         t:        t,
148                 }
149                 if !info.IsDir() {
150                         _node = fileNode{__node, t.Files()[0]}
151                 } else {
152                         _node = dirNode{__node}
153                 }
154                 break
155         }
156         if _node == nil {
157                 err = fuse.ENOENT
158         }
159         return
160 }
161
162 func (rn rootNode) ReadDirAll(ctx context.Context) (dirents []fuse.Dirent, err error) {
163         for _, t := range rn.fs.Client.Torrents() {
164                 info := t.Info()
165                 if info == nil {
166                         continue
167                 }
168                 dirents = append(dirents, fuse.Dirent{
169                         Name: info.Name,
170                         Type: func() fuse.DirentType {
171                                 if !info.IsDir() {
172                                         return fuse.DT_File
173                                 } else {
174                                         return fuse.DT_Dir
175                                 }
176                         }(),
177                 })
178         }
179         return
180 }
181
182 func (rn rootNode) Attr(ctx context.Context, attr *fuse.Attr) error {
183         attr.Mode = os.ModeDir
184         return nil
185 }
186
187 // TODO(anacrolix): Why should rootNode implement this?
188 func (rn rootNode) Forget() {
189         rn.fs.Destroy()
190 }
191
192 func (tfs *TorrentFS) Root() (fusefs.Node, error) {
193         return rootNode{tfs}, nil
194 }
195
196 func (tfs *TorrentFS) Destroy() {
197         tfs.mu.Lock()
198         select {
199         case <-tfs.destroyed:
200         default:
201                 close(tfs.destroyed)
202         }
203         tfs.mu.Unlock()
204 }
205
206 func New(cl *torrent.Client) *TorrentFS {
207         fs := &TorrentFS{
208                 Client:    cl,
209                 destroyed: make(chan struct{}),
210         }
211         fs.event.L = &fs.mu
212         return fs
213 }