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 ...@@ -12,3 +12,5 @@ example/loopback/loopback
example/multizip/multizip example/multizip/multizip
example/bulkstat/bulkstat example/bulkstat/bulkstat
example/zipfs/zipfs example/zipfs/zipfs
# test binaries
*.test
...@@ -46,6 +46,7 @@ func main() { ...@@ -46,6 +46,7 @@ func main() {
debug := flag.Bool("debug", false, "print debugging messages.") debug := flag.Bool("debug", false, "print debugging messages.")
other := flag.Bool("allow-other", false, "mount with -o allowother.") other := flag.Bool("allow-other", false, "mount with -o allowother.")
quiet := flag.Bool("q", false, "quiet") quiet := flag.Bool("q", false, "quiet")
ro := flag.Bool("ro", false, "mount read-only")
cpuprofile := flag.String("cpuprofile", "", "write cpu profile to this file") cpuprofile := flag.String("cpuprofile", "", "write cpu profile to this file")
memprofile := flag.String("memprofile", "", "write memory profile to this file") memprofile := flag.String("memprofile", "", "write memory profile to this file")
flag.Parse() flag.Parse()
...@@ -100,6 +101,9 @@ func main() { ...@@ -100,6 +101,9 @@ func main() {
// Make the kernel check file permissions for us // Make the kernel check file permissions for us
opts.MountOptions.Options = append(opts.MountOptions.Options, "default_permissions") 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 // First column in "df -T": original dir
opts.MountOptions.Options = append(opts.MountOptions.Options, "fsname="+orig) opts.MountOptions.Options = append(opts.MountOptions.Options, "fsname="+orig)
// Second column in "df -T" will be shown as "fuse." + Name // Second column in "df -T" will be shown as "fuse." + Name
......
...@@ -33,6 +33,12 @@ type fileEntry struct { ...@@ -33,6 +33,12 @@ type fileEntry struct {
dirStream DirStream dirStream DirStream
hasOverflow bool hasOverflow bool
overflow fuse.DirEntry 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 wg sync.WaitGroup
} }
...@@ -842,23 +848,47 @@ func (b *rawBridge) OpenDir(cancel <-chan struct{}, input *fuse.OpenIn, out *fus ...@@ -842,23 +848,47 @@ func (b *rawBridge) OpenDir(cancel <-chan struct{}, input *fuse.OpenIn, out *fus
return fuse.OK return fuse.OK
} }
// setStream sets the directory part of f. Must hold f.mu // setStream makes sure `f.dirStream` and associated state variables are set and
func (b *rawBridge) setStream(cancel <-chan struct{}, input *fuse.ReadIn, inode *Inode, f *fileEntry) syscall.Errno { // seeks to offset requested in `input`. Caller must hold `f.mu`.
if f.dirStream == nil || input.Offset == 0 { // 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 { if f.dirStream != nil {
f.dirStream.Close() f.dirStream.Close()
f.dirStream = nil f.dirStream = nil
} }
str, errno := b.getStream(&fuse.Context{Caller: input.Caller, Cancel: cancel}, inode) str, errno := b.getStream(&fuse.Context{Caller: input.Caller, Cancel: cancel}, inode)
if errno != 0 { if errno != 0 {
return errno return errno, false
} }
f.dirOffset = 0
f.hasOverflow = false f.hasOverflow = false
f.dirStream = str 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) { 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 ...@@ -880,14 +910,19 @@ func (b *rawBridge) ReadDir(cancel <-chan struct{}, input *fuse.ReadIn, out *fus
f.mu.Lock() f.mu.Lock()
defer f.mu.Unlock() 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) return errnoToStatus(errno)
} else if eof {
return fuse.OK
} }
if f.hasOverflow { if f.hasOverflow {
// always succeeds. // always succeeds.
out.AddDirEntry(f.overflow) out.AddDirEntry(f.overflow)
f.hasOverflow = false f.hasOverflow = false
f.dirOffset++
} }
for f.dirStream.HasNext() { for f.dirStream.HasNext() {
...@@ -901,6 +936,7 @@ func (b *rawBridge) ReadDir(cancel <-chan struct{}, input *fuse.ReadIn, out *fus ...@@ -901,6 +936,7 @@ func (b *rawBridge) ReadDir(cancel <-chan struct{}, input *fuse.ReadIn, out *fus
f.hasOverflow = true f.hasOverflow = true
return errnoToStatus(errno) return errnoToStatus(errno)
} }
f.dirOffset++
} }
return fuse.OK return fuse.OK
...@@ -911,8 +947,12 @@ func (b *rawBridge) ReadDirPlus(cancel <-chan struct{}, input *fuse.ReadIn, out ...@@ -911,8 +947,12 @@ func (b *rawBridge) ReadDirPlus(cancel <-chan struct{}, input *fuse.ReadIn, out
f.mu.Lock() f.mu.Lock()
defer f.mu.Unlock() 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) return errnoToStatus(errno)
} else if eof {
return fuse.OK
} }
ctx := &fuse.Context{Caller: input.Caller, Cancel: cancel} ctx := &fuse.Context{Caller: input.Caller, Cancel: cancel}
...@@ -937,6 +977,7 @@ func (b *rawBridge) ReadDirPlus(cancel <-chan struct{}, input *fuse.ReadIn, out ...@@ -937,6 +977,7 @@ func (b *rawBridge) ReadDirPlus(cancel <-chan struct{}, input *fuse.ReadIn, out
f.hasOverflow = true f.hasOverflow = true
return fuse.OK return fuse.OK
} }
f.dirOffset++
// Virtual entries "." and ".." should be part of the // Virtual entries "." and ".." should be part of the
// directory listing, but not part of the filesystem tree. // directory listing, but not part of the filesystem tree.
......
...@@ -83,13 +83,14 @@ func TestForget(t *testing.T) { ...@@ -83,13 +83,14 @@ func TestForget(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer os.RemoveAll(dir)
rawFS := NewNodeFS(root, options) rawFS := NewNodeFS(root, options)
server, err := fuse.NewServer(rawFS, dir, &options.MountOptions) server, err := fuse.NewServer(rawFS, dir, &options.MountOptions)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer server.Unmount()
go server.Serve() go server.Serve()
if err := server.WaitMount(); err != nil { if err := server.WaitMount(); err != nil {
t.Fatal(err) t.Fatal(err)
......
...@@ -380,3 +380,8 @@ func TestParallelDiropsHang(t *testing.T) { ...@@ -380,3 +380,8 @@ func TestParallelDiropsHang(t *testing.T) {
wg.Wait() wg.Wait()
server.Unmount() server.Unmount()
} }
func TestRoMount(t *testing.T) {
tc := newTestCase(t, &testOptions{ro: true})
defer tc.Clean()
}
...@@ -53,6 +53,7 @@ type testOptions struct { ...@@ -53,6 +53,7 @@ type testOptions struct {
attrCache bool attrCache bool
suppressDebug bool suppressDebug bool
testDir string testDir string
ro bool
} }
// newTestCase creates the directories `orig` and `mnt` inside a temporary // newTestCase creates the directories `orig` and `mnt` inside a temporary
...@@ -102,6 +103,9 @@ func newTestCase(t *testing.T, opts *testOptions) *testCase { ...@@ -102,6 +103,9 @@ func newTestCase(t *testing.T, opts *testOptions) *testCase {
if !opts.suppressDebug { if !opts.suppressDebug {
mOpts.Debug = testutil.VerboseTest() mOpts.Debug = testutil.VerboseTest()
} }
if opts.ro {
mOpts.Options = append(mOpts.Options, "ro")
}
tc.server, err = fuse.NewServer(tc.rawFS, tc.mntDir, mOpts) tc.server, err = fuse.NewServer(tc.rawFS, tc.mntDir, mOpts)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
......
...@@ -36,10 +36,17 @@ func (d DirEntry) String() string { ...@@ -36,10 +36,17 @@ func (d DirEntry) String() string {
// DirEntryList holds the return value for READDIR and READDIRPLUS // DirEntryList holds the return value for READDIR and READDIRPLUS
// opcodes. // opcodes.
type DirEntryList struct { type DirEntryList struct {
buf []byte buf []byte
size int // capacity of the underlying buffer // capacity of the underlying buffer
offset uint64 // entry count (NOT a byte offset) size int
lastDirent *_Dirent // pointer to the last serialized _Dirent. Used by FixMode(). // 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 // NewDirEntryList creates a DirEntryList with the given data buffer
......
...@@ -15,24 +15,19 @@ func doPollHackLookup(ms *Server, req *request) { ...@@ -15,24 +15,19 @@ func doPollHackLookup(ms *Server, req *request) {
Nlink: 1, Nlink: 1,
} }
switch req.inHeader.Opcode { switch req.inHeader.Opcode {
case _OP_CREATE: case _OP_LOOKUP:
out := (*CreateOut)(req.outData()) out := (*EntryOut)(req.outData())
out.EntryOut = EntryOut{ *out = EntryOut{
NodeId: pollHackInode, NodeId: pollHackInode,
Attr: attr, Attr: attr,
} }
out.OpenOut = OpenOut{ req.status = OK
case _OP_OPEN:
out := (*OpenOut)(req.outData())
*out = OpenOut{
Fh: pollHackInode, Fh: pollHackInode,
} }
req.status = OK 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: case _OP_POLL:
req.status = ENOSYS req.status = ENOSYS
default: default:
......
...@@ -8,7 +8,7 @@ import ( ...@@ -8,7 +8,7 @@ import (
) )
func pollHack(mountPoint string) error { 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 { if err != nil {
return err return err
} }
......
...@@ -90,14 +90,19 @@ func (r *request) InputDebug() string { ...@@ -90,14 +90,19 @@ func (r *request) InputDebug() string {
names = fmt.Sprintf("%q", r.filenames) names = fmt.Sprintf("%q", r.filenames)
} }
if len(r.arg) > 0 { if l := len(r.arg); l > 0 {
l := len(r.arg) data := ""
s := "" if len(r.filenames) == 0 {
if l > debugDataDumpMax { dots := ""
l = debugDataDumpMax if l > debugDataDumpMax {
s = "..." l = debugDataDumpMax
dots = "..."
}
data = fmt.Sprintf("%q%s", r.arg[:l], dots)
} }
names += fmt.Sprintf(" %db %q%s", len(r.arg), r.arg[:l], s)
names += fmt.Sprintf("%s %db", data, len(r.arg))
} }
return fmt.Sprintf("rx %d: %s i%d %s%s", return fmt.Sprintf("rx %d: %s i%d %s%s",
......
...@@ -40,6 +40,7 @@ var All = map[string]func(*testing.T, string){ ...@@ -40,6 +40,7 @@ var All = map[string]func(*testing.T, string){
"DirectIO": DirectIO, "DirectIO": DirectIO,
"OpenAt": OpenAt, "OpenAt": OpenAt,
"Fallocate": Fallocate, "Fallocate": Fallocate,
"DirSeek": DirSeek,
} }
func DirectIO(t *testing.T, mnt string) { 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