diff --git a/cmd/mount/file.go b/cmd/mount/file.go index 73535dccf..343783b2d 100644 --- a/cmd/mount/file.go +++ b/cmd/mount/file.go @@ -112,13 +112,11 @@ func (f *File) Open(ctx context.Context, req *fuse.OpenRequest, resp *fuse.OpenR fs.Debug(o, "File.Open") - // Files aren't seekable - resp.Flags |= fuse.OpenNonSeekable - switch { case req.Flags.IsReadOnly(): return newReadFileHandle(o) case req.Flags.IsWriteOnly(): + resp.Flags |= fuse.OpenNonSeekable src := newCreateInfo(f.d.f, o.Remote()) fh, err := newWriteFileHandle(f.d, f, src) if err != nil { diff --git a/cmd/mount/mount.go b/cmd/mount/mount.go index 5f8e68f00..afed31715 100644 --- a/cmd/mount/mount.go +++ b/cmd/mount/mount.go @@ -82,10 +82,9 @@ Or with OS X ### Limitations ### -This can only read files seqentially, or write files sequentially. It -can't read and write or seek in files. +This can only write files seqentially, it can only seek when reading. -rclonefs inherits rclone's directory handling. In rclone's world +Rclone mount inherits rclone's directory handling. In rclone's world directories don't really exist. This means that empty directories will have a tendency to disappear once they fall out of the directory cache. diff --git a/cmd/mount/read.go b/cmd/mount/read.go index 97f7aaa7c..a8d119b1c 100644 --- a/cmd/mount/read.go +++ b/cmd/mount/read.go @@ -19,6 +19,7 @@ type ReadFileHandle struct { r io.ReadCloser o fs.Object readCalled bool // set if read has been called + offset int64 } func newReadFileHandle(o fs.Object) (*ReadFileHandle, error) { @@ -38,14 +39,38 @@ var _ fusefs.Handle = (*ReadFileHandle)(nil) // Check interface satisfied var _ fusefs.HandleReader = (*ReadFileHandle)(nil) +// seek to a new offset +func (fh *ReadFileHandle) seek(offset int64) error { + fs.Debug(fh.o, "ReadFileHandle.seek from %d to %d", fh.offset, offset) + r, err := fh.o.Open(&fs.SeekOption{Offset: offset}) + if err != nil { + fs.Debug(fh.o, "ReadFileHandle.Read seek failed: %v", err) + return err + } + err = fh.r.Close() + if err != nil { + fs.Debug(fh.o, "ReadFileHandle.Read seek close old failed: %v", err) + } + fh.r = r + return nil +} + // Read from the file handle func (fh *ReadFileHandle) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error { - fs.Debug(fh.o, "ReadFileHandle.Open") + fs.Debug(fh.o, "ReadFileHandle.Read size %d offset %d", req.Size, req.Offset) if fh.closed { fs.ErrorLog(fh.o, "ReadFileHandle.Read error: %v", errClosedFileHandle) return errClosedFileHandle } - fh.readCalled = true + if req.Offset != fh.offset { + err := fh.seek(req.Offset) + if err != nil { + return err + } + } + if req.Size > 0 { + fh.readCalled = true + } // We don't actually enforce Offset to match where previous read // ended. Maybe we should, but that would mean'd we need to track // it. The kernel *should* do it for us, based on the @@ -60,10 +85,11 @@ func (fh *ReadFileHandle) Read(ctx context.Context, req *fuse.ReadRequest, resp err = nil } resp.Data = buf[:n] + fh.offset += int64(n) if err != nil { - fs.ErrorLog(fh.o, "ReadFileHandle.Open error: %v", err) + fs.ErrorLog(fh.o, "ReadFileHandle.Read error: %v", err) } else { - fs.Debug(fh.o, "ReadFileHandle.Open OK") + fs.Debug(fh.o, "ReadFileHandle.Read OK") } return err } diff --git a/cmd/mount/read_test.go b/cmd/mount/read_test.go index 7b385afd4..ba10f64f1 100644 --- a/cmd/mount/read_test.go +++ b/cmd/mount/read_test.go @@ -4,6 +4,7 @@ package mount import ( "io" + "io/ioutil" "os" "syscall" "testing" @@ -77,3 +78,34 @@ func TestReadFileDoubleClose(t *testing.T) { run.rm(t, "testdoubleclose") } + +// Test seeking +func TestReadSeek(t *testing.T) { + run.skipIfNoFUSE(t) + + var data = []byte("helloHELLO") + run.createFile(t, "testfile", string(data)) + run.checkDir(t, "testfile 10") + + fd, err := os.Open(run.path("testfile")) + assert.NoError(t, err) + + _, err = fd.Seek(5, 0) + assert.NoError(t, err) + + buf, err := ioutil.ReadAll(fd) + assert.NoError(t, err) + assert.Equal(t, buf, []byte("HELLO")) + + _, err = fd.Seek(0, 0) + assert.NoError(t, err) + + buf, err = ioutil.ReadAll(fd) + assert.NoError(t, err) + assert.Equal(t, buf, []byte("helloHELLO")) + + err = fd.Close() + assert.NoError(t, err) + + run.rm(t, "testfile") +}