1
2
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
18
19 package fs
20
21 import (
22 "context"
23 "os"
24 "strings"
25 "sync"
26 "time"
27
28 "bazil.org/fuse"
29 "bazil.org/fuse/fs"
30 "go4.org/syncutil"
31 "perkeep.org/pkg/blob"
32 "perkeep.org/pkg/schema"
33 "perkeep.org/pkg/search"
34 )
35
36 const refreshTime = 1 * time.Minute
37
38 type rootsDir struct {
39 fs *CamliFileSystem
40 at time.Time
41
42 mu sync.Mutex
43 lastQuery time.Time
44 m map[string]blob.Ref
45 children map[string]fs.Node
46 }
47
48 var (
49 _ fs.Node = (*rootsDir)(nil)
50 _ fs.HandleReadDirAller = (*rootsDir)(nil)
51 _ fs.NodeRemover = (*rootsDir)(nil)
52 _ fs.NodeRenamer = (*rootsDir)(nil)
53 _ fs.NodeStringLookuper = (*rootsDir)(nil)
54 _ fs.NodeMkdirer = (*rootsDir)(nil)
55 )
56
57 func (n *rootsDir) isRO() bool {
58 return !n.at.IsZero()
59 }
60
61 func (n *rootsDir) dirMode() os.FileMode {
62 if n.isRO() {
63 return 0500
64 }
65 return 0700
66 }
67
68 func (n *rootsDir) Attr(ctx context.Context, a *fuse.Attr) error {
69 a.Mode = os.ModeDir | n.dirMode()
70 a.Uid = uint32(os.Getuid())
71 a.Gid = uint32(os.Getgid())
72 return nil
73 }
74
75 func (n *rootsDir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) {
76 n.mu.Lock()
77 defer n.mu.Unlock()
78 if err := n.condRefresh(ctx); err != nil {
79 return nil, handleEIOorEINTR(err)
80 }
81 var ents []fuse.Dirent
82 for name := range n.m {
83 ents = append(ents, fuse.Dirent{Name: name})
84 }
85 Logger.Printf("rootsDir.ReadDirAll() -> %v", ents)
86 return ents, nil
87 }
88
89 func (n *rootsDir) Remove(ctx context.Context, req *fuse.RemoveRequest) error {
90 if n.isRO() {
91 return fuse.EPERM
92 }
93 n.mu.Lock()
94 defer n.mu.Unlock()
95
96 if err := n.condRefresh(ctx); err != nil {
97 return handleEIOorEINTR(err)
98 }
99 br := n.m[req.Name]
100 if !br.Valid() {
101 return fuse.ENOENT
102 }
103
104 claim := schema.NewDelAttributeClaim(br, "camliRoot", "")
105 _, err := n.fs.client.UploadAndSignBlob(ctx, claim)
106 if err != nil {
107 Logger.Println("rootsDir.Remove:", err)
108 return handleEIOorEINTR(err)
109 }
110
111 delete(n.m, req.Name)
112 delete(n.children, req.Name)
113
114 return nil
115 }
116
117 func (n *rootsDir) Rename(ctx context.Context, req *fuse.RenameRequest, newDir fs.Node) error {
118 Logger.Printf("rootsDir.Rename %q -> %q", req.OldName, req.NewName)
119 if n.isRO() {
120 return fuse.EPERM
121 }
122
123 n.mu.Lock()
124 target, exists := n.m[req.OldName]
125 _, collision := n.m[req.NewName]
126 n.mu.Unlock()
127 if !exists {
128 Logger.Printf("*rootsDir.Rename src name %q isn't known", req.OldName)
129 return fuse.ENOENT
130 }
131 if collision {
132 Logger.Printf("*rootsDir.Rename dest %q already exists", req.NewName)
133 return fuse.EIO
134 }
135
136
137
138
139 res, err := n.fs.client.Describe(ctx, &search.DescribeRequest{BlobRef: target})
140 if err != nil {
141 Logger.Println("rootsDir.Rename:", err)
142 return handleEIOorEINTR(err)
143 }
144 db := res.Meta[target.String()]
145 if db == nil {
146 Logger.Printf("Failed to pull meta for target: %v", target)
147 return fuse.EIO
148 }
149
150 for k := range db.Permanode.Attr {
151 const p = "camliPath:"
152 if strings.HasPrefix(k, p) {
153 Logger.Printf("Found file in %q: %q, disallowing rename", req.OldName, k[len(p):])
154 return fuse.EIO
155 }
156 }
157
158 claim := schema.NewSetAttributeClaim(target, "camliRoot", req.NewName)
159 _, err = n.fs.client.UploadAndSignBlob(ctx, claim)
160 if err != nil {
161 Logger.Printf("Upload rename link error: %v", err)
162 return handleEIOorEINTR(err)
163 }
164
165
166
167
168
169
170 n.mu.Lock()
171 if n.m[req.OldName] != target {
172 panic("Race.")
173 }
174 delete(n.m, req.OldName)
175 delete(n.children, req.OldName)
176 delete(n.children, req.NewName)
177 n.m[req.NewName] = target
178 n.mu.Unlock()
179
180 return nil
181 }
182
183 func (n *rootsDir) Lookup(ctx context.Context, name string) (fs.Node, error) {
184 Logger.Printf("fs.roots: Lookup(%q)", name)
185 n.mu.Lock()
186 defer n.mu.Unlock()
187 if err := n.condRefresh(ctx); err != nil {
188 return nil, handleEIOorEINTR(err)
189 }
190 br := n.m[name]
191 if !br.Valid() {
192 return nil, fuse.ENOENT
193 }
194
195 nod, ok := n.children[name]
196 if ok {
197 return nod, nil
198 }
199
200 if n.isRO() {
201 nod = newRODir(n.fs, br, name, n.at)
202 } else {
203 nod = &mutDir{
204 fs: n.fs,
205 permanode: br,
206 name: name,
207 xattrs: make(map[string][]byte),
208 children: make(map[string]mutFileOrDir),
209 }
210 }
211 n.children[name] = nod
212
213 return nod, nil
214 }
215
216
217 func (n *rootsDir) condRefresh(ctx context.Context) error {
218 if n.lastQuery.After(time.Now().Add(-refreshTime)) {
219 return nil
220 }
221 Logger.Printf("fs.roots: querying")
222
223 var rootRes, impRes *search.WithAttrResponse
224 var grp syncutil.Group
225 grp.Go(func() (err error) {
226
227 rootRes, err = n.fs.client.GetPermanodesWithAttr(ctx, &search.WithAttrRequest{N: 100, Attr: "camliRoot", At: n.at})
228 return
229 })
230 grp.Go(func() (err error) {
231 impRes, err = n.fs.client.GetPermanodesWithAttr(ctx, &search.WithAttrRequest{N: 100, Attr: "camliImportRoot", At: n.at})
232 return
233 })
234 if err := grp.Err(); err != nil {
235 Logger.Printf("fs.roots: error refreshing permanodes: %v", err)
236 return err
237 }
238
239 n.m = make(map[string]blob.Ref)
240 if n.children == nil {
241 n.children = make(map[string]fs.Node)
242 }
243
244 dr := &search.DescribeRequest{
245 Depth: 1,
246 }
247 for _, wi := range rootRes.WithAttr {
248 dr.BlobRefs = append(dr.BlobRefs, wi.Permanode)
249 }
250 for _, wi := range impRes.WithAttr {
251 dr.BlobRefs = append(dr.BlobRefs, wi.Permanode)
252 }
253 if len(dr.BlobRefs) == 0 {
254 return nil
255 }
256
257 dres, err := n.fs.client.Describe(ctx, dr)
258 if err != nil {
259 Logger.Printf("Describe failure: %v", err)
260 return err
261 }
262
263
264 currentRoots := map[string]bool{}
265 for _, wi := range rootRes.WithAttr {
266 pn := wi.Permanode
267 db := dres.Meta[pn.String()]
268 if db != nil && db.Permanode != nil {
269 name := db.Permanode.Attr.Get("camliRoot")
270 if name != "" {
271 currentRoots[name] = true
272 n.m[name] = pn
273 }
274 }
275 }
276
277
278
279 for name := range n.children {
280 if !currentRoots[name] {
281 delete(n.children, name)
282 }
283 }
284
285
286 for _, wi := range impRes.WithAttr {
287 pn := wi.Permanode
288 db := dres.Meta[pn.String()]
289 if db != nil && db.Permanode != nil {
290 name := db.Permanode.Attr.Get("camliImportRoot")
291 if name != "" {
292 name = strings.Replace(name, ":", "-", -1)
293 name = strings.Replace(name, "/", "-", -1)
294 n.m["importer-"+name] = pn
295 }
296 }
297 }
298
299 n.lastQuery = time.Now()
300 return nil
301 }
302
303 func (n *rootsDir) Mkdir(ctx context.Context, req *fuse.MkdirRequest) (fs.Node, error) {
304 if n.isRO() {
305 return nil, fuse.EPERM
306 }
307
308 name := req.Name
309
310
311 pr, err := n.fs.client.UploadNewPermanode(ctx)
312 if err != nil {
313 Logger.Printf("rootsDir.Create(%q): %v", name, err)
314 return nil, handleEIOorEINTR(err)
315 }
316
317 var grp syncutil.Group
318
319 grp.Go(func() (err error) {
320 claim := schema.NewSetAttributeClaim(pr.BlobRef, "camliRoot", name)
321 _, err = n.fs.client.UploadAndSignBlob(ctx, claim)
322 return
323 })
324
325 grp.Go(func() (err error) {
326 claim := schema.NewSetAttributeClaim(pr.BlobRef, "title", name)
327 _, err = n.fs.client.UploadAndSignBlob(ctx, claim)
328 return
329 })
330 if err := grp.Err(); err != nil {
331 Logger.Printf("rootsDir.Create(%q): %v", name, err)
332 return nil, handleEIOorEINTR(err)
333 }
334
335 nod := &mutDir{
336 fs: n.fs,
337 permanode: pr.BlobRef,
338 name: name,
339 xattrs: make(map[string][]byte),
340 children: make(map[string]mutFileOrDir),
341 }
342 n.mu.Lock()
343 n.m[name] = pr.BlobRef
344 n.mu.Unlock()
345
346 return nod, nil
347 }