Commit eea9afa9 authored by Han-Wen Nienhuys's avatar Han-Wen Nienhuys

nodefs: implement lseek

Test Lseek together with FOPEN_DIRECT_IO, and document this use-case
in directio_test.go
parent 85a08fb9
......@@ -192,10 +192,16 @@ type FileOperations interface {
// FSetAttr is like SetAttr but provides a file handle if available.
FSetAttr(ctx context.Context, f FileHandle, in *fuse.SetAttrIn, out *fuse.AttrOut) fuse.Status
// CopyFileRange copies data between sections of two files.
// CopyFileRange copies data between sections of two files,
// without the data having to pass through the calling process.
CopyFileRange(ctx context.Context, fhIn FileHandle,
offIn uint64, out *Inode, fhOut FileHandle, offOut uint64,
len uint64, flags uint64) (uint32, fuse.Status)
// Lseek is used to implement holes: it should return the
// first offset beyond `off` where there is data (SEEK_DATA)
// or where there is a hole (SEEK_HOLE).
Lseek(ctx context.Context, f FileHandle, Off uint64, whence uint32) (uint64, fuse.Status)
}
// LockOperations are operations for locking regions of regular files.
......@@ -290,8 +296,20 @@ type MutableDirOperations interface {
Rename(ctx context.Context, name string, newParent Operations, newName string, flags uint32) fuse.Status
}
// FileHandle is a resource identifier for opened files. For a
// description, see the equivalent operations in FileOperations.
// FileHandle is a resource identifier for opened files. FileHandles
// are useful in two cases: First, if the underlying storage systems
// needs a handle for reading/writing. See the function
// `NewLoopbackFile` for an example. Second, it is useful for
// implementing files whose contents are not tied to an inode. For
// example, a file like `/proc/interrupts` has no fixed content, but
// changes on each open call. This means that each file handle must
// have its own view of the content; this view can be tied to a
// FileHandle. Files that have such dynamic content should return the
// FOPEN_DIRECT_IO flag from their `Open` method. See directio_test.go
// for an example.
//
// For a description of individual operations, see the equivalent
// operations in FileOperations.
type FileHandle interface {
Read(ctx context.Context, dest []byte, off int64) (fuse.ReadResult, fuse.Status)
......@@ -300,6 +318,9 @@ type FileHandle interface {
GetLk(ctx context.Context, owner uint64, lk *fuse.FileLock, flags uint32, out *fuse.FileLock) (status fuse.Status)
SetLk(ctx context.Context, owner uint64, lk *fuse.FileLock, flags uint32) (status fuse.Status)
SetLkw(ctx context.Context, owner uint64, lk *fuse.FileLock, flags uint32) (status fuse.Status)
Lseek(ctx context.Context, off uint64, whence uint32) (uint64, fuse.Status)
Flush(ctx context.Context) fuse.Status
Fsync(ctx context.Context, flags uint32) fuse.Status
......
......@@ -746,3 +746,12 @@ func (b *rawBridge) CopyFileRange(cancel <-chan struct{}, in *fuse.CopyFileRange
return n1.fileOps().CopyFileRange(&fuse.Context{Caller: in.Caller, Cancel: cancel},
f1.file, in.OffIn, n2, f2.file, in.OffOut, in.Len, in.Flags)
}
func (b *rawBridge) Lseek(cancel <-chan struct{}, in *fuse.LseekIn, out *fuse.LseekOut) fuse.Status {
n, f := b.inode(in.NodeId, in.Fh)
off, status := n.fileOps().Lseek(&fuse.Context{Caller: in.Caller, Cancel: cancel},
f.file, in.Offset, in.Whence)
out.Offset = off
return status
}
......@@ -207,6 +207,13 @@ func (n *DefaultOperations) CopyFileRange(ctx context.Context, fhIn FileHandle,
return 0, fuse.EROFS
}
func (n *DefaultOperations) Lseek(ctx context.Context, f FileHandle, off uint64, whence uint32) (uint64, fuse.Status) {
if f != nil {
return f.Lseek(ctx, off, whence)
}
return 0, fuse.ENOTSUP
}
// GetLk delegates to the FileHandlef
func (n *DefaultOperations) GetLk(ctx context.Context, f FileHandle, owner uint64, lk *fuse.FileLock, flags uint32, out *fuse.FileLock) (status fuse.Status) {
if f != nil {
......@@ -354,3 +361,7 @@ func (f *DefaultFileHandle) Allocate(ctx context.Context, off uint64, size uint6
func (f *DefaultFileHandle) Fsync(ctx context.Context, flags uint32) (status fuse.Status) {
return fuse.ENOTSUP
}
func (f *DefaultFileHandle) Lseek(ctx context.Context, off uint64, whence uint32) (uint64, fuse.Status) {
return 0, fuse.ENOTSUP
}
// Copyright 2019 the Go-FUSE Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package nodefs
import (
"bytes"
"context"
"fmt"
"os"
"syscall"
"testing"
"github.com/hanwen/go-fuse/fuse"
"github.com/hanwen/go-fuse/internal/testutil"
)
type dioRoot struct {
DefaultOperations
}
func (r *dioRoot) OnAdd(ctx context.Context) {
r.Inode().AddChild("file", r.Inode().NewInode(ctx, &dioFile{}, NodeAttr{}), false)
}
// A file handle that pretends that every hole/data starts at
// multiples of 1024
type dioFH struct {
DefaultFileHandle
}
func (f *dioFH) Lseek(ctx context.Context, off uint64, whence uint32) (uint64, fuse.Status) {
next := (off + 1023) & (^uint64(1023))
return next, fuse.OK
}
func (fh *dioFH) Read(ctx context.Context, data []byte, off int64) (fuse.ReadResult, fuse.Status) {
r := bytes.Repeat([]byte(fmt.Sprintf("%010d", off)), 1+len(data)/10)
return fuse.ReadResultData(r[:len(data)]), fuse.OK
}
// overrides Open so it can return a dioFH file handle
type dioFile struct {
DefaultOperations
}
func (f *dioFile) Open(ctx context.Context, flags uint32) (fh FileHandle, fuseFlags uint32, status fuse.Status) {
return &dioFH{}, fuse.FOPEN_DIRECT_IO, fuse.OK
}
func TestDirectIO(t *testing.T) {
root := &dioRoot{}
mntDir := testutil.TempDir()
defer os.RemoveAll(mntDir)
server, err := Mount(mntDir, root, &Options{
MountOptions: fuse.MountOptions{
Debug: testutil.VerboseTest(),
},
FirstAutomaticIno: 1,
// no caching.
})
defer server.Unmount()
f, err := os.Open(mntDir + "/file")
if err != nil {
t.Fatalf("Open %v", err)
}
defer f.Close()
var buf [10]byte
n, err := f.Read(buf[:])
if err != nil {
t.Fatalf("Read %v", err)
}
want := bytes.Repeat([]byte{'0'}, 10)
got := buf[:n]
if bytes.Compare(got, want) != 0 {
t.Errorf("got %q want %q", got, want)
}
const SEEK_DATA = 3 /* seek to the next data */
if n, err := syscall.Seek(int(f.Fd()), 512, SEEK_DATA); err != nil {
t.Errorf("Seek: %v", err)
} else if n != 1024 {
t.Errorf("seek: got %d, want %d", n, 1024)
}
n, err = f.Read(buf[:])
if err != nil {
t.Fatalf("Read %v", err)
}
want = []byte(fmt.Sprintf("%010d", 1024))
got = buf[:n]
if bytes.Compare(got, want) != 0 {
t.Errorf("got %q want %q", got, want)
}
}
......@@ -11,6 +11,7 @@ import (
"syscall"
"github.com/hanwen/go-fuse/fuse"
"golang.org/x/sys/unix"
)
// NewLoopbackFile creates a FileHandle out of a file descriptor. All
......@@ -181,3 +182,8 @@ func (f *loopbackFile) GetAttr(ctx context.Context, a *fuse.AttrOut) fuse.Status
return fuse.OK
}
func (f *loopbackFile) Lseek(ctx context.Context, off uint64, whence uint32) (uint64, fuse.Status) {
n, err := unix.Seek(f.fd, int64(off), int(whence))
return uint64(n), fuse.ToStatus(err)
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment