Compare commits

..

1 Commits

Author SHA1 Message Date
Vitaliy Filippov 003d54287c Allow to use "zero-copy" writes 2022-05-17 23:15:26 +03:00
29 changed files with 330 additions and 993 deletions

View File

@ -17,7 +17,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v2.1.4
with:
go-version: ^1.19
go-version: ^1.17
id: go
# Check the codebase has been formatted by `gofmt`.
# We run two `gofmt` commands for different purposes:
@ -38,7 +38,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v2.1.4
with:
go-version: ^1.19
go-version: ^1.17
id: go
- name: Install fuse
run: sudo apt-get update && sudo apt-get install -y fuse3 libfuse-dev
@ -56,7 +56,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v2.1.4
with:
go-version: ^1.19
go-version: ^1.17
id: go
- name: Install macfuse
run: HOMEBREW_NO_AUTO_UPDATE=1 brew install macfuse

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
@ -194,11 +193,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 +369,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 +517,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.OutHeaderBytes())
}
if err != nil && c.errorLogger != nil {
c.errorLogger.Printf("writeMessage: %v %v", err, outMsg.OutHeaderBytes())
}
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},
}
@ -303,15 +268,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},
}
@ -341,8 +299,6 @@ func convertInMessage(
}
o = to
case fusekernel.OpReaddirplus:
fallthrough
case fusekernel.OpReaddir:
in := (*fusekernel.ReadIn)(inMsg.Consume(fusekernel.ReadInSize(protocol)))
if in == nil {
@ -353,7 +309,6 @@ 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
@ -610,42 +565,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 +595,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 +638,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 +676,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
@ -905,13 +818,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 +825,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

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

@ -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
@ -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.
@ -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
@ -658,8 +620,6 @@ type OpenFileOp struct {
// advance, for example, because contents are generated on the fly.
UseDirectIO bool
OpenFlags fusekernel.OpenFlags
OpContext OpContext
}
@ -708,13 +668,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 +738,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 +762,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 +772,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
@ -982,127 +942,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
@ -63,9 +62,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 +94,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 +151,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)
@ -241,9 +219,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 {
@ -204,14 +198,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

@ -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,11 +151,6 @@ 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.

View File

@ -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

@ -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)
@ -922,7 +907,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 +917,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 +931,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

@ -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+%.*$`)
////////////////////////////////////////////////////////////////////////