1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: Apache-2.0
14 type CustomFileSystem interface {
16 MountByID(mount string)
17 MountProject(mount, uuid string)
18 MountUsers(mount string)
19 ForwardSlashNameSubstitution(string)
22 type customFileSystem struct {
27 staleThreshold time.Time
30 forwardSlashNameSubstitution string
33 func (c *Client) CustomFileSystem(kc keepClient) CustomFileSystem {
35 fs := &customFileSystem{
37 fileSystem: fileSystem{
38 fsBackend: keepBackend{apiClient: c, keepClient: kc},
40 thr: newThrottle(concurrentWriters),
43 root.treenode = treenode{
48 mode: os.ModeDir | 0755,
51 inodes: make(map[string]inode),
56 func (fs *customFileSystem) MountByID(mount string) {
57 fs.root.treenode.Lock()
58 defer fs.root.treenode.Unlock()
59 fs.root.treenode.Child(mount, func(inode) (inode, error) {
64 inodes: make(map[string]inode),
68 mode: 0755 | os.ModeDir,
76 func (fs *customFileSystem) MountProject(mount, uuid string) {
77 fs.root.treenode.Lock()
78 defer fs.root.treenode.Unlock()
79 fs.root.treenode.Child(mount, func(inode) (inode, error) {
80 return fs.newProjectNode(fs.root, mount, uuid, nil), nil
84 func (fs *customFileSystem) MountUsers(mount string) {
85 fs.root.treenode.Lock()
86 defer fs.root.treenode.Unlock()
87 fs.root.treenode.Child(mount, func(inode) (inode, error) {
90 loadOne: fs.usersLoadOne,
91 loadAll: fs.usersLoadAll,
95 inodes: make(map[string]inode),
99 mode: 0755 | os.ModeDir,
106 func (fs *customFileSystem) ForwardSlashNameSubstitution(repl string) {
107 fs.forwardSlashNameSubstitution = repl
110 // SiteFileSystem returns a FileSystem that maps collections and other
111 // Arvados objects onto a filesystem layout.
113 // This is experimental: the filesystem layout is not stable, and
114 // there are significant known bugs and shortcomings. For example,
115 // writes are not persisted until Sync() is called.
116 func (c *Client) SiteFileSystem(kc keepClient) CustomFileSystem {
117 fs := c.CustomFileSystem(kc)
118 fs.MountByID("by_id")
119 fs.MountUsers("users")
123 func (fs *customFileSystem) Sync() error {
124 return fs.root.Sync()
127 // Stale returns true if information obtained at time t should be
129 func (fs *customFileSystem) Stale(t time.Time) bool {
131 defer fs.staleLock.Unlock()
132 return !fs.staleThreshold.Before(t)
135 func (fs *customFileSystem) newNode(name string, perm os.FileMode, modTime time.Time) (node inode, err error) {
136 return nil, ErrInvalidOperation
139 func (fs *customFileSystem) mountByID(parent inode, id string) inode {
140 if strings.Contains(id, "-4zz18-") || pdhRegexp.MatchString(id) {
141 return fs.mountCollection(parent, id)
142 } else if strings.Contains(id, "-j7d0g-") {
143 return fs.newProjectNode(fs.root, id, id, nil)
149 func (fs *customFileSystem) mountCollection(parent inode, id string) inode {
151 err := fs.RequestAndDecode(&coll, "GET", "arvados/v1/collections/"+id, nil, nil)
155 newfs, err := coll.FileSystem(fs, fs)
159 cfs := newfs.(*collectionFileSystem)
160 cfs.SetParent(parent, id)
164 func (fs *customFileSystem) newProjectNode(root inode, name, uuid string, proj *Group) inode {
165 var projLoading sync.Mutex
168 loadOne: func(parent inode, name string) (inode, error) { return fs.projectsLoadOne(parent, uuid, name) },
169 loadAll: func(parent inode) ([]inode, error) { return fs.projectsLoadAll(parent, uuid) },
173 inodes: make(map[string]inode),
177 mode: 0755 | os.ModeDir,
178 sys: func() interface{} {
180 defer projLoading.Unlock()
185 err := fs.RequestAndDecode(&g, "GET", "arvados/v1/groups/"+uuid, nil, nil)
197 // vdirnode wraps an inode by rejecting (with ErrInvalidOperation)
198 // calls that add/replace children directly, instead calling a
199 // create() func when a non-existing child is looked up.
201 // create() can return either a new node, which will be added to the
202 // treenode, or nil for ENOENT.
203 type vdirnode struct {
205 create func(parent inode, name string) inode
208 func (vn *vdirnode) Child(name string, replace func(inode) (inode, error)) (inode, error) {
209 return vn.treenode.Child(name, func(existing inode) (inode, error) {
210 if existing == nil && vn.create != nil {
211 existing = vn.create(vn, name)
213 existing.SetParent(vn, name)
214 vn.treenode.fileinfo.modTime = time.Now()
219 } else if tryRepl, err := replace(existing); err != nil {
221 } else if tryRepl != existing {
222 return existing, ErrInvalidOperation