Merge branch '19088-s3-properties-tags'
[arvados.git] / sdk / go / arvados / fs_collection.go
index afe92c991152c1aaf344fd9ebb0a04bb2bb82d67..26012e240603d0be43a1019346c4e946e2821790 100644 (file)
@@ -85,6 +85,7 @@ func (c *Collection) FileSystem(client apiClient, kc keepClient) (CollectionFile
                                name:    ".",
                                mode:    os.ModeDir | 0755,
                                modTime: modTime,
+                               sys:     func() interface{} { return c },
                        },
                        inodes: make(map[string]inode),
                },
@@ -1159,15 +1160,17 @@ func (dn *dirnode) MemorySize() (size int64) {
                case *dirnode:
                        size += node.MemorySize()
                case *filenode:
+                       size += 64
                        for _, seg := range node.segments {
                                switch seg := seg.(type) {
                                case *memSegment:
                                        size += int64(seg.Len())
                                }
+                               size += 64
                        }
                }
        }
-       return
+       return 64 + size
 }
 
 // caller must have write lock.
@@ -1563,18 +1566,46 @@ func (dn *dirnode) snapshot() (*dirnode, error) {
 }
 
 func (dn *dirnode) Splice(repl inode) error {
-       repldn, ok := repl.(*dirnode)
-       if !ok {
-               return fmt.Errorf("cannot use Splice to replace a directory with a file: %w", ErrInvalidArgument)
-       }
-       repldn, err := repldn.snapshot()
+       repl, err := repl.Snapshot()
        if err != nil {
-               return err
+               return fmt.Errorf("cannot copy snapshot: %w", err)
+       }
+       switch repl := repl.(type) {
+       default:
+               return fmt.Errorf("cannot splice snapshot containing %T: %w", repl, ErrInvalidArgument)
+       case *dirnode:
+               dn.Lock()
+               defer dn.Unlock()
+               dn.inodes = repl.inodes
+               dn.setTreeFS(dn.fs)
+       case *filenode:
+               dn.parent.Lock()
+               defer dn.parent.Unlock()
+               removing, err := dn.parent.Child(dn.fileinfo.name, nil)
+               if err != nil {
+                       return fmt.Errorf("cannot use Splice to replace a top-level directory with a file: %w", ErrInvalidOperation)
+               } else if removing != dn {
+                       // If ../thisdirname is not this dirnode, it
+                       // must be an inode that wraps a dirnode, like
+                       // a collectionFileSystem or deferrednode.
+                       if deferred, ok := removing.(*deferrednode); ok {
+                               // More useful to report the type of
+                               // the wrapped node rather than just
+                               // *deferrednode. (We know the real
+                               // inode is already loaded because dn
+                               // is inside it.)
+                               removing = deferred.realinode()
+                       }
+                       return fmt.Errorf("cannot use Splice to attach a file at top level of %T: %w", removing, ErrInvalidOperation)
+               }
+               dn.Lock()
+               defer dn.Unlock()
+               _, err = dn.parent.Child(dn.fileinfo.name, func(inode) (inode, error) { return repl, nil })
+               if err != nil {
+                       return fmt.Errorf("error replacing filenode: dn.parent.Child(): %w", err)
+               }
+               repl.fs = dn.fs
        }
-       dn.Lock()
-       defer dn.Unlock()
-       dn.inodes = repldn.inodes
-       dn.setTreeFS(dn.fs)
        return nil
 }