Compare commits

..

6 Commits

Author SHA1 Message Date
Vitaliy Filippov a7dcac672f Fix RenameOp compatibility with macfuse 4.x
Closed-source macfuse 4.x has broken compatibility with osxfuse 3.x:
it passes an additional 64-bit field (flags) after RenameIn regardless
that we don't enable the support for RENAME_SWAP/RENAME_EXCL.

The simplest fix is just to check for the presence of all-zero flags
and strip them when they're present.
2021-10-08 17:26:53 +03:00
Vitaliy Filippov ec521aa7b7 Use newer mounting method (similar to fusermount) with macfuse 4.x
macfuse 4.x turns out to be incompatible with the old mounting method where
you open the device by yourself and only supports the newer method where you
receive a file descriptor from `mount_macfuse` through a unix socket.
2021-09-30 18:31:06 +03:00
Vitaliy Filippov 575b70f3fd Allow to use "zero-copy" writes 2021-09-01 14:14:51 +03:00
Vitaliy Filippov 513d4815ce Add vectored read to readbenchfs
You can now run `./readbenchfs --mount_point dir --vectored` and then
`dd if=dir/test of=/dev/null iflag=direct bs=1M status=progress` to test
vectored read speed.

My results with GOMAXPROCS=1:
- Before vectored read patch: 390 MB/s read
- Non-vectored read after vectored read patch: 830 MB/s read
- Vectored read: 1200 MB/s read
2021-09-01 14:14:15 +03:00
Vitaliy Filippov fcabfc89e9 Introduce VectoredReadOp
Read requests can now take vectored responses from the filesystem
implementation and send them to FUSE device via the writev() system call.

This allows file systems to send data without copying it into the
library-provided buffer if the data is already in memory.

The change also speeds up normal ReadFileOps as a side effect because
it removes extra memory allocations.
2021-09-01 14:14:15 +03:00
Vitaliy Filippov f1a2d0d300 Add ReadBenchFS to test linear read speed 2021-08-31 13:15:55 +03:00
45 changed files with 660 additions and 1259 deletions

View File

@ -1,67 +0,0 @@
name: ci
on:
push:
branches:
- master
pull_request:
branches:
- '*'
jobs:
gofmt:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- name: Set up Go
uses: actions/setup-go@v2.1.4
with:
go-version: ^1.19
id: go
# Check the codebase has been formatted by `gofmt`.
# We run two `gofmt` commands for different purposes:
# 1. `gofmt -d .`: to print diffs in the CI log if any.
# 2. `gofmt -l`: to test there is a diff in the codebase. This is because
# `gofmt` exits with exit status 0 even if there is a diff.
# This is recommended in https://github.com/golang/go/issues/24230)
- name: Run gofmt
run: |
gofmt -d .
test -z $(gofmt -l .)
linux-tests:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- name: Set up Go
uses: actions/setup-go@v2.1.4
with:
go-version: ^1.19
id: go
- name: Install fuse
run: sudo apt-get update && sudo apt-get install -y fuse3 libfuse-dev
- name: Build
run: go build ./...
# Disabled running `go test` because running tests hung at random,
# preventing us from running the tests in CI reliably.
# (cf. https://github.com/jacobsa/fuse/issues/97)
macos-build:
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
- name: Set up Go
uses: actions/setup-go@v2.1.4
with:
go-version: ^1.19
id: go
- name: Install macfuse
run: HOMEBREW_NO_AUTO_UPDATE=1 brew install macfuse
- name: Build
run: |
go build ./...
go build ./samples/mount_hello/... ./samples/mount_roloopbackfs/... ./samples/mount_sample/...
# Skip running tests as `go test` hung in macOS.

1
.gitignore vendored
View File

@ -7,7 +7,6 @@
*.so
# Folders
.idea/
_obj
_test

35
.travis.yml Normal file
View File

@ -0,0 +1,35 @@
# Cf. http://docs.travis-ci.com/user/getting-started/
# Cf. http://docs.travis-ci.com/user/languages/go/
matrix:
include:
- os: linux
language: go
go: 1.16
# Use the virtualized Trusty beta Travis is running in order to get
# support for installing fuse.
#
# Cf. Personal communication from support@travis-ci.com.
dist: trusty
sudo: required
- os: osx
# Pin to macOS 10.12 (indirectly by using the Xcode 8.3 image). We must
# do this to get the OSXFUSE kernel extension to work because there
# currently is no know way to programmatically grant permissions to load
# a kernel extension in macOS 10.13.
#
# See https://github.com/travis-ci/travis-ci/issues/10017 for details.
osx_image: xcode8.3
language: go
go: 1.16
# Install fuse before installing our code.
before_install:
# For linux: install fuse.
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
sudo apt-get install -qq fuse;
fi
# For macOS: update homebrew and then install osxfuse.
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update; fi
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew cask install osxfuse; fi

View File

