Rejiggered the ReadFile and ReadDir API.

The new API lends itself toward reading directly into the output buffer, saving
a memmove and allowing for easy reuse of output buffers.
geesefs-0-30-9
Aaron Jacobs 2015-07-29 10:24:11 +10:00
commit 4fd46371a2
7 changed files with 83 additions and 67 deletions

View File

@ -252,7 +252,7 @@ func convertInMessage(
Inode: fuseops.InodeID(m.Header().Nodeid), Inode: fuseops.InodeID(m.Header().Nodeid),
Handle: fuseops.HandleID(in.Fh), Handle: fuseops.HandleID(in.Fh),
Offset: int64(in.Offset), Offset: int64(in.Offset),
Size: int(in.Size), Dst: make([]byte, in.Size),
} }
case fusekernel.OpReaddir: case fusekernel.OpReaddir:
@ -266,7 +266,7 @@ func convertInMessage(
Inode: fuseops.InodeID(m.Header().Nodeid), Inode: fuseops.InodeID(m.Header().Nodeid),
Handle: fuseops.HandleID(in.Fh), Handle: fuseops.HandleID(in.Fh),
Offset: fuseops.DirOffset(in.Offset), Offset: fuseops.DirOffset(in.Offset),
Size: int(in.Size), Dst: make([]byte, in.Size),
} }
case fusekernel.OpRelease: case fusekernel.OpRelease:
@ -486,7 +486,7 @@ func (c *Connection) kernelResponseForOp(
case *fuseops.ReadDirOp: case *fuseops.ReadDirOp:
m = c.getOutMessage() m = c.getOutMessage()
m.Append(o.Data) m.Append(o.Dst[:o.BytesRead])
case *fuseops.ReleaseDirHandleOp: case *fuseops.ReleaseDirHandleOp:
m = c.getOutMessage() m = c.getOutMessage()
@ -498,7 +498,7 @@ func (c *Connection) kernelResponseForOp(
case *fuseops.ReadFileOp: case *fuseops.ReadFileOp:
m = c.getOutMessage() m = c.getOutMessage()
m.Append(o.Data) m.Append(o.Dst[:o.BytesRead])
case *fuseops.WriteFileOp: case *fuseops.WriteFileOp:
m = c.getOutMessage() m = c.getOutMessage()

View File

@ -384,25 +384,29 @@ type ReadDirOp struct {
// offset, and return array offsets into that cached listing. // offset, and return array offsets into that cached listing.
Offset DirOffset Offset DirOffset
// The maximum number of bytes to return in ReadDirResponse.Data. A smaller // The destination buffer, whose length gives the size of the read.
// number is acceptable.
Size int
// Set by the file system: a buffer consisting of a sequence of FUSE
// directory entries in the format generated by fuse_add_direntry
// (http://goo.gl/qCcHCV), which is consumed by parse_dirfile
// (http://goo.gl/2WUmD2). Use fuseutil.AppendDirent to generate this data.
// //
// The buffer must not exceed the length specified in ReadDirRequest.Size. It // The output data should consist of a sequence of FUSE directory entries in
// is okay for the final entry to be truncated; parse_dirfile copes with this // the format generated by fuse_add_direntry (http://goo.gl/qCcHCV), which is
// by ignoring the partial record. // consumed by parse_dirfile (http://goo.gl/2WUmD2). Use fuseutil.WriteDirent
// to generate this data.
// //
// Each entry returned exposes a directory offset to the user that may later // Each entry returned exposes a directory offset to the user that may later
// show up in ReadDirRequest.Offset. See notes on that field for more // show up in ReadDirRequest.Offset. See notes on that field for more
// information. // information.
Dst []byte
// Set by the file system: the number of bytes read into Dst.
// //
// An empty buffer indicates the end of the directory has been reached. // It is okay for this to be less than len(Dst) if there are not enough
Data []byte // entries available or the final entry would not fit.
//
// Zero means that the end of the directory has been reached. This is
// unambiguous because NAME_MAX (https://goo.gl/ZxzKaE) plus the size of
// fuse_dirent (https://goo.gl/WO8s3F) plus the 8-byte alignment of
// FUSE_DIRENT_ALIGN (http://goo.gl/UziWvH) is less than the read size of
// PAGE_SIZE used by fuse_readdir (cf. https://goo.gl/VajtS2).
BytesRead int
} }
// Release a previously-minted directory handle. The kernel sends this when // Release a previously-minted directory handle. The kernel sends this when
@ -455,20 +459,21 @@ type ReadFileOp struct {
Inode InodeID Inode InodeID
Handle HandleID Handle HandleID
// The range of the file to read. // The offset within the file at which to read.
Offset int64
// The destination buffer, whose length gives the size of the read.
Dst []byte
// Set by the file system: the number of bytes read.
// //
// The FUSE documentation requires that exactly the number of bytes be // The FUSE documentation requires that exactly the requested number of bytes
// returned, except in the case of EOF or error (http://goo.gl/ZgfBkF). This // be returned, except in the case of EOF or error (http://goo.gl/ZgfBkF).
// appears to be because it uses file mmapping machinery // This appears to be because it uses file mmapping machinery
// (http://goo.gl/SGxnaN) to read a page at a time. It appears to understand // (http://goo.gl/SGxnaN) to read a page at a time. It appears to understand
// where EOF is by checking the inode size (http://goo.gl/0BkqKD), returned // where EOF is by checking the inode size (http://goo.gl/0BkqKD), returned
// by a previous call to LookUpInode, GetInodeAttributes, etc. // by a previous call to LookUpInode, GetInodeAttributes, etc.
Offset int64 BytesRead int
Size int
// Set by the file system: the data read. If this is less than the requested
// size, it indicates EOF. An error should not be returned in this case.
Data []byte
} }
// Write data to a file previously opened with CreateFile or OpenFile. // Write data to a file previously opened with CreateFile or OpenFile.

