X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/a3c509e8eca36f1291f7547999f16d9fd127c4a0..0f5b0542513b572959e39400bae42e69aeb1a7b6:/sdk/go/arvados/fs_base.go diff --git a/sdk/go/arvados/fs_base.go b/sdk/go/arvados/fs_base.go index 5bccdc51db..274d207022 100644 --- a/sdk/go/arvados/fs_base.go +++ b/sdk/go/arvados/fs_base.go @@ -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{} @@ -393,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 { @@ -447,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 @@ -604,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 @@ -750,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)