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 "errors"
24 "fmt"
25 "os"
26 "path/filepath"
27 "strings"
28 "sync"
29 "time"
30
31 "bazil.org/fuse"
32 "bazil.org/fuse/fs"
33 "perkeep.org/pkg/blob"
34 "perkeep.org/pkg/schema"
35 "perkeep.org/pkg/search"
36 )
37
38
39
40
41
42
43
44
45
46
47 type roVersionsDir struct {
48 fs *CamliFileSystem
49 permanode blob.Ref
50 parent *roVersionsDir
51 name string
52
53 mu sync.Mutex
54 children map[string]roFileOrDir
55 xattrs map[string][]byte
56 }
57
58 func newROVersionsDir(fs *CamliFileSystem, permanode blob.Ref, name string) *roVersionsDir {
59 return &roVersionsDir{
60 fs: fs,
61 permanode: permanode,
62 name: name,
63 }
64 }
65
66
67 func (n *roVersionsDir) fullPath() string {
68 if n == nil {
69 return ""
70 }
71 return filepath.Join(n.parent.fullPath(), n.name)
72 }
73
74 func (n *roVersionsDir) Attr(ctx context.Context, a *fuse.Attr) error {
75 a.Mode = os.ModeDir | 0500
76 a.Uid = uint32(os.Getuid())
77 a.Gid = uint32(os.Getgid())
78 a.Inode = n.permanode.Sum64()
79 return nil
80 }
81
82
83 func (n *roVersionsDir) populate(ctx context.Context) error {
84 n.mu.Lock()
85 defer n.mu.Unlock()
86
87
88
89 if n.children != nil {
90 return nil
91 }
92
93 Logger.Printf("roVersionsDir.populate(%q)", n.fullPath())
94
95 res, err := n.fs.client.Describe(ctx, &search.DescribeRequest{
96 BlobRef: n.permanode,
97 Depth: 3,
98 })
99 if err != nil {
100 Logger.Println("roVersionsDir.paths:", err)
101 return fmt.Errorf("error while describing permanode: %w", err)
102 }
103 db := res.Meta[n.permanode.String()]
104 if db == nil {
105 return errors.New("dir blobref not described")
106 }
107
108
109 n.children = make(map[string]roFileOrDir)
110
111 for k, v := range db.Permanode.Attr {
112 const p = "camliPath:"
113 if !strings.HasPrefix(k, p) || len(v) < 1 {
114 continue
115 }
116 name := k[len(p):]
117 childRef := v[0]
118 child := res.Meta[childRef]
119 if child == nil {
120 Logger.Printf("child not described: %v", childRef)
121 continue
122 }
123 if child.Permanode == nil {
124 Logger.Printf("child Permanode is nil: %v", childRef)
125 continue
126 }
127 if target := child.Permanode.Attr.Get("camliSymlinkTarget"); target != "" {
128
129 n.children[name] = &roFileVersionsDir{
130 fs: n.fs,
131 permanode: blob.ParseOrZero(childRef),
132 parent: n,
133 name: name,
134 symLink: true,
135 target: target,
136 }
137 } else if isDir(child.Permanode) {
138
139 n.children[name] = &roVersionsDir{
140 fs: n.fs,
141 permanode: blob.ParseOrZero(childRef),
142 parent: n,
143 name: name,
144 }
145 } else if contentRef := child.Permanode.Attr.Get("camliContent"); contentRef != "" {
146
147 content := res.Meta[contentRef]
148 if content == nil {
149 Logger.Printf("child content not described: %v", childRef)
150 continue
151 }
152 if content.CamliType != "file" {
153 Logger.Printf("child not a file: %v", childRef)
154 continue
155 }
156 n.children[name] = &roFileVersionsDir{
157 fs: n.fs,
158 permanode: blob.ParseOrZero(childRef),
159 parent: n,
160 name: name,
161 }
162 } else {
163
164 continue
165 }
166 n.children[name].xattr().load(child.Permanode)
167 }
168 return nil
169 }
170
171 func (n *roVersionsDir) ReadDir(ctx context.Context) ([]fuse.Dirent, error) {
172 if err := n.populate(ctx); err != nil {
173 Logger.Println("populate:", err)
174 return nil, handleEIOorEINTR(err)
175 }
176 n.mu.Lock()
177 defer n.mu.Unlock()
178 var ents []fuse.Dirent
179 for name, childNode := range n.children {
180 var ino uint64
181 switch v := childNode.(type) {
182 case *roVersionsDir:
183 ino = v.permanode.Sum64()
184 case *roFileVersion:
185 ino = v.permanode.Sum64()
186 default:
187 Logger.Printf("roVersionsDir.ReadDir: unknown child type %T", childNode)
188 }
189
190
191
192 dirent := fuse.Dirent{
193 Name: name,
194 Inode: ino,
195 }
196 Logger.Printf("roVersionsDir(%q) appending inode %x, %+v", n.fullPath(), dirent.Inode, dirent)
197 ents = append(ents, dirent)
198 }
199 return ents, nil
200 }
201
202 func (n *roVersionsDir) Lookup(ctx context.Context, name string) (ret fs.Node, err error) {
203 defer func() {
204 Logger.Printf("roVersionsDir(%q).Lookup(%q) = %#v, %v", n.fullPath(), name, ret, err)
205 }()
206 if err := n.populate(ctx); err != nil {
207 Logger.Println("populate:", err)
208 return nil, handleEIOorEINTR(err)
209 }
210 n.mu.Lock()
211 defer n.mu.Unlock()
212 if n2 := n.children[name]; n2 != nil {
213 return n2, nil
214 }
215 return nil, fuse.ENOENT
216 }
217
218
219
220
221
222 type roFileVersionsDir struct {
223 fs *CamliFileSystem
224 permanode blob.Ref
225 parent *roVersionsDir
226 name string
227
228 symLink bool
229 target string
230
231 mu sync.Mutex
232 children map[string]roFileOrDir
233 xattrs map[string][]byte
234 }
235
236
237 func (n *roFileVersionsDir) fullPath() string {
238 if n == nil {
239 return ""
240 }
241 return filepath.Join(n.parent.fullPath(), n.name)
242 }
243
244 func (n *roFileVersionsDir) Attr(ctx context.Context, a *fuse.Attr) error {
245 a.Inode = n.permanode.Sum64()
246 a.Mode = os.ModeDir | 0500
247 a.Uid = uint32(os.Getuid())
248 a.Gid = uint32(os.Getgid())
249 return nil
250 }
251
252
253 func (n *roFileVersionsDir) populate(ctx context.Context) error {
254 n.mu.Lock()
255 defer n.mu.Unlock()
256
257
258
259 if n.children != nil {
260 return nil
261 }
262
263 Logger.Printf("roFileVersionsDir.populate(%q)", n.fullPath())
264 res, err := n.fs.client.GetClaims(ctx, &search.ClaimsRequest{Permanode: n.permanode, AttrFilter: "camliContent"})
265 if err != nil {
266 return fmt.Errorf("error while getting claims: %w", err)
267 }
268
269 n.children = make(map[string]roFileOrDir)
270 for _, cl := range res.Claims {
271 pn, ok := blob.Parse(cl.Value)
272 if !ok {
273 return errors.New("invalid blobref")
274 }
275 res, err := n.fs.client.Describe(ctx, &search.DescribeRequest{
276 BlobRef: pn,
277 Depth: 1,
278 At: cl.Date,
279 })
280 if err != nil {
281 return fmt.Errorf("blobref not described: %w", err)
282 }
283 db := res.Meta[cl.Value]
284 if db == nil {
285 return errors.New("blobref not described")
286 }
287 name := cl.Date.String()
288 n.children[name] = &roFileVersion{
289 fs: n.fs,
290 permanode: n.permanode,
291 parent: n,
292 name: name,
293 content: db.BlobRef,
294 size: db.File.Size,
295 mtime: cl.Date.Time(),
296 }
297
298 }
299 return nil
300 }
301
302 func (n *roFileVersionsDir) ReadDir(ctx context.Context) ([]fuse.Dirent, error) {
303 if err := n.populate(ctx); err != nil {
304 Logger.Println("populate:", err)
305 return nil, handleEIOorEINTR(err)
306 }
307 n.mu.Lock()
308 defer n.mu.Unlock()
309 var ents []fuse.Dirent
310 for name, childNode := range n.children {
311 var ino uint64
312 switch v := childNode.(type) {
313 case *roDir:
314 ino = v.permanode.Sum64()
315 case *roFile:
316 ino = v.permanode.Sum64()
317 default:
318 Logger.Printf("roFileVersionsDir.ReadDir: unknown child type %T", childNode)
319 }
320
321
322
323 dirent := fuse.Dirent{
324 Name: name,
325 Inode: ino,
326 }
327 Logger.Printf("roFileVersionsDir(%q) appending inode %x, %+v", n.fullPath(), dirent.Inode, dirent)
328 ents = append(ents, dirent)
329 }
330 return ents, nil
331 }
332
333 func (n *roFileVersionsDir) Lookup(ctx context.Context, name string) (ret fs.Node, err error) {
334 defer func() {
335 Logger.Printf("roFileVersionsDir(%q).Lookup(%q) = %#v, %v", n.fullPath(), name, ret, err)
336 }()
337 if err := n.populate(ctx); err != nil {
338 Logger.Println("populate:", err)
339 return nil, handleEIOorEINTR(err)
340 }
341 n.mu.Lock()
342 defer n.mu.Unlock()
343 if n2 := n.children[name]; n2 != nil {
344 return n2, nil
345 }
346 return nil, fuse.ENOENT
347 }
348
349
350
351
352 type roFileVersion struct {
353 fs *CamliFileSystem
354 permanode blob.Ref
355 parent *roFileVersionsDir
356 name string
357
358 mu sync.Mutex
359 symLink bool
360 content blob.Ref
361 size int64
362 mtime, atime time.Time
363 xattrs map[string][]byte
364 }
365
366 func (n *roFileVersion) Open(ctx context.Context, req *fuse.OpenRequest, res *fuse.OpenResponse) (fs.Handle, error) {
367 roFileOpen.Incr()
368
369 if isWriteFlags(req.Flags) {
370 return nil, fuse.EPERM
371 }
372
373 Logger.Printf("roFile.Open: %v: content: %v dir=%v flags=%v", n.permanode, n.content, req.Dir, req.Flags)
374 r, err := schema.NewFileReader(ctx, n.fs.fetcher, n.content)
375 if err != nil {
376 roFileOpenError.Incr()
377 Logger.Printf("roFile.Open: %v", err)
378 return nil, handleEIOorEINTR(err)
379 }
380
381
382
383 res.Flags &= ^fuse.OpenDirectIO
384
385
386 nod := &node{
387 fs: n.fs,
388 blobref: n.content,
389 }
390 return &nodeReader{n: nod, fr: r}, nil
391 }
392
393 func (n *roVersionsDir) Getxattr(ctx context.Context, req *fuse.GetxattrRequest, res *fuse.GetxattrResponse) error {
394 return n.xattr().get(req, res)
395 }
396
397 func (n *roVersionsDir) Listxattr(ctx context.Context, req *fuse.ListxattrRequest, res *fuse.ListxattrResponse) error {
398 return n.xattr().list(req, res)
399 }
400
401 func (n *roFileVersion) Getxattr(ctx context.Context, req *fuse.GetxattrRequest, res *fuse.GetxattrResponse) error {
402 return n.xattr().get(req, res)
403 }
404
405 func (n *roFileVersion) Listxattr(ctx context.Context, req *fuse.ListxattrRequest, res *fuse.ListxattrResponse) error {
406 return n.xattr().list(req, res)
407 }
408
409 func (n *roFileVersion) Removexattr(ctx context.Context, req *fuse.RemovexattrRequest) error {
410 return fuse.EPERM
411 }
412
413 func (n *roFileVersion) Setxattr(ctx context.Context, req *fuse.SetxattrRequest) error {
414 return fuse.EPERM
415 }
416
417 func (n *roFileVersion) Attr(ctx context.Context, a *fuse.Attr) error {
418
419 var mode os.FileMode = 0400
420
421 n.mu.Lock()
422 size := n.size
423 var blocks uint64
424 if size > 0 {
425 blocks = uint64(size)/512 + 1
426 }
427 inode := n.permanode.Sum64()
428 if n.symLink {
429 mode |= os.ModeSymlink
430 }
431 n.mu.Unlock()
432
433 *a = fuse.Attr{
434 Inode: inode,
435 Mode: mode,
436 Uid: uint32(os.Getuid()),
437 Gid: uint32(os.Getgid()),
438 Size: uint64(size),
439 Blocks: blocks,
440 Mtime: n.modTime(),
441 Atime: n.accessTime(),
442 Ctime: serverStart,
443 }
444 return nil
445 }
446
447 func (n *roFileVersion) accessTime() time.Time {
448 n.mu.Lock()
449 if !n.atime.IsZero() {
450 defer n.mu.Unlock()
451 return n.atime
452 }
453 n.mu.Unlock()
454 return n.modTime()
455 }
456
457 func (n *roFileVersion) modTime() time.Time {
458 n.mu.Lock()
459 defer n.mu.Unlock()
460 if !n.mtime.IsZero() {
461 return n.mtime
462 }
463 return serverStart
464 }
465
466 func (n *roFileVersion) Fsync(ctx context.Context, r *fuse.FsyncRequest) error {
467
468 return nil
469 }
470
471 func (n *roFileVersion) permanodeString() string {
472 return n.permanode.String()
473 }
474
475 func (n *roFileVersionsDir) permanodeString() string {
476 return n.permanode.String()
477 }
478
479 func (n *roVersionsDir) permanodeString() string {
480 return n.permanode.String()
481 }
482
483 func (n *roFileVersion) xattr() *xattr {
484 return &xattr{"roFileVersion", n.fs, n.permanode, &n.mu, &n.xattrs}
485 }
486
487 func (n *roFileVersionsDir) xattr() *xattr {
488 return &xattr{"roFileVersionsDir", n.fs, n.permanode, &n.mu, &n.xattrs}
489 }
490
491 func (n *roVersionsDir) xattr() *xattr {
492 return &xattr{"roVersionsDir", n.fs, n.permanode, &n.mu, &n.xattrs}
493 }