View File

@ -35,7 +35,7 @@ const (
) )
// A struct representing an entry within a directory file, describing a child. // A struct representing an entry within a directory file, describing a child.
// See notes on fuseops.ReadDirOp and on AppendDirent for details. // See notes on fuseops.ReadDirOp and on WriteDirent for details.
type Dirent struct { type Dirent struct {
// The (opaque) offset within the directory file of the entry following this // The (opaque) offset within the directory file of the entry following this
// one. See notes on fuseops.ReadDirOp.Offset for details. // one. See notes on fuseops.ReadDirOp.Offset for details.
@ -50,10 +50,11 @@ type Dirent struct {
Type DirentType Type DirentType
} }
// Append the supplied directory entry to the given buffer in the format // Write the supplied directory entry intto the given buffer in the format
// expected in fuseops.ReadFileOp.Data, returning the resulting buffer. // expected in fuseops.ReadFileOp.Data, returning the number of bytes written.
func AppendDirent(input []byte, d Dirent) (output []byte) { // Return zero if the entry would not fit.
// We want to append bytes with the layout of fuse_dirent func WriteDirent(buf []byte, d Dirent) (n int) {
// We want to write bytes with the layout of fuse_dirent
// (http://goo.gl/BmFxob) in host order. The struct must be aligned according // (http://goo.gl/BmFxob) in host order. The struct must be aligned according
// to FUSE_DIRENT_ALIGN (http://goo.gl/UziWvH), which dictates 8-byte // to FUSE_DIRENT_ALIGN (http://goo.gl/UziWvH), which dictates 8-byte
// alignment. // alignment.
@ -65,10 +66,23 @@ func AppendDirent(input []byte, d Dirent) (output []byte) {
name [0]byte name [0]byte
} }
const alignment = 8 const direntAlignment = 8
const nameOffset = 8 + 8 + 4 + 4 const direntSize = 8 + 8 + 4 + 4
// Write the header into the buffer. // Compute the number of bytes of padding we'll need to maintain alignment
// for the next entry.
var padLen int
if len(d.Name)%direntAlignment != 0 {
padLen = direntAlignment - (len(d.Name) % direntAlignment)
}
// Do we have enough room?
totalLen := direntSize + len(d.Name) + padLen
if totalLen > len(buf) {
return
}
// Write the header.
de := fuse_dirent{ de := fuse_dirent{
ino: uint64(d.Inode), ino: uint64(d.Inode),
off: uint64(d.Offset), off: uint64(d.Offset),
@ -76,17 +90,15 @@ func AppendDirent(input []byte, d Dirent) (output []byte) {
type_: uint32(d.Type), type_: uint32(d.Type),
} }
output = append(input, (*[nameOffset]byte)(unsafe.Pointer(&de))[:]...) n += copy(buf[n:], (*[direntSize]byte)(unsafe.Pointer(&de))[:])
// Write the name afterward. // Write the name afterward.
output = append(output, d.Name...) n += copy(buf[n:], d.Name)
// Add any necessary padding. // Add any necessary padding.
if len(d.Name)%alignment != 0 { if padLen != 0 {
padLen := alignment - (len(d.Name) % alignment) var padding [direntAlignment]byte
n += copy(buf[n:], padding[:padLen])
var padding [alignment]byte
output = append(output, padding[:padLen]...)
} }
return return

View File

@ -180,8 +180,7 @@ func (fs *flushFS) ReadFile(
} }
// Read what we can. // Read what we can.
op.Data = make([]byte, op.Size) op.BytesRead = copy(op.Dst, fs.fooContents[op.Offset:])
copy(op.Data, fs.fooContents[op.Offset:])
return return
} }
@ -298,13 +297,15 @@ func (fs *flushFS) ReadDir(
// Fill in the listing. // Fill in the listing.
for _, de := range dirents { for _, de := range dirents {
op.Data = fuseutil.AppendDirent(op.Data, de) n := fuseutil.WriteDirent(op.Dst[op.BytesRead:], de)
}
// We don't support doing this in anything more than one shot. // We don't support doing this in anything more than one shot.
if len(op.Data) > op.Size { if n == 0 {
err = fmt.Errorf("Couldn't fit listing in %v bytes", op.Size) err = fmt.Errorf("Couldn't fit listing in %v bytes", len(op.Dst))
return return
}
op.BytesRead += n
} }
return return

View File

@ -228,11 +228,12 @@ func (fs *helloFS) ReadDir(
// Resume at the specified offset into the array. // Resume at the specified offset into the array.
for _, e := range entries { for _, e := range entries {
op.Data = fuseutil.AppendDirent(op.Data, e) n := fuseutil.WriteDirent(op.Dst[op.BytesRead:], e)
if len(op.Data) > op.Size { if n == 0 {
op.Data = op.Data[:op.Size]
break break
} }
op.BytesRead += n
} }
return return
@ -251,9 +252,7 @@ func (fs *helloFS) ReadFile(
// Let io.ReaderAt deal with the semantics. // Let io.ReaderAt deal with the semantics.
reader := strings.NewReader("Hello, world!") reader := strings.NewReader("Hello, world!")
op.Data = make([]byte, op.Size) op.BytesRead, err = reader.ReadAt(op.Dst, op.Offset)
n, err := reader.ReadAt(op.Data, op.Offset)
op.Data = op.Data[:n]
// Special case: FUSE doesn't expect us to return io.EOF. // Special case: FUSE doesn't expect us to return io.EOF.
if err == io.EOF { if err == io.EOF {

View File

@ -278,7 +278,7 @@ func (in *inode) RemoveChild(name string) {
// Serve a ReadDir request. // Serve a ReadDir request.
// //
// REQUIRES: in.isDir() // REQUIRES: in.isDir()
func (in *inode) ReadDir(offset int, size int) (data []byte) { func (in *inode) ReadDir(p []byte, offset int) (n int) {
if !in.isDir() { if !in.isDir() {
panic("ReadDir called on non-directory.") panic("ReadDir called on non-directory.")
} }
@ -291,13 +291,12 @@ func (in *inode) ReadDir(offset int, size int) (data []byte) {
continue continue
} }
data = fuseutil.AppendDirent(data, in.entries[i]) tmp := fuseutil.WriteDirent(p[n:], in.entries[i])
if tmp == 0 {
// Trim and stop early if we've exceeded the requested size.
if len(data) > size {
data = data[:size]
break break
} }
n += tmp
} }
return return

View File

@ -428,7 +428,9 @@ func (fs *memFS) Rename(
existingID, _, ok := newParent.LookUpChild(op.NewName) existingID, _, ok := newParent.LookUpChild(op.NewName)
if ok { if ok {
existing := fs.getInodeOrDie(existingID) existing := fs.getInodeOrDie(existingID)
if existing.isDir() && len(existing.ReadDir(0, 1024)) > 0 {
var buf [4096]byte
if existing.isDir() && existing.ReadDir(buf[:], 0) > 0 {
err = fuse.ENOTEMPTY err = fuse.ENOTEMPTY
return return
} }
@ -538,7 +540,7 @@ func (fs *memFS) ReadDir(
inode := fs.getInodeOrDie(op.Inode) inode := fs.getInodeOrDie(op.Inode)
// Serve the request. // Serve the request.
op.Data = inode.ReadDir(int(op.Offset), op.Size) op.BytesRead = inode.ReadDir(op.Dst, int(op.Offset))
return return
} }
@ -571,9 +573,7 @@ func (fs *memFS) ReadFile(
inode := fs.getInodeOrDie(op.Inode) inode := fs.getInodeOrDie(op.Inode)
// Serve the request. // Serve the request.
op.Data = make([]byte, op.Size) op.BytesRead, err = inode.ReadAt(op.Dst, op.Offset)
n, err := inode.ReadAt(op.Data, op.Offset)
op.Data = op.Data[:n]
// Don't return EOF errors; we just indicate EOF to fuse using a short read. // Don't return EOF errors; we just indicate EOF to fuse using a short read.
if err == io.EOF { if err == io.EOF {