Commit 3757ff4b authored by Kirill Smelkov's avatar Kirill Smelkov

Merge branch 'y/nodefs-cancel' into t

* y/nodefs-cancel:
  Y nodefs: Propagate context to File methods
  fuse: dump WRITE argument in debug logs
  Make pollHack work on read-only mounts
  example: loopback: implement -ro flag
  fs: add TestRoMount
  posixtest: add DirSeek test, Go port of xfstests generic/257
  fs: bridge: implement simple directory seeking
  fs: cleanup after TestForget
parents ad6d7f35 c246ae51
......@@ -12,3 +12,5 @@ example/loopback/loopback
example/multizip/multizip
example/bulkstat/bulkstat
example/zipfs/zipfs
# test binaries
*.test
......@@ -46,6 +46,7 @@ func main() {
debug := flag.Bool("debug", false, "print debugging messages.")
other := flag.Bool("allow-other", false, "mount with -o allowother.")
quiet := flag.Bool("q", false, "quiet")
ro := flag.Bool("ro", false, "mount read-only")
cpuprofile := flag.String("cpuprofile", "", "write cpu profile to this file")
memprofile := flag.String("memprofile", "", "write memory profile to this file")
flag.Parse()
......@@ -100,6 +101,9 @@ func main() {
// Make the kernel check file permissions for us
opts.MountOptions.Options = append(opts.MountOptions.Options, "default_permissions")
}
if *ro {
opts.MountOptions.Options = append(opts.MountOptions.Options, "ro")
}
// First column in "df -T": original dir
opts.MountOptions.Options = append(opts.MountOptions.Options, "fsname="+orig)
// Second column in "df -T" will be shown as "fuse." + Name
......
......@@ -33,6 +33,12 @@ type fileEntry struct {
dirStream DirStream
hasOverflow bool
overflow fuse.DirEntry
// dirOffset is the current location in the directory (see `telldir(3)`).
// The value is equivalent to `d_off` (see `getdents(2)`) of the last
// directory entry sent to the kernel so far.
// If `dirOffset` and `fuse.DirEntryList.offset` disagree, then a
// directory seek has taken place.
dirOffset uint64
wg sync.WaitGroup
}
......@@ -842,23 +848,47 @@ func (b *rawBridge) OpenDir(cancel <-chan struct{}, input *fuse.OpenIn, out *fus
return fuse.OK
}
// setStream sets the directory part of f. Must hold f.mu
func (b *rawBridge) setStream(cancel <-chan struct{}, input *fuse.ReadIn, inode *Inode, f *fileEntry) syscall.Errno {
if f.dirStream == nil || input.Offset == 0 {
// setStream makes sure `f.dirStream` and associated state variables are set and
// seeks to offset requested in `input`. Caller must hold `f.mu`.
// The `eof` return value shows if `f.dirStream` ended before the requested
// offset was reached.
func (b *rawBridge) setStream(cancel <-chan struct{}, input *fuse.ReadIn, inode *Inode, f *fileEntry) (errno syscall.Errno, eof bool) {
// Get a new directory stream in the following cases:
// 1) f.dirStream == nil ............ First READDIR[PLUS] on this file handle.
// 2) input.Offset == 0 ............. Start reading the directory again from
// the beginning (user called rewinddir(3) or lseek(2)).
// 3) input.Offset < f.nextOffset ... Seek back (user called seekdir(3) or lseek(2)).
if f.dirStream == nil || input.Offset == 0 || input.Offset < f.dirOffset {
if f.dirStream != nil {
f.dirStream.Close()
f.dirStream = nil
}
str, errno := b.getStream(&fuse.Context{Caller: input.Caller, Cancel: cancel}, inode)
if errno != 0 {
return errno
return errno, false
}
f.dirOffset = 0
f.hasOverflow = false
f.dirStream = str
}
return 0
// Seek forward?
for f.dirOffset < input.Offset {
f.hasOverflow = false
if !f.dirStream.HasNext() {
// Seek past end of directory. This is not an error, but the
// user will get an empty directory listing.
return 0, true
}
_, errno := f.dirStream.Next()
if errno != 0 {
return errno, true
}
f.dirOffset++
}
return 0, false
}
func (b *rawBridge) getStream(ctx context.Context, inode *Inode) (DirStream, syscall.Errno) {
......@@ -880,14 +910,19 @@ func (b *rawBridge) ReadDir(cancel <-chan struct{}, input *fuse.ReadIn, out *fus
f.mu.Lock()
defer f.mu.Unlock()
if errno := b.setStream(cancel, input, n, f); errno != 0 {
errno, eof := b.setStream(cancel, input, n, f)
if errno != 0 {
return errnoToStatus(errno)
} else if eof {
return fuse.OK
}
if f.hasOverflow {
// always succeeds.
out.AddDirEntry(f.overflow)
f.hasOverflow = false
f.dirOffset++
}
for f.dirStream.HasNext() {
......@@ -901,6 +936,7 @@ func (b *rawBridge) ReadDir(cancel <-chan struct{}, input *fuse.ReadIn, out *fus
f.hasOverflow = true
return errnoToStatus(errno)
}
f.dirOffset++
}
return fuse.OK
......@@ -911,8 +947,12 @@ func (b *rawBridge) ReadDirPlus(cancel <-chan struct{}, input *fuse.ReadIn, out
f.mu.Lock()
defer f.mu.Unlock()
if errno := b.setStream(cancel, input, n, f); errno != 0 {
errno, eof := b.setStream(cancel, input, n, f)
if errno != 0 {
return errnoToStatus(errno)
} else if eof {
return fuse.OK
}
ctx := &fuse.Context{Caller: input.Caller, Cancel: cancel}
......@@ -937,6 +977,7 @@ func (b *rawBridge) ReadDirPlus(cancel <-chan struct{}, input *fuse.ReadIn, out
f.hasOverflow = true
return fuse.OK
}
f.dirOffset++
// Virtual entries "." and ".." should be part of the
// directory listing, but not part of the filesystem tree.
......
......@@ -83,13 +83,14 @@ func TestForget(t *testing.T) {
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
rawFS := NewNodeFS(root, options)
server, err := fuse.NewServer(rawFS, dir, &options.MountOptions)
if err != nil {
t.Fatal(err)
}
defer server.Unmount()
go server.Serve()
if err := server.WaitMount(); err != nil {
t.Fatal(err)
......
......@@ -380,3 +380,8 @@ func TestParallelDiropsHang(t *testing.T) {
wg.Wait()
server.Unmount()
}
func TestRoMount(t *testing.T) {
tc := newTestCase(t, &testOptions{ro: true})
defer tc.Clean()
}
......@@ -53,6 +53,7 @@ type testOptions struct {
attrCache bool
suppressDebug bool
testDir string
ro bool
}
// newTestCase creates the directories `orig` and `mnt` inside a temporary
......@@ -102,6 +103,9 @@ func newTestCase(t *testing.T, opts *testOptions) *testCase {
if !opts.suppressDebug {
mOpts.Debug = testutil.VerboseTest()
}
if opts.ro {
mOpts.Options = append(mOpts.Options, "ro")
}
tc.server, err = fuse.NewServer(tc.rawFS, tc.mntDir, mOpts)
if err != nil {
t.Fatal(err)
......
......@@ -37,9 +37,16 @@ func (d DirEntry) String() string {
// opcodes.
type DirEntryList struct {
buf []byte
size int // capacity of the underlying buffer
offset uint64 // entry count (NOT a byte offset)
lastDirent *_Dirent // pointer to the last serialized _Dirent. Used by FixMode().
// capacity of the underlying buffer
size int
// offset is the requested location in the directory. go-fuse
// currently counts in number of directory entries, but this is an
// implementation detail and may change in the future.
// If `offset` and `fs.fileEntry.dirOffset` disagree, then a
// directory seek has taken place.
offset uint64
// pointer to the last serialized _Dirent. Used by FixMode().
lastDirent *_Dirent
}
// NewDirEntryList creates a DirEntryList with the given data buffer
......
......@@ -15,24 +15,19 @@ func doPollHackLookup(ms *Server, req *request) {
Nlink: 1,
}
switch req.inHeader.Opcode {
case _OP_CREATE:
out := (*CreateOut)(req.outData())
out.EntryOut = EntryOut{
case _OP_LOOKUP:
out := (*EntryOut)(req.outData())
*out = EntryOut{
NodeId: pollHackInode,
Attr: attr,
}
out.OpenOut = OpenOut{
req.status = OK
case _OP_OPEN:
out := (*OpenOut)(req.outData())
*out = OpenOut{
Fh: pollHackInode,
}
req.status = OK
case _OP_LOOKUP:
out := (*EntryOut)(req.outData())
*out = EntryOut{}
req.status = ENOENT
case _OP_GETATTR:
out := (*AttrOut)(req.outData())
out.Attr = attr
req.status = OK
case _OP_POLL:
req.status = ENOSYS
default:
......
......@@ -8,7 +8,7 @@ import (
)
func pollHack(mountPoint string) error {
fd, err := syscall.Creat(filepath.Join(mountPoint, pollHackName), syscall.O_CREAT)
fd, err := syscall.Open(filepath.Join(mountPoint, pollHackName), syscall.O_RDONLY, 0)
if err != nil {
return err
}
......
......@@ -90,14 +90,19 @@ func (r *request) InputDebug() string {
names = fmt.Sprintf("%q", r.filenames)
}
if len(r.arg) > 0 {
l := len(r.arg)
s := ""
if l := len(r.arg); l > 0 {
data := ""
if len(r.filenames) == 0 {
dots := ""
if l > debugDataDumpMax {
l = debugDataDumpMax
s = "..."
dots = "..."
}
names += fmt.Sprintf(" %db %q%s", len(r.arg), r.arg[:l], s)
data = fmt.Sprintf("%q%s", r.arg[:l], dots)
}
names += fmt.Sprintf("%s %db", data, len(r.arg))
}
return fmt.Sprintf("rx %d: %s i%d %s%s",
......
......@@ -40,6 +40,7 @@ var All = map[string]func(*testing.T, string){
"DirectIO": DirectIO,
"OpenAt": OpenAt,
"Fallocate": Fallocate,
"DirSeek": DirSeek,
}
func DirectIO(t *testing.T, mnt string) {
......
package posixtest
import (
"fmt"
"io/ioutil"
"os"
"syscall"
"testing"
"unsafe"
"golang.org/x/sys/unix"
)
// DirSeek tests that seeking on a directory works for
// https://github.com/hanwen/go-fuse/issues/344 .
//
// Go port of xfstests generic/257.
//
// Hint:
// $ go test ./fs -run TestPosix/DirSeek -v
func DirSeek(t *testing.T, mnt string) {
// From bash script xfstests/tests/generic/257
ttt := mnt + "/ttt"
err := os.Mkdir(ttt, 0700)
if err != nil {
t.Fatal(err)
}
for i := 1; i <= 168; i++ {
path := fmt.Sprintf("%s/%d", ttt, i)
err = ioutil.WriteFile(path, nil, 0600)
if err != nil {
t.Fatal(err)
}
}
// From C app xfstests/src/t_dir_offset2.c
const bufSize = 4096
const historyLen = 1024
offHistory := make([]int64, historyLen)
inoHistory := make([]uint64, historyLen)
buf := make([]byte, bufSize)
fd, err := syscall.Open(ttt, syscall.O_RDONLY|syscall.O_DIRECTORY, 0)
if err != nil {
t.Fatal(err)
}
defer syscall.Close(fd)
total := 0
for {
n, err := unix.Getdents(fd, buf)
if err != nil {
t.Fatal(err)
}
if n == 0 {
break
}
for bpos := 0; bpos < n; total++ {
d := (*unix.Dirent)(unsafe.Pointer(&buf[bpos]))
if total > historyLen {
t.Fatal("too many files")
}
for i := 0; i < total; i++ {
if offHistory[i] == d.Off {
t.Errorf("entries %d and %d gave duplicate d.Off %d",
i, total, d.Off)
}
}
offHistory[total] = d.Off
inoHistory[total] = d.Ino
bpos += int(d.Reclen)
}
}
// check if seek works correctly
d := (*unix.Dirent)(unsafe.Pointer(&buf[0]))
for i := total - 1; i >= 0; i-- {
var seekTo int64
if i > 0 {
seekTo = offHistory[i-1]
}
_, err = unix.Seek(fd, seekTo, os.SEEK_SET)
if err != nil {
t.Fatal(err)
}
n, err := unix.Getdents(fd, buf)
if err != nil {
t.Fatal(err)
}
if n == 0 {
t.Errorf("getdents returned 0 on entry %d", i)
continue
}
if d.Ino != inoHistory[i] {
t.Errorf("entry %d has inode %d, expected %d",
i, d.Ino, inoHistory[i])
}
}
}
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