1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: Apache-2.0
13 // lookupnode is a caching tree node that is initially empty and calls
14 // loadOne and loadAll to load/update child nodes as needed.
16 // See (*customFileSystem)MountUsers for example usage.
17 type lookupnode struct {
19 loadOne func(parent inode, name string) (inode, error)
20 loadAll func(parent inode) ([]inode, error)
21 stale func(time.Time) bool
26 staleOne map[string]time.Time
29 // Sync flushes pending writes for loaded children and, if successful,
30 // triggers a reload on next lookup.
31 func (ln *lookupnode) Sync() error {
32 err := ln.treenode.Sync()
37 ln.staleAll = time.Time{}
43 func (ln *lookupnode) Readdir() ([]os.FileInfo, error) {
45 defer ln.staleLock.Unlock()
46 checkTime := time.Now()
47 if ln.stale(ln.staleAll) {
48 all, err := ln.loadAll(ln)
52 for _, child := range all {
53 _, err = ln.treenode.Child(child.FileInfo().Name(), func(inode) (inode, error) {
60 ln.staleAll = checkTime
61 // No value in ln.staleOne can make a difference to an
62 // "entry is stale?" test now, because no value is
63 // newer than ln.staleAll. Reclaim memory.
66 return ln.treenode.Readdir()
69 // Child rejects (with ErrInvalidArgument) calls to add/replace
70 // children, instead calling loadOne when a non-existing child is
72 func (ln *lookupnode) Child(name string, replace func(inode) (inode, error)) (inode, error) {
74 defer ln.staleLock.Unlock()
75 checkTime := time.Now()
78 if ln.stale(ln.staleAll) && ln.stale(ln.staleOne[name]) {
79 existing, err = ln.treenode.Child(name, func(inode) (inode, error) {
80 return ln.loadOne(ln, name)
82 if err == nil && existing != nil {
83 if ln.staleOne == nil {
84 ln.staleOne = map[string]time.Time{name: checkTime}
86 ln.staleOne[name] = checkTime
90 existing, err = ln.treenode.Child(name, nil)
91 if err != nil && !os.IsNotExist(err) {
96 // Let the callback try to delete or replace the
97 // existing node; if it does, return
98 // ErrInvalidArgument.
99 if tryRepl, err := replace(existing); err != nil {
100 // Propagate error from callback
102 } else if tryRepl != existing {
103 return existing, ErrInvalidArgument
106 // Return original error from ln.treenode.Child() (it might be