12483: Allow seek and write beyond EOF.
authorTom Clegg <tclegg@veritasgenetics.com>
Sat, 18 Nov 2017 06:44:55 +0000 (01:44 -0500)
committerTom Clegg <tclegg@veritasgenetics.com>
Sat, 18 Nov 2017 07:28:41 +0000 (02:28 -0500)
Arvados-DCO-1.1-Signed-off-by: Tom Clegg <tclegg@veritasgenetics.com>

sdk/go/arvados/collection_fs.go
sdk/go/arvados/collection_fs_test.go

index fd524c9aa65b9ad828d859a6130e9a40da2b47d7..459b476428c74b62b394a7081243ed18cc06aeca 100644 (file)
@@ -200,7 +200,6 @@ func (fn *filenode) seek(startPtr filenodePtr) (ptr filenodePtr) {
                // meaningless anyway
                return
        } else if ptr.off >= fn.fileinfo.size {
-               ptr.off = fn.fileinfo.size
                ptr.extentIdx = len(fn.extents)
                ptr.extentOff = 0
                ptr.repacked = fn.repacked
@@ -296,8 +295,16 @@ func (fn *filenode) Stat() os.FileInfo {
 func (fn *filenode) Truncate(size int64) error {
        fn.Lock()
        defer fn.Unlock()
+       return fn.truncate(size)
+}
+
+func (fn *filenode) truncate(size int64) error {
+       if size == fn.fileinfo.size {
+               return nil
+       }
+       fn.repacked++
        if size < fn.fileinfo.size {
-               ptr := fn.seek(filenodePtr{off: size, repacked: fn.repacked - 1})
+               ptr := fn.seek(filenodePtr{off: size})
                for i := ptr.extentIdx; i < len(fn.extents); i++ {
                        if ext, ok := fn.extents[i].(*memExtent); ok {
                                fn.memsize -= int64(ext.Len())
@@ -316,7 +323,6 @@ func (fn *filenode) Truncate(size int64) error {
                        }
                }
                fn.fileinfo.size = size
-               fn.repacked++
                return nil
        }
        for size > fn.fileinfo.size {
@@ -329,8 +335,6 @@ func (fn *filenode) Truncate(size int64) error {
                } else if e, ok = fn.extents[len(fn.extents)-1].(writableExtent); !ok || e.Len() >= maxBlockSize {
                        e = &memExtent{}
                        fn.extents = append(fn.extents, e)
-               } else {
-                       fn.repacked++
                }
                if maxgrow := int64(maxBlockSize - e.Len()); maxgrow < grow {
                        grow = maxgrow
@@ -342,7 +346,13 @@ func (fn *filenode) Truncate(size int64) error {
        return nil
 }
 
+// Caller must hold lock.
 func (fn *filenode) Write(p []byte, startPtr filenodePtr) (n int, ptr filenodePtr, err error) {
+       if startPtr.off > fn.fileinfo.size {
+               if err = fn.truncate(startPtr.off); err != nil {
+                       return 0, startPtr, err
+               }
+       }
        ptr = fn.seek(startPtr)
        if ptr.off < 0 {
                err = ErrNegativeOffset
@@ -548,9 +558,6 @@ func (f *file) Seek(off int64, whence int) (pos int64, err error) {
        if ptr.off < 0 {
                return f.ptr.off, ErrNegativeOffset
        }
-       if ptr.off > size {
-               ptr.off = size
-       }
        if ptr.off != f.ptr.off {
                f.ptr = ptr
                // force filenode to recompute f.ptr fields on next
index 5ef357fc1d38af078a61c7040fd4112bc4230e70..07714ebdf2d118463013583122c3562cc28875f8 100644 (file)
@@ -281,6 +281,7 @@ func (s *CollectionFSSuite) TestReadWriteFile(c *check.C) {
        c.Check(string(buf2), check.Equals, "f0123456789abcd\x00\x00\x00\x00\x00")
 
        f.Truncate(0)
+       f2.Seek(0, os.SEEK_SET)
        f2.Write([]byte("12345678abcdefghijkl"))
 
        // grow to block/extent boundary
@@ -330,6 +331,46 @@ func (s *CollectionFSSuite) TestReadWriteFile(c *check.C) {
        c.Check(m, check.Equals, "./dir1 3858f62230ac3c915f300c664312c63f+6 25d55ad283aa400af464c76d713c07ad+8 3:3:bar 6:3:foo\n")
 }
 
+func (s *CollectionFSSuite) TestSeekSparse(c *check.C) {
+       fs, err := (&Collection{}).FileSystem(s.client, s.kc)
+       c.Assert(err, check.IsNil)
+       f, err := fs.OpenFile("test", os.O_CREATE|os.O_RDWR, 0755)
+       c.Assert(err, check.IsNil)
+       defer f.Close()
+
+       checkSize := func(size int64) {
+               fi, err := f.Stat()
+               c.Check(fi.Size(), check.Equals, size)
+
+               f, err := fs.OpenFile("test", os.O_CREATE|os.O_RDWR, 0755)
+               c.Assert(err, check.IsNil)
+               defer f.Close()
+               fi, err = f.Stat()
+               c.Check(fi.Size(), check.Equals, size)
+               pos, err := f.Seek(0, os.SEEK_END)
+               c.Check(pos, check.Equals, size)
+       }
+
+       f.Seek(2, os.SEEK_END)
+       checkSize(0)
+       f.Write([]byte{1})
+       checkSize(3)
+
+       f.Seek(2, os.SEEK_CUR)
+       checkSize(3)
+       f.Write([]byte{})
+       checkSize(5)
+
+       f.Seek(8, os.SEEK_SET)
+       checkSize(5)
+       n, err := f.Read(make([]byte, 1))
+       c.Check(n, check.Equals, 0)
+       c.Check(err, check.Equals, io.EOF)
+       checkSize(5)
+       f.Write([]byte{1, 2, 3})
+       checkSize(11)
+}
+
 func (s *CollectionFSSuite) TestMarshalSmallBlocks(c *check.C) {
        maxBlockSize = 8
        defer func() { maxBlockSize = 2 << 26 }()