13111: Merge branch 'master' into 12308-go-fuse
[arvados.git] / sdk / go / arvados / fs_collection.go
index fc00335ce5eca8a20d8e4db03384be71417a671b..fbd9775b0cde0876e68328fd63a019485eeac4a9 100644 (file)
@@ -21,28 +21,6 @@ import (
 
 var maxBlockSize = 1 << 26
 
-type fsBackend interface {
-       keepClient
-       apiClient
-}
-
-// Ideally *Client would do everything; meanwhile keepBackend
-// implements fsBackend by merging the two kinds of arvados client.
-type keepBackend struct {
-       keepClient
-       apiClient
-}
-
-type keepClient interface {
-       ReadAt(locator string, p []byte, off int) (int, error)
-       PutB(p []byte) (string, int, error)
-}
-
-type apiClient interface {
-       RequestAndDecode(dst interface{}, method, path string, body io.Reader, params interface{}) error
-       UpdateBody(rsc resource) io.Reader
-}
-
 // A CollectionFileSystem is a FileSystem that can be serialized as a
 // manifest and stored as a collection.
 type CollectionFileSystem interface {
@@ -55,6 +33,11 @@ type CollectionFileSystem interface {
        MarshalManifest(prefix string) (string, error)
 }
 
+type collectionFileSystem struct {
+       fileSystem
+       uuid string
+}
+
 // FileSystem returns a CollectionFileSystem for the collection.
 func (c *Collection) FileSystem(client apiClient, kc keepClient) (CollectionFileSystem, error) {
        var modTime time.Time
@@ -64,14 +47,14 @@ func (c *Collection) FileSystem(client apiClient, kc keepClient) (CollectionFile
                modTime = *c.ModifiedAt
        }
        fs := &collectionFileSystem{
+               uuid: c.UUID,
                fileSystem: fileSystem{
                        fsBackend: keepBackend{apiClient: client, keepClient: kc},
                },
-               uuid: c.UUID,
        }
-       dn := &dirnode{
+       root := &dirnode{
+               fs: fs,
                treenode: treenode{
-                       fs: fs,
                        fileinfo: fileinfo{
                                name:    ".",
                                mode:    os.ModeDir | 0755,
@@ -80,17 +63,53 @@ func (c *Collection) FileSystem(client apiClient, kc keepClient) (CollectionFile
                        inodes: make(map[string]inode),
                },
        }
-       dn.parent = dn
-       fs.fileSystem.root = dn
-       if err := dn.loadManifest(c.ManifestText); err != nil {
+       root.SetParent(root, ".")
+       if err := root.loadManifest(c.ManifestText); err != nil {
                return nil, err
        }
+       backdateTree(root, modTime)
+       fs.root = root
        return fs, nil
 }
 
-type collectionFileSystem struct {
-       fileSystem
-       uuid string
+func backdateTree(n inode, modTime time.Time) {
+       switch n := n.(type) {
+       case *filenode:
+               n.fileinfo.modTime = modTime
+       case *dirnode:
+               n.fileinfo.modTime = modTime
+               for _, n := range n.inodes {
+                       backdateTree(n, modTime)
+               }
+       }
+}
+
+func (fs *collectionFileSystem) newNode(name string, perm os.FileMode, modTime time.Time) (node inode, err error) {
+       if name == "" || name == "." || name == ".." {
+               return nil, ErrInvalidArgument
+       }
+       if perm.IsDir() {
+               return &dirnode{
+                       fs: fs,
+                       treenode: treenode{
+                               fileinfo: fileinfo{
+                                       name:    name,
+                                       mode:    perm | os.ModeDir,
+                                       modTime: modTime,
+                               },
+                               inodes: make(map[string]inode),
+                       },
+               }, nil
+       } else {
+               return &filenode{
+                       fs: fs,
+                       fileinfo: fileinfo{
+                               name:    name,
+                               mode:    perm & ^os.ModeDir,
+                               modTime: modTime,
+                       },
+               }, nil
+       }
 }
 
 func (fs *collectionFileSystem) Sync() error {
@@ -114,25 +133,6 @@ func (fs *collectionFileSystem) Sync() error {
        return err
 }
 
-func (fs *collectionFileSystem) Child(name string, replace func(inode) inode) inode {
-       if name == ".arvados#collection" {
-               return &getternode{Getter: func() ([]byte, error) {
-                       var coll Collection
-                       var err error
-                       coll.ManifestText, err = fs.MarshalManifest(".")
-                       if err != nil {
-                               return nil, err
-                       }
-                       data, err := json.Marshal(&coll)
-                       if err == nil {
-                               data = append(data, 10)
-                       }
-                       return data, err
-               }}
-       }
-       return fs.fileSystem.root.Child(name, replace)
-}
-
 func (fs *collectionFileSystem) MarshalManifest(prefix string) (string, error) {
        fs.fileSystem.root.Lock()
        defer fs.fileSystem.root.Unlock()
@@ -232,6 +232,13 @@ func (fn *filenode) appendSegment(e segment) {
        fn.fileinfo.size += int64(e.Len())
 }
 
+func (fn *filenode) SetParent(p inode, name string) {
+       fn.Lock()
+       defer fn.Unlock()
+       fn.parent = p
+       fn.fileinfo.name = name
+}
+
 func (fn *filenode) Parent() inode {
        fn.RLock()
        defer fn.RUnlock()
@@ -487,7 +494,7 @@ func (fn *filenode) pruneMemSegments() {
                if !ok || seg.Len() < maxBlockSize {
                        continue
                }
-               locator, _, err := fn.parent.(fsBackend).PutB(seg.buf)
+               locator, _, err := fn.FS().PutB(seg.buf)
                if err != nil {
                        // TODO: stall (or return errors from)
                        // subsequent writes until flushing
@@ -496,7 +503,7 @@ func (fn *filenode) pruneMemSegments() {
                }
                fn.memsize -= int64(seg.Len())
                fn.segments[idx] = storedSegment{
-                       kc:      fn.parent.(fsBackend),
+                       kc:      fn.FS(),
                        locator: locator,
                        size:    seg.Len(),
                        offset:  0,
@@ -506,9 +513,35 @@ func (fn *filenode) pruneMemSegments() {
 }
 
 type dirnode struct {
+       fs *collectionFileSystem
        treenode
 }
 
+func (dn *dirnode) FS() FileSystem {
+       return dn.fs
+}
+
+func (dn *dirnode) Child(name string, replace func(inode) inode) inode {
+       if dn == dn.fs.rootnode() && name == ".arvados#collection" {
+               gn := &getternode{Getter: func() ([]byte, error) {
+                       var coll Collection
+                       var err error
+                       coll.ManifestText, err = dn.fs.MarshalManifest(".")
+                       if err != nil {
+                               return nil, err
+                       }
+                       data, err := json.Marshal(&coll)
+                       if err == nil {
+                               data = append(data, '\n')
+                       }
+                       return data, err
+               }}
+               gn.SetParent(dn, name)
+               return gn
+       }
+       return dn.treenode.Child(name, replace)
+}
+
 // sync flushes in-memory data (for all files in the tree rooted at
 // dn) to persistent storage. Caller must hold dn.Lock().
 func (dn *dirnode) sync() error {
@@ -801,13 +834,14 @@ func (dn *dirnode) createFileAndParents(path string) (fn *filenode, err error) {
                                // can't be sure parent will be a *dirnode
                                return nil, ErrInvalidArgument
                        }
-                       node = node.Parent().(*dirnode)
+                       node = node.Parent()
                        continue
                }
                node.Child(name, func(child inode) inode {
                        if child == nil {
-                               node, err = node.FS().newDirnode(node, name, 0755|os.ModeDir, node.Parent().FileInfo().ModTime())
-                               child = node
+                               child, err = node.FS().newNode(name, 0755|os.ModeDir, node.Parent().FileInfo().ModTime())
+                               child.SetParent(node, name)
+                               node = child
                        } else if !child.IsDir() {
                                err = ErrFileExists
                        } else {
@@ -822,7 +856,8 @@ func (dn *dirnode) createFileAndParents(path string) (fn *filenode, err error) {
        node.Child(basename, func(child inode) inode {
                switch child := child.(type) {
                case nil:
-                       child, err = node.FS().newFilenode(node, basename, 0755, node.FileInfo().ModTime())
+                       child, err = node.FS().newNode(basename, 0755, node.FileInfo().ModTime())
+                       child.SetParent(node, basename)
                        fn = child.(*filenode)
                        return child
                case *filenode:
@@ -832,7 +867,7 @@ func (dn *dirnode) createFileAndParents(path string) (fn *filenode, err error) {
                        err = ErrIsDirectory
                        return child
                default:
-                       err = ErrInvalidOperation
+                       err = ErrInvalidArgument
                        return child
                }
        })