Merge branch '19088-s3-properties-tags'
[arvados.git] / sdk / go / arvados / fs_base.go
index 719e0e08ac38aa6deeca3148b28ff698d2ba7f89..2ad4d1f859f1141035c04cb4180c5ef623d1fa04 100644 (file)
@@ -8,6 +8,7 @@ import (
        "errors"
        "fmt"
        "io"
+       "io/fs"
        "log"
        "net/http"
        "os"
@@ -81,10 +82,13 @@ type File interface {
        // then be spliced onto a different path or a different
        // collection.
        Snapshot() (*Subtree, error)
-       // Replace this file or directory with the given snapshot. It
-       // is an error to replace a directory with a file. If snapshot
-       // is (or might be) a directory, remove the directory, create
-       // a file with the same name, and splice the file.
+       // Replace this file or directory with the given snapshot.
+       // The target must be inside a collection: Splice returns an
+       // error if the File is a virtual file or directory like
+       // by_id, a project directory, .arvados#collection,
+       // etc. Splice can replace directories with regular files and
+       // vice versa, except it cannot replace the root directory of
+       // a collection with a regular file.
        Splice(snapshot *Subtree) error
 }
 
@@ -156,6 +160,18 @@ type FileSystem interface {
        MemorySize() int64
 }
 
+type fsFS struct {
+       FileSystem
+}
+
+// FS returns an fs.FS interface to the given FileSystem, to enable
+// the use of fs.WalkDir, etc.
+func FS(fs FileSystem) fs.FS { return fsFS{fs} }
+func (fs fsFS) Open(path string) (fs.File, error) {
+       f, err := fs.FileSystem.Open(path)
+       return f, err
+}
+
 type inode interface {
        SetParent(parent inode, name string)
        Parent() inode
@@ -171,7 +187,7 @@ type inode interface {
        Snapshot() (inode, error)
        // Replace this node with a copy of the provided snapshot.
        // Caller may provide the same snapshot to multiple Splice
-       // calls, but must not modify the the snapshot concurrently.
+       // calls, but must not modify the snapshot concurrently.
        Splice(inode) error
 
        // Child() performs lookups and updates of named child nodes.
@@ -218,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.
@@ -245,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{}
@@ -399,7 +426,7 @@ func (n *treenode) MemorySize() (size int64) {
        for _, inode := range n.inodes {
                size += inode.MemorySize()
        }
-       return
+       return 64 + size
 }
 
 type fileSystem struct {
@@ -447,14 +474,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
@@ -742,7 +769,7 @@ func Snapshot(fs FileSystem, path string) (*Subtree, error) {
 //
 // Splice returns an error if target is not inside a collection.
 //
-// Splice returns an error if target is an existing directory and
+// Splice returns an error if target is the root of a collection and
 // newsubtree is a snapshot of a file.
 func Splice(fs FileSystem, target string, newsubtree *Subtree) error {
        f, err := fs.OpenFile(target, os.O_WRONLY, 0)
@@ -750,7 +777,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)