13111: Reload project dir if fsync(2) was called since last load.
[arvados.git] / sdk / go / arvados / fs_site.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: Apache-2.0
4
5 package arvados
6
7 import (
8         "os"
9         "sync"
10         "time"
11 )
12
13 type siteFileSystem struct {
14         fileSystem
15
16         staleThreshold time.Time
17         staleLock      sync.Mutex
18 }
19
20 // SiteFileSystem returns a FileSystem that maps collections and other
21 // Arvados objects onto a filesystem layout.
22 //
23 // This is experimental: the filesystem layout is not stable, and
24 // there are significant known bugs and shortcomings. For example,
25 // writes are not persisted until Sync() is called.
26 func (c *Client) SiteFileSystem(kc keepClient) FileSystem {
27         root := &vdirnode{}
28         fs := &siteFileSystem{
29                 fileSystem: fileSystem{
30                         fsBackend: keepBackend{apiClient: c, keepClient: kc},
31                         root:      root,
32                 },
33         }
34         root.inode = &treenode{
35                 fs:     fs,
36                 parent: root,
37                 fileinfo: fileinfo{
38                         name:    "/",
39                         mode:    os.ModeDir | 0755,
40                         modTime: time.Now(),
41                 },
42                 inodes: make(map[string]inode),
43         }
44         root.inode.Child("by_id", func(inode) (inode, error) {
45                 return &vdirnode{
46                         inode: &treenode{
47                                 fs:     fs,
48                                 parent: fs.root,
49                                 inodes: make(map[string]inode),
50                                 fileinfo: fileinfo{
51                                         name:    "by_id",
52                                         modTime: time.Now(),
53                                         mode:    0755 | os.ModeDir,
54                                 },
55                         },
56                         create: fs.mountCollection,
57                 }, nil
58         })
59         root.inode.Child("home", func(inode) (inode, error) {
60                 return fs.newProjectNode(fs.root, "home", ""), nil
61         })
62         return fs
63 }
64
65 func (fs *siteFileSystem) Sync() error {
66         fs.staleLock.Lock()
67         defer fs.staleLock.Unlock()
68         fs.staleThreshold = time.Now()
69         return nil
70 }
71
72 // Stale returns true if information obtained at time t should be
73 // considered stale.
74 func (fs *siteFileSystem) Stale(t time.Time) bool {
75         fs.staleLock.Lock()
76         defer fs.staleLock.Unlock()
77         return !fs.staleThreshold.Before(t)
78 }
79
80 func (fs *siteFileSystem) newNode(name string, perm os.FileMode, modTime time.Time) (node inode, err error) {
81         return nil, ErrInvalidOperation
82 }
83
84 func (fs *siteFileSystem) mountCollection(parent inode, id string) inode {
85         var coll Collection
86         err := fs.RequestAndDecode(&coll, "GET", "arvados/v1/collections/"+id, nil, nil)
87         if err != nil {
88                 return nil
89         }
90         cfs, err := coll.FileSystem(fs, fs)
91         if err != nil {
92                 return nil
93         }
94         root := cfs.rootnode()
95         root.SetParent(parent, id)
96         return root
97 }
98
99 func (fs *siteFileSystem) newProjectNode(root inode, name, uuid string) inode {
100         return &projectnode{
101                 uuid: uuid,
102                 inode: &treenode{
103                         fs:     fs,
104                         parent: root,
105                         inodes: make(map[string]inode),
106                         fileinfo: fileinfo{
107                                 name:    name,
108                                 modTime: time.Now(),
109                                 mode:    0755 | os.ModeDir,
110                         },
111                 },
112         }
113 }
114
115 // vdirnode wraps an inode by ignoring any requests to add/replace
116 // children, and calling a create() func when a non-existing child is
117 // looked up.
118 //
119 // create() can return either a new node, which will be added to the
120 // treenode, or nil for ENOENT.
121 type vdirnode struct {
122         inode
123         create func(parent inode, name string) inode
124 }
125
126 func (vn *vdirnode) Child(name string, replace func(inode) (inode, error)) (inode, error) {
127         return vn.inode.Child(name, func(existing inode) (inode, error) {
128                 if existing == nil && vn.create != nil {
129                         existing = vn.create(vn, name)
130                         if existing != nil {
131                                 existing.SetParent(vn, name)
132                                 vn.inode.(*treenode).fileinfo.modTime = time.Now()
133                         }
134                 }
135                 if replace == nil {
136                         return existing, nil
137                 } else if tryRepl, err := replace(existing); err != nil {
138                         return existing, err
139                 } else if tryRepl != existing {
140                         return existing, ErrInvalidArgument
141                 } else {
142                         return existing, nil
143                 }
144         })
145 }