1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: Apache-2.0
12 // lookupnode is a caching tree node that is initially empty and calls
13 // loadOne and loadAll to load/update child nodes as needed.
15 // See (*customFileSystem)MountUsers for example usage.
16 type lookupnode struct {
18 loadOne func(parent inode, name string) (inode, error)
19 loadAll func(parent inode) ([]inode, error)
20 stale func(time.Time) bool
24 staleOne map[string]time.Time
27 // Sync flushes pending writes for loaded children and, if successful,
28 // triggers a reload on next lookup.
29 func (ln *lookupnode) Sync() error {
30 err := ln.treenode.Sync()
35 ln.staleAll = time.Time{}
41 func (ln *lookupnode) Readdir() ([]os.FileInfo, error) {
43 checkTime := time.Now()
44 if ln.stale(ln.staleAll) {
45 all, err := ln.loadAll(ln)
50 for _, child := range all {
52 if hl, ok := child.(*hardlink); ok && hl.inode == ln {
53 // If child is a hardlink to its
54 // parent, FileInfo()->RLock() will
55 // deadlock, because we already have
56 // the write lock. In this situation
57 // we can safely access the hardlink's
61 name = child.FileInfo().Name()
63 _, err = ln.treenode.Child(name, func(inode) (inode, error) {
71 ln.staleAll = checkTime
72 // No value in ln.staleOne can make a difference to an
73 // "entry is stale?" test now, because no value is
74 // newer than ln.staleAll. Reclaim memory.
78 return ln.treenode.Readdir()
81 // Child rejects (with ErrInvalidOperation) calls to add/replace
82 // children, instead calling loadOne when a non-existing child is
84 func (ln *lookupnode) Child(name string, replace func(inode) (inode, error)) (inode, error) {
85 checkTime := time.Now()
88 if ln.stale(ln.staleAll) && ln.stale(ln.staleOne[name]) {
89 existing, err = ln.treenode.Child(name, func(inode) (inode, error) {
90 return ln.loadOne(ln, name)
92 if err == nil && existing != nil {
93 if ln.staleOne == nil {
94 ln.staleOne = map[string]time.Time{name: checkTime}
96 ln.staleOne[name] = checkTime
100 existing, err = ln.treenode.Child(name, nil)
101 if err != nil && !os.IsNotExist(err) {
106 // Let the callback try to delete or replace the
107 // existing node; if it does, return
108 // ErrInvalidOperation.
109 if tryRepl, err := replace(existing); err != nil {
110 // Propagate error from callback
112 } else if tryRepl != existing {
113 return existing, ErrInvalidOperation
116 // Return original error from ln.treenode.Child() (it might be