21204: Merge branch '21204-stable-log-sort' from arvados-workbench2.git
[arvados.git] / sdk / go / arvados / fs_base.go
index 387b6a737ff334cba74cb44dc83761a88fd56571..274d20702287ed464d4ea8f3796e528c3f61b30b 100644 (file)
@@ -234,6 +234,14 @@ type fileinfo struct {
        mode    os.FileMode
        size    int64
        modTime time.Time
+       // If not nil, sys() returns the source data structure, which
+       // can be a *Collection, *Group, or nil. Currently populated
+       // only for project dirs and top-level collection dirs. Does
+       // not stay up to date with upstream changes.
+       //
+       // Intended to support keep-web's properties-as-s3-metadata
+       // feature (https://dev.arvados.org/issues/19088).
+       sys func() interface{}
 }
 
 // Name implements os.FileInfo.
@@ -261,9 +269,12 @@ func (fi fileinfo) Size() int64 {
        return fi.size
 }
 
-// Sys implements os.FileInfo.
+// Sys implements os.FileInfo. See comment in fileinfo struct.
 func (fi fileinfo) Sys() interface{} {
-       return nil
+       if fi.sys == nil {
+               return nil
+       }
+       return fi.sys()
 }
 
 type nullnode struct{}
@@ -409,13 +420,23 @@ func (n *treenode) Sync() error {
 }
 
 func (n *treenode) MemorySize() (size int64) {
+       // To avoid making other callers wait while we count the
+       // entire filesystem size, we lock the node only long enough
+       // to copy the list of children. We accept that the resulting
+       // size will sometimes be misleading (e.g., we will
+       // double-count an item that moves from A to B after we check
+       // A's size but before we check B's size).
        n.RLock()
-       defer n.RUnlock()
        debugPanicIfNotLocked(n, false)
+       todo := make([]inode, 0, len(n.inodes))
        for _, inode := range n.inodes {
+               todo = append(todo, inode)
+       }
+       n.RUnlock()
+       for _, inode := range todo {
                size += inode.MemorySize()
        }
-       return
+       return 64 + size
 }
 
 type fileSystem struct {
@@ -463,14 +484,14 @@ func (fs *fileSystem) openFile(name string, flag int, perm os.FileMode) (*fileha
        default:
                return nil, fmt.Errorf("invalid flags 0x%x", flag)
        }
-       if !writable && parent.IsDir() {
+       if parent.IsDir() {
                // A directory can be opened via "foo/", "foo/.", or
                // "foo/..".
                switch name {
                case ".", "":
-                       return &filehandle{inode: parent}, nil
+                       return &filehandle{inode: parent, readable: readable, writable: writable}, nil
                case "..":
-                       return &filehandle{inode: parent.Parent()}, nil
+                       return &filehandle{inode: parent.Parent(), readable: readable, writable: writable}, nil
                }
        }
        createMode := flag&os.O_CREATE != 0
@@ -620,7 +641,15 @@ func (fs *fileSystem) Rename(oldname, newname string) error {
        }
        locked := map[sync.Locker]bool{}
        for i := len(needLock) - 1; i >= 0; i-- {
-               if n := needLock[i]; !locked[n] {
+               n := needLock[i]
+               if fs, ok := n.(interface{ rootnode() inode }); ok {
+                       // Lock the fs's root dir directly, not
+                       // through the fs. Otherwise our "locked" map
+                       // would not reliably prevent double-locking
+                       // the fs's root dir.
+                       n = fs.rootnode()
+               }
+               if !locked[n] {
                        n.Lock()
                        defer n.Unlock()
                        locked[n] = true
@@ -766,7 +795,7 @@ func Splice(fs FileSystem, target string, newsubtree *Subtree) error {
                f, err = fs.OpenFile(target, os.O_CREATE|os.O_WRONLY, 0700)
        }
        if err != nil {
-               return err
+               return fmt.Errorf("open %s: %w", target, err)
        }
        defer f.Close()
        return f.Splice(newsubtree)