@ -1,4 +1,3 @@
[![ci](https://github.com/jacobsa/fuse/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/jacobsa/fuse/actions/workflows/ci.yml)
[![GoDoc](https://godoc.org/github.com/jacobsa/ogletest?status.svg)](https://godoc.org/github.com/jacobsa/fuse)
This package allows for writing and mounting user-space file systems from Go.

View File

@ -39,17 +39,17 @@ var contextKey interface{} = contextKeyType(0)
//
// As of 2015-03-26, the behavior in the kernel is:
//
// - (http://goo.gl/bQ1f1i, http://goo.gl/HwBrR6) Set the local variable
// * (http://goo.gl/bQ1f1i, http://goo.gl/HwBrR6) Set the local variable
// ra_pages to be init_response->max_readahead divided by the page size.
//
// - (http://goo.gl/gcIsSh, http://goo.gl/LKV2vA) Set
// * (http://goo.gl/gcIsSh, http://goo.gl/LKV2vA) Set
// backing_dev_info::ra_pages to the min of that value and what was sent
// in the request's max_readahead field.
//
// - (http://goo.gl/u2SqzH) Use backing_dev_info::ra_pages when deciding
// * (http://goo.gl/u2SqzH) Use backing_dev_info::ra_pages when deciding
// how much to read ahead.
//
// - (http://goo.gl/JnhbdL) Don't read ahead at all if that field is zero.
// * (http://goo.gl/JnhbdL) Don't read ahead at all if that field is zero.
//
// Reading a page at a time is a drag. Ask for a larger size.
const maxReadahead = 1 << 20
@ -151,7 +151,6 @@ func (c *Connection) Init() error {
cacheSymlinks := initOp.Flags&fusekernel.InitCacheSymlinks > 0
noOpenSupport := initOp.Flags&fusekernel.InitNoOpenSupport > 0
noOpendirSupport := initOp.Flags&fusekernel.InitNoOpendirSupport > 0
readdirplusSupport := initOp.Flags&fusekernel.InitDoReaddirplus > 0
// Respond to the init op.
initOp.Library = c.protocol
@ -162,11 +161,6 @@ func (c *Connection) Init() error {
// Tell the kernel not to use pitifully small 4 KiB writes.
initOp.Flags |= fusekernel.InitBigWrites
if c.cfg.EnableAsyncReads {
initOp.Flags |= fusekernel.InitAsyncRead
}
// kernel 4.20 increases the max from 32 -> 256
initOp.Flags |= fusekernel.InitMaxPages
initOp.MaxPages = 256
@ -194,11 +188,6 @@ func (c *Connection) Init() error {
initOp.Flags |= fusekernel.InitNoOpendirSupport
}
// Tell the kernel to do readdirplus (readdir+lookup in one call)
if c.cfg.UseReadDirPlus && readdirplusSupport {
initOp.Flags |= fusekernel.InitDoReaddirplus
}
c.Reply(ctx, nil)
return nil
}
@ -375,24 +364,18 @@ func (c *Connection) readMessage() (*buffer.InMessage, error) {
}
// Write the supplied message to the kernel.
func (c *Connection) writeMessage(outMsg *buffer.OutMessage) error {
var err error
var n int
expectedLen := outMsg.Len()
if outMsg.Sglist != nil {
n, err = writev(int(c.dev.Fd()), outMsg.Sglist)
} else {
// Avoid the retry loop in os.File.Write.
n, err = syscall.Write(int(c.dev.Fd()), outMsg.OutHeaderBytes())
func (c *Connection) writeMessage(msg []byte) error {
// Avoid the retry loop in os.File.Write.
n, err := syscall.Write(int(c.dev.Fd()), msg)
if err != nil {
return err
}
if err == nil && n != expectedLen {
err = fmt.Errorf("Wrote %d bytes; expected %d", n, expectedLen)
if n != len(msg) {
return fmt.Errorf("Wrote %d bytes; expected %d", n, len(msg))
}
if err != nil && c.errorLogger != nil {
c.errorLogger.Printf("writeMessage: %v %v", err, outMsg.OutHeaderBytes())
}
outMsg.Sglist = nil
return err
return nil
}
// ReadOp consumes the next op from the kernel process, returning the op and a
@ -529,21 +512,19 @@ func (c *Connection) Reply(ctx context.Context, opErr error) {
noResponse := c.kernelResponse(outMsg, inMsg.Header().Unique, op, opErr)
if !noResponse {
c.writeMessage(outMsg)
var err error
if outMsg.Sglist != nil {
_, err = writev(int(c.dev.Fd()), outMsg.Sglist)
} else {
err = c.writeMessage(outMsg.Bytes())
}
if err != nil && c.errorLogger != nil {
c.errorLogger.Printf("writeMessage: %v %v", err, outMsg.Bytes())
}
outMsg.Sglist = nil
}
}
// Send a notification to the kernel
// notification must be a pointer to one of fuseops.NotifyXXX structures
// To avoid a deadlock notifications must not be called in the execution path of a related filesytem operation or within any code that could hold a lock that could be needed to execute such an operation. As of kernel 4.18, a "related operation" is a lookup(), symlink(), mknod(), mkdir(), unlink(), rename(), link() or create() request for the parent, and a setattr(), unlink(), rmdir(), rename(), setxattr(), removexattr(), readdir() or readdirplus() request for the inode itself.
func (c *Connection) Notify(notification interface{}) error {
outMsg := c.getOutMessage()
defer c.putOutMessage(outMsg)
c.kernelNotification(outMsg, notification)
outMsg.OutHeader().Len = uint32(outMsg.Len())
return c.writeMessage(outMsg)
}
// Close the connection. Must not be called until operations that were read
// from the connection have been responded to.
func (c *Connection) close() error {

View File

@ -76,20 +76,12 @@ func convertInMessage(
o = to
valid := fusekernel.SetattrValid(in.Valid)
if valid&fusekernel.SetattrUid != 0 {
to.Uid = &in.Uid
}
if valid&fusekernel.SetattrGid != 0 {
to.Gid = &in.Gid
}
if valid&fusekernel.SetattrSize != 0 {
to.Size = &in.Size
}
if valid&fusekernel.SetattrMode != 0 {
mode := fuseops.ConvertFileMode(in.Mode)
mode := convertFileMode(in.Mode)
to.Mode = &mode
}
@ -121,32 +113,6 @@ func convertInMessage(
OpContext: fuseops.OpContext{Pid: inMsg.Header().Pid},
}
case fusekernel.OpBatchForget:
type input fusekernel.BatchForgetCountIn
in := (*input)(inMsg.Consume(unsafe.Sizeof(input{})))
if in == nil {
return nil, errors.New("Corrupt OpBatchForget")
}
entries := make([]fuseops.BatchForgetEntry, 0, in.Count)
for i := uint32(0); i < in.Count; i++ {
type entry fusekernel.BatchForgetEntryIn
ein := (*entry)(inMsg.Consume(unsafe.Sizeof(entry{})))
if ein == nil {
return nil, errors.New("Corrupt OpBatchForget")
}
entries = append(entries, fuseops.BatchForgetEntry{
Inode: fuseops.InodeID(ein.Inode),
N: ein.Nlookup,
})
}
o = &fuseops.BatchForgetOp{
Entries: entries,
OpContext: fuseops.OpContext{Pid: inMsg.Header().Pid},
}
case fusekernel.OpMkdir:
in := (*fusekernel.MkdirIn)(inMsg.Consume(fusekernel.MkdirInSize(protocol)))
if in == nil {
@ -170,7 +136,7 @@ func convertInMessage(
// the fact that this is a directory is implicit in the fact that the
// opcode is mkdir. But we want the correct mode to go through, so ensure
// that os.ModeDir is set.
Mode: fuseops.ConvertFileMode(in.Mode) | os.ModeDir,
Mode: convertFileMode(in.Mode) | os.ModeDir,
OpContext: fuseops.OpContext{Pid: inMsg.Header().Pid},
}
@ -190,8 +156,7 @@ func convertInMessage(
o = &fuseops.MkNodeOp{
Parent: fuseops.InodeID(inMsg.Header().Nodeid),
Name: string(name),
Mode: fuseops.ConvertFileMode(in.Mode),
Rdev: in.Rdev,
Mode: convertFileMode(in.Mode),
OpContext: fuseops.OpContext{Pid: inMsg.Header().Pid},
}
@ -211,7 +176,7 @@ func convertInMessage(
o = &fuseops.CreateFileOp{
Parent: fuseops.InodeID(inMsg.Header().Nodeid),
Name: string(name),
Mode: fuseops.ConvertFileMode(in.Mode),
Mode: convertFileMode(in.Mode),
OpContext: fuseops.OpContext{Pid: inMsg.Header().Pid},
}
@ -245,15 +210,11 @@ func convertInMessage(
// closed-source macfuse 4.x has broken compatibility with osxfuse 3.x:
// it passes an additional 64-bit field (flags) after RenameIn regardless
// that we don't enable the support for RENAME_SWAP/RENAME_EXCL
// macfuse doesn't want change the behaviour back which is motivated by
// not breaking compatibility the second time, look here for details:
// https://github.com/osxfuse/osxfuse/issues/839
//
// the simplest fix is just to check for the presence of all-zero flags
if len(names) >= 8 &&
names[0] == 0 && names[1] == 0 && names[2] == 0 && names[3] == 0 &&
names[4] == 0 && names[5] == 0 && names[6] == 0 && names[7] == 0 {
names = names[8:]
names = names[8 : ]
}
// names should be "old\x00new\x00"
if len(names) < 4 {
@ -303,15 +264,8 @@ func convertInMessage(
}
case fusekernel.OpOpen:
type input fusekernel.OpenIn
in := (*input)(inMsg.Consume(unsafe.Sizeof(input{})))
if in == nil {
return nil, errors.New("Corrupt OpOpen")
}
o = &fuseops.OpenFileOp{
Inode: fuseops.InodeID(inMsg.Header().Nodeid),
OpenFlags: fusekernel.OpenFlags(in.Flags),
OpContext: fuseops.OpContext{Pid: inMsg.Header().Pid},
}
@ -327,22 +281,29 @@ func convertInMessage(
return nil, errors.New("Corrupt OpRead")
}
to := &fuseops.ReadFileOp{
Inode: fuseops.InodeID(inMsg.Header().Nodeid),
Handle: fuseops.HandleID(in.Fh),
Offset: int64(in.Offset),
Size: int64(in.Size),
OpContext: fuseops.OpContext{Pid: inMsg.Header().Pid},
}
if !config.UseVectoredRead {
// Use part of the incoming message storage as the read buffer
// For vectored zero-copy reads, don't allocate any buffers
to.Dst = inMsg.GetFree(int(in.Size))
// Use part of the incoming message storage as a read buffer
buf := inMsg.GetFree(int(in.Size))
to := &fuseops.ReadFileOp{
Inode: fuseops.InodeID(inMsg.Header().Nodeid),
Handle: fuseops.HandleID(in.Fh),
Offset: int64(in.Offset),
Dst: buf,
OpContext: fuseops.OpContext{Pid: inMsg.Header().Pid},
}
o = to
} else {
// Don't allocate any buffers when zero-copy is used
to := &fuseops.VectoredReadOp{
Inode: fuseops.InodeID(inMsg.Header().Nodeid),
Handle: fuseops.HandleID(in.Fh),
Offset: int64(in.Offset),
Size: int64(in.Size),
OpContext: fuseops.OpContext{Pid: inMsg.Header().Pid},
}
o = to
}
o = to
case fusekernel.OpReaddirplus:
fallthrough
case fusekernel.OpReaddir:
in := (*fusekernel.ReadIn)(inMsg.Consume(fusekernel.ReadInSize(protocol)))
if in == nil {
@ -353,13 +314,12 @@ func convertInMessage(
Inode: fuseops.InodeID(inMsg.Header().Nodeid),
Handle: fuseops.HandleID(in.Fh),
Offset: fuseops.DirOffset(in.Offset),
Plus: inMsg.Header().Opcode == fusekernel.OpReaddirplus,
OpContext: fuseops.OpContext{Pid: inMsg.Header().Pid},
}
o = to
readSize := int(in.Size)
p := outMsg.Grow(readSize)
p := outMsg.GrowNoZero(readSize)
if p == nil {
return nil, fmt.Errorf("Can't grow for %d-byte read", readSize)
}
@ -531,7 +491,7 @@ func convertInMessage(
readSize := int(in.Size)
if readSize > 0 {
p := outMsg.Grow(readSize)
p := outMsg.GrowNoZero(readSize)
if p == nil {
return nil, fmt.Errorf("Can't grow for %d-byte read", readSize)
}
@ -559,7 +519,7 @@ func convertInMessage(
readSize := int(in.Size)
if readSize != 0 {
p := outMsg.Grow(readSize)
p := outMsg.GrowNoZero(readSize)
if p == nil {
return nil, fmt.Errorf("Can't grow for %d-byte read", readSize)
}
@ -610,42 +570,6 @@ func convertInMessage(
OpContext: fuseops.OpContext{Pid: inMsg.Header().Pid},
}
case fusekernel.OpPoll:
type input fusekernel.PollIn
in := (*input)(inMsg.Consume(unsafe.Sizeof(input{})))
if in == nil {
return nil, errors.New("Corrupt OpPoll")
}
o = &fuseops.PollOp{
Inode: fuseops.InodeID(inMsg.Header().Nodeid),
Handle: fuseops.HandleID(in.Fh),
Kh: in.Kh,
Flags: fusekernel.PollFlags(in.Flags),
Events: fusekernel.PollEvents(in.Events),
OpContext: fuseops.OpContext{Pid: inMsg.Header().Pid},
}
case fusekernel.OpNotifyReply:
type input fusekernel.NotifyRetrieveIn
in := (*input)(inMsg.Consume(unsafe.Sizeof(input{})))
if in == nil {
return nil, errors.New("Corrupt OpNotifyReply")
}
buf := inMsg.ConsumeBytes(inMsg.Len())
if len(buf) < int(in.Size) {
return nil, errors.New("Corrupt OpNotifyReply")
}
o = &fuseops.NotifyRetrieveReplyOp{
Inode: fuseops.InodeID(inMsg.Header().Nodeid),
Unique: inMsg.Header().Unique,
Offset: in.Offset,
Length: in.Size,
OpContext: fuseops.OpContext{Pid: inMsg.Header().Pid},
}
default:
o = &unknownOp{
OpCode: inMsg.Header().Opcode,
@ -676,12 +600,6 @@ func (c *Connection) kernelResponse(
case *fuseops.ForgetInodeOp:
return true
case *fuseops.BatchForgetOp:
return true
case *fuseops.NotifyRetrieveReplyOp:
return true
case *interruptOp:
return true
}
@ -725,37 +643,37 @@ func (c *Connection) kernelResponseForOp(
case *fuseops.LookUpInodeOp:
size := int(fusekernel.EntryOutSize(c.protocol))
out := (*fusekernel.EntryOut)(m.Grow(size))
fuseops.ConvertChildInodeEntry(&o.Entry, out)
convertChildInodeEntry(&o.Entry, out)
case *fuseops.GetInodeAttributesOp:
size := int(fusekernel.AttrOutSize(c.protocol))
out := (*fusekernel.AttrOut)(m.Grow(size))
out.AttrValid, out.AttrValidNsec = fuseops.ConvertExpirationTime(
out.AttrValid, out.AttrValidNsec = convertExpirationTime(
o.AttributesExpiration)
fuseops.ConvertAttributes(o.Inode, &o.Attributes, &out.Attr)
convertAttributes(o.Inode, &o.Attributes, &out.Attr)
case *fuseops.SetInodeAttributesOp:
size := int(fusekernel.AttrOutSize(c.protocol))
out := (*fusekernel.AttrOut)(m.Grow(size))
out.AttrValid, out.AttrValidNsec = fuseops.ConvertExpirationTime(
out.AttrValid, out.AttrValidNsec = convertExpirationTime(
o.AttributesExpiration)
fuseops.ConvertAttributes(o.Inode, &o.Attributes, &out.Attr)
convertAttributes(o.Inode, &o.Attributes, &out.Attr)
case *fuseops.MkDirOp:
size := int(fusekernel.EntryOutSize(c.protocol))
out := (*fusekernel.EntryOut)(m.Grow(size))
fuseops.ConvertChildInodeEntry(&o.Entry, out)
convertChildInodeEntry(&o.Entry, out)
case *fuseops.MkNodeOp:
size := int(fusekernel.EntryOutSize(c.protocol))
out := (*fusekernel.EntryOut)(m.Grow(size))
fuseops.ConvertChildInodeEntry(&o.Entry, out)
convertChildInodeEntry(&o.Entry, out)
case *fuseops.CreateFileOp:
eSize := int(fusekernel.EntryOutSize(c.protocol))
e := (*fusekernel.EntryOut)(m.Grow(eSize))
fuseops.ConvertChildInodeEntry(&o.Entry, e)
convertChildInodeEntry(&o.Entry, e)
oo := (*fusekernel.OpenOut)(m.Grow(int(unsafe.Sizeof(fusekernel.OpenOut{}))))
oo.Fh = uint64(o.Handle)
@ -763,12 +681,12 @@ func (c *Connection) kernelResponseForOp(
case *fuseops.CreateSymlinkOp:
size := int(fusekernel.EntryOutSize(c.protocol))
out := (*fusekernel.EntryOut)(m.Grow(size))
fuseops.ConvertChildInodeEntry(&o.Entry, out)
convertChildInodeEntry(&o.Entry, out)
case *fuseops.CreateLinkOp:
size := int(fusekernel.EntryOutSize(c.protocol))
out := (*fusekernel.EntryOut)(m.Grow(size))
fuseops.ConvertChildInodeEntry(&o.Entry, out)
convertChildInodeEntry(&o.Entry, out)
case *fuseops.RenameOp:
// Empty response
@ -805,11 +723,11 @@ func (c *Connection) kernelResponseForOp(
}
case *fuseops.ReadFileOp:
if o.Dst != nil {
m.Append(o.Dst)
} else {
m.Append(o.Data...)
}
m.Append(o.Dst)
m.ShrinkTo(buffer.OutMessageHeaderSize + o.BytesRead)
case *fuseops.VectoredReadOp:
m.Append(o.Data...)
m.ShrinkTo(buffer.OutMessageHeaderSize + o.BytesRead)
case *fuseops.WriteFileOp:
@ -905,13 +823,6 @@ func (c *Connection) kernelResponseForOp(
out.TimeGran = 1
out.MaxPages = o.MaxPages
case *fuseops.PollOp:
out := (*fusekernel.PollOut)(m.Grow(int(unsafe.Sizeof(fusekernel.PollOut{}))))
out.Revents = uint32(o.Revents)
case *fuseops.NotifyRetrieveReplyOp:
// Empty response
default:
panic(fmt.Sprintf("Unexpected op: %#v", op))
}
@ -919,73 +830,115 @@ func (c *Connection) kernelResponseForOp(
return
}
// Like kernelResponse, but assumes the user replied with a nil error to the op.
func (c *Connection) kernelNotification(
m *buffer.OutMessage,
op interface{}) {
h := m.OutHeader()
h.Unique = 0
// Create the appropriate output message
switch o := op.(type) {
case *fuseops.NotifyPollWakeup:
h.Error = fusekernel.NotifyCodePoll
out := (*fusekernel.NotifyPollWakeupOut)(m.Grow(int(unsafe.Sizeof(fusekernel.NotifyPollWakeupOut{}))))
out.Kh = o.Kh
case *fuseops.NotifyInvalInode:
h.Error = fusekernel.NotifyCodeInvalInode
out := (*fusekernel.NotifyInvalInodeOut)(m.Grow(int(unsafe.Sizeof(fusekernel.NotifyInvalInodeOut{}))))
out.Ino = uint64(o.Inode)
out.Off = o.Offset
out.Len = o.Length
case *fuseops.NotifyInvalEntry:
h.Error = fusekernel.NotifyCodeInvalEntry
out := (*fusekernel.NotifyInvalEntryOut)(m.Grow(int(unsafe.Sizeof(fusekernel.NotifyInvalEntryOut{}))))
out.Parent = uint64(o.Parent)
out.Namelen = uint32(len(o.Name))
m.AppendString(o.Name)
m.AppendString("\x00")
case *fuseops.NotifyDelete:
h.Error = fusekernel.NotifyCodeDelete
out := (*fusekernel.NotifyDeleteOut)(m.Grow(int(unsafe.Sizeof(fusekernel.NotifyDeleteOut{}))))
out.Parent = uint64(o.Parent)
out.Child = uint64(o.Child)
out.Namelen = uint32(len(o.Name))
m.AppendString(o.Name)
m.AppendString("\x00")
case *fuseops.NotifyStore:
h.Error = fusekernel.NotifyCodeStore
out := (*fusekernel.NotifyStoreOut)(m.Grow(int(unsafe.Sizeof(fusekernel.NotifyStoreOut{}))))
out.Nodeid = uint64(o.Inode)
out.Offset = o.Offset
out.Size = o.Length
m.Append(o.Data...)
m.ShrinkTo(buffer.OutMessageHeaderSize + int(unsafe.Sizeof(fusekernel.NotifyStoreOut{})) + int(o.Length))
case *fuseops.NotifyRetrieve:
h.Error = fusekernel.NotifyCodeRetrieve
out := (*fusekernel.NotifyRetrieveOut)(m.Grow(int(unsafe.Sizeof(fusekernel.NotifyRetrieveOut{}))))
out.Unique = o.Unique
out.Nodeid = uint64(o.Inode)
out.Offset = o.Offset
out.Size = o.Length
default:
panic(fmt.Sprintf("Unexpected notification: %#v", op))
}
return
}
////////////////////////////////////////////////////////////////////////
// General conversions
////////////////////////////////////////////////////////////////////////
func convertTime(t time.Time) (secs uint64, nsec uint32) {
totalNano := t.UnixNano()
secs = uint64(totalNano / 1e9)
nsec = uint32(totalNano % 1e9)
return secs, nsec
}
func convertAttributes(
inodeID fuseops.InodeID,
in *fuseops.InodeAttributes,
out *fusekernel.Attr) {
out.Ino = uint64(inodeID)
out.Size = in.Size
out.Atime, out.AtimeNsec = convertTime(in.Atime)
out.Mtime, out.MtimeNsec = convertTime(in.Mtime)
out.Ctime, out.CtimeNsec = convertTime(in.Ctime)
out.SetCrtime(convertTime(in.Crtime))
out.Nlink = in.Nlink
out.Uid = in.Uid
out.Gid = in.Gid
// round up to the nearest 512 boundary
out.Blocks = (in.Size + 512 - 1) / 512
// Set the mode.
out.Mode = uint32(in.Mode) & 0777
switch {
default:
out.Mode |= syscall.S_IFREG
case in.Mode&os.ModeDir != 0:
out.Mode |= syscall.S_IFDIR
case in.Mode&os.ModeDevice != 0:
if in.Mode&os.ModeCharDevice != 0 {
out.Mode |= syscall.S_IFCHR
} else {
out.Mode |= syscall.S_IFBLK
}
case in.Mode&os.ModeNamedPipe != 0:
out.Mode |= syscall.S_IFIFO
case in.Mode&os.ModeSymlink != 0:
out.Mode |= syscall.S_IFLNK
case in.Mode&os.ModeSocket != 0:
out.Mode |= syscall.S_IFSOCK
}
if in.Mode&os.ModeSetuid != 0 {
out.Mode |= syscall.S_ISUID
}
}
// Convert an absolute cache expiration time to a relative time from now for
// consumption by the fuse kernel module.
func convertExpirationTime(t time.Time) (secs uint64, nsecs uint32) {
// Fuse represents durations as unsigned 64-bit counts of seconds and 32-bit
// counts of nanoseconds (cf. http://goo.gl/EJupJV). So negative durations
// are right out. There is no need to cap the positive magnitude, because
// 2^64 seconds is well longer than the 2^63 ns range of time.Duration.
d := t.Sub(time.Now())
if d > 0 {
secs = uint64(d / time.Second)
nsecs = uint32((d % time.Second) / time.Nanosecond)
}
return secs, nsecs
}
func convertChildInodeEntry(
in *fuseops.ChildInodeEntry,
out *fusekernel.EntryOut) {
out.Nodeid = uint64(in.Child)
out.Generation = uint64(in.Generation)
out.EntryValid, out.EntryValidNsec = convertExpirationTime(in.EntryExpiration)
out.AttrValid, out.AttrValidNsec = convertExpirationTime(in.AttributesExpiration)
convertAttributes(in.Child, &in.Attributes, &out.Attr)
}
func convertFileMode(unixMode uint32) os.FileMode {
mode := os.FileMode(unixMode & 0777)
switch unixMode & syscall.S_IFMT {
case syscall.S_IFREG:
// nothing
case syscall.S_IFDIR:
mode |= os.ModeDir
case syscall.S_IFCHR:
mode |= os.ModeCharDevice | os.ModeDevice
case syscall.S_IFBLK:
mode |= os.ModeDevice
case syscall.S_IFIFO:
mode |= os.ModeNamedPipe
case syscall.S_IFLNK:
mode |= os.ModeSymlink
case syscall.S_IFSOCK:
mode |= os.ModeSocket
default:
// no idea
mode |= os.ModeDevice
}
if unixMode&syscall.S_ISUID != 0 {
mode |= os.ModeSetuid
}
if unixMode&syscall.S_ISGID != 0 {
mode |= os.ModeSetgid
}
return mode
}
func writeXattrSize(m *buffer.OutMessage, size uint32) {
out := (*fusekernel.GetxattrOut)(m.Grow(int(unsafe.Sizeof(fusekernel.GetxattrOut{}))))
out.Size = size

View File

@ -93,6 +93,11 @@ func describeRequest(op interface{}) (s string) {
addComponent("new_name %q", typed.NewName)
case *fuseops.ReadFileOp:
addComponent("handle %d", typed.Handle)
addComponent("offset %d", typed.Offset)
addComponent("%d bytes", len(typed.Dst))
case *fuseops.VectoredReadOp:
addComponent("handle %d", typed.Handle)
addComponent("offset %d", typed.Offset)
addComponent("%d bytes", typed.Size)

8
doc.go
View File

@ -16,15 +16,15 @@
//
// The primary elements of interest are:
//
// - The fuseops package, which defines the operations that fuse might send
// * The fuseops package, which defines the operations that fuse might send
// to your userspace daemon.
//
// - The Server interface, which your daemon must implement.
// * The Server interface, which your daemon must implement.
//
// - fuseutil.NewFileSystemServer, which offers a convenient way to implement
// * fuseutil.NewFileSystemServer, which offers a convenient way to implement
// the Server interface.
//
// - Mount, a function that allows for mounting a Server as a file system.
// * Mount, a function that allows for mounting a Server as a file system.
//
// Make sure to see the examples in the sub-packages of samples/, which double
// as tests for this package: http://godoc.org/github.com/jacobsa/fuse/samples

View File

@ -31,7 +31,7 @@ func (c *Connection) getInMessage() *buffer.InMessage {
c.mu.Unlock()
if x == nil {
x = buffer.NewInMessage()
x = new(buffer.InMessage)
}
return x

View File

@ -1,84 +0,0 @@
// Copyright 2023 Vitaliy Filippov
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package fuseops
import (
"time"
"syscall"
"github.com/jacobsa/fuse/internal/fusekernel"
)
////////////////////////////////////////////////////////////////////////
// General conversions
////////////////////////////////////////////////////////////////////////
func ConvertTime(t time.Time) (secs uint64, nsec uint32) {
totalNano := t.UnixNano()
secs = uint64(totalNano / 1e9)
nsec = uint32(totalNano % 1e9)
return secs, nsec
}
func ConvertAttributes(
inodeID InodeID,
in *InodeAttributes,
out *fusekernel.Attr) {
out.Ino = uint64(inodeID)
out.Size = in.Size
out.Atime, out.AtimeNsec = ConvertTime(in.Atime)
out.Mtime, out.MtimeNsec = ConvertTime(in.Mtime)
out.Ctime, out.CtimeNsec = ConvertTime(in.Ctime)
out.SetCrtime(ConvertTime(in.Crtime))
out.Nlink = in.Nlink
out.Uid = in.Uid
out.Gid = in.Gid
// round up to the nearest 512 boundary
out.Blocks = (in.Size + 512 - 1) / 512
// Set the mode.
out.Mode = ConvertGolangMode(in.Mode)
if out.Mode & (syscall.S_IFCHR | syscall.S_IFBLK) != 0 {
out.Rdev = in.Rdev
}
}
// Convert an absolute cache expiration time to a relative time from now for
// consumption by the fuse kernel module.
func ConvertExpirationTime(t time.Time) (secs uint64, nsecs uint32) {
// Fuse represents durations as unsigned 64-bit counts of seconds and 32-bit
// counts of nanoseconds (cf. http://goo.gl/EJupJV). So negative durations
// are right out. There is no need to cap the positive magnitude, because
// 2^64 seconds is well longer than the 2^63 ns range of time.Duration.
d := t.Sub(time.Now())
if d > 0 {
secs = uint64(d / time.Second)
nsecs = uint32((d % time.Second) / time.Nanosecond)
}
return secs, nsecs
}
func ConvertChildInodeEntry(
in *ChildInodeEntry,
out *fusekernel.EntryOut) {
out.Nodeid = uint64(in.Child)
out.Generation = uint64(in.Generation)
out.EntryValid, out.EntryValidNsec = ConvertExpirationTime(in.EntryExpiration)
out.AttrValid, out.AttrValidNsec = ConvertExpirationTime(in.AttributesExpiration)
ConvertAttributes(in.Child, &in.Attributes, &out.Attr)
}

View File

@ -1,70 +0,0 @@
package fuseops
import (
"os"
"syscall"
)
func ConvertFileMode(unixMode uint32) os.FileMode {
mode := os.FileMode(unixMode & 0777)
switch unixMode & syscall.S_IFMT {
case syscall.S_IFREG:
// nothing
case syscall.S_IFDIR:
mode |= os.ModeDir
case syscall.S_IFCHR:
mode |= os.ModeCharDevice | os.ModeDevice
case syscall.S_IFBLK:
mode |= os.ModeDevice
case syscall.S_IFIFO:
mode |= os.ModeNamedPipe
case syscall.S_IFLNK:
mode |= os.ModeSymlink
case syscall.S_IFSOCK:
mode |= os.ModeSocket
default:
// no idea
}
if unixMode&syscall.S_ISUID != 0 {
mode |= os.ModeSetuid
}
if unixMode&syscall.S_ISGID != 0 {
mode |= os.ModeSetgid
}
if unixMode&syscall.S_ISVTX != 0 {
mode |= os.ModeSticky
}
return mode
}
func ConvertGolangMode(inMode os.FileMode) uint32 {
outMode := uint32(inMode) & 0777
switch {
default:
outMode |= syscall.S_IFREG
case inMode&os.ModeDir != 0:
outMode |= syscall.S_IFDIR
case inMode&os.ModeDevice != 0:
if inMode&os.ModeCharDevice != 0 {
outMode |= syscall.S_IFCHR
} else {
outMode |= syscall.S_IFBLK
}
case inMode&os.ModeNamedPipe != 0:
outMode |= syscall.S_IFIFO
case inMode&os.ModeSymlink != 0:
outMode |= syscall.S_IFLNK
case inMode&os.ModeSocket != 0:
outMode |= syscall.S_IFSOCK
}
if inMode&os.ModeSetuid != 0 {
outMode |= syscall.S_ISUID
}
if inMode&os.ModeSetgid != 0 {
outMode |= syscall.S_ISGID
}
if inMode&os.ModeSticky != 0 {
outMode |= syscall.S_ISVTX
}
return outMode
}

View File

@ -17,8 +17,6 @@ package fuseops
import (
"os"
"time"
"github.com/jacobsa/fuse/internal/fusekernel"
)
////////////////////////////////////////////////////////////////////////
@ -37,16 +35,16 @@ type OpContext struct {
//
// Called by statfs(2) and friends:
//
// - (https://goo.gl/Xi1lDr) sys_statfs called user_statfs, which calls
// vfs_statfs, which calls statfs_by_dentry.
// * (https://goo.gl/Xi1lDr) sys_statfs called user_statfs, which calls
// vfs_statfs, which calls statfs_by_dentry.
//
// - (https://goo.gl/VAIOwU) statfs_by_dentry calls the superblock
// operation statfs, which in our case points at
// fuse_statfs (cf. https://goo.gl/L7BTM3)
// * (https://goo.gl/VAIOwU) statfs_by_dentry calls the superblock
// operation statfs, which in our case points at
// fuse_statfs (cf. https://goo.gl/L7BTM3)
//
// - (https://goo.gl/Zn7Sgl) fuse_statfs sends a statfs op, then uses
// convert_fuse_statfs to convert the response in a straightforward
// manner.
// * (https://goo.gl/Zn7Sgl) fuse_statfs sends a statfs op, then uses
// convert_fuse_statfs to convert the response in a straightforward
// manner.
//
// This op is particularly important on OS X: if you don't implement it, the
// file system will not successfully mount. If you don't model a sane amount of
@ -161,8 +159,6 @@ type SetInodeAttributesOp struct {
Handle *HandleID
// The attributes to modify, or nil for attributes that don't need a change.
Uid *uint32
Gid *uint32
Size *uint64
Mode *os.FileMode
Atime *time.Time
@ -193,10 +189,10 @@ type SetInodeAttributesOp struct {
// The reference count corresponds to fuse_inode::nlookup
// (http://goo.gl/ut48S4). Some examples of where the kernel manipulates it:
//
// - (http://goo.gl/vPD9Oh) Any caller to fuse_iget increases the count.
// - (http://goo.gl/B6tTTC) fuse_lookup_name calls fuse_iget.
// - (http://goo.gl/IlcxWv) fuse_create_open calls fuse_iget.
// - (http://goo.gl/VQMQul) fuse_dentry_revalidate increments after
// * (http://goo.gl/vPD9Oh) Any caller to fuse_iget increases the count.
// * (http://goo.gl/B6tTTC) fuse_lookup_name calls fuse_iget.
// * (http://goo.gl/IlcxWv) fuse_create_open calls fuse_iget.
// * (http://goo.gl/VQMQul) fuse_dentry_revalidate increments after
// revalidating.
//
// In contrast to all other inodes, RootInodeID begins with an implicit
@ -204,12 +200,12 @@ type SetInodeAttributesOp struct {
// could be no such op, because the root cannot be referred to by name.) Code
// walk:
//
// - (http://goo.gl/gWAheU) fuse_fill_super calls fuse_get_root_inode.
// * (http://goo.gl/gWAheU) fuse_fill_super calls fuse_get_root_inode.
//
// - (http://goo.gl/AoLsbb) fuse_get_root_inode calls fuse_iget without
// * (http://goo.gl/AoLsbb) fuse_get_root_inode calls fuse_iget without
// sending any particular request.
//
// - (http://goo.gl/vPD9Oh) fuse_iget increments nlookup.
// * (http://goo.gl/vPD9Oh) fuse_iget increments nlookup.
//
// File systems should tolerate but not rely on receiving forget ops for
// remaining inodes when the file system unmounts, including the root inode.
@ -224,32 +220,6 @@ type ForgetInodeOp struct {
OpContext OpContext
}
// BatchForgetEntry represents one Inode entry to forget in the BatchForgetOp.
//
// Everything written in the ForgetInodeOp docs applies for the BatchForgetEntry
// too.
type BatchForgetEntry struct {
// The inode whose reference count should be decremented.
Inode InodeID
// The amount to decrement the reference count.
N uint64
}
// Decrement the reference counts for a list of inode IDs previously issued by the file
// system.
//
// This operation is a batch of ForgetInodeOp operations. Every entry in
// Entries is one ForgetInodeOp operation. See the docs of ForgetInodeOp
// for further details.
type BatchForgetOp struct {
// Entries is a list of Forget operations. One could treat every entry in the
// list as a single ForgetInodeOp operation.
Entries []BatchForgetEntry
OpContext OpContext
}
////////////////////////////////////////////////////////////////////////
// Inode creation
////////////////////////////////////////////////////////////////////////
@ -301,9 +271,6 @@ type MkNodeOp struct {
Name string
Mode os.FileMode
// The device number (only valid if created file is a device)
Rdev uint32
// Set by the file system: information about the inode that was created.
//
// The lookup count for the inode is implicitly incremented. See notes on
@ -344,7 +311,7 @@ type CreateFileOp struct {
// The handle may be supplied in future ops like ReadFileOp that contain a
// file handle. The file system must ensure this ID remains valid until a
// later call to ReleaseFileHandle.
Handle HandleID
Handle HandleID
OpContext OpContext
}
@ -405,19 +372,19 @@ type CreateLinkOp struct {
// file system boundaries, and that the destination doesn't already exist with
// the wrong type. Some subtleties that the file system must care about:
//
// - If the new name is an existing directory, the file system must ensure it
// * If the new name is an existing directory, the file system must ensure it
// is empty before replacing it, returning ENOTEMPTY otherwise. (This is
// per the posix spec: http://goo.gl/4XtT79)
//
// - The rename must be atomic from the point of view of an observer of the
// * The rename must be atomic from the point of view of an observer of the
// new name. That is, if the new name already exists, there must be no
// point at which it doesn't exist.
//
// - It is okay for the new name to be modified before the old name is
// * It is okay for the new name to be modified before the old name is
// removed; these need not be atomic. In fact, the Linux man page
// explicitly says this is likely (cf. https://goo.gl/Y1wVZc).
//
// - Linux bends over backwards (https://goo.gl/pLDn3r) to ensure that
// * Linux bends over backwards (https://goo.gl/pLDn3r) to ensure that
// neither the old nor the new parent can be concurrently modified. But
// it's not clear whether OS X does this, and in any case it doesn't matter
// for file systems that may be modified remotely. Therefore a careful file
@ -427,6 +394,7 @@ type CreateLinkOp struct {
// posix and the man pages are imprecise about the actual semantics of a
// rename if it's not atomic, so it is probably not disastrous to be loose
// about this.
//
type RenameOp struct {
// The old parent directory, and the name of the entry within it to be
// relocated.
@ -475,7 +443,7 @@ type UnlinkOp struct {
// Open a directory inode.
//
// On Linux the kernel sends this when setting up a struct file for a particular inode
// On Linux the sends this when setting up a struct file for a particular inode
// with type directory, usually in response to an open(2) call from a
// user-space process. On OS X it may not be sent for every open(2) (cf.
// https://github.com/osxfuse/osxfuse/issues/199).
@ -562,18 +530,12 @@ type ReadDirOp struct {
// offset, and return array offsets into that cached listing.
Offset DirOffset
// Whether this operation is a READDIRPLUS
//
// If true, then the FS must return inode attributes and expiration time
// along with each directory entry and increment its reference count.
Plus bool
// The destination buffer, whose length gives the size of the read.
//
// The output data should consist 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.WriteDirent
// or fuseutil.WriteDirentPlus to generate this data.
// to generate this data.
//
// 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
@ -616,7 +578,7 @@ type ReleaseDirHandleOp struct {
// Open a file inode.
//
// On Linux the kernel sends this when setting up a struct file for a particular inode
// On Linux the sends this when setting up a struct file for a particular inode
// with type file, usually in response to an open(2) call from a user-space
// process. On OS X it may not be sent for every open(2)
// (cf.https://github.com/osxfuse/osxfuse/issues/199).
@ -658,8 +620,6 @@ type OpenFileOp struct {
// advance, for example, because contents are generated on the fly.
UseDirectIO bool
OpenFlags fusekernel.OpenFlags
OpContext OpContext
}
@ -677,15 +637,38 @@ type ReadFileOp struct {
// 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 requested number of bytes
// be returned, except in the case of EOF or error (http://goo.gl/ZgfBkF).
// 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
// where EOF is by checking the inode size (http://goo.gl/0BkqKD), returned
// by a previous call to LookUpInode, GetInodeAttributes, etc.
//
// If direct IO is enabled, semantics should match those of read(2).
BytesRead int
OpContext OpContext
}
// Vectored read - same as ReadFileOp, but the buffer isn't provided by the library.
// The file system returns a list of slices instead.
type VectoredReadOp struct {
// The file inode that we are reading, and the handle previously returned by
// CreateFile or OpenFile when opening that inode.
Inode InodeID
Handle HandleID
// The offset within the file at which to read.
Offset int64
// The size of the read.
Size int64
// The destination buffer, whose length gives the size of the read.
// For vectored reads, this field is always nil as the buffer is not provided.
Dst []byte
// Set by the file system:
// A list of slices of data to send back to the client for vectored reads.
// Set by the file system: data to send back to the client.
Data [][]byte
// Set by the file system: the number of bytes read.
@ -708,13 +691,13 @@ type ReadFileOp struct {
// cache and the page is marked dirty. Later the kernel may write back the
// page via the FUSE VFS layer, causing this op to be sent:
//
// - The kernel calls address_space_operations::writepage when a dirty page
// * The kernel calls address_space_operations::writepage when a dirty page
// needs to be written to backing store (cf. http://goo.gl/Ezbewg). Fuse
// sets this to fuse_writepage (cf. http://goo.gl/IeNvLT).
//
// - (http://goo.gl/Eestuy) fuse_writepage calls fuse_writepage_locked.
// * (http://goo.gl/Eestuy) fuse_writepage calls fuse_writepage_locked.
//
// - (http://goo.gl/RqYIxY) fuse_writepage_locked makes a write request to
// * (http://goo.gl/RqYIxY) fuse_writepage_locked makes a write request to
// the userspace server.
//
// Note that the kernel *will* ensure that writes are received and acknowledged
@ -778,10 +761,10 @@ type WriteFileOp struct {
// vfs.txt documents this as being called for by the fsync(2) system call
// (cf. http://goo.gl/j9X8nB). Code walk for that case:
//
// - (http://goo.gl/IQkWZa) sys_fsync calls do_fsync, calls vfs_fsync, calls
// * (http://goo.gl/IQkWZa) sys_fsync calls do_fsync, calls vfs_fsync, calls
// vfs_fsync_range.
//
// - (http://goo.gl/5L2SMy) vfs_fsync_range calls f_op->fsync.
// * (http://goo.gl/5L2SMy) vfs_fsync_range calls f_op->fsync.
//
// Note that this is also sent by fdatasync(2) (cf. http://goo.gl/01R7rF), and
// may be sent for msync(2) with the MS_SYNC flag (see the notes on
@ -802,8 +785,8 @@ type SyncFileOp struct {
// vfs.txt documents this as being sent for each close(2) system call (cf.
// http://goo.gl/FSkbrq). Code walk for that case:
//
// - (http://goo.gl/e3lv0e) sys_close calls __close_fd, calls filp_close.
// - (http://goo.gl/nI8fxD) filp_close calls f_op->flush (fuse_flush).
// * (http://goo.gl/e3lv0e) sys_close calls __close_fd, calls filp_close.
// * (http://goo.gl/nI8fxD) filp_close calls f_op->flush (fuse_flush).
//
// But note that this is also sent in other contexts where a file descriptor is
// closed, such as dup2(2) (cf. http://goo.gl/NQDvFS). In the case of close(2),
@ -812,15 +795,15 @@ type SyncFileOp struct {
// One potentially significant case where this may not be sent is mmap'd files,
// where the behavior is complicated:
//
// - munmap(2) does not cause flushes (cf. http://goo.gl/j8B9g0).
// * munmap(2) does not cause flushes (cf. http://goo.gl/j8B9g0).
//
// - On OS X, if a user modifies a mapped file via the mapping before
// * On OS X, if a user modifies a mapped file via the mapping before
// closing the file with close(2), the WriteFileOps for the modifications
// may not be received before the FlushFileOp for the close(2) (cf.
// https://github.com/osxfuse/osxfuse/issues/202). It appears that this may
// be fixed in osxfuse 3 (cf. https://goo.gl/rtvbko).
//
// - However, you safely can arrange for writes via a mapping to be
// * However, you safely can arrange for writes via a mapping to be
// flushed by calling msync(2) followed by close(2). On OS X msync(2)
// will cause a WriteFileOps to go through and close(2) will cause a
// FlushFile as usual (cf. http://goo.gl/kVmNcx). On Linux, msync(2) does
@ -846,8 +829,8 @@ type SyncFileOp struct {
// return any errors that occur.
type FlushFileOp struct {
// The file and handle being flushed.
Inode InodeID
Handle HandleID
Inode InodeID
Handle HandleID
OpContext OpContext
}
@ -982,127 +965,3 @@ type FallocateOp struct {
Mode uint32
OpContext OpContext
}
// Request notifications when the file system user calls poll/select or
// similar operations on a file.
type PollOp struct {
// The inode and handle the user wants to poll
Inode InodeID
Handle HandleID
// Kh is the "kernel handle". The reason behind it is that it's allocated
// by the kernel on file allocation and guaranteed to be unique as opposed
// to regular file handles (HandleID) generated by the userland server
// (by us). Kh has to be used in NotifyPollWakeupOut replies.
Kh uint64
// Poll flags
Flags fusekernel.PollFlags
// Requested events
Events fusekernel.PollEvents
// Set by the file system: the actual events that have happened
// since the last poll
Revents fusekernel.PollEvents
OpContext OpContext
}
// Notify consumers waiting for poll/epoll that events are incoming
// for the specified kernel handle. The kernel will send a PollOp request
// to get the event mask after receiving this notification
type NotifyPollWakeup struct {
Kh uint64
}
// Notify to invalidate cache for an inode.
//
// If the filesystem has writeback caching enabled, invalidating an inode
// will first trigger a writeback of all dirty pages. The call will block
// until all writeback requests have completed and the inode has been
// invalidated. It will, however, not wait for completion of pending writeback
// requests that have been issued before.
type NotifyInvalInode struct {
Inode InodeID
Offset int64
Length int64
}
// Notify to invalidate parent attributes and the dentry matching parent/name
//
// To avoid a deadlock this request must not be sent in the execution path
// of a related filesytem operation or within any code that could hold a lock
// that could be needed to execute such an operation. As of kernel 4.18, a
// "related operation" is a lookup(), symlink(), mknod(), mkdir(), unlink(),
// rename(), link() or create() request for the parent, and a setattr(),
// unlink(), rmdir(), rename(), setxattr(), removexattr(), readdir() or
// readdirplus() request for the inode itself.
//
// When called correctly, it will never block.
type NotifyInvalEntry struct {
Parent InodeID
Name string
}
// This request behaves like NotifyInvalEntry with the following additional
// effect (at least as of Linux kernel 4.8):
//
// If the provided child inode matches the inode that is currently associated
// with the cached dentry, and if there are any inotify watches registered for
// the dentry, then the watchers are informed that the dentry has been deleted.
//
// To avoid a deadlock this request must not be sent while executing a
// related filesytem operation or while holding a lock that could be needed to
// execute such an operation.
type NotifyDelete struct {
Parent InodeID
Child InodeID
Name string
}
// Store data to the kernel buffers
//
// Synchronously store data in the kernel buffers belonging to the given inode.
// The stored data is marked up-to-date (no read will be performed against it,
// unless it's invalidated or evicted from the cache).
//
// If the stored data overflows the current file size, then the size is extended,
// similarly to a write(2) on the filesystem.
//
// If this request returns an error, then the store wasn't fully completed, but
// it may have been partially completed.
type NotifyStore struct {
Inode InodeID
Offset uint64
Length uint32
Data [][]byte
}
// Retrieve data from the kernel buffers belonging to the given inode
//
// If successful then the kernel will send a NotifyRetrieveReplyOp as a reply.
// Only present pages are returned in the retrieve reply. Retrieving stops when it
// finds a non-present page and only data prior to that is returned.
//
// If this request returns an error, then the retrieve will not be completed and
// no reply will be sent.
//
// This request doesn't change the dirty state of pages in the kernel buffer. For
// dirty pages the write() method will be called regardless of having been retrieved
// previously.
type NotifyRetrieve struct {
Inode InodeID
Unique uint64
Offset uint64
Length uint32
}
// Matches the size of WriteIn
type NotifyRetrieveReplyOp struct {
Inode InodeID
Unique uint64
Offset uint64
Length uint32
OpContext OpContext
}

View File

@ -87,9 +87,6 @@ type InodeAttributes struct {
//
Mode os.FileMode
// The device number. Only valid if the file is a device
Rdev uint32
// Time information. See `man 2 stat` for full details.
Atime time.Time // Time of last access
Mtime time.Time // Time of last modification
@ -121,11 +118,12 @@ func (a *InodeAttributes) DebugString() string {
//
// Some related reading:
//
// http://fuse.sourceforge.net/doxygen/structfuse__entry__param.html
// http://stackoverflow.com/q/11071996/1505451
// http://goo.gl/CqvwyX
// http://julipedia.meroh.net/2005/09/nfs-file-handles.html
// http://goo.gl/wvo3MB
// http://fuse.sourceforge.net/doxygen/structfuse__entry__param.html
// http://stackoverflow.com/q/11071996/1505451
// http://goo.gl/CqvwyX
// http://julipedia.meroh.net/2005/09/nfs-file-handles.html
// http://goo.gl/wvo3MB
//
type GenerationNumber uint64
// HandleID is an opaque 64-bit number used to identify a particular open

View File

@ -19,7 +19,6 @@ import (
"unsafe"
"github.com/jacobsa/fuse/fuseops"
"github.com/jacobsa/fuse/internal/fusekernel"
)
type DirentType uint32
@ -51,18 +50,10 @@ type Dirent struct {
Type DirentType
}
// Write the supplied directory entry into the given buffer in the format
// expected in fuseops.ReadDirOp.Data, returning the number of bytes written.
// Write the supplied directory entry intto the given buffer in the format
// expected in fuseops.ReadFileOp.Data, returning the number of bytes written.
// Return zero if the entry would not fit.
func WriteDirent(buf []byte, d Dirent) (n int) {
return WriteDirentPlus(buf, nil, d)
}
// Write the supplied directory entry and, optionally, inode entry into the
// given buffer in the format expected in fuseops.ReadDirOp.Data with enabled
// READDIRPLUS capability, returning the number of bytes written.
// Returns zero if the entry would not fit.
func WriteDirentPlus(buf []byte, e *fuseops.ChildInodeEntry, 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
// to FUSE_DIRENT_ALIGN (http://goo.gl/UziWvH), which dictates 8-byte
@ -87,21 +78,10 @@ func WriteDirentPlus(buf []byte, e *fuseops.ChildInodeEntry, d Dirent) (n int) {
// Do we have enough room?
totalLen := direntSize + len(d.Name) + padLen
if e != nil {
// READDIRPLUS was added in protocol 7.21, entry attributes were added in 7.9
// So here EntryOut is always full-length
totalLen += int(unsafe.Sizeof(fusekernel.EntryOut{}))
}
if totalLen > len(buf) {
return n
}
if e != nil {
out := (*fusekernel.EntryOut)(unsafe.Pointer(&buf[n]))
fuseops.ConvertChildInodeEntry(e, out)
n += int(unsafe.Sizeof(fusekernel.EntryOut{}))
}
// Write the header.
de := fuse_dirent{
ino: uint64(d.Inode),

View File

@ -39,7 +39,6 @@ type FileSystem interface {
GetInodeAttributes(context.Context, *fuseops.GetInodeAttributesOp) error
SetInodeAttributes(context.Context, *fuseops.SetInodeAttributesOp) error
ForgetInode(context.Context, *fuseops.ForgetInodeOp) error
BatchForget(context.Context, *fuseops.BatchForgetOp) error
MkDir(context.Context, *fuseops.MkDirOp) error
MkNode(context.Context, *fuseops.MkNodeOp) error
CreateFile(context.Context, *fuseops.CreateFileOp) error
@ -53,6 +52,7 @@ type FileSystem interface {
ReleaseDirHandle(context.Context, *fuseops.ReleaseDirHandleOp) error
OpenFile(context.Context, *fuseops.OpenFileOp) error
ReadFile(context.Context, *fuseops.ReadFileOp) error
VectoredRead(context.Context, *fuseops.VectoredReadOp) error
WriteFile(context.Context, *fuseops.WriteFileOp) error
SyncFile(context.Context, *fuseops.SyncFileOp) error
FlushFile(context.Context, *fuseops.FlushFileOp) error
@ -63,9 +63,6 @@ type FileSystem interface {
ListXattr(context.Context, *fuseops.ListXattrOp) error
SetXattr(context.Context, *fuseops.SetXattrOp) error
Fallocate(context.Context, *fuseops.FallocateOp) error
Poll(context.Context, *fuseops.PollOp) error
SetConnection(*fuse.Connection)
// Regard all inodes (including the root inode) as having their lookup counts
// decremented to zero, and clean up any resources associated with the file
@ -98,8 +95,6 @@ type fileSystemServer struct {
}
func (s *fileSystemServer) ServeOps(c *fuse.Connection) {
s.fs.SetConnection(c)
// When we are done, we clean up by waiting for all in-flight ops then
// destroying the file system.
defer func() {
@ -157,22 +152,6 @@ func (s *fileSystemServer) handleOp(
case *fuseops.ForgetInodeOp:
err = s.fs.ForgetInode(ctx, typed)
case *fuseops.BatchForgetOp:
err = s.fs.BatchForget(ctx, typed)
if err == fuse.ENOSYS {
// Handle as a series of single-inode forget operations
for _, entry := range typed.Entries {
err = s.fs.ForgetInode(ctx, &fuseops.ForgetInodeOp{
Inode: entry.Inode,
N: entry.N,
OpContext: typed.OpContext,
})
if err != nil {
break
}
}
}
case *fuseops.MkDirOp:
err = s.fs.MkDir(ctx, typed)
@ -212,6 +191,9 @@ func (s *fileSystemServer) handleOp(
case *fuseops.ReadFileOp:
err = s.fs.ReadFile(ctx, typed)
case *fuseops.VectoredReadOp:
err = s.fs.VectoredRead(ctx, typed)
case *fuseops.WriteFileOp:
err = s.fs.WriteFile(ctx, typed)
@ -241,9 +223,6 @@ func (s *fileSystemServer) handleOp(
case *fuseops.FallocateOp:
err = s.fs.Fallocate(ctx, typed)
case *fuseops.PollOp:
err = s.fs.Poll(ctx, typed)
}
c.Reply(ctx, err)

View File

@ -60,12 +60,6 @@ func (fs *NotImplementedFileSystem) ForgetInode(
return fuse.ENOSYS
}
func (fs *NotImplementedFileSystem) BatchForget(
ctx context.Context,
op *fuseops.BatchForgetOp) error {
return fuse.ENOSYS
}
func (fs *NotImplementedFileSystem) MkDir(
ctx context.Context,
op *fuseops.MkDirOp) error {
@ -144,6 +138,12 @@ func (fs *NotImplementedFileSystem) ReadFile(
return fuse.ENOSYS
}
func (fs *NotImplementedFileSystem) VectoredRead(
ctx context.Context,
op *fuseops.VectoredReadOp) error {
return fuse.ENOSYS
}
func (fs *NotImplementedFileSystem) WriteFile(
ctx context.Context,
op *fuseops.WriteFileOp) error {
@ -204,14 +204,5 @@ func (fs *NotImplementedFileSystem) Fallocate(
return fuse.ENOSYS
}
func (fs *NotImplementedFileSystem) Poll(
ctx context.Context,
op *fuseops.PollOp) error {
return fuse.ENOSYS
}
func (fs *NotImplementedFileSystem) SetConnection(*fuse.Connection) {
}
func (fs *NotImplementedFileSystem) Destroy() {
}

7
go.mod
View File

@ -1,6 +1,6 @@
module github.com/jacobsa/fuse
go 1.18
go 1.16
require (
github.com/detailyang/go-fallocate v0.0.0-20180908115635-432fa640bd2e
@ -11,7 +11,6 @@ require (
github.com/jacobsa/syncutil v0.0.0-20180201203307-228ac8e5a6c3
github.com/jacobsa/timeutil v0.0.0-20170205232429-577e5acbbcf6
github.com/kylelemons/godebug v1.1.0
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect
golang.org/x/net v0.0.0-20220526153639-5463443f8c37
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a
golang.org/x/net v0.0.0-20200301022130-244492dfa37a
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527
)

15
go.sum
View File

@ -15,24 +15,9 @@ github.com/jacobsa/timeutil v0.0.0-20170205232429-577e5acbbcf6/go.mod h1:JEWKD6V
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e h1:T8NU3HyQ8ClP4SEE+KbFlg6n0NhuTsN4MyznaarGsZM=
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a h1:GuSPYbZzB5/dcLNCwLQLsg3obCJtX9IJhpXkvY7kzk0=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220526153639-5463443f8c37 h1:lUkvobShwKsOesNfWWlCS5q7fnbG1MEliIzwu886fn8=
golang.org/x/net v0.0.0-20220526153639-5463443f8c37/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 h1:uYVVQ9WP/Ds2ROhcaGPeIdVq0RIXVLwsHlnvJ+cT1So=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

View File

@ -45,17 +45,13 @@ type InMessage struct {
size int
}
// NewInMessage creates a new InMessage with its storage initialized.
func NewInMessage() *InMessage {
return &InMessage{
storage: make([]byte, bufSize),
}
}
// Initialize with the data read by a single call to r.Read. The first call to
// Consume will consume the bytes directly after the fusekernel.InHeader
// struct.
func (m *InMessage) Init(r io.Reader) error {
if m.storage == nil {
m.storage = make([]byte, bufSize, bufSize)
}
n, err := r.Read(m.storage[:])
if err != nil {
return err

View File

@ -32,8 +32,8 @@ const OutMessageHeaderSize = int(unsafe.Sizeof(fusekernel.OutHeader{}))
//
// Must be initialized with Reset.
type OutMessage struct {
header fusekernel.OutHeader
Sglist [][]byte
header fusekernel.OutHeader
Sglist [][]byte
}
// Reset resets m so that it's ready to be used again. Afterward, the contents
@ -48,9 +48,17 @@ func (m *OutMessage) OutHeader() *fusekernel.OutHeader {
return &m.header
}
// Grow adds a new buffer of <n> bytes to the message, returning a pointer to
// the start of the new segment, which is guaranteed to be zeroed.
// Grow grows m's buffer by the given number of bytes, returning a pointer to
// the start of the new segment, which is guaranteed to be zeroed. If there is
// insufficient space, it returns nil.
func (m *OutMessage) Grow(n int) unsafe.Pointer {
p := m.GrowNoZero(n)
return p
}
// GrowNoZero is equivalent to Grow, except the new segment is not zeroed. Use
// with caution!
func (m *OutMessage) GrowNoZero(n int) unsafe.Pointer {
b := make([]byte, n)
m.Append(b)
p := unsafe.Pointer(&b[0])
@ -76,10 +84,10 @@ func (m *OutMessage) ShrinkTo(n int) {
i++
}
if n > 0 {
m.Sglist[i] = m.Sglist[i][0:n]
m.Sglist[i] = m.Sglist[i][0 : n]
i++
}
m.Sglist = m.Sglist[0:i]
m.Sglist = m.Sglist[0 : i]
}
}
@ -87,10 +95,12 @@ func (m *OutMessage) ShrinkTo(n int) {
// segment. Int panics if there is not enough room available.
func (m *OutMessage) Append(src ...[]byte) {
if m.Sglist == nil {
// First element of Sglist is pre-filled with a pointer to the header
// to allow sending it with a single writev() call without copying the
// slice again
m.Sglist = append(m.Sglist, m.OutHeaderBytes())
m.Sglist = append(m.Sglist, nil)
*(*reflect.SliceHeader)(unsafe.Pointer(&m.Sglist[0])) = reflect.SliceHeader{
Data: uintptr(unsafe.Pointer(&m.header)),
Len: OutMessageHeaderSize,
Cap: OutMessageHeaderSize,
}
}
m.Sglist = append(m.Sglist, src...)
return
@ -107,7 +117,6 @@ func (m *OutMessage) Len() int {
if m.Sglist == nil {
return OutMessageHeaderSize
}
// First element of Sglist is the header, so we don't need to count it here
r := 0
for _, b := range m.Sglist {
r += len(b)
@ -115,8 +124,8 @@ func (m *OutMessage) Len() int {
return r
}
// OutHeaderBytes returns a byte slice containing the current header.
func (m *OutMessage) OutHeaderBytes() []byte {
// Bytes returns a byte slice containing the current header.
func (m *OutMessage) Bytes() []byte {
l := OutMessageHeaderSize
sh := reflect.SliceHeader{
Data: uintptr(unsafe.Pointer(&m.header)),

View File

@ -49,6 +49,47 @@ func findNonZero(p unsafe.Pointer, n int) int {
return n
}
func TestMemclr(t *testing.T) {
// All sizes up to 32 bytes.
var sizes []int
for i := 0; i <= 32; i++ {
sizes = append(sizes, i)
}
// And a few hand-chosen sizes.
sizes = append(sizes, []int{
39, 41, 64, 127, 128, 129,
1<<20 - 1,
1 << 20,
1<<20 + 1,
}...)
// For each size, fill a buffer with random bytes and then zero it.
for _, size := range sizes {
size := size
t.Run(fmt.Sprintf("size=%d", size), func(t *testing.T) {
// Generate
b, err := randBytes(size)
if err != nil {
t.Fatalf("randBytes: %v", err)
}
// Clear
var p unsafe.Pointer
if len(b) != 0 {
p = unsafe.Pointer(&b[0])
}
jacobsa_fuse_memclr(p, uintptr(len(b)))
// Check
if i := findNonZero(p, len(b)); i != len(b) {
t.Fatalf("non-zero byte at offset %d", i)
}
})
}
}
func TestOutMessageAppend(t *testing.T) {
var om OutMessage
om.Reset()
@ -71,7 +112,7 @@ func TestOutMessageAppend(t *testing.T) {
b = append(b, om.Sglist[i]...)
}
if got, want := len(b), wantLen; got != want {
t.Fatalf("len(om.OutHeaderBytes()) = %d, want %d", got, want)
t.Fatalf("len(om.Bytes()) = %d, want %d", got, want)
}
want := append(
@ -104,7 +145,7 @@ func TestOutMessageAppendString(t *testing.T) {
b = append(b, om.Sglist[i]...)
}
if got, want := len(b), wantLen; got != want {
t.Fatalf("len(om.OutHeaderBytes()) = %d, want %d", got, want)
t.Fatalf("len(om.Bytes()) = %d, want %d", got, want)
}
want := append(
@ -138,7 +179,7 @@ func TestOutMessageShrinkTo(t *testing.T) {
b = append(b, om.Sglist[i]...)
}
if got, want := len(b), wantLen; got != want {
t.Fatalf("len(om.OutHeaderBytes()) = %d, want %d", got, want)
t.Fatalf("len(om.Bytes()) = %d, want %d", got, want)
}
want := append(
@ -169,7 +210,7 @@ func TestOutMessageHeader(t *testing.T) {
*h = want
// Check that the result is as expected.
b := om.OutHeaderBytes()
b := om.Bytes()
if len(b) != int(unsafe.Sizeof(want)) {
t.Fatalf("unexpected length %d; want %d", len(b), unsafe.Sizeof(want))
}
@ -193,7 +234,9 @@ func TestOutMessageReset(t *testing.T) {
}
// Ensure a non-zero payload length.
om.Grow(128)
if p := om.GrowNoZero(128); p == nil {
t.Fatal("GrowNoZero failed")
}
// Reset.
om.Reset()
@ -225,7 +268,10 @@ func TestOutMessageGrow(t *testing.T) {
// Set up garbage where the payload will soon be.
const payloadSize = 1234
{
p := om.Grow(payloadSize)
p := om.GrowNoZero(payloadSize)
if p == nil {
t.Fatal("GrowNoZero failed")
}
err := fillWithGarbage(p, payloadSize)
if err != nil {

View File

@ -0,0 +1,31 @@
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package buffer
import "unsafe"
//go:noescape
// Zero the n bytes starting at p.
//
// REQUIRES: the region does not contain any Go pointers.
//go:linkname jacobsa_fuse_memclr runtime.memclrNoHeapPointers
func jacobsa_fuse_memclr(p unsafe.Pointer, n uintptr)
//go:noescape
// Copy from src to dst, allowing overlap.
//go:linkname jacobsa_fuse_memmove runtime.memmove
func jacobsa_fuse_memmove(dst unsafe.Pointer, src unsafe.Pointer, n uintptr)

View File

@ -0,0 +1,18 @@
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// The buffer package uses //go:linkname to push a few functions into this
// package but we still need a .s file so the Go tool does not pass -complete
// to the go tool compile so the latter does not complain about Go functions
// with no bodies.

View File

@ -168,7 +168,7 @@ const (
// OpenAccessModeMask is a bitmask that separates the access mode
// from the other flags in OpenFlags.
const OpenAccessModeMask OpenFlags = OpenReadOnly | OpenWriteOnly | OpenReadWrite
const OpenAccessModeMask OpenFlags = syscall.O_ACCMODE
// OpenFlags are the O_FOO flags passed to open/create/etc calls. For
// example, os.O_WRONLY | os.O_APPEND.
@ -346,34 +346,6 @@ var releaseFlagNames = []flagName{
{uint32(ReleaseFlush), "ReleaseFlush"},
}
// Poll flags and events are used in the Poll exchange.
type PollFlags uint32
const (
// From the kernel source:
// Ask for notification if there's someone waiting for it.
// The client may ignore the flag and always notify.
PollScheduleNotify PollFlags = 1 << 0
)
type PollEvents uint32
const (
PollInEvent PollEvents = 0x0001
PollPriEvent PollEvents = 0x0002
PollOutEvent PollEvents = 0x0004
PollErrEvent PollEvents = 0x0008
PollHupEvent PollEvents = 0x0010
PollNvalEvent PollEvents = 0x0020
PollRdNormEvent PollEvents = 0x0040
PollRdBandEvent PollEvents = 0x0080
PollWrNormEvent PollEvents = 0x0100
PollWrBandEvent PollEvents = 0x0200
PollMsgEvent PollEvents = 0x0400
PollRemoveEvent PollEvents = 0x1000
PollRdHupEvent PollEvents = 0x2000
)
// Opcodes
const (
OpLookup = 1
@ -414,10 +386,7 @@ const (
OpDestroy = 38
OpIoctl = 39 // Linux?
OpPoll = 40 // Linux?
OpNotifyReply = 41
OpBatchForget = 42
OpFallocate = 43
OpReaddirplus = 44
// OS X
OpSetvolname = 61
@ -448,16 +417,6 @@ type ForgetIn struct {
Nlookup uint64
}
type BatchForgetCountIn struct {
Count uint32
dummy uint32
}
type BatchForgetEntryIn struct {
Inode int64
Nlookup uint64
}
type GetattrIn struct {
GetattrFlags uint32
dummy uint32
@ -582,18 +541,6 @@ func CreateInSize(p Protocol) uintptr {
}
}
type PollIn struct {
Fh uint64
Kh uint64
Flags uint32
Events uint32
}
type PollOut struct {
Revents uint32
padding uint32
}
type ReleaseIn struct {
Fh uint64
Flags uint32
@ -829,15 +776,8 @@ const (
NotifyCodePoll int32 = 1
NotifyCodeInvalInode int32 = 2
NotifyCodeInvalEntry int32 = 3
NotifyCodeStore int32 = 4
NotifyCodeRetrieve int32 = 5
NotifyCodeDelete int32 = 6
)
type NotifyPollWakeupOut struct {
Kh uint64
}
type NotifyInvalInodeOut struct {
Ino uint64
Off int64
@ -849,35 +789,3 @@ type NotifyInvalEntryOut struct {
Namelen uint32
padding uint32
}
type NotifyDeleteOut struct {
Parent uint64
Child uint64
Namelen uint32
padding uint32
}
type NotifyStoreOut struct {
Nodeid uint64
Offset uint64
Size uint32
padding uint32
}
type NotifyRetrieveOut struct {
Unique uint64
Nodeid uint64
Offset uint64
Size uint32
padding uint32
}
// Matches the size of WriteIn
type NotifyRetrieveIn struct {
dummy1 uint64
Offset uint64
Size uint32
dummy2 uint32
dummy3 uint64
dummy4 uint64
}

View File

@ -1,5 +1,3 @@
// +build linux windows
package fusekernel
import "time"

View File

@ -0,0 +1 @@
package fusekernel

View File

@ -17,11 +17,9 @@ package fuse
import (
"context"
"fmt"
"log"
"net"
"os"
"os/exec"
"strings"
"syscall"
)
@ -43,8 +41,16 @@ func Mount(
config *MountConfig) (*MountedFileSystem, error) {
// Sanity check: make sure the mount point exists and is a directory. This
// saves us from some confusing errors later on OS X.
if err := checkMountPoint(dir); err != nil {
fi, err := os.Stat(dir)
switch {
case os.IsNotExist(err):
return nil, err
case err != nil:
return nil, fmt.Errorf("Statting mount point: %v", err)
case !fi.IsDir():
return nil, fmt.Errorf("Mount point %s is not a directory", dir)
}
// Initialize the struct.
@ -54,17 +60,11 @@ func Mount(
}
// Begin the mounting process, which will continue in the background.
if config.DebugLogger != nil {
config.DebugLogger.Println("Beginning the mounting kickoff process")
}
ready := make(chan error, 1)
dev, err := mount(dir, config, ready)
if err != nil {
return nil, fmt.Errorf("mount: %v", err)
}
if config.DebugLogger != nil {
config.DebugLogger.Println("Completed the mounting kickoff process")
}
// Choose a parent context for ops.
cfgCopy := *config
@ -72,9 +72,6 @@ func Mount(
cfgCopy.OpContext = context.Background()
}
if config.DebugLogger != nil {
config.DebugLogger.Println("Creating a connection object")
}
// Create a Connection object wrapping the device.
connection, err := newConnection(
cfgCopy,
@ -84,9 +81,6 @@ func Mount(
if err != nil {
return nil, fmt.Errorf("newConnection: %v", err)
}
if config.DebugLogger != nil {
config.DebugLogger.Println("Successfully created the connection")
}
// Serve the connection in the background. When done, set the join status.
go func() {
@ -95,10 +89,6 @@ func Mount(
close(mfs.joinStatusAvailable)
}()
if config.DebugLogger != nil {
config.DebugLogger.Println("Waiting for mounting process to complete")
}
// Wait for the mount process to complete.
if err := <-ready; err != nil {
return nil, fmt.Errorf("mount (background): %v", err)
@ -107,39 +97,13 @@ func Mount(
return mfs, nil
}
func checkMountPoint(dir string) error {
if strings.HasPrefix(dir, "/dev/fd") {
return nil
}
fi, err := os.Stat(dir)
switch {
case os.IsNotExist(err):
return err
case err != nil:
return fmt.Errorf("Statting mount point: %v", err)
case !fi.IsDir():
return fmt.Errorf("Mount point %s is not a directory", dir)
}
return nil
}
func fusermount(binary string, argv []string, additionalEnv []string, wait bool, debugLogger *log.Logger) (*os.File, error) {
if debugLogger != nil {
debugLogger.Println("Creating a socket pair")
}
func fusermount(binary string, argv []string, additionalEnv []string, wait bool) (*os.File, error) {
// Create a socket pair.
fds, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM, 0)
if err != nil {
return nil, fmt.Errorf("Socketpair: %v", err)
}
if debugLogger != nil {
debugLogger.Println("Creating files to wrap the sockets")
}
// Wrap the sockets into os.File objects that we will pass off to fusermount.
writeFile := os.NewFile(uintptr(fds[0]), "fusermount-child-writes")
defer writeFile.Close()
@ -147,9 +111,6 @@ func fusermount(binary string, argv []string, additionalEnv []string, wait bool,
readFile := os.NewFile(uintptr(fds[1]), "fusermount-parent-reads")
defer readFile.Close()
if debugLogger != nil {
debugLogger.Println("Starting fusermount/os mount")
}
// Start fusermount/mount_macfuse/mount_osxfuse.
cmd := exec.Command(binary, argv...)
cmd.Env = append(os.Environ(), "_FUSE_COMMFD=3")
@ -167,9 +128,6 @@ func fusermount(binary string, argv []string, additionalEnv []string, wait bool,
return nil, fmt.Errorf("running %v: %v", binary, err)
}
if debugLogger != nil {
debugLogger.Println("Wrapping socket pair in a connection")
}
// Wrap the socket file in a connection.
c, err := net.FileConn(readFile)
if err != nil {
@ -177,18 +135,12 @@ func fusermount(binary string, argv []string, additionalEnv []string, wait bool,
}
defer c.Close()
if debugLogger != nil {
debugLogger.Println("Checking that we have a unix domain socket")
}
// We expect to have a Unix domain socket.
uc, ok := c.(*net.UnixConn)
if !ok {
return nil, fmt.Errorf("Expected UnixConn, got %T", c)
}
if debugLogger != nil {
debugLogger.Println("Read a message from socket")
}
// Read a message.
buf := make([]byte, 32) // expect 1 byte
oob := make([]byte, 32) // expect 24 bytes
@ -210,10 +162,6 @@ func fusermount(binary string, argv []string, additionalEnv []string, wait bool,
scm := scms[0]
if debugLogger != nil {
debugLogger.Println("Successfully read the socket message.")
}
// Pull out the FD returned by fusermount
gotFds, err := syscall.ParseUnixRights(&scm)
if err != nil {
@ -224,9 +172,6 @@ func fusermount(binary string, argv []string, additionalEnv []string, wait bool,
return nil, fmt.Errorf("wanted 1 fd; got %#v", gotFds)
}
if debugLogger != nil {
debugLogger.Println("Converting FD into os.File")
}
// Turn the FD into an os.File.
return os.NewFile(uintptr(gotFds[0]), "/dev/fuse"), nil
}

View File

@ -151,21 +151,14 @@ type MountConfig struct {
// OpenDir calls at all (Linux >= 5.1):
EnableNoOpendirSupport bool
// Tell the kernel to use READDIRPLUS.
// Note that the implementation may still fall back to READDIR if the running
// kernel doesn't have support for READDIRPLUS.
UseReadDirPlus bool
// Disable FUSE default permissions.
// This is useful for situations where the backing data store (e.g., S3) doesn't
// actually utilise any form of qualifiable UNIX permissions.
DisableDefaultPermissions bool
// Use vectored reads.
// Vectored read allows file systems to avoid memory copying overhead if
// Use VectoredReadOp instead of ReadFileOp.
// Vectored read allows file systems to reduce memory copying overhead if
// the data is already in memory when they return it to FUSE.
// When turned on, ReadFileOp.Dst is always nil and the FS must return data
// being read from the file as a list of slices in ReadFileOp.Data.
UseVectoredRead bool
// OS X only.
@ -186,10 +179,6 @@ type MountConfig struct {
// /proc/mounts will show the filesystem type as fuse.<Subtype>.
// If not set, /proc/mounts will show the filesystem type as fuse/fuseblk.
Subtype string
// Flag to enable async reads that are received from
// the kernel
EnableAsyncReads bool
}
// Create a map containing all of the key=value mount options to be given to

View File

@ -124,7 +124,7 @@ func convertMountArgs(daemonVar string, libVar string,
}
}
env := []string{libVar + "="}
env := []string{ libVar+"=" }
if daemonVar != "" {
env = append(env, daemonVar+"="+os.Args[0])
}
@ -135,7 +135,7 @@ func convertMountArgs(daemonVar string, libVar string,
//
// OSXFUSE seems to ignore InitResponse.MaxWrite, and uses
// this instead.
"-o", "iosize=" + strconv.FormatUint(buffer.MaxWriteSize, 10),
"-o", "iosize="+strconv.FormatUint(buffer.MaxWriteSize, 10),
}
return argv, env, nil
@ -205,7 +205,7 @@ func callMountCommFD(
env = append(env, "_FUSE_COMMVERS=2")
argv = append(argv, dir)
return fusermount(bin, argv, env, false, cfg.DebugLogger)
return fusermount(bin, argv, env, false)
}
// Begin the process of mounting at the given directory, returning a connection

View File

@ -5,8 +5,6 @@ import (
"fmt"
"os"
"os/exec"
"strconv"
"strings"
"syscall"
"golang.org/x/sys/unix"
@ -55,9 +53,6 @@ var mountflagopts = map[string]func(uintptr) uintptr{
var errFallback = errors.New("sentinel: fallback to fusermount(1)")
func directmount(dir string, cfg *MountConfig) (*os.File, error) {
if cfg.DebugLogger != nil {
cfg.DebugLogger.Println("Preparing for direct mounting")
}
// We use syscall.Open + os.NewFile instead of os.OpenFile so that the file
// is opened in blocking mode. When opened in non-blocking mode, the Go
// runtime tries to use poll(2), which does not work with /dev/fuse.
@ -66,10 +61,6 @@ func directmount(dir string, cfg *MountConfig) (*os.File, error) {
return nil, errFallback
}
dev := os.NewFile(uintptr(fd), "/dev/fuse")
if cfg.DebugLogger != nil {
cfg.DebugLogger.Println("Successfully opened the /dev/fuse in blocking mode")
}
// As per libfuse/fusermount.c:847: https://bit.ly/2SgtWYM#L847
data := fmt.Sprintf("fd=%d,rootmode=40000,user_id=%d,group_id=%d",
dev.Fd(), os.Getuid(), os.Getgid())
@ -91,10 +82,6 @@ func directmount(dir string, cfg *MountConfig) (*os.File, error) {
}
delete(opts, "subtype")
data += "," + mapToOptionsString(opts)
if cfg.DebugLogger != nil {
cfg.DebugLogger.Println("Starting the unix mounting")
}
if err := unix.Mount(
cfg.FSName, // source
dir, // target
@ -108,9 +95,6 @@ func directmount(dir string, cfg *MountConfig) (*os.File, error) {
}
return nil, err
}
if cfg.DebugLogger != nil {
cfg.DebugLogger.Println("Unix mounting completed successfully")
}
return dev, nil
}
@ -122,24 +106,10 @@ func mount(dir string, cfg *MountConfig, ready chan<- error) (*os.File, error) {
// On linux, mounting is never delayed.
ready <- nil
if cfg.DebugLogger != nil {
cfg.DebugLogger.Println("Parsing fuse file descriptor")
}
// If the mountpoint is /dev/fd/N, assume that the file descriptor N is an
// already open FUSE channel. Parse it, cast it to an fd, and don't do any
// other part of the mount dance.
if fd, err := parseFuseFd(dir); err == nil {
dev := os.NewFile(uintptr(fd), "/dev/fuse")
return dev, nil
}
// Try mounting without fusermount(1) first: we might be running as root or
// have the CAP_SYS_ADMIN capability.
dev, err := directmount(dir, cfg)
if err == errFallback {
if cfg.DebugLogger != nil {
cfg.DebugLogger.Println("Directmount failed. Trying fallback.")
}
fusermountPath, err := findFusermount()
if err != nil {
return nil, err
@ -149,20 +119,7 @@ func mount(dir string, cfg *MountConfig, ready chan<- error) (*os.File, error) {
"--",
dir,
}
return fusermount(fusermountPath, argv, []string{}, true, cfg.DebugLogger)
return fusermount(fusermountPath, argv, []string{}, true)
}
return dev, err
}
func parseFuseFd(dir string) (int, error) {
if !strings.HasPrefix(dir, "/dev/fd/") {
return -1, fmt.Errorf("not a /dev/fd path")
}
fd, err := strconv.ParseUint(strings.TrimPrefix(dir, "/dev/fd/"), 10, 32)
if err != nil {
return -1, fmt.Errorf("invalid /dev/fd/N path: N must be a positive integer")
}
return int(fd), nil
}

View File

@ -1,37 +0,0 @@
package fuse
import (
"testing"
)
func Test_parseFuseFd(t *testing.T) {
t.Run("valid", func(t *testing.T) {
fd, err := parseFuseFd("/dev/fd/42")
if fd != 42 {
t.Errorf("expected 42, got %d", fd)
}
if err != nil {
t.Errorf("expected no error, got %#v", err)
}
})
t.Run("negative", func(t *testing.T) {
fd, err := parseFuseFd("/dev/fd/-42")
if fd != -1 {
t.Errorf("expected an invalid fd, got %d", fd)
}
if err == nil {
t.Errorf("expected an error, nil")
}
})
t.Run("not an int", func(t *testing.T) {
fd, err := parseFuseFd("/dev/fd/3.14159")
if fd != -1 {
t.Errorf("expected an invalid fd, got %d", fd)
}
if err == nil {
t.Errorf("expected an error, nil")
}
})
}

View File

@ -14,10 +14,7 @@
package fuse
import (
"context"
"fmt"
)
import "context"
// MountedFileSystem represents the status of a mount operation, with a method
// that waits for unmounting.
@ -50,17 +47,3 @@ func (mfs *MountedFileSystem) Join(ctx context.Context) error {
return ctx.Err()
}
}
// GetFuseContext implements the equiv. of FUSE-C fuse_get_context() and thus
// returns the UID / GID / PID associated with all FUSE requests send by the kernel.
// ctx parameter must be one of the context from the fuseops handlers (e.g.: CreateFile)
func (mfs *MountedFileSystem) GetFuseContext(ctx context.Context) (uid, gid, pid uint32, err error) {
foo := ctx.Value(contextKey)
state, ok := foo.(opState)
if !ok {
return 0, 0, 0, fmt.Errorf("GetFuseContext called with invalid context: %#v", ctx)
}
inMsg := state.inMsg
header := inMsg.Header()
return header.Uid, header.Gid, header.Pid, nil
}

View File

@ -36,9 +36,9 @@ const (
// A file system with a fixed structure that looks like this:
//
// foo
// dir/
// bar
// foo
// dir/
// bar
//
// The file system is configured with durations that specify how long to allow
// inode entries and attributes to be cached, used when responding to fuse
@ -71,14 +71,15 @@ type CachingFS interface {
// Create a file system that issues cacheable responses according to the
// following rules:
//
// - LookUpInodeResponse.Entry.EntryExpiration is set according to
// * LookUpInodeResponse.Entry.EntryExpiration is set according to
// lookupEntryTimeout.
//
// - GetInodeAttributesResponse.AttributesExpiration is set according to
// * GetInodeAttributesResponse.AttributesExpiration is set according to
// getattrTimeout.
//
// - Nothing else is marked cacheable. (In particular, the attributes
// * Nothing else is marked cacheable. (In particular, the attributes
// returned by LookUpInode are not cacheable.)
//
func NewCachingFS(
lookupEntryTimeout time.Duration,
getattrTimeout time.Duration) (CachingFS, error) {

View File

@ -28,9 +28,9 @@ import (
// Create a file system with a fixed structure that looks like this:
//
// hello
// dir/
// world
// hello
// dir/
// world
//
// Each file contains the string "Hello, world!".
func NewHelloFS(clock timeutil.Clock) (fuse.Server, error) {

View File

@ -33,10 +33,6 @@ type inode struct {
// Mutable state
/////////////////////////
// Name of the inode, only contains relative path.
// For example, if the full path for an inode is /foo/bar/f1, its name is f1.
name string
// The current attributes of this inode.
//
// INVARIANT: attrs.Mode &^ (os.ModePerm|os.ModeDir|os.ModeSymlink) == 0
@ -77,7 +73,7 @@ type inode struct {
// Create a new inode with the supplied attributes, which need not contain
// time-related information (the inode object will take care of that).
func newInode(attrs fuseops.InodeAttributes, name string) *inode {
func newInode(attrs fuseops.InodeAttributes) *inode {
// Update time info.
now := time.Now()
attrs.Mtime = now
@ -85,7 +81,6 @@ func newInode(attrs fuseops.InodeAttributes, name string) *inode {
// Create the object.
return &inode{
name: name,
attrs: attrs,
xattrs: make(map[string][]byte),
}

View File

@ -16,7 +16,6 @@ package memfs
import (
"context"
"encoding/binary"
"fmt"
"io"
"os"
@ -30,11 +29,6 @@ import (
"golang.org/x/sys/unix"
)
const (
FileOpenFlagsXattrName = "fileOpenFlagsXattr"
CheckFileOpenFlagsFileName = "checkFileOpenFlags"
)
type memFS struct {
fuseutil.NotImplementedFileSystem
@ -91,7 +85,7 @@ func NewMemFS(
Gid: gid,
}
fs.inodes[fuseops.RootInodeID] = newInode(rootAttrs, "")
fs.inodes[fuseops.RootInodeID] = newInode(rootAttrs)
// Set up invariant checking.
fs.mu = syncutil.NewInvariantMutex(fs.checkInvariants)
@ -163,9 +157,9 @@ func (fs *memFS) getInodeOrDie(id fuseops.InodeID) *inode {
//
// LOCKS_REQUIRED(fs.mu)
func (fs *memFS) allocateInode(
attrs fuseops.InodeAttributes, name string) (id fuseops.InodeID, inode *inode) {
attrs fuseops.InodeAttributes) (id fuseops.InodeID, inode *inode) {
// Create the inode.
inode = newInode(attrs, name)
inode = newInode(attrs)
// Re-use a free ID if possible. Otherwise mint a new one.
numFree := len(fs.freeInodes)
@ -200,6 +194,10 @@ func (fs *memFS) StatFS(
func (fs *memFS) LookUpInode(
ctx context.Context,
op *fuseops.LookUpInodeOp) error {
if op.OpContext.Pid == 0 {
return fuse.EINVAL
}
fs.mu.Lock()
defer fs.mu.Unlock()
@ -230,6 +228,10 @@ func (fs *memFS) LookUpInode(
func (fs *memFS) GetInodeAttributes(
ctx context.Context,
op *fuseops.GetInodeAttributesOp) error {
if op.OpContext.Pid == 0 {
return fuse.EINVAL
}
fs.mu.Lock()
defer fs.mu.Unlock()
@ -249,6 +251,10 @@ func (fs *memFS) GetInodeAttributes(
func (fs *memFS) SetInodeAttributes(
ctx context.Context,
op *fuseops.SetInodeAttributesOp) error {
if op.OpContext.Pid == 0 {
return fuse.EINVAL
}
fs.mu.Lock()
defer fs.mu.Unlock()
@ -278,6 +284,10 @@ func (fs *memFS) SetInodeAttributes(
func (fs *memFS) MkDir(
ctx context.Context,
op *fuseops.MkDirOp) error {
if op.OpContext.Pid == 0 {
return fuse.EINVAL
}
fs.mu.Lock()
defer fs.mu.Unlock()
@ -300,7 +310,7 @@ func (fs *memFS) MkDir(
}
// Allocate a child.
childID, child := fs.allocateInode(childAttrs, op.Name)
childID, child := fs.allocateInode(childAttrs)
// Add an entry in the parent.
parent.AddChild(childID, op.Name, fuseutil.DT_Directory)
@ -320,6 +330,10 @@ func (fs *memFS) MkDir(
func (fs *memFS) MkNode(
ctx context.Context,
op *fuseops.MkNodeOp) error {
if op.OpContext.Pid == 0 {
return fuse.EINVAL
}
fs.mu.Lock()
defer fs.mu.Unlock()
@ -357,7 +371,7 @@ func (fs *memFS) createFile(
}
// Allocate a child.
childID, child := fs.allocateInode(childAttrs, name)
childID, child := fs.allocateInode(childAttrs)
// Add an entry in the parent.
parent.AddChild(childID, name, fuseutil.DT_File)
@ -378,6 +392,11 @@ func (fs *memFS) createFile(
func (fs *memFS) CreateFile(
ctx context.Context,
op *fuseops.CreateFileOp) (err error) {
if op.OpContext.Pid == 0 {
// CreateFileOp should have a valid pid in context.
return fuse.EINVAL
}
fs.mu.Lock()
defer fs.mu.Unlock()
@ -388,6 +407,10 @@ func (fs *memFS) CreateFile(
func (fs *memFS) CreateSymlink(
ctx context.Context,
op *fuseops.CreateSymlinkOp) error {
if op.OpContext.Pid == 0 {
return fuse.EINVAL
}
fs.mu.Lock()
defer fs.mu.Unlock()
@ -415,7 +438,7 @@ func (fs *memFS) CreateSymlink(
}
// Allocate a child.
childID, child := fs.allocateInode(childAttrs, op.Name)
childID, child := fs.allocateInode(childAttrs)
// Set up its target.
child.target = op.Target
@ -438,6 +461,10 @@ func (fs *memFS) CreateSymlink(
func (fs *memFS) CreateLink(
ctx context.Context,
op *fuseops.CreateLinkOp) error {
if op.OpContext.Pid == 0 {
return fuse.EINVAL
}
fs.mu.Lock()
defer fs.mu.Unlock()
@ -477,6 +504,10 @@ func (fs *memFS) CreateLink(
func (fs *memFS) Rename(
ctx context.Context,
op *fuseops.RenameOp) error {
if op.OpContext.Pid == 0 {
return fuse.EINVAL
}
fs.mu.Lock()
defer fs.mu.Unlock()
@ -518,6 +549,10 @@ func (fs *memFS) Rename(
func (fs *memFS) RmDir(
ctx context.Context,
op *fuseops.RmDirOp) error {
if op.OpContext.Pid == 0 {
return fuse.EINVAL
}
fs.mu.Lock()
defer fs.mu.Unlock()
@ -550,6 +585,10 @@ func (fs *memFS) RmDir(
func (fs *memFS) Unlink(
ctx context.Context,
op *fuseops.UnlinkOp) error {
if op.OpContext.Pid == 0 {
return fuse.EINVAL
}
fs.mu.Lock()
defer fs.mu.Unlock()
@ -577,6 +616,10 @@ func (fs *memFS) Unlink(
func (fs *memFS) OpenDir(
ctx context.Context,
op *fuseops.OpenDirOp) error {
if op.OpContext.Pid == 0 {
return fuse.EINVAL
}
fs.mu.Lock()
defer fs.mu.Unlock()
@ -595,6 +638,10 @@ func (fs *memFS) OpenDir(
func (fs *memFS) ReadDir(
ctx context.Context,
op *fuseops.ReadDirOp) error {
if op.OpContext.Pid == 0 {
return fuse.EINVAL
}
fs.mu.Lock()
defer fs.mu.Unlock()
@ -610,6 +657,11 @@ func (fs *memFS) ReadDir(
func (fs *memFS) OpenFile(
ctx context.Context,
op *fuseops.OpenFileOp) error {
if op.OpContext.Pid == 0 {
// OpenFileOp should have a valid pid in context.
return fuse.EINVAL
}
fs.mu.Lock()
defer fs.mu.Unlock()
@ -622,27 +674,16 @@ func (fs *memFS) OpenFile(
panic("Found non-file.")
}
if inode.name == CheckFileOpenFlagsFileName {
// For testing purpose only.
// Set attribute (name=fileOpenFlagsXattr, value=OpenFlags) to test whether
// we set OpenFlags correctly. The value is checked in test with getXattr.
value := make([]byte, 4)
binary.LittleEndian.PutUint32(value, uint32(op.OpenFlags))
err := fs.setXattrHelper(inode, &fuseops.SetXattrOp{
Name: FileOpenFlagsXattrName,
Value: value,
})
if err != nil {
panic("unable to set fileOpenFlagsXattr")
}
}
return nil
}
func (fs *memFS) ReadFile(
ctx context.Context,
op *fuseops.ReadFileOp) error {
if op.OpContext.Pid == 0 {
return fuse.EINVAL
}
fs.mu.Lock()
defer fs.mu.Unlock()
@ -664,6 +705,10 @@ func (fs *memFS) ReadFile(
func (fs *memFS) WriteFile(
ctx context.Context,
op *fuseops.WriteFileOp) error {
if op.OpContext.Pid == 0 {
return fuse.EINVAL
}
fs.mu.Lock()
defer fs.mu.Unlock()
@ -679,12 +724,20 @@ func (fs *memFS) WriteFile(
func (fs *memFS) FlushFile(
ctx context.Context,
op *fuseops.FlushFileOp) (err error) {
if op.OpContext.Pid == 0 {
// FlushFileOp should have a valid pid in context.
return fuse.EINVAL
}
return
}
func (fs *memFS) ReadSymlink(
ctx context.Context,
op *fuseops.ReadSymlinkOp) error {
if op.OpContext.Pid == 0 {
return fuse.EINVAL
}
fs.mu.Lock()
defer fs.mu.Unlock()
@ -699,6 +752,10 @@ func (fs *memFS) ReadSymlink(
func (fs *memFS) GetXattr(ctx context.Context,
op *fuseops.GetXattrOp) error {
if op.OpContext.Pid == 0 {
return fuse.EINVAL
}
fs.mu.Lock()
defer fs.mu.Unlock()
@ -719,6 +776,10 @@ func (fs *memFS) GetXattr(ctx context.Context,
func (fs *memFS) ListXattr(ctx context.Context,
op *fuseops.ListXattrOp) error {
if op.OpContext.Pid == 0 {
return fuse.EINVAL
}
fs.mu.Lock()
defer fs.mu.Unlock()
@ -742,6 +803,10 @@ func (fs *memFS) ListXattr(ctx context.Context,
func (fs *memFS) RemoveXattr(ctx context.Context,
op *fuseops.RemoveXattrOp) error {
if op.OpContext.Pid == 0 {
return fuse.EINVAL
}
fs.mu.Lock()
defer fs.mu.Unlock()
inode := fs.getInodeOrDie(op.Inode)
@ -756,15 +821,14 @@ func (fs *memFS) RemoveXattr(ctx context.Context,
func (fs *memFS) SetXattr(ctx context.Context,
op *fuseops.SetXattrOp) error {
if op.OpContext.Pid == 0 {
return fuse.EINVAL
}
fs.mu.Lock()
defer fs.mu.Unlock()
inode := fs.getInodeOrDie(op.Inode)
return fs.setXattrHelper(inode, op)
}
// Required to hold fs.mu
func (fs *memFS) setXattrHelper(inode *inode, op *fuseops.SetXattrOp) error {
_, ok := inode.xattrs[op.Name]
switch op.Flags {
@ -786,6 +850,10 @@ func (fs *memFS) setXattrHelper(inode *inode, op *fuseops.SetXattrOp) error {
func (fs *memFS) Fallocate(ctx context.Context,
op *fuseops.FallocateOp) error {
if op.OpContext.Pid == 0 {
return fuse.EINVAL
}
fs.mu.Lock()
defer fs.mu.Unlock()
inode := fs.getInodeOrDie(op.Inode)

View File

@ -1,4 +1,3 @@
//go:build go1.8
// +build go1.8
package memfs_test

View File

@ -1,4 +1,3 @@
//go:build !go1.8
// +build !go1.8
package memfs_test

View File

@ -16,7 +16,6 @@ package memfs_test
import (
"bytes"
"encoding/binary"
"io"
"io/ioutil"
"os"
@ -32,7 +31,6 @@ import (
fallocate "github.com/detailyang/go-fallocate"
"github.com/jacobsa/fuse"
"github.com/jacobsa/fuse/fusetesting"
"github.com/jacobsa/fuse/internal/fusekernel"
"github.com/jacobsa/fuse/samples"
"github.com/jacobsa/fuse/samples/memfs"
. "github.com/jacobsa/oglematchers"
@ -90,15 +88,6 @@ func applyUmask(m os.FileMode) os.FileMode {
return m &^ os.FileMode(umask)
}
func (t *MemFSTest) checkOpenFlagsXattr(
fileName string, expectedOpenFlags fusekernel.OpenFlags) {
dest := make([]byte, 4)
_, err := unix.Getxattr(fileName, memfs.FileOpenFlagsXattrName, dest)
AssertEq(nil, err)
openFlags := binary.LittleEndian.Uint32(dest)
AssertEq(openFlags, uint32(expectedOpenFlags))
}
////////////////////////////////////////////////////////////////////////
// Boilerplate
////////////////////////////////////////////////////////////////////////
@ -303,7 +292,7 @@ func (t *MemFSTest) CreateNewFile_InRoot() {
var stat *syscall.Stat_t
// Write a file.
fileName := path.Join(t.Dir, memfs.CheckFileOpenFlagsFileName)
fileName := path.Join(t.Dir, "foo")
const contents = "Hello\x00world"
createTime := time.Now()
@ -315,7 +304,7 @@ func (t *MemFSTest) CreateNewFile_InRoot() {
stat = fi.Sys().(*syscall.Stat_t)
AssertEq(nil, err)
ExpectEq(memfs.CheckFileOpenFlagsFileName, fi.Name())
ExpectEq("foo", fi.Name())
ExpectEq(len(contents), fi.Size())
ExpectEq(applyUmask(0400), fi.Mode())
ExpectThat(fi, fusetesting.MtimeIsWithin(createTime, timeSlop))
@ -332,7 +321,6 @@ func (t *MemFSTest) CreateNewFile_InRoot() {
slice, err := ioutil.ReadFile(fileName)
AssertEq(nil, err)
ExpectEq(contents, string(slice))
t.checkOpenFlagsXattr(fileName, fusekernel.OpenReadOnly)
}
func (t *MemFSTest) CreateNewFile_InSubDir() {
@ -384,7 +372,7 @@ func (t *MemFSTest) ModifyExistingFile_InRoot() {
var stat *syscall.Stat_t
// Write a file.
fileName := path.Join(t.Dir, memfs.CheckFileOpenFlagsFileName)
fileName := path.Join(t.Dir, "foo")
createTime := time.Now()
err = ioutil.WriteFile(fileName, []byte("Hello, world!"), 0600)
@ -394,7 +382,6 @@ func (t *MemFSTest) ModifyExistingFile_InRoot() {
f, err := os.OpenFile(fileName, os.O_WRONLY, 0400)
t.ToClose = append(t.ToClose, f)
AssertEq(nil, err)
t.checkOpenFlagsXattr(fileName, fusekernel.OpenWriteOnly)
modifyTime := time.Now()
n, err = f.WriteAt([]byte("H"), 0)
@ -406,7 +393,7 @@ func (t *MemFSTest) ModifyExistingFile_InRoot() {
stat = fi.Sys().(*syscall.Stat_t)
AssertEq(nil, err)
ExpectEq(memfs.CheckFileOpenFlagsFileName, fi.Name())
ExpectEq("foo", fi.Name())
ExpectEq(len("Hello, world!"), fi.Size())
ExpectEq(applyUmask(0600), fi.Mode())
ExpectThat(fi, fusetesting.MtimeIsWithin(modifyTime, timeSlop))
@ -423,7 +410,6 @@ func (t *MemFSTest) ModifyExistingFile_InRoot() {
slice, err := ioutil.ReadFile(fileName)
AssertEq(nil, err)
ExpectEq("Hello, world!", string(slice))
t.checkOpenFlagsXattr(fileName, fusekernel.OpenReadOnly)
}
func (t *MemFSTest) ModifyExistingFile_InSubDir() {
@ -846,7 +832,7 @@ func (t *MemFSTest) AppendMode() {
buf := make([]byte, 1024)
// Create a file with some contents.
fileName := path.Join(t.Dir, memfs.CheckFileOpenFlagsFileName)
fileName := path.Join(t.Dir, "foo")
err = ioutil.WriteFile(fileName, []byte("Jello, "), 0600)
AssertEq(nil, err)
@ -854,7 +840,6 @@ func (t *MemFSTest) AppendMode() {
f, err := os.OpenFile(fileName, os.O_RDWR|os.O_APPEND, 0600)
t.ToClose = append(t.ToClose, f)
AssertEq(nil, err)
t.checkOpenFlagsXattr(fileName, fusekernel.OpenReadWrite)
// Seek to somewhere silly and then write.
off, err = f.Seek(2, 0)
@ -870,6 +855,10 @@ func (t *MemFSTest) AppendMode() {
AssertEq(nil, err)
ExpectEq(13, off)
off, err = getFileOffset(f)
AssertEq(nil, err)
ExpectEq(13, off)
// Read back the contents of the file, which should be correct even though we
// seeked to a silly place before writing the world part.
//
@ -922,7 +911,7 @@ func (t *MemFSTest) ReadsPastEndOfFile() {
func (t *MemFSTest) Truncate_Smaller() {
var err error
fileName := path.Join(t.Dir, memfs.CheckFileOpenFlagsFileName)
fileName := path.Join(t.Dir, "foo")
// Create a file.
err = ioutil.WriteFile(fileName, []byte("taco"), 0600)
@ -932,7 +921,6 @@ func (t *MemFSTest) Truncate_Smaller() {
f, err := os.OpenFile(fileName, os.O_RDWR, 0)
t.ToClose = append(t.ToClose, f)
AssertEq(nil, err)
t.checkOpenFlagsXattr(fileName, fusekernel.OpenReadWrite)
// Truncate it.
err = f.Truncate(2)
@ -947,7 +935,6 @@ func (t *MemFSTest) Truncate_Smaller() {
contents, err := ioutil.ReadFile(fileName)
AssertEq(nil, err)
ExpectEq("ta", string(contents))
t.checkOpenFlagsXattr(fileName, fusekernel.OpenReadOnly)
}
func (t *MemFSTest) Truncate_SameSize() {

View File

@ -1,68 +0,0 @@
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"context"
"flag"
"log"
"os/user"
"strconv"
"github.com/jacobsa/fuse"
"github.com/jacobsa/fuse/samples/memfs"
)
var fMountPoint = flag.String("mount_point", "", "Path to mount point.")
func main() {
flag.Parse()
if *fMountPoint == "" {
log.Fatalf("You must set --mount_point.")
}
user, err := user.Current()
if err != nil {
panic(err)
}
uid, err := strconv.ParseUint(user.Uid, 10, 32)
if err != nil {
panic(err)
}
gid, err := strconv.ParseUint(user.Gid, 10, 32)
if err != nil {
panic(err)
}
server := memfs.NewMemFS(uint32(uid), uint32(gid))
cfg := &fuse.MountConfig{
// Disable writeback caching so that pid is always available in OpContext
DisableWritebackCaching: true,
}
mfs, err := fuse.Mount(*fMountPoint, server, cfg)
if err != nil {
log.Fatalf("Mount: %v", err)
}
// Wait for it to be unmounted.
if err = mfs.Join(context.Background()); err != nil {
log.Fatalf("Join: %v", err)
}
}

View File

@ -3,31 +3,21 @@ package main
import (
"context"
"flag"
"fmt"
"log"
"os"
"github.com/jacobsa/fuse"
"github.com/jacobsa/fuse/samples/readbenchfs"
"log"
"net/http"
_ "net/http/pprof"
"os"
)
var fMountPoint = flag.String("mount_point", "", "Path to mount point.")
var fReadOnly = flag.Bool("read_only", false, "Mount in read-only mode.")
var fVectored = flag.Bool("vectored", false, "Use vectored read.")
var fDebug = flag.Bool("debug", false, "Enable debug logging.")
var fPprof = flag.Int("pprof", 0, "Enable pprof profiling on the specified port.")
func main() {
flag.Parse()
if *fPprof != 0 {
go func() {
fmt.Printf("%v", http.ListenAndServe(fmt.Sprintf("localhost:%v", *fPprof), nil))
}()
}
server, err := readbenchfs.NewReadBenchServer(*fVectored)
server, err := readbenchfs.NewReadBenchServer()
if err != nil {
log.Fatalf("makeFS: %v", err)
}
@ -38,7 +28,7 @@ func main() {
}
cfg := &fuse.MountConfig{
ReadOnly: *fReadOnly,
ReadOnly: *fReadOnly,
UseVectoredRead: *fVectored,
}

View File

@ -16,9 +16,9 @@ package readbenchfs
import (
"golang.org/x/net/context"
"os"
"io"
"math/rand"
"os"
"github.com/jacobsa/fuse"
"github.com/jacobsa/fuse/fuseops"
@ -27,38 +27,39 @@ import (
type readBenchFS struct {
fuseutil.NotImplementedFileSystem
buf []byte
useVectoredRead bool
buf []byte
}
// 1 TB
const fileSize = 1024 * 1024 * 1024 * 1024
const FILE_SIZE = 1024*1024*1024*1024
var _ fuseutil.FileSystem = &readBenchFS{}
func NewReadBenchServer(useVectoredRead bool) (server fuse.Server, err error) {
func NewReadBenchServer() (server fuse.Server, err error) {
// 1 GB of random data to exceed CPU cache
buf := make([]byte, 1024*1024*1024)
rand.Read(buf)
server = fuseutil.NewFileSystemServer(&readBenchFS{
buf: buf,
useVectoredRead: useVectoredRead,
buf: buf,
})
return
}
func (fs *readBenchFS) StatFS(ctx context.Context, op *fuseops.StatFSOp) error {
func (fs *readBenchFS) StatFS(
ctx context.Context,
op *fuseops.StatFSOp) error {
return nil
}
func (fs *readBenchFS) LookUpInode(ctx context.Context, op *fuseops.LookUpInodeOp) error {
func (fs *readBenchFS) LookUpInode(
ctx context.Context,
op *fuseops.LookUpInodeOp) error {
if op.Name == "test" {
op.Entry = fuseops.ChildInodeEntry{
Child: 2,
Attributes: fuseops.InodeAttributes{
Size: fileSize,
Size: FILE_SIZE,
Nlink: 1,
Mode: 0444,
Mode: 0444,
},
}
return nil
@ -66,30 +67,36 @@ func (fs *readBenchFS) LookUpInode(ctx context.Context, op *fuseops.LookUpInodeO
return fuse.ENOENT
}
func (fs *readBenchFS) GetInodeAttributes(ctx context.Context, op *fuseops.GetInodeAttributesOp) error {
func (fs *readBenchFS) GetInodeAttributes(
ctx context.Context,
op *fuseops.GetInodeAttributesOp) error {
if op.Inode == 1 {
op.Attributes = fuseops.InodeAttributes{
Nlink: 1,
Mode: 0755 | os.ModeDir,
Mode: 0755 | os.ModeDir,
}
return nil
} else if op.Inode == 2 {
op.Attributes = fuseops.InodeAttributes{
Size: fileSize,
Size: FILE_SIZE,
Nlink: 1,
Mode: 0444,
Mode: 0444,
}
return nil
}
return fuse.ENOENT
}
func (fs *readBenchFS) OpenDir(ctx context.Context, op *fuseops.OpenDirOp) error {
func (fs *readBenchFS) OpenDir(
ctx context.Context,
op *fuseops.OpenDirOp) error {
// Allow opening any directory.
return nil
}
func (fs *readBenchFS) ReadDir(ctx context.Context, op *fuseops.ReadDirOp) error {
func (fs *readBenchFS) ReadDir(
ctx context.Context,
op *fuseops.ReadDirOp) error {
if op.Inode != 1 {
return fuse.ENOENT
}
@ -114,57 +121,93 @@ func (fs *readBenchFS) ReadDir(ctx context.Context, op *fuseops.ReadDirOp) error
return nil
}
func (fs *readBenchFS) OpenFile(ctx context.Context, op *fuseops.OpenFileOp) error {
func (fs *readBenchFS) OpenFile(
ctx context.Context,
op *fuseops.OpenFileOp) error {
// Allow opening any file.
return nil
}
func (fs *readBenchFS) ReadFile(ctx context.Context, op *fuseops.ReadFileOp) error {
if op.Offset > fileSize {
func (fs *readBenchFS) ReadFile(
ctx context.Context,
op *fuseops.ReadFileOp) error {
if op.Offset > FILE_SIZE {
return io.EOF
}
end := op.Offset + op.Size
if end > fileSize {
end = fileSize
end := op.Offset+int64(len(op.Dst))
if end > FILE_SIZE {
end = FILE_SIZE
}
buflen := int64(len(fs.buf))
for pos := op.Offset; pos < end; {
s := pos % buflen
e := buflen
if e-s > end-pos {
e = s + end - pos
e = s+end-pos
}
if fs.useVectoredRead {
op.Data = append(op.Data, fs.buf[s:e])
} else {
copy(op.Dst[pos-op.Offset:], fs.buf[s:])
}
pos = op.Offset + e
copy(op.Dst[pos-op.Offset : ], fs.buf[s : ])
pos = op.Offset+e
}
op.BytesRead = int(end - op.Offset)
op.BytesRead = int(end-op.Offset)
return nil
}
func (fs *readBenchFS) ReleaseDirHandle(ctx context.Context, op *fuseops.ReleaseDirHandleOp) error {
func (fs *readBenchFS) VectoredRead(
ctx context.Context,
op *fuseops.VectoredReadOp) error {
if op.Offset > FILE_SIZE {
return io.EOF
}
end := op.Offset+op.Size
if end > FILE_SIZE {
end = FILE_SIZE
}
buflen := int64(len(fs.buf))
for pos := op.Offset; pos < end; {
s := pos % buflen
e := buflen
if e-s > end-pos {
e = s+end-pos
}
op.Data = append(op.Data, fs.buf[s : e])
pos = op.Offset+e
}
op.BytesRead = int(end-op.Offset)
return nil
}
func (fs *readBenchFS) GetXattr(ctx context.Context, op *fuseops.GetXattrOp) error {
func (fs *readBenchFS) ReleaseDirHandle(
ctx context.Context,
op *fuseops.ReleaseDirHandleOp) error {
return nil
}
func (fs *readBenchFS) ListXattr(ctx context.Context, op *fuseops.ListXattrOp) error {
func (fs *readBenchFS) GetXattr(
ctx context.Context,
op *fuseops.GetXattrOp) error {
return nil
}
func (fs *readBenchFS) ForgetInode(ctx context.Context, op *fuseops.ForgetInodeOp) error {
func (fs *readBenchFS) ListXattr(
ctx context.Context,
op *fuseops.ListXattrOp) error {
return nil
}
func (fs *readBenchFS) ReleaseFileHandle(ctx context.Context, op *fuseops.ReleaseFileHandleOp) error {
func (fs *readBenchFS) ForgetInode(
ctx context.Context,
op *fuseops.ForgetInodeOp) error {
return nil
}
func (fs *readBenchFS) FlushFile(ctx context.Context, op *fuseops.FlushFileOp) error {
func (fs *readBenchFS) ReleaseFileHandle(
ctx context.Context,
op *fuseops.ReleaseFileHandleOp) error {
return nil
}
func (fs *readBenchFS) FlushFile(
ctx context.Context,
op *fuseops.FlushFileOp) error {
return nil
}

View File

@ -26,8 +26,9 @@ import (
// Sample output:
//
// Filesystem 1024-blocks Used Available Capacity iused ifree %iused Mounted on
// fake@bucket 32 16 16 50% 0 0 100% /Users/jacobsa/tmp/mp
// Filesystem 1024-blocks Used Available Capacity iused ifree %iused Mounted on
// fake@bucket 32 16 16 50% 0 0 100% /Users/jacobsa/tmp/mp
//
var gDfOutputRegexp = regexp.MustCompile(`^\S+\s+(\d+)\s+(\d+)\s+(\d+)\s+\d+%\s+\d+\s+\d+\s+\d+%.*$`)
////////////////////////////////////////////////////////////////////////

View File

@ -26,8 +26,9 @@ import (
// Sample output:
//
// Filesystem 1K-blocks Used Available Use% Mounted on
// some_fuse_file_system 512 64 384 15% /tmp/sample_test001288095
// Filesystem 1K-blocks Used Available Use% Mounted on
// some_fuse_file_system 512 64 384 15% /tmp/sample_test001288095
//
var gDfOutputRegexp = regexp.MustCompile(`^\S+\s+(\d+)\s+(\d+)\s+(\d+)\s+\d+%.*$`)
////////////////////////////////////////////////////////////////////////

View File

@ -1,4 +1,3 @@
//go:build !linux
// +build !linux
package fuse