Compare commits
41 Commits
geesefs-0-
...
master
Author | SHA1 | Date |
---|---|---|
Vitaliy Filippov | 8d4d89b65d | |
Vitaliy Filippov | efe41d860d | |
Vitaliy Filippov | a307ab844b | |
Vitaliy Filippov | 8312d62874 | |
Vitaliy Filippov | 2d9f6f635e | |
Vitaliy Filippov | 8156bfadb4 | |
Vitaliy Filippov | caac53a719 | |
Vitaliy Filippov | 0f0f6e3670 | |
Vitaliy Filippov | 062810a628 | |
Vitaliy Filippov | c02e24f8ce | |
Vitaliy Filippov | 76071bba22 | |
Vitaliy Filippov | f54cd84222 | |
Eric Gouyer | a4cd154343 | |
Eric Gouyer | 5e958a41f6 | |
Eric Gouyer | 2681cd5156 | |
Doug Schaapveld | 63437da750 | |
Avi | c62d7682a6 | |
Michael Stapelberg | 4e67748df3 | |
Michael Stapelberg | 66d6bd9e7b | |
Mei Gui | 226fec2ce9 | |
Mei Gui | 9cc4ff0bc9 | |
Vitaliy Filippov | 13117049f3 | |
Ben Linsay | 21122235c7 | |
Doychin Atanasov | 37d63df227 | |
Michael Stapelberg | 468f285a46 | |
Michael Stapelberg | 48612565d5 | |
Michael Stapelberg | 1c9fe7bc84 | |
Vitaliy Filippov | 84920d11dd | |
Vitaliy Filippov | c818f6216b | |
Vitaliy Filippov | da71c70600 | |
Jakob Waibel | c4197873da | |
Vitaliy Filippov | 108387eec1 | |
Jakob Waibel | 1b9b09fd17 | |
Jakob Waibel | ffd6c474e8 | |
Tetsuo Kiso | 7c4418392f | |
Tetsuo Kiso | c0eeb00f17 | |
Tetsuo Kiso | 233e2c82a4 | |
Tetsuo Kiso | a621cd4bb0 | |
Oliver Le Zhuang | 8da59ba998 | |
Vitaliy Filippov | c75d3f26fc | |
mcdhee-msft | 95fc8d1181 |
|
@ -0,0 +1,67 @@
|
||||||
|
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.
|
|
@ -7,6 +7,7 @@
|
||||||
*.so
|
*.so
|
||||||
|
|
||||||
# Folders
|
# Folders
|
||||||
|
.idea/
|
||||||
_obj
|
_obj
|
||||||
_test
|
_test
|
||||||
|
|
||||||
|
|
35
.travis.yml
35
.travis.yml
|
@ -1,35 +0,0 @@
|
||||||
# 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
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
[![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)
|
[![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.
|
This package allows for writing and mounting user-space file systems from Go.
|
||||||
|
|
|
@ -39,17 +39,17 @@ var contextKey interface{} = contextKeyType(0)
|
||||||
//
|
//
|
||||||
// As of 2015-03-26, the behavior in the kernel is:
|
// 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.
|
// 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
|
// backing_dev_info::ra_pages to the min of that value and what was sent
|
||||||
// in the request's max_readahead field.
|
// 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.
|
// 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.
|
// Reading a page at a time is a drag. Ask for a larger size.
|
||||||
const maxReadahead = 1 << 20
|
const maxReadahead = 1 << 20
|
||||||
|
@ -151,6 +151,7 @@ func (c *Connection) Init() error {
|
||||||
cacheSymlinks := initOp.Flags&fusekernel.InitCacheSymlinks > 0
|
cacheSymlinks := initOp.Flags&fusekernel.InitCacheSymlinks > 0
|
||||||
noOpenSupport := initOp.Flags&fusekernel.InitNoOpenSupport > 0
|
noOpenSupport := initOp.Flags&fusekernel.InitNoOpenSupport > 0
|
||||||
noOpendirSupport := initOp.Flags&fusekernel.InitNoOpendirSupport > 0
|
noOpendirSupport := initOp.Flags&fusekernel.InitNoOpendirSupport > 0
|
||||||
|
readdirplusSupport := initOp.Flags&fusekernel.InitDoReaddirplus > 0
|
||||||
|
|
||||||
// Respond to the init op.
|
// Respond to the init op.
|
||||||
initOp.Library = c.protocol
|
initOp.Library = c.protocol
|
||||||
|
@ -161,6 +162,11 @@ func (c *Connection) Init() error {
|
||||||
|
|
||||||
// Tell the kernel not to use pitifully small 4 KiB writes.
|
// Tell the kernel not to use pitifully small 4 KiB writes.
|
||||||
initOp.Flags |= fusekernel.InitBigWrites
|
initOp.Flags |= fusekernel.InitBigWrites
|
||||||
|
|
||||||
|
if c.cfg.EnableAsyncReads {
|
||||||
|
initOp.Flags |= fusekernel.InitAsyncRead
|
||||||
|
}
|
||||||
|
|
||||||
// kernel 4.20 increases the max from 32 -> 256
|
// kernel 4.20 increases the max from 32 -> 256
|
||||||
initOp.Flags |= fusekernel.InitMaxPages
|
initOp.Flags |= fusekernel.InitMaxPages
|
||||||
initOp.MaxPages = 256
|
initOp.MaxPages = 256
|
||||||
|
@ -188,6 +194,11 @@ func (c *Connection) Init() error {
|
||||||
initOp.Flags |= fusekernel.InitNoOpendirSupport
|
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)
|
c.Reply(ctx, nil)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -364,18 +375,24 @@ func (c *Connection) readMessage() (*buffer.InMessage, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write the supplied message to the kernel.
|
// Write the supplied message to the kernel.
|
||||||
func (c *Connection) writeMessage(msg []byte) error {
|
func (c *Connection) writeMessage(outMsg *buffer.OutMessage) error {
|
||||||
// Avoid the retry loop in os.File.Write.
|
var err error
|
||||||
n, err := syscall.Write(int(c.dev.Fd()), msg)
|
var n int
|
||||||
if err != nil {
|
expectedLen := outMsg.Len()
|
||||||
return err
|
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())
|
||||||
}
|
}
|
||||||
|
if err == nil && n != expectedLen {
|
||||||
if n != len(msg) {
|
err = fmt.Errorf("Wrote %d bytes; expected %d", n, expectedLen)
|
||||||
return fmt.Errorf("Wrote %d bytes; expected %d", n, len(msg))
|
|
||||||
}
|
}
|
||||||
|
if err != nil && c.errorLogger != nil {
|
||||||
return nil
|
c.errorLogger.Printf("writeMessage: %v %v", err, outMsg.OutHeaderBytes())
|
||||||
|
}
|
||||||
|
outMsg.Sglist = nil
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadOp consumes the next op from the kernel process, returning the op and a
|
// ReadOp consumes the next op from the kernel process, returning the op and a
|
||||||
|
@ -512,19 +529,21 @@ func (c *Connection) Reply(ctx context.Context, opErr error) {
|
||||||
noResponse := c.kernelResponse(outMsg, inMsg.Header().Unique, op, opErr)
|
noResponse := c.kernelResponse(outMsg, inMsg.Header().Unique, op, opErr)
|
||||||
|
|
||||||
if !noResponse {
|
if !noResponse {
|
||||||
var err error
|
c.writeMessage(outMsg)
|
||||||
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
|
// Close the connection. Must not be called until operations that were read
|
||||||
// from the connection have been responded to.
|
// from the connection have been responded to.
|
||||||
func (c *Connection) close() error {
|
func (c *Connection) close() error {
|
||||||
|
|
345
conversions.go
345
conversions.go
|
@ -76,12 +76,20 @@ func convertInMessage(
|
||||||
o = to
|
o = to
|
||||||
|
|
||||||
valid := fusekernel.SetattrValid(in.Valid)
|
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 {
|
if valid&fusekernel.SetattrSize != 0 {
|
||||||
to.Size = &in.Size
|
to.Size = &in.Size
|
||||||
}
|
}
|
||||||
|
|
||||||
if valid&fusekernel.SetattrMode != 0 {
|
if valid&fusekernel.SetattrMode != 0 {
|
||||||
mode := convertFileMode(in.Mode)
|
mode := fuseops.ConvertFileMode(in.Mode)
|
||||||
to.Mode = &mode
|
to.Mode = &mode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -113,6 +121,32 @@ func convertInMessage(
|
||||||
OpContext: fuseops.OpContext{Pid: inMsg.Header().Pid},
|
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:
|
case fusekernel.OpMkdir:
|
||||||
in := (*fusekernel.MkdirIn)(inMsg.Consume(fusekernel.MkdirInSize(protocol)))
|
in := (*fusekernel.MkdirIn)(inMsg.Consume(fusekernel.MkdirInSize(protocol)))
|
||||||
if in == nil {
|
if in == nil {
|
||||||
|
@ -136,7 +170,7 @@ func convertInMessage(
|
||||||
// the fact that this is a directory is implicit in the fact that the
|
// 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
|
// opcode is mkdir. But we want the correct mode to go through, so ensure
|
||||||
// that os.ModeDir is set.
|
// that os.ModeDir is set.
|
||||||
Mode: convertFileMode(in.Mode) | os.ModeDir,
|
Mode: fuseops.ConvertFileMode(in.Mode) | os.ModeDir,
|
||||||
OpContext: fuseops.OpContext{Pid: inMsg.Header().Pid},
|
OpContext: fuseops.OpContext{Pid: inMsg.Header().Pid},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,7 +190,8 @@ func convertInMessage(
|
||||||
o = &fuseops.MkNodeOp{
|
o = &fuseops.MkNodeOp{
|
||||||
Parent: fuseops.InodeID(inMsg.Header().Nodeid),
|
Parent: fuseops.InodeID(inMsg.Header().Nodeid),
|
||||||
Name: string(name),
|
Name: string(name),
|
||||||
Mode: convertFileMode(in.Mode),
|
Mode: fuseops.ConvertFileMode(in.Mode),
|
||||||
|
Rdev: in.Rdev,
|
||||||
OpContext: fuseops.OpContext{Pid: inMsg.Header().Pid},
|
OpContext: fuseops.OpContext{Pid: inMsg.Header().Pid},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,7 +211,7 @@ func convertInMessage(
|
||||||
o = &fuseops.CreateFileOp{
|
o = &fuseops.CreateFileOp{
|
||||||
Parent: fuseops.InodeID(inMsg.Header().Nodeid),
|
Parent: fuseops.InodeID(inMsg.Header().Nodeid),
|
||||||
Name: string(name),
|
Name: string(name),
|
||||||
Mode: convertFileMode(in.Mode),
|
Mode: fuseops.ConvertFileMode(in.Mode),
|
||||||
OpContext: fuseops.OpContext{Pid: inMsg.Header().Pid},
|
OpContext: fuseops.OpContext{Pid: inMsg.Header().Pid},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -210,11 +245,15 @@ func convertInMessage(
|
||||||
// closed-source macfuse 4.x has broken compatibility with osxfuse 3.x:
|
// closed-source macfuse 4.x has broken compatibility with osxfuse 3.x:
|
||||||
// it passes an additional 64-bit field (flags) after RenameIn regardless
|
// it passes an additional 64-bit field (flags) after RenameIn regardless
|
||||||
// that we don't enable the support for RENAME_SWAP/RENAME_EXCL
|
// 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
|
// the simplest fix is just to check for the presence of all-zero flags
|
||||||
if len(names) >= 8 &&
|
if len(names) >= 8 &&
|
||||||
names[0] == 0 && names[1] == 0 && names[2] == 0 && names[3] == 0 &&
|
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[4] == 0 && names[5] == 0 && names[6] == 0 && names[7] == 0 {
|
||||||
names = names[8 : ]
|
names = names[8:]
|
||||||
}
|
}
|
||||||
// names should be "old\x00new\x00"
|
// names should be "old\x00new\x00"
|
||||||
if len(names) < 4 {
|
if len(names) < 4 {
|
||||||
|
@ -264,8 +303,15 @@ func convertInMessage(
|
||||||
}
|
}
|
||||||
|
|
||||||
case fusekernel.OpOpen:
|
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{
|
o = &fuseops.OpenFileOp{
|
||||||
Inode: fuseops.InodeID(inMsg.Header().Nodeid),
|
Inode: fuseops.InodeID(inMsg.Header().Nodeid),
|
||||||
|
OpenFlags: fusekernel.OpenFlags(in.Flags),
|
||||||
OpContext: fuseops.OpContext{Pid: inMsg.Header().Pid},
|
OpContext: fuseops.OpContext{Pid: inMsg.Header().Pid},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -281,29 +327,22 @@ func convertInMessage(
|
||||||
return nil, errors.New("Corrupt OpRead")
|
return nil, errors.New("Corrupt OpRead")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !config.UseVectoredRead {
|
to := &fuseops.ReadFileOp{
|
||||||
// Use part of the incoming message storage as a read buffer
|
Inode: fuseops.InodeID(inMsg.Header().Nodeid),
|
||||||
buf := inMsg.GetFree(int(in.Size))
|
Handle: fuseops.HandleID(in.Fh),
|
||||||
to := &fuseops.ReadFileOp{
|
Offset: int64(in.Offset),
|
||||||
Inode: fuseops.InodeID(inMsg.Header().Nodeid),
|
Size: int64(in.Size),
|
||||||
Handle: fuseops.HandleID(in.Fh),
|
OpContext: fuseops.OpContext{Pid: inMsg.Header().Pid},
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
o = to
|
||||||
|
|
||||||
|
case fusekernel.OpReaddirplus:
|
||||||
|
fallthrough
|
||||||
case fusekernel.OpReaddir:
|
case fusekernel.OpReaddir:
|
||||||
in := (*fusekernel.ReadIn)(inMsg.Consume(fusekernel.ReadInSize(protocol)))
|
in := (*fusekernel.ReadIn)(inMsg.Consume(fusekernel.ReadInSize(protocol)))
|
||||||
if in == nil {
|
if in == nil {
|
||||||
|
@ -314,12 +353,13 @@ func convertInMessage(
|
||||||
Inode: fuseops.InodeID(inMsg.Header().Nodeid),
|
Inode: fuseops.InodeID(inMsg.Header().Nodeid),
|
||||||
Handle: fuseops.HandleID(in.Fh),
|
Handle: fuseops.HandleID(in.Fh),
|
||||||
Offset: fuseops.DirOffset(in.Offset),
|
Offset: fuseops.DirOffset(in.Offset),
|
||||||
|
Plus: inMsg.Header().Opcode == fusekernel.OpReaddirplus,
|
||||||
OpContext: fuseops.OpContext{Pid: inMsg.Header().Pid},
|
OpContext: fuseops.OpContext{Pid: inMsg.Header().Pid},
|
||||||
}
|
}
|
||||||
o = to
|
o = to
|
||||||
|
|
||||||
readSize := int(in.Size)
|
readSize := int(in.Size)
|
||||||
p := outMsg.GrowNoZero(readSize)
|
p := outMsg.Grow(readSize)
|
||||||
if p == nil {
|
if p == nil {
|
||||||
return nil, fmt.Errorf("Can't grow for %d-byte read", readSize)
|
return nil, fmt.Errorf("Can't grow for %d-byte read", readSize)
|
||||||
}
|
}
|
||||||
|
@ -491,7 +531,7 @@ func convertInMessage(
|
||||||
|
|
||||||
readSize := int(in.Size)
|
readSize := int(in.Size)
|
||||||
if readSize > 0 {
|
if readSize > 0 {
|
||||||
p := outMsg.GrowNoZero(readSize)
|
p := outMsg.Grow(readSize)
|
||||||
if p == nil {
|
if p == nil {
|
||||||
return nil, fmt.Errorf("Can't grow for %d-byte read", readSize)
|
return nil, fmt.Errorf("Can't grow for %d-byte read", readSize)
|
||||||
}
|
}
|
||||||
|
@ -519,7 +559,7 @@ func convertInMessage(
|
||||||
|
|
||||||
readSize := int(in.Size)
|
readSize := int(in.Size)
|
||||||
if readSize != 0 {
|
if readSize != 0 {
|
||||||
p := outMsg.GrowNoZero(readSize)
|
p := outMsg.Grow(readSize)
|
||||||
if p == nil {
|
if p == nil {
|
||||||
return nil, fmt.Errorf("Can't grow for %d-byte read", readSize)
|
return nil, fmt.Errorf("Can't grow for %d-byte read", readSize)
|
||||||
}
|
}
|
||||||
|
@ -570,6 +610,42 @@ func convertInMessage(
|
||||||
OpContext: fuseops.OpContext{Pid: inMsg.Header().Pid},
|
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:
|
default:
|
||||||
o = &unknownOp{
|
o = &unknownOp{
|
||||||
OpCode: inMsg.Header().Opcode,
|
OpCode: inMsg.Header().Opcode,
|
||||||
|
@ -600,6 +676,12 @@ func (c *Connection) kernelResponse(
|
||||||
case *fuseops.ForgetInodeOp:
|
case *fuseops.ForgetInodeOp:
|
||||||
return true
|
return true
|
||||||
|
|
||||||
|
case *fuseops.BatchForgetOp:
|
||||||
|
return true
|
||||||
|
|
||||||
|
case *fuseops.NotifyRetrieveReplyOp:
|
||||||
|
return true
|
||||||
|
|
||||||
case *interruptOp:
|
case *interruptOp:
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -643,37 +725,37 @@ func (c *Connection) kernelResponseForOp(
|
||||||
case *fuseops.LookUpInodeOp:
|
case *fuseops.LookUpInodeOp:
|
||||||
size := int(fusekernel.EntryOutSize(c.protocol))
|
size := int(fusekernel.EntryOutSize(c.protocol))
|
||||||
out := (*fusekernel.EntryOut)(m.Grow(size))
|
out := (*fusekernel.EntryOut)(m.Grow(size))
|
||||||
convertChildInodeEntry(&o.Entry, out)
|
fuseops.ConvertChildInodeEntry(&o.Entry, out)
|
||||||
|
|
||||||
case *fuseops.GetInodeAttributesOp:
|
case *fuseops.GetInodeAttributesOp:
|
||||||
size := int(fusekernel.AttrOutSize(c.protocol))
|
size := int(fusekernel.AttrOutSize(c.protocol))
|
||||||
out := (*fusekernel.AttrOut)(m.Grow(size))
|
out := (*fusekernel.AttrOut)(m.Grow(size))
|
||||||
out.AttrValid, out.AttrValidNsec = convertExpirationTime(
|
out.AttrValid, out.AttrValidNsec = fuseops.ConvertExpirationTime(
|
||||||
o.AttributesExpiration)
|
o.AttributesExpiration)
|
||||||
convertAttributes(o.Inode, &o.Attributes, &out.Attr)
|
fuseops.ConvertAttributes(o.Inode, &o.Attributes, &out.Attr)
|
||||||
|
|
||||||
case *fuseops.SetInodeAttributesOp:
|
case *fuseops.SetInodeAttributesOp:
|
||||||
size := int(fusekernel.AttrOutSize(c.protocol))
|
size := int(fusekernel.AttrOutSize(c.protocol))
|
||||||
out := (*fusekernel.AttrOut)(m.Grow(size))
|
out := (*fusekernel.AttrOut)(m.Grow(size))
|
||||||
out.AttrValid, out.AttrValidNsec = convertExpirationTime(
|
out.AttrValid, out.AttrValidNsec = fuseops.ConvertExpirationTime(
|
||||||
o.AttributesExpiration)
|
o.AttributesExpiration)
|
||||||
convertAttributes(o.Inode, &o.Attributes, &out.Attr)
|
fuseops.ConvertAttributes(o.Inode, &o.Attributes, &out.Attr)
|
||||||
|
|
||||||
case *fuseops.MkDirOp:
|
case *fuseops.MkDirOp:
|
||||||
size := int(fusekernel.EntryOutSize(c.protocol))
|
size := int(fusekernel.EntryOutSize(c.protocol))
|
||||||
out := (*fusekernel.EntryOut)(m.Grow(size))
|
out := (*fusekernel.EntryOut)(m.Grow(size))
|
||||||
convertChildInodeEntry(&o.Entry, out)
|
fuseops.ConvertChildInodeEntry(&o.Entry, out)
|
||||||
|
|
||||||
case *fuseops.MkNodeOp:
|
case *fuseops.MkNodeOp:
|
||||||
size := int(fusekernel.EntryOutSize(c.protocol))
|
size := int(fusekernel.EntryOutSize(c.protocol))
|
||||||
out := (*fusekernel.EntryOut)(m.Grow(size))
|
out := (*fusekernel.EntryOut)(m.Grow(size))
|
||||||
convertChildInodeEntry(&o.Entry, out)
|
fuseops.ConvertChildInodeEntry(&o.Entry, out)
|
||||||
|
|
||||||
case *fuseops.CreateFileOp:
|
case *fuseops.CreateFileOp:
|
||||||
eSize := int(fusekernel.EntryOutSize(c.protocol))
|
eSize := int(fusekernel.EntryOutSize(c.protocol))
|
||||||
|
|
||||||
e := (*fusekernel.EntryOut)(m.Grow(eSize))
|
e := (*fusekernel.EntryOut)(m.Grow(eSize))
|
||||||
convertChildInodeEntry(&o.Entry, e)
|
fuseops.ConvertChildInodeEntry(&o.Entry, e)
|
||||||
|
|
||||||
oo := (*fusekernel.OpenOut)(m.Grow(int(unsafe.Sizeof(fusekernel.OpenOut{}))))
|
oo := (*fusekernel.OpenOut)(m.Grow(int(unsafe.Sizeof(fusekernel.OpenOut{}))))
|
||||||
oo.Fh = uint64(o.Handle)
|
oo.Fh = uint64(o.Handle)
|
||||||
|
@ -681,12 +763,12 @@ func (c *Connection) kernelResponseForOp(
|
||||||
case *fuseops.CreateSymlinkOp:
|
case *fuseops.CreateSymlinkOp:
|
||||||
size := int(fusekernel.EntryOutSize(c.protocol))
|
size := int(fusekernel.EntryOutSize(c.protocol))
|
||||||
out := (*fusekernel.EntryOut)(m.Grow(size))
|
out := (*fusekernel.EntryOut)(m.Grow(size))
|
||||||
convertChildInodeEntry(&o.Entry, out)
|
fuseops.ConvertChildInodeEntry(&o.Entry, out)
|
||||||
|
|
||||||
case *fuseops.CreateLinkOp:
|
case *fuseops.CreateLinkOp:
|
||||||
size := int(fusekernel.EntryOutSize(c.protocol))
|
size := int(fusekernel.EntryOutSize(c.protocol))
|
||||||
out := (*fusekernel.EntryOut)(m.Grow(size))
|
out := (*fusekernel.EntryOut)(m.Grow(size))
|
||||||
convertChildInodeEntry(&o.Entry, out)
|
fuseops.ConvertChildInodeEntry(&o.Entry, out)
|
||||||
|
|
||||||
case *fuseops.RenameOp:
|
case *fuseops.RenameOp:
|
||||||
// Empty response
|
// Empty response
|
||||||
|
@ -723,11 +805,11 @@ func (c *Connection) kernelResponseForOp(
|
||||||
}
|
}
|
||||||
|
|
||||||
case *fuseops.ReadFileOp:
|
case *fuseops.ReadFileOp:
|
||||||
m.Append(o.Dst)
|
if o.Dst != nil {
|
||||||
m.ShrinkTo(buffer.OutMessageHeaderSize + o.BytesRead)
|
m.Append(o.Dst)
|
||||||
|
} else {
|
||||||
case *fuseops.VectoredReadOp:
|
m.Append(o.Data...)
|
||||||
m.Append(o.Data...)
|
}
|
||||||
m.ShrinkTo(buffer.OutMessageHeaderSize + o.BytesRead)
|
m.ShrinkTo(buffer.OutMessageHeaderSize + o.BytesRead)
|
||||||
|
|
||||||
case *fuseops.WriteFileOp:
|
case *fuseops.WriteFileOp:
|
||||||
|
@ -823,6 +905,13 @@ func (c *Connection) kernelResponseForOp(
|
||||||
out.TimeGran = 1
|
out.TimeGran = 1
|
||||||
out.MaxPages = o.MaxPages
|
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:
|
default:
|
||||||
panic(fmt.Sprintf("Unexpected op: %#v", op))
|
panic(fmt.Sprintf("Unexpected op: %#v", op))
|
||||||
}
|
}
|
||||||
|
@ -830,115 +919,73 @@ func (c *Connection) kernelResponseForOp(
|
||||||
return
|
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
|
// 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) {
|
func writeXattrSize(m *buffer.OutMessage, size uint32) {
|
||||||
out := (*fusekernel.GetxattrOut)(m.Grow(int(unsafe.Sizeof(fusekernel.GetxattrOut{}))))
|
out := (*fusekernel.GetxattrOut)(m.Grow(int(unsafe.Sizeof(fusekernel.GetxattrOut{}))))
|
||||||
out.Size = size
|
out.Size = size
|
||||||
|
|
5
debug.go
5
debug.go
|
@ -93,11 +93,6 @@ func describeRequest(op interface{}) (s string) {
|
||||||
addComponent("new_name %q", typed.NewName)
|
addComponent("new_name %q", typed.NewName)
|
||||||
|
|
||||||
case *fuseops.ReadFileOp:
|
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("handle %d", typed.Handle)
|
||||||
addComponent("offset %d", typed.Offset)
|
addComponent("offset %d", typed.Offset)
|
||||||
addComponent("%d bytes", typed.Size)
|
addComponent("%d bytes", typed.Size)
|
||||||
|
|
8
doc.go
8
doc.go
|
@ -16,15 +16,15 @@
|
||||||
//
|
//
|
||||||
// The primary elements of interest are:
|
// 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.
|
// 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.
|
// 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
|
// 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
|
// as tests for this package: http://godoc.org/github.com/jacobsa/fuse/samples
|
||||||
|
|
|
@ -31,7 +31,7 @@ func (c *Connection) getInMessage() *buffer.InMessage {
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
|
|
||||||
if x == nil {
|
if x == nil {
|
||||||
x = new(buffer.InMessage)
|
x = buffer.NewInMessage()
|
||||||
}
|
}
|
||||||
|
|
||||||
return x
|
return x
|
||||||
|
|
|
@ -0,0 +1,84 @@
|
||||||
|
// 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)
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
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
|
||||||
|
}
|
271
fuseops/ops.go
271
fuseops/ops.go
|
@ -17,6 +17,8 @@ package fuseops
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/jacobsa/fuse/internal/fusekernel"
|
||||||
)
|
)
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -35,16 +37,16 @@ type OpContext struct {
|
||||||
//
|
//
|
||||||
// Called by statfs(2) and friends:
|
// Called by statfs(2) and friends:
|
||||||
//
|
//
|
||||||
// * (https://goo.gl/Xi1lDr) sys_statfs called user_statfs, which calls
|
// - (https://goo.gl/Xi1lDr) sys_statfs called user_statfs, which calls
|
||||||
// vfs_statfs, which calls statfs_by_dentry.
|
// vfs_statfs, which calls statfs_by_dentry.
|
||||||
//
|
//
|
||||||
// * (https://goo.gl/VAIOwU) statfs_by_dentry calls the superblock
|
// - (https://goo.gl/VAIOwU) statfs_by_dentry calls the superblock
|
||||||
// operation statfs, which in our case points at
|
// operation statfs, which in our case points at
|
||||||
// fuse_statfs (cf. https://goo.gl/L7BTM3)
|
// fuse_statfs (cf. https://goo.gl/L7BTM3)
|
||||||
//
|
//
|
||||||
// * (https://goo.gl/Zn7Sgl) fuse_statfs sends a statfs op, then uses
|
// - (https://goo.gl/Zn7Sgl) fuse_statfs sends a statfs op, then uses
|
||||||
// convert_fuse_statfs to convert the response in a straightforward
|
// convert_fuse_statfs to convert the response in a straightforward
|
||||||
// manner.
|
// manner.
|
||||||
//
|
//
|
||||||
// This op is particularly important on OS X: if you don't implement it, the
|
// 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
|
// file system will not successfully mount. If you don't model a sane amount of
|
||||||
|
@ -159,6 +161,8 @@ type SetInodeAttributesOp struct {
|
||||||
Handle *HandleID
|
Handle *HandleID
|
||||||
|
|
||||||
// The attributes to modify, or nil for attributes that don't need a change.
|
// The attributes to modify, or nil for attributes that don't need a change.
|
||||||
|
Uid *uint32
|
||||||
|
Gid *uint32
|
||||||
Size *uint64
|
Size *uint64
|
||||||
Mode *os.FileMode
|
Mode *os.FileMode
|
||||||
Atime *time.Time
|
Atime *time.Time
|
||||||
|
@ -189,10 +193,10 @@ type SetInodeAttributesOp struct {
|
||||||
// The reference count corresponds to fuse_inode::nlookup
|
// The reference count corresponds to fuse_inode::nlookup
|
||||||
// (http://goo.gl/ut48S4). Some examples of where the kernel manipulates it:
|
// (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/vPD9Oh) Any caller to fuse_iget increases the count.
|
||||||
// * (http://goo.gl/B6tTTC) fuse_lookup_name calls fuse_iget.
|
// - (http://goo.gl/B6tTTC) fuse_lookup_name calls fuse_iget.
|
||||||
// * (http://goo.gl/IlcxWv) fuse_create_open 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/VQMQul) fuse_dentry_revalidate increments after
|
||||||
// revalidating.
|
// revalidating.
|
||||||
//
|
//
|
||||||
// In contrast to all other inodes, RootInodeID begins with an implicit
|
// In contrast to all other inodes, RootInodeID begins with an implicit
|
||||||
|
@ -200,12 +204,12 @@ type SetInodeAttributesOp struct {
|
||||||
// could be no such op, because the root cannot be referred to by name.) Code
|
// could be no such op, because the root cannot be referred to by name.) Code
|
||||||
// walk:
|
// 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.
|
// 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
|
// File systems should tolerate but not rely on receiving forget ops for
|
||||||
// remaining inodes when the file system unmounts, including the root inode.
|
// remaining inodes when the file system unmounts, including the root inode.
|
||||||
|
@ -220,6 +224,32 @@ type ForgetInodeOp struct {
|
||||||
OpContext OpContext
|
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
|
// Inode creation
|
||||||
////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -271,6 +301,9 @@ type MkNodeOp struct {
|
||||||
Name string
|
Name string
|
||||||
Mode os.FileMode
|
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.
|
// Set by the file system: information about the inode that was created.
|
||||||
//
|
//
|
||||||
// The lookup count for the inode is implicitly incremented. See notes on
|
// The lookup count for the inode is implicitly incremented. See notes on
|
||||||
|
@ -311,7 +344,7 @@ type CreateFileOp struct {
|
||||||
// The handle may be supplied in future ops like ReadFileOp that contain a
|
// 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
|
// file handle. The file system must ensure this ID remains valid until a
|
||||||
// later call to ReleaseFileHandle.
|
// later call to ReleaseFileHandle.
|
||||||
Handle HandleID
|
Handle HandleID
|
||||||
OpContext OpContext
|
OpContext OpContext
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -372,19 +405,19 @@ type CreateLinkOp struct {
|
||||||
// file system boundaries, and that the destination doesn't already exist with
|
// file system boundaries, and that the destination doesn't already exist with
|
||||||
// the wrong type. Some subtleties that the file system must care about:
|
// 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
|
// is empty before replacing it, returning ENOTEMPTY otherwise. (This is
|
||||||
// per the posix spec: http://goo.gl/4XtT79)
|
// 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
|
// new name. That is, if the new name already exists, there must be no
|
||||||
// point at which it doesn't exist.
|
// 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
|
// removed; these need not be atomic. In fact, the Linux man page
|
||||||
// explicitly says this is likely (cf. https://goo.gl/Y1wVZc).
|
// 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
|
// 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
|
// 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
|
// for file systems that may be modified remotely. Therefore a careful file
|
||||||
|
@ -394,7 +427,6 @@ type CreateLinkOp struct {
|
||||||
// posix and the man pages are imprecise about the actual semantics of a
|
// 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
|
// rename if it's not atomic, so it is probably not disastrous to be loose
|
||||||
// about this.
|
// about this.
|
||||||
//
|
|
||||||
type RenameOp struct {
|
type RenameOp struct {
|
||||||
// The old parent directory, and the name of the entry within it to be
|
// The old parent directory, and the name of the entry within it to be
|
||||||
// relocated.
|
// relocated.
|
||||||
|
@ -443,7 +475,7 @@ type UnlinkOp struct {
|
||||||
|
|
||||||
// Open a directory inode.
|
// Open a directory inode.
|
||||||
//
|
//
|
||||||
// On Linux the sends this when setting up a struct file for a particular inode
|
// On Linux the kernel 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
|
// 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.
|
// user-space process. On OS X it may not be sent for every open(2) (cf.
|
||||||
// https://github.com/osxfuse/osxfuse/issues/199).
|
// https://github.com/osxfuse/osxfuse/issues/199).
|
||||||
|
@ -530,12 +562,18 @@ type ReadDirOp struct {
|
||||||
// offset, and return array offsets into that cached listing.
|
// offset, and return array offsets into that cached listing.
|
||||||
Offset DirOffset
|
Offset DirOffset
|
||||||
|
|
||||||
|
// 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 destination buffer, whose length gives the size of the read.
|
||||||
//
|
//
|
||||||
// The output data should consist of a sequence of FUSE directory entries in
|
// 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
|
// the format generated by fuse_add_direntry (http://goo.gl/qCcHCV), which is
|
||||||
// consumed by parse_dirfile (http://goo.gl/2WUmD2). Use fuseutil.WriteDirent
|
// consumed by parse_dirfile (http://goo.gl/2WUmD2). Use fuseutil.WriteDirent
|
||||||
// to generate this data.
|
// or fuseutil.WriteDirentPlus to generate this data.
|
||||||
//
|
//
|
||||||
// Each entry returned exposes a directory offset to the user that may later
|
// Each entry returned exposes a directory offset to the user that may later
|
||||||
// show up in ReadDirRequest.Offset. See notes on that field for more
|
// show up in ReadDirRequest.Offset. See notes on that field for more
|
||||||
|
@ -578,7 +616,7 @@ type ReleaseDirHandleOp struct {
|
||||||
|
|
||||||
// Open a file inode.
|
// Open a file inode.
|
||||||
//
|
//
|
||||||
// On Linux the sends this when setting up a struct file for a particular inode
|
// On Linux the kernel 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
|
// 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)
|
// process. On OS X it may not be sent for every open(2)
|
||||||
// (cf.https://github.com/osxfuse/osxfuse/issues/199).
|
// (cf.https://github.com/osxfuse/osxfuse/issues/199).
|
||||||
|
@ -620,6 +658,8 @@ type OpenFileOp struct {
|
||||||
// advance, for example, because contents are generated on the fly.
|
// advance, for example, because contents are generated on the fly.
|
||||||
UseDirectIO bool
|
UseDirectIO bool
|
||||||
|
|
||||||
|
OpenFlags fusekernel.OpenFlags
|
||||||
|
|
||||||
OpContext OpContext
|
OpContext OpContext
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -637,38 +677,15 @@ type ReadFileOp struct {
|
||||||
// The offset within the file at which to read.
|
// The offset within the file at which to read.
|
||||||
Offset int64
|
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.
|
// The size of the read.
|
||||||
Size int64
|
Size int64
|
||||||
|
|
||||||
// Set by the file system: data to send back to the client.
|
// 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.
|
||||||
Data [][]byte
|
Data [][]byte
|
||||||
|
|
||||||
// Set by the file system: the number of bytes read.
|
// Set by the file system: the number of bytes read.
|
||||||
|
@ -691,13 +708,13 @@ type VectoredReadOp struct {
|
||||||
// cache and the page is marked dirty. Later the kernel may write back the
|
// 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:
|
// 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
|
// needs to be written to backing store (cf. http://goo.gl/Ezbewg). Fuse
|
||||||
// sets this to fuse_writepage (cf. http://goo.gl/IeNvLT).
|
// 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.
|
// the userspace server.
|
||||||
//
|
//
|
||||||
// Note that the kernel *will* ensure that writes are received and acknowledged
|
// Note that the kernel *will* ensure that writes are received and acknowledged
|
||||||
|
@ -761,10 +778,10 @@ type WriteFileOp struct {
|
||||||
// vfs.txt documents this as being called for by the fsync(2) system call
|
// vfs.txt documents this as being called for by the fsync(2) system call
|
||||||
// (cf. http://goo.gl/j9X8nB). Code walk for that case:
|
// (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.
|
// 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
|
// 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
|
// may be sent for msync(2) with the MS_SYNC flag (see the notes on
|
||||||
|
@ -785,8 +802,8 @@ type SyncFileOp struct {
|
||||||
// vfs.txt documents this as being sent for each close(2) system call (cf.
|
// 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/FSkbrq). Code walk for that case:
|
||||||
//
|
//
|
||||||
// * (http://goo.gl/e3lv0e) sys_close calls __close_fd, calls filp_close.
|
// - (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/nI8fxD) filp_close calls f_op->flush (fuse_flush).
|
||||||
//
|
//
|
||||||
// But note that this is also sent in other contexts where a file descriptor is
|
// 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),
|
// closed, such as dup2(2) (cf. http://goo.gl/NQDvFS). In the case of close(2),
|
||||||
|
@ -795,15 +812,15 @@ type SyncFileOp struct {
|
||||||
// One potentially significant case where this may not be sent is mmap'd files,
|
// One potentially significant case where this may not be sent is mmap'd files,
|
||||||
// where the behavior is complicated:
|
// 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
|
// closing the file with close(2), the WriteFileOps for the modifications
|
||||||
// may not be received before the FlushFileOp for the close(2) (cf.
|
// may not be received before the FlushFileOp for the close(2) (cf.
|
||||||
// https://github.com/osxfuse/osxfuse/issues/202). It appears that this may
|
// https://github.com/osxfuse/osxfuse/issues/202). It appears that this may
|
||||||
// be fixed in osxfuse 3 (cf. https://goo.gl/rtvbko).
|
// 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)
|
// 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
|
// 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
|
// FlushFile as usual (cf. http://goo.gl/kVmNcx). On Linux, msync(2) does
|
||||||
|
@ -829,8 +846,8 @@ type SyncFileOp struct {
|
||||||
// return any errors that occur.
|
// return any errors that occur.
|
||||||
type FlushFileOp struct {
|
type FlushFileOp struct {
|
||||||
// The file and handle being flushed.
|
// The file and handle being flushed.
|
||||||
Inode InodeID
|
Inode InodeID
|
||||||
Handle HandleID
|
Handle HandleID
|
||||||
OpContext OpContext
|
OpContext OpContext
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -965,3 +982,127 @@ type FallocateOp struct {
|
||||||
Mode uint32
|
Mode uint32
|
||||||
OpContext OpContext
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -87,6 +87,9 @@ type InodeAttributes struct {
|
||||||
//
|
//
|
||||||
Mode os.FileMode
|
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.
|
// Time information. See `man 2 stat` for full details.
|
||||||
Atime time.Time // Time of last access
|
Atime time.Time // Time of last access
|
||||||
Mtime time.Time // Time of last modification
|
Mtime time.Time // Time of last modification
|
||||||
|
@ -118,12 +121,11 @@ func (a *InodeAttributes) DebugString() string {
|
||||||
//
|
//
|
||||||
// Some related reading:
|
// Some related reading:
|
||||||
//
|
//
|
||||||
// http://fuse.sourceforge.net/doxygen/structfuse__entry__param.html
|
// http://fuse.sourceforge.net/doxygen/structfuse__entry__param.html
|
||||||
// http://stackoverflow.com/q/11071996/1505451
|
// http://stackoverflow.com/q/11071996/1505451
|
||||||
// http://goo.gl/CqvwyX
|
// http://goo.gl/CqvwyX
|
||||||
// http://julipedia.meroh.net/2005/09/nfs-file-handles.html
|
// http://julipedia.meroh.net/2005/09/nfs-file-handles.html
|
||||||
// http://goo.gl/wvo3MB
|
// http://goo.gl/wvo3MB
|
||||||
//
|
|
||||||
type GenerationNumber uint64
|
type GenerationNumber uint64
|
||||||
|
|
||||||
// HandleID is an opaque 64-bit number used to identify a particular open
|
// HandleID is an opaque 64-bit number used to identify a particular open
|
||||||
|
|
|
@ -19,6 +19,7 @@ import (
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/jacobsa/fuse/fuseops"
|
"github.com/jacobsa/fuse/fuseops"
|
||||||
|
"github.com/jacobsa/fuse/internal/fusekernel"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DirentType uint32
|
type DirentType uint32
|
||||||
|
@ -50,10 +51,18 @@ type Dirent struct {
|
||||||
Type DirentType
|
Type DirentType
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write the supplied directory entry intto the given buffer in the format
|
// Write the supplied directory entry into the given buffer in the format
|
||||||
// expected in fuseops.ReadFileOp.Data, returning the number of bytes written.
|
// expected in fuseops.ReadDirOp.Data, returning the number of bytes written.
|
||||||
// Return zero if the entry would not fit.
|
// Return zero if the entry would not fit.
|
||||||
func WriteDirent(buf []byte, d Dirent) (n int) {
|
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
|
// We want to write bytes with the layout of fuse_dirent
|
||||||
// (http://goo.gl/BmFxob) in host order. The struct must be aligned according
|
// (http://goo.gl/BmFxob) in host order. The struct must be aligned according
|
||||||
// to FUSE_DIRENT_ALIGN (http://goo.gl/UziWvH), which dictates 8-byte
|
// to FUSE_DIRENT_ALIGN (http://goo.gl/UziWvH), which dictates 8-byte
|
||||||
|
@ -78,10 +87,21 @@ func WriteDirent(buf []byte, d Dirent) (n int) {
|
||||||
|
|
||||||
// Do we have enough room?
|
// Do we have enough room?
|
||||||
totalLen := direntSize + len(d.Name) + padLen
|
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) {
|
if totalLen > len(buf) {
|
||||||
return n
|
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.
|
// Write the header.
|
||||||
de := fuse_dirent{
|
de := fuse_dirent{
|
||||||
ino: uint64(d.Inode),
|
ino: uint64(d.Inode),
|
||||||
|
|
|
@ -39,6 +39,7 @@ type FileSystem interface {
|
||||||
GetInodeAttributes(context.Context, *fuseops.GetInodeAttributesOp) error
|
GetInodeAttributes(context.Context, *fuseops.GetInodeAttributesOp) error
|
||||||
SetInodeAttributes(context.Context, *fuseops.SetInodeAttributesOp) error
|
SetInodeAttributes(context.Context, *fuseops.SetInodeAttributesOp) error
|
||||||
ForgetInode(context.Context, *fuseops.ForgetInodeOp) error
|
ForgetInode(context.Context, *fuseops.ForgetInodeOp) error
|
||||||
|
BatchForget(context.Context, *fuseops.BatchForgetOp) error
|
||||||
MkDir(context.Context, *fuseops.MkDirOp) error
|
MkDir(context.Context, *fuseops.MkDirOp) error
|
||||||
MkNode(context.Context, *fuseops.MkNodeOp) error
|
MkNode(context.Context, *fuseops.MkNodeOp) error
|
||||||
CreateFile(context.Context, *fuseops.CreateFileOp) error
|
CreateFile(context.Context, *fuseops.CreateFileOp) error
|
||||||
|
@ -52,7 +53,6 @@ type FileSystem interface {
|
||||||
ReleaseDirHandle(context.Context, *fuseops.ReleaseDirHandleOp) error
|
ReleaseDirHandle(context.Context, *fuseops.ReleaseDirHandleOp) error
|
||||||
OpenFile(context.Context, *fuseops.OpenFileOp) error
|
OpenFile(context.Context, *fuseops.OpenFileOp) error
|
||||||
ReadFile(context.Context, *fuseops.ReadFileOp) error
|
ReadFile(context.Context, *fuseops.ReadFileOp) error
|
||||||
VectoredRead(context.Context, *fuseops.VectoredReadOp) error
|
|
||||||
WriteFile(context.Context, *fuseops.WriteFileOp) error
|
WriteFile(context.Context, *fuseops.WriteFileOp) error
|
||||||
SyncFile(context.Context, *fuseops.SyncFileOp) error
|
SyncFile(context.Context, *fuseops.SyncFileOp) error
|
||||||
FlushFile(context.Context, *fuseops.FlushFileOp) error
|
FlushFile(context.Context, *fuseops.FlushFileOp) error
|
||||||
|
@ -63,6 +63,9 @@ type FileSystem interface {
|
||||||
ListXattr(context.Context, *fuseops.ListXattrOp) error
|
ListXattr(context.Context, *fuseops.ListXattrOp) error
|
||||||
SetXattr(context.Context, *fuseops.SetXattrOp) error
|
SetXattr(context.Context, *fuseops.SetXattrOp) error
|
||||||
Fallocate(context.Context, *fuseops.FallocateOp) 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
|
// Regard all inodes (including the root inode) as having their lookup counts
|
||||||
// decremented to zero, and clean up any resources associated with the file
|
// decremented to zero, and clean up any resources associated with the file
|
||||||
|
@ -95,6 +98,8 @@ type fileSystemServer struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *fileSystemServer) ServeOps(c *fuse.Connection) {
|
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
|
// When we are done, we clean up by waiting for all in-flight ops then
|
||||||
// destroying the file system.
|
// destroying the file system.
|
||||||
defer func() {
|
defer func() {
|
||||||
|
@ -152,6 +157,22 @@ func (s *fileSystemServer) handleOp(
|
||||||
case *fuseops.ForgetInodeOp:
|
case *fuseops.ForgetInodeOp:
|
||||||
err = s.fs.ForgetInode(ctx, typed)
|
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:
|
case *fuseops.MkDirOp:
|
||||||
err = s.fs.MkDir(ctx, typed)
|
err = s.fs.MkDir(ctx, typed)
|
||||||
|
|
||||||
|
@ -191,9 +212,6 @@ func (s *fileSystemServer) handleOp(
|
||||||
case *fuseops.ReadFileOp:
|
case *fuseops.ReadFileOp:
|
||||||
err = s.fs.ReadFile(ctx, typed)
|
err = s.fs.ReadFile(ctx, typed)
|
||||||
|
|
||||||
case *fuseops.VectoredReadOp:
|
|
||||||
err = s.fs.VectoredRead(ctx, typed)
|
|
||||||
|
|
||||||
case *fuseops.WriteFileOp:
|
case *fuseops.WriteFileOp:
|
||||||
err = s.fs.WriteFile(ctx, typed)
|
err = s.fs.WriteFile(ctx, typed)
|
||||||
|
|
||||||
|
@ -223,6 +241,9 @@ func (s *fileSystemServer) handleOp(
|
||||||
|
|
||||||
case *fuseops.FallocateOp:
|
case *fuseops.FallocateOp:
|
||||||
err = s.fs.Fallocate(ctx, typed)
|
err = s.fs.Fallocate(ctx, typed)
|
||||||
|
|
||||||
|
case *fuseops.PollOp:
|
||||||
|
err = s.fs.Poll(ctx, typed)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Reply(ctx, err)
|
c.Reply(ctx, err)
|
||||||
|
|
|
@ -60,6 +60,12 @@ func (fs *NotImplementedFileSystem) ForgetInode(
|
||||||
return fuse.ENOSYS
|
return fuse.ENOSYS
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (fs *NotImplementedFileSystem) BatchForget(
|
||||||
|
ctx context.Context,
|
||||||
|
op *fuseops.BatchForgetOp) error {
|
||||||
|
return fuse.ENOSYS
|
||||||
|
}
|
||||||
|
|
||||||
func (fs *NotImplementedFileSystem) MkDir(
|
func (fs *NotImplementedFileSystem) MkDir(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
op *fuseops.MkDirOp) error {
|
op *fuseops.MkDirOp) error {
|
||||||
|
@ -138,12 +144,6 @@ func (fs *NotImplementedFileSystem) ReadFile(
|
||||||
return fuse.ENOSYS
|
return fuse.ENOSYS
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fs *NotImplementedFileSystem) VectoredRead(
|
|
||||||
ctx context.Context,
|
|
||||||
op *fuseops.VectoredReadOp) error {
|
|
||||||
return fuse.ENOSYS
|
|
||||||
}
|
|
||||||
|
|
||||||
func (fs *NotImplementedFileSystem) WriteFile(
|
func (fs *NotImplementedFileSystem) WriteFile(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
op *fuseops.WriteFileOp) error {
|
op *fuseops.WriteFileOp) error {
|
||||||
|
@ -204,5 +204,14 @@ func (fs *NotImplementedFileSystem) Fallocate(
|
||||||
return fuse.ENOSYS
|
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() {
|
func (fs *NotImplementedFileSystem) Destroy() {
|
||||||
}
|
}
|
||||||
|
|
7
go.mod
7
go.mod
|
@ -1,6 +1,6 @@
|
||||||
module github.com/jacobsa/fuse
|
module github.com/jacobsa/fuse
|
||||||
|
|
||||||
go 1.16
|
go 1.18
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/detailyang/go-fallocate v0.0.0-20180908115635-432fa640bd2e
|
github.com/detailyang/go-fallocate v0.0.0-20180908115635-432fa640bd2e
|
||||||
|
@ -11,6 +11,7 @@ require (
|
||||||
github.com/jacobsa/syncutil v0.0.0-20180201203307-228ac8e5a6c3
|
github.com/jacobsa/syncutil v0.0.0-20180201203307-228ac8e5a6c3
|
||||||
github.com/jacobsa/timeutil v0.0.0-20170205232429-577e5acbbcf6
|
github.com/jacobsa/timeutil v0.0.0-20170205232429-577e5acbbcf6
|
||||||
github.com/kylelemons/godebug v1.1.0
|
github.com/kylelemons/godebug v1.1.0
|
||||||
golang.org/x/net v0.0.0-20200301022130-244492dfa37a
|
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e // indirect
|
||||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527
|
golang.org/x/net v0.0.0-20220526153639-5463443f8c37
|
||||||
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a
|
||||||
)
|
)
|
||||||
|
|
15
go.sum
15
go.sum
|
@ -15,9 +15,24 @@ 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 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
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-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 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-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-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 h1:uYVVQ9WP/Ds2ROhcaGPeIdVq0RIXVLwsHlnvJ+cT1So=
|
||||||
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
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.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=
|
||||||
|
|
|
@ -45,13 +45,17 @@ type InMessage struct {
|
||||||
size int
|
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
|
// 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
|
// Consume will consume the bytes directly after the fusekernel.InHeader
|
||||||
// struct.
|
// struct.
|
||||||
func (m *InMessage) Init(r io.Reader) error {
|
func (m *InMessage) Init(r io.Reader) error {
|
||||||
if m.storage == nil {
|
|
||||||
m.storage = make([]byte, bufSize, bufSize)
|
|
||||||
}
|
|
||||||
n, err := r.Read(m.storage[:])
|
n, err := r.Read(m.storage[:])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -32,8 +32,8 @@ const OutMessageHeaderSize = int(unsafe.Sizeof(fusekernel.OutHeader{}))
|
||||||
//
|
//
|
||||||
// Must be initialized with Reset.
|
// Must be initialized with Reset.
|
||||||
type OutMessage struct {
|
type OutMessage struct {
|
||||||
header fusekernel.OutHeader
|
header fusekernel.OutHeader
|
||||||
Sglist [][]byte
|
Sglist [][]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset resets m so that it's ready to be used again. Afterward, the contents
|
// Reset resets m so that it's ready to be used again. Afterward, the contents
|
||||||
|
@ -48,17 +48,9 @@ func (m *OutMessage) OutHeader() *fusekernel.OutHeader {
|
||||||
return &m.header
|
return &m.header
|
||||||
}
|
}
|
||||||
|
|
||||||
// Grow grows m's buffer by the given number of bytes, returning a pointer to
|
// 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. If there is
|
// the start of the new segment, which is guaranteed to be zeroed.
|
||||||
// insufficient space, it returns nil.
|
|
||||||
func (m *OutMessage) Grow(n int) unsafe.Pointer {
|
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)
|
b := make([]byte, n)
|
||||||
m.Append(b)
|
m.Append(b)
|
||||||
p := unsafe.Pointer(&b[0])
|
p := unsafe.Pointer(&b[0])
|
||||||
|
@ -84,10 +76,10 @@ func (m *OutMessage) ShrinkTo(n int) {
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
if n > 0 {
|
if n > 0 {
|
||||||
m.Sglist[i] = m.Sglist[i][0 : n]
|
m.Sglist[i] = m.Sglist[i][0:n]
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
m.Sglist = m.Sglist[0 : i]
|
m.Sglist = m.Sglist[0:i]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,12 +87,10 @@ func (m *OutMessage) ShrinkTo(n int) {
|
||||||
// segment. Int panics if there is not enough room available.
|
// segment. Int panics if there is not enough room available.
|
||||||
func (m *OutMessage) Append(src ...[]byte) {
|
func (m *OutMessage) Append(src ...[]byte) {
|
||||||
if m.Sglist == nil {
|
if m.Sglist == nil {
|
||||||
m.Sglist = append(m.Sglist, nil)
|
// First element of Sglist is pre-filled with a pointer to the header
|
||||||
*(*reflect.SliceHeader)(unsafe.Pointer(&m.Sglist[0])) = reflect.SliceHeader{
|
// to allow sending it with a single writev() call without copying the
|
||||||
Data: uintptr(unsafe.Pointer(&m.header)),
|
// slice again
|
||||||
Len: OutMessageHeaderSize,
|
m.Sglist = append(m.Sglist, m.OutHeaderBytes())
|
||||||
Cap: OutMessageHeaderSize,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
m.Sglist = append(m.Sglist, src...)
|
m.Sglist = append(m.Sglist, src...)
|
||||||
return
|
return
|
||||||
|
@ -117,6 +107,7 @@ func (m *OutMessage) Len() int {
|
||||||
if m.Sglist == nil {
|
if m.Sglist == nil {
|
||||||
return OutMessageHeaderSize
|
return OutMessageHeaderSize
|
||||||
}
|
}
|
||||||
|
// First element of Sglist is the header, so we don't need to count it here
|
||||||
r := 0
|
r := 0
|
||||||
for _, b := range m.Sglist {
|
for _, b := range m.Sglist {
|
||||||
r += len(b)
|
r += len(b)
|
||||||
|
@ -124,8 +115,8 @@ func (m *OutMessage) Len() int {
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bytes returns a byte slice containing the current header.
|
// OutHeaderBytes returns a byte slice containing the current header.
|
||||||
func (m *OutMessage) Bytes() []byte {
|
func (m *OutMessage) OutHeaderBytes() []byte {
|
||||||
l := OutMessageHeaderSize
|
l := OutMessageHeaderSize
|
||||||
sh := reflect.SliceHeader{
|
sh := reflect.SliceHeader{
|
||||||
Data: uintptr(unsafe.Pointer(&m.header)),
|
Data: uintptr(unsafe.Pointer(&m.header)),
|
||||||
|
|
|
@ -49,47 +49,6 @@ func findNonZero(p unsafe.Pointer, n int) int {
|
||||||
return n
|
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) {
|
func TestOutMessageAppend(t *testing.T) {
|
||||||
var om OutMessage
|
var om OutMessage
|
||||||
om.Reset()
|
om.Reset()
|
||||||
|
@ -112,7 +71,7 @@ func TestOutMessageAppend(t *testing.T) {
|
||||||
b = append(b, om.Sglist[i]...)
|
b = append(b, om.Sglist[i]...)
|
||||||
}
|
}
|
||||||
if got, want := len(b), wantLen; got != want {
|
if got, want := len(b), wantLen; got != want {
|
||||||
t.Fatalf("len(om.Bytes()) = %d, want %d", got, want)
|
t.Fatalf("len(om.OutHeaderBytes()) = %d, want %d", got, want)
|
||||||
}
|
}
|
||||||
|
|
||||||
want := append(
|
want := append(
|
||||||
|
@ -145,7 +104,7 @@ func TestOutMessageAppendString(t *testing.T) {
|
||||||
b = append(b, om.Sglist[i]...)
|
b = append(b, om.Sglist[i]...)
|
||||||
}
|
}
|
||||||
if got, want := len(b), wantLen; got != want {
|
if got, want := len(b), wantLen; got != want {
|
||||||
t.Fatalf("len(om.Bytes()) = %d, want %d", got, want)
|
t.Fatalf("len(om.OutHeaderBytes()) = %d, want %d", got, want)
|
||||||
}
|
}
|
||||||
|
|
||||||
want := append(
|
want := append(
|
||||||
|
@ -179,7 +138,7 @@ func TestOutMessageShrinkTo(t *testing.T) {
|
||||||
b = append(b, om.Sglist[i]...)
|
b = append(b, om.Sglist[i]...)
|
||||||
}
|
}
|
||||||
if got, want := len(b), wantLen; got != want {
|
if got, want := len(b), wantLen; got != want {
|
||||||
t.Fatalf("len(om.Bytes()) = %d, want %d", got, want)
|
t.Fatalf("len(om.OutHeaderBytes()) = %d, want %d", got, want)
|
||||||
}
|
}
|
||||||
|
|
||||||
want := append(
|
want := append(
|
||||||
|
@ -210,7 +169,7 @@ func TestOutMessageHeader(t *testing.T) {
|
||||||
*h = want
|
*h = want
|
||||||
|
|
||||||
// Check that the result is as expected.
|
// Check that the result is as expected.
|
||||||
b := om.Bytes()
|
b := om.OutHeaderBytes()
|
||||||
if len(b) != int(unsafe.Sizeof(want)) {
|
if len(b) != int(unsafe.Sizeof(want)) {
|
||||||
t.Fatalf("unexpected length %d; want %d", len(b), unsafe.Sizeof(want))
|
t.Fatalf("unexpected length %d; want %d", len(b), unsafe.Sizeof(want))
|
||||||
}
|
}
|
||||||
|
@ -234,9 +193,7 @@ func TestOutMessageReset(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure a non-zero payload length.
|
// Ensure a non-zero payload length.
|
||||||
if p := om.GrowNoZero(128); p == nil {
|
om.Grow(128)
|
||||||
t.Fatal("GrowNoZero failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset.
|
// Reset.
|
||||||
om.Reset()
|
om.Reset()
|
||||||
|
@ -268,10 +225,7 @@ func TestOutMessageGrow(t *testing.T) {
|
||||||
// Set up garbage where the payload will soon be.
|
// Set up garbage where the payload will soon be.
|
||||||
const payloadSize = 1234
|
const payloadSize = 1234
|
||||||
{
|
{
|
||||||
p := om.GrowNoZero(payloadSize)
|
p := om.Grow(payloadSize)
|
||||||
if p == nil {
|
|
||||||
t.Fatal("GrowNoZero failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
err := fillWithGarbage(p, payloadSize)
|
err := fillWithGarbage(p, payloadSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -1,31 +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 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)
|
|
|
@ -1,18 +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.
|
|
||||||
|
|
||||||
// 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.
|
|
|
@ -168,7 +168,7 @@ const (
|
||||||
|
|
||||||
// OpenAccessModeMask is a bitmask that separates the access mode
|
// OpenAccessModeMask is a bitmask that separates the access mode
|
||||||
// from the other flags in OpenFlags.
|
// from the other flags in OpenFlags.
|
||||||
const OpenAccessModeMask OpenFlags = syscall.O_ACCMODE
|
const OpenAccessModeMask OpenFlags = OpenReadOnly | OpenWriteOnly | OpenReadWrite
|
||||||
|
|
||||||
// OpenFlags are the O_FOO flags passed to open/create/etc calls. For
|
// OpenFlags are the O_FOO flags passed to open/create/etc calls. For
|
||||||
// example, os.O_WRONLY | os.O_APPEND.
|
// example, os.O_WRONLY | os.O_APPEND.
|
||||||
|
@ -346,6 +346,34 @@ var releaseFlagNames = []flagName{
|
||||||
{uint32(ReleaseFlush), "ReleaseFlush"},
|
{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
|
// Opcodes
|
||||||
const (
|
const (
|
||||||
OpLookup = 1
|
OpLookup = 1
|
||||||
|
@ -386,7 +414,10 @@ const (
|
||||||
OpDestroy = 38
|
OpDestroy = 38
|
||||||
OpIoctl = 39 // Linux?
|
OpIoctl = 39 // Linux?
|
||||||
OpPoll = 40 // Linux?
|
OpPoll = 40 // Linux?
|
||||||
|
OpNotifyReply = 41
|
||||||
|
OpBatchForget = 42
|
||||||
OpFallocate = 43
|
OpFallocate = 43
|
||||||
|
OpReaddirplus = 44
|
||||||
|
|
||||||
// OS X
|
// OS X
|
||||||
OpSetvolname = 61
|
OpSetvolname = 61
|
||||||
|
@ -417,6 +448,16 @@ type ForgetIn struct {
|
||||||
Nlookup uint64
|
Nlookup uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type BatchForgetCountIn struct {
|
||||||
|
Count uint32
|
||||||
|
dummy uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
type BatchForgetEntryIn struct {
|
||||||
|
Inode int64
|
||||||
|
Nlookup uint64
|
||||||
|
}
|
||||||
|
|
||||||
type GetattrIn struct {
|
type GetattrIn struct {
|
||||||
GetattrFlags uint32
|
GetattrFlags uint32
|
||||||
dummy uint32
|
dummy uint32
|
||||||
|
@ -541,6 +582,18 @@ 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 {
|
type ReleaseIn struct {
|
||||||
Fh uint64
|
Fh uint64
|
||||||
Flags uint32
|
Flags uint32
|
||||||
|
@ -776,8 +829,15 @@ const (
|
||||||
NotifyCodePoll int32 = 1
|
NotifyCodePoll int32 = 1
|
||||||
NotifyCodeInvalInode int32 = 2
|
NotifyCodeInvalInode int32 = 2
|
||||||
NotifyCodeInvalEntry int32 = 3
|
NotifyCodeInvalEntry int32 = 3
|
||||||
|
NotifyCodeStore int32 = 4
|
||||||
|
NotifyCodeRetrieve int32 = 5
|
||||||
|
NotifyCodeDelete int32 = 6
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type NotifyPollWakeupOut struct {
|
||||||
|
Kh uint64
|
||||||
|
}
|
||||||
|
|
||||||
type NotifyInvalInodeOut struct {
|
type NotifyInvalInodeOut struct {
|
||||||
Ino uint64
|
Ino uint64
|
||||||
Off int64
|
Off int64
|
||||||
|
@ -789,3 +849,35 @@ type NotifyInvalEntryOut struct {
|
||||||
Namelen uint32
|
Namelen uint32
|
||||||
padding 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
|
||||||
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
// +build linux windows
|
||||||
|
|
||||||
package fusekernel
|
package fusekernel
|
||||||
|
|
||||||
import "time"
|
import "time"
|
|
@ -1 +0,0 @@
|
||||||
package fusekernel
|
|
75
mount.go
75
mount.go
|
@ -17,9 +17,11 @@ package fuse
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -41,16 +43,8 @@ func Mount(
|
||||||
config *MountConfig) (*MountedFileSystem, error) {
|
config *MountConfig) (*MountedFileSystem, error) {
|
||||||
// Sanity check: make sure the mount point exists and is a directory. This
|
// Sanity check: make sure the mount point exists and is a directory. This
|
||||||
// saves us from some confusing errors later on OS X.
|
// saves us from some confusing errors later on OS X.
|
||||||
fi, err := os.Stat(dir)
|
if err := checkMountPoint(dir); err != nil {
|
||||||
switch {
|
|
||||||
case os.IsNotExist(err):
|
|
||||||
return nil, 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.
|
// Initialize the struct.
|
||||||
|
@ -60,11 +54,17 @@ func Mount(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Begin the mounting process, which will continue in the background.
|
// 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)
|
ready := make(chan error, 1)
|
||||||
dev, err := mount(dir, config, ready)
|
dev, err := mount(dir, config, ready)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("mount: %v", err)
|
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.
|
// Choose a parent context for ops.
|
||||||
cfgCopy := *config
|
cfgCopy := *config
|
||||||
|
@ -72,6 +72,9 @@ func Mount(
|
||||||
cfgCopy.OpContext = context.Background()
|
cfgCopy.OpContext = context.Background()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if config.DebugLogger != nil {
|
||||||
|
config.DebugLogger.Println("Creating a connection object")
|
||||||
|
}
|
||||||
// Create a Connection object wrapping the device.
|
// Create a Connection object wrapping the device.
|
||||||
connection, err := newConnection(
|
connection, err := newConnection(
|
||||||
cfgCopy,
|
cfgCopy,
|
||||||
|
@ -81,6 +84,9 @@ func Mount(
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("newConnection: %v", err)
|
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.
|
// Serve the connection in the background. When done, set the join status.
|
||||||
go func() {
|
go func() {
|
||||||
|
@ -89,6 +95,10 @@ func Mount(
|
||||||
close(mfs.joinStatusAvailable)
|
close(mfs.joinStatusAvailable)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
if config.DebugLogger != nil {
|
||||||
|
config.DebugLogger.Println("Waiting for mounting process to complete")
|
||||||
|
}
|
||||||
|
|
||||||
// Wait for the mount process to complete.
|
// Wait for the mount process to complete.
|
||||||
if err := <-ready; err != nil {
|
if err := <-ready; err != nil {
|
||||||
return nil, fmt.Errorf("mount (background): %v", err)
|
return nil, fmt.Errorf("mount (background): %v", err)
|
||||||
|
@ -97,13 +107,39 @@ func Mount(
|
||||||
return mfs, nil
|
return mfs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func fusermount(binary string, argv []string, additionalEnv []string, wait bool) (*os.File, error) {
|
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")
|
||||||
|
}
|
||||||
// Create a socket pair.
|
// Create a socket pair.
|
||||||
fds, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM, 0)
|
fds, err := syscall.Socketpair(syscall.AF_UNIX, syscall.SOCK_STREAM, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Socketpair: %v", err)
|
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.
|
// Wrap the sockets into os.File objects that we will pass off to fusermount.
|
||||||
writeFile := os.NewFile(uintptr(fds[0]), "fusermount-child-writes")
|
writeFile := os.NewFile(uintptr(fds[0]), "fusermount-child-writes")
|
||||||
defer writeFile.Close()
|
defer writeFile.Close()
|
||||||
|
@ -111,6 +147,9 @@ func fusermount(binary string, argv []string, additionalEnv []string, wait bool)
|
||||||
readFile := os.NewFile(uintptr(fds[1]), "fusermount-parent-reads")
|
readFile := os.NewFile(uintptr(fds[1]), "fusermount-parent-reads")
|
||||||
defer readFile.Close()
|
defer readFile.Close()
|
||||||
|
|
||||||
|
if debugLogger != nil {
|
||||||
|
debugLogger.Println("Starting fusermount/os mount")
|
||||||
|
}
|
||||||
// Start fusermount/mount_macfuse/mount_osxfuse.
|
// Start fusermount/mount_macfuse/mount_osxfuse.
|
||||||
cmd := exec.Command(binary, argv...)
|
cmd := exec.Command(binary, argv...)
|
||||||
cmd.Env = append(os.Environ(), "_FUSE_COMMFD=3")
|
cmd.Env = append(os.Environ(), "_FUSE_COMMFD=3")
|
||||||
|
@ -128,6 +167,9 @@ func fusermount(binary string, argv []string, additionalEnv []string, wait bool)
|
||||||
return nil, fmt.Errorf("running %v: %v", binary, err)
|
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.
|
// Wrap the socket file in a connection.
|
||||||
c, err := net.FileConn(readFile)
|
c, err := net.FileConn(readFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -135,12 +177,18 @@ func fusermount(binary string, argv []string, additionalEnv []string, wait bool)
|
||||||
}
|
}
|
||||||
defer c.Close()
|
defer c.Close()
|
||||||
|
|
||||||
|
if debugLogger != nil {
|
||||||
|
debugLogger.Println("Checking that we have a unix domain socket")
|
||||||
|
}
|
||||||
// We expect to have a Unix domain socket.
|
// We expect to have a Unix domain socket.
|
||||||
uc, ok := c.(*net.UnixConn)
|
uc, ok := c.(*net.UnixConn)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("Expected UnixConn, got %T", c)
|
return nil, fmt.Errorf("Expected UnixConn, got %T", c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if debugLogger != nil {
|
||||||
|
debugLogger.Println("Read a message from socket")
|
||||||
|
}
|
||||||
// Read a message.
|
// Read a message.
|
||||||
buf := make([]byte, 32) // expect 1 byte
|
buf := make([]byte, 32) // expect 1 byte
|
||||||
oob := make([]byte, 32) // expect 24 bytes
|
oob := make([]byte, 32) // expect 24 bytes
|
||||||
|
@ -162,6 +210,10 @@ func fusermount(binary string, argv []string, additionalEnv []string, wait bool)
|
||||||
|
|
||||||
scm := scms[0]
|
scm := scms[0]
|
||||||
|
|
||||||
|
if debugLogger != nil {
|
||||||
|
debugLogger.Println("Successfully read the socket message.")
|
||||||
|
}
|
||||||
|
|
||||||
// Pull out the FD returned by fusermount
|
// Pull out the FD returned by fusermount
|
||||||
gotFds, err := syscall.ParseUnixRights(&scm)
|
gotFds, err := syscall.ParseUnixRights(&scm)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -172,6 +224,9 @@ func fusermount(binary string, argv []string, additionalEnv []string, wait bool)
|
||||||
return nil, fmt.Errorf("wanted 1 fd; got %#v", gotFds)
|
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.
|
// Turn the FD into an os.File.
|
||||||
return os.NewFile(uintptr(gotFds[0]), "/dev/fuse"), nil
|
return os.NewFile(uintptr(gotFds[0]), "/dev/fuse"), nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -151,14 +151,21 @@ type MountConfig struct {
|
||||||
// OpenDir calls at all (Linux >= 5.1):
|
// OpenDir calls at all (Linux >= 5.1):
|
||||||
EnableNoOpendirSupport bool
|
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.
|
// Disable FUSE default permissions.
|
||||||
// This is useful for situations where the backing data store (e.g., S3) doesn't
|
// This is useful for situations where the backing data store (e.g., S3) doesn't
|
||||||
// actually utilise any form of qualifiable UNIX permissions.
|
// actually utilise any form of qualifiable UNIX permissions.
|
||||||
DisableDefaultPermissions bool
|
DisableDefaultPermissions bool
|
||||||
|
|
||||||
// Use VectoredReadOp instead of ReadFileOp.
|
// Use vectored reads.
|
||||||
// Vectored read allows file systems to reduce memory copying overhead if
|
// Vectored read allows file systems to avoid memory copying overhead if
|
||||||
// the data is already in memory when they return it to FUSE.
|
// 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
|
UseVectoredRead bool
|
||||||
|
|
||||||
// OS X only.
|
// OS X only.
|
||||||
|
@ -179,6 +186,10 @@ type MountConfig struct {
|
||||||
// /proc/mounts will show the filesystem type as fuse.<Subtype>.
|
// /proc/mounts will show the filesystem type as fuse.<Subtype>.
|
||||||
// If not set, /proc/mounts will show the filesystem type as fuse/fuseblk.
|
// If not set, /proc/mounts will show the filesystem type as fuse/fuseblk.
|
||||||
Subtype string
|
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
|
// Create a map containing all of the key=value mount options to be given to
|
||||||
|
|
|
@ -124,7 +124,7 @@ func convertMountArgs(daemonVar string, libVar string,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
env := []string{ libVar+"=" }
|
env := []string{libVar + "="}
|
||||||
if daemonVar != "" {
|
if daemonVar != "" {
|
||||||
env = append(env, daemonVar+"="+os.Args[0])
|
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
|
// OSXFUSE seems to ignore InitResponse.MaxWrite, and uses
|
||||||
// this instead.
|
// this instead.
|
||||||
"-o", "iosize="+strconv.FormatUint(buffer.MaxWriteSize, 10),
|
"-o", "iosize=" + strconv.FormatUint(buffer.MaxWriteSize, 10),
|
||||||
}
|
}
|
||||||
|
|
||||||
return argv, env, nil
|
return argv, env, nil
|
||||||
|
@ -205,7 +205,7 @@ func callMountCommFD(
|
||||||
env = append(env, "_FUSE_COMMVERS=2")
|
env = append(env, "_FUSE_COMMVERS=2")
|
||||||
argv = append(argv, dir)
|
argv = append(argv, dir)
|
||||||
|
|
||||||
return fusermount(bin, argv, env, false)
|
return fusermount(bin, argv, env, false, cfg.DebugLogger)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Begin the process of mounting at the given directory, returning a connection
|
// Begin the process of mounting at the given directory, returning a connection
|
||||||
|
|
|
@ -5,6 +5,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
|
@ -53,6 +55,9 @@ var mountflagopts = map[string]func(uintptr) uintptr{
|
||||||
var errFallback = errors.New("sentinel: fallback to fusermount(1)")
|
var errFallback = errors.New("sentinel: fallback to fusermount(1)")
|
||||||
|
|
||||||
func directmount(dir string, cfg *MountConfig) (*os.File, error) {
|
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
|
// 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
|
// 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.
|
// runtime tries to use poll(2), which does not work with /dev/fuse.
|
||||||
|
@ -61,6 +66,10 @@ func directmount(dir string, cfg *MountConfig) (*os.File, error) {
|
||||||
return nil, errFallback
|
return nil, errFallback
|
||||||
}
|
}
|
||||||
dev := os.NewFile(uintptr(fd), "/dev/fuse")
|
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
|
// As per libfuse/fusermount.c:847: https://bit.ly/2SgtWYM#L847
|
||||||
data := fmt.Sprintf("fd=%d,rootmode=40000,user_id=%d,group_id=%d",
|
data := fmt.Sprintf("fd=%d,rootmode=40000,user_id=%d,group_id=%d",
|
||||||
dev.Fd(), os.Getuid(), os.Getgid())
|
dev.Fd(), os.Getuid(), os.Getgid())
|
||||||
|
@ -82,6 +91,10 @@ func directmount(dir string, cfg *MountConfig) (*os.File, error) {
|
||||||
}
|
}
|
||||||
delete(opts, "subtype")
|
delete(opts, "subtype")
|
||||||
data += "," + mapToOptionsString(opts)
|
data += "," + mapToOptionsString(opts)
|
||||||
|
|
||||||
|
if cfg.DebugLogger != nil {
|
||||||
|
cfg.DebugLogger.Println("Starting the unix mounting")
|
||||||
|
}
|
||||||
if err := unix.Mount(
|
if err := unix.Mount(
|
||||||
cfg.FSName, // source
|
cfg.FSName, // source
|
||||||
dir, // target
|
dir, // target
|
||||||
|
@ -95,6 +108,9 @@ func directmount(dir string, cfg *MountConfig) (*os.File, error) {
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if cfg.DebugLogger != nil {
|
||||||
|
cfg.DebugLogger.Println("Unix mounting completed successfully")
|
||||||
|
}
|
||||||
return dev, nil
|
return dev, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -106,10 +122,24 @@ func mount(dir string, cfg *MountConfig, ready chan<- error) (*os.File, error) {
|
||||||
// On linux, mounting is never delayed.
|
// On linux, mounting is never delayed.
|
||||||
ready <- nil
|
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
|
// Try mounting without fusermount(1) first: we might be running as root or
|
||||||
// have the CAP_SYS_ADMIN capability.
|
// have the CAP_SYS_ADMIN capability.
|
||||||
dev, err := directmount(dir, cfg)
|
dev, err := directmount(dir, cfg)
|
||||||
if err == errFallback {
|
if err == errFallback {
|
||||||
|
if cfg.DebugLogger != nil {
|
||||||
|
cfg.DebugLogger.Println("Directmount failed. Trying fallback.")
|
||||||
|
}
|
||||||
fusermountPath, err := findFusermount()
|
fusermountPath, err := findFusermount()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -119,7 +149,20 @@ func mount(dir string, cfg *MountConfig, ready chan<- error) (*os.File, error) {
|
||||||
"--",
|
"--",
|
||||||
dir,
|
dir,
|
||||||
}
|
}
|
||||||
return fusermount(fusermountPath, argv, []string{}, true)
|
return fusermount(fusermountPath, argv, []string{}, true, cfg.DebugLogger)
|
||||||
}
|
}
|
||||||
return dev, err
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -14,7 +14,10 @@
|
||||||
|
|
||||||
package fuse
|
package fuse
|
||||||
|
|
||||||
import "context"
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
// MountedFileSystem represents the status of a mount operation, with a method
|
// MountedFileSystem represents the status of a mount operation, with a method
|
||||||
// that waits for unmounting.
|
// that waits for unmounting.
|
||||||
|
@ -47,3 +50,17 @@ func (mfs *MountedFileSystem) Join(ctx context.Context) error {
|
||||||
return ctx.Err()
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -36,9 +36,9 @@ const (
|
||||||
|
|
||||||
// A file system with a fixed structure that looks like this:
|
// A file system with a fixed structure that looks like this:
|
||||||
//
|
//
|
||||||
// foo
|
// foo
|
||||||
// dir/
|
// dir/
|
||||||
// bar
|
// bar
|
||||||
//
|
//
|
||||||
// The file system is configured with durations that specify how long to allow
|
// 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
|
// inode entries and attributes to be cached, used when responding to fuse
|
||||||
|
@ -71,15 +71,14 @@ type CachingFS interface {
|
||||||
// Create a file system that issues cacheable responses according to the
|
// Create a file system that issues cacheable responses according to the
|
||||||
// following rules:
|
// following rules:
|
||||||
//
|
//
|
||||||
// * LookUpInodeResponse.Entry.EntryExpiration is set according to
|
// - LookUpInodeResponse.Entry.EntryExpiration is set according to
|
||||||
// lookupEntryTimeout.
|
// lookupEntryTimeout.
|
||||||
//
|
//
|
||||||
// * GetInodeAttributesResponse.AttributesExpiration is set according to
|
// - GetInodeAttributesResponse.AttributesExpiration is set according to
|
||||||
// getattrTimeout.
|
// 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.)
|
// returned by LookUpInode are not cacheable.)
|
||||||
//
|
|
||||||
func NewCachingFS(
|
func NewCachingFS(
|
||||||
lookupEntryTimeout time.Duration,
|
lookupEntryTimeout time.Duration,
|
||||||
getattrTimeout time.Duration) (CachingFS, error) {
|
getattrTimeout time.Duration) (CachingFS, error) {
|
||||||
|
|
|
@ -28,9 +28,9 @@ import (
|
||||||
|
|
||||||
// Create a file system with a fixed structure that looks like this:
|
// Create a file system with a fixed structure that looks like this:
|
||||||
//
|
//
|
||||||
// hello
|
// hello
|
||||||
// dir/
|
// dir/
|
||||||
// world
|
// world
|
||||||
//
|
//
|
||||||
// Each file contains the string "Hello, world!".
|
// Each file contains the string "Hello, world!".
|
||||||
func NewHelloFS(clock timeutil.Clock) (fuse.Server, error) {
|
func NewHelloFS(clock timeutil.Clock) (fuse.Server, error) {
|
||||||
|
|
|
@ -33,6 +33,10 @@ type inode struct {
|
||||||
// Mutable state
|
// 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.
|
// The current attributes of this inode.
|
||||||
//
|
//
|
||||||
// INVARIANT: attrs.Mode &^ (os.ModePerm|os.ModeDir|os.ModeSymlink) == 0
|
// INVARIANT: attrs.Mode &^ (os.ModePerm|os.ModeDir|os.ModeSymlink) == 0
|
||||||
|
@ -73,7 +77,7 @@ type inode struct {
|
||||||
|
|
||||||
// Create a new inode with the supplied attributes, which need not contain
|
// Create a new inode with the supplied attributes, which need not contain
|
||||||
// time-related information (the inode object will take care of that).
|
// time-related information (the inode object will take care of that).
|
||||||
func newInode(attrs fuseops.InodeAttributes) *inode {
|
func newInode(attrs fuseops.InodeAttributes, name string) *inode {
|
||||||
// Update time info.
|
// Update time info.
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
attrs.Mtime = now
|
attrs.Mtime = now
|
||||||
|
@ -81,6 +85,7 @@ func newInode(attrs fuseops.InodeAttributes) *inode {
|
||||||
|
|
||||||
// Create the object.
|
// Create the object.
|
||||||
return &inode{
|
return &inode{
|
||||||
|
name: name,
|
||||||
attrs: attrs,
|
attrs: attrs,
|
||||||
xattrs: make(map[string][]byte),
|
xattrs: make(map[string][]byte),
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ package memfs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
|
@ -29,6 +30,11 @@ import (
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
FileOpenFlagsXattrName = "fileOpenFlagsXattr"
|
||||||
|
CheckFileOpenFlagsFileName = "checkFileOpenFlags"
|
||||||
|
)
|
||||||
|
|
||||||
type memFS struct {
|
type memFS struct {
|
||||||
fuseutil.NotImplementedFileSystem
|
fuseutil.NotImplementedFileSystem
|
||||||
|
|
||||||
|
@ -85,7 +91,7 @@ func NewMemFS(
|
||||||
Gid: gid,
|
Gid: gid,
|
||||||
}
|
}
|
||||||
|
|
||||||
fs.inodes[fuseops.RootInodeID] = newInode(rootAttrs)
|
fs.inodes[fuseops.RootInodeID] = newInode(rootAttrs, "")
|
||||||
|
|
||||||
// Set up invariant checking.
|
// Set up invariant checking.
|
||||||
fs.mu = syncutil.NewInvariantMutex(fs.checkInvariants)
|
fs.mu = syncutil.NewInvariantMutex(fs.checkInvariants)
|
||||||
|
@ -157,9 +163,9 @@ func (fs *memFS) getInodeOrDie(id fuseops.InodeID) *inode {
|
||||||
//
|
//
|
||||||
// LOCKS_REQUIRED(fs.mu)
|
// LOCKS_REQUIRED(fs.mu)
|
||||||
func (fs *memFS) allocateInode(
|
func (fs *memFS) allocateInode(
|
||||||
attrs fuseops.InodeAttributes) (id fuseops.InodeID, inode *inode) {
|
attrs fuseops.InodeAttributes, name string) (id fuseops.InodeID, inode *inode) {
|
||||||
// Create the inode.
|
// Create the inode.
|
||||||
inode = newInode(attrs)
|
inode = newInode(attrs, name)
|
||||||
|
|
||||||
// Re-use a free ID if possible. Otherwise mint a new one.
|
// Re-use a free ID if possible. Otherwise mint a new one.
|
||||||
numFree := len(fs.freeInodes)
|
numFree := len(fs.freeInodes)
|
||||||
|
@ -194,10 +200,6 @@ func (fs *memFS) StatFS(
|
||||||
func (fs *memFS) LookUpInode(
|
func (fs *memFS) LookUpInode(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
op *fuseops.LookUpInodeOp) error {
|
op *fuseops.LookUpInodeOp) error {
|
||||||
if op.OpContext.Pid == 0 {
|
|
||||||
return fuse.EINVAL
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.mu.Lock()
|
fs.mu.Lock()
|
||||||
defer fs.mu.Unlock()
|
defer fs.mu.Unlock()
|
||||||
|
|
||||||
|
@ -228,10 +230,6 @@ func (fs *memFS) LookUpInode(
|
||||||
func (fs *memFS) GetInodeAttributes(
|
func (fs *memFS) GetInodeAttributes(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
op *fuseops.GetInodeAttributesOp) error {
|
op *fuseops.GetInodeAttributesOp) error {
|
||||||
if op.OpContext.Pid == 0 {
|
|
||||||
return fuse.EINVAL
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.mu.Lock()
|
fs.mu.Lock()
|
||||||
defer fs.mu.Unlock()
|
defer fs.mu.Unlock()
|
||||||
|
|
||||||
|
@ -251,10 +249,6 @@ func (fs *memFS) GetInodeAttributes(
|
||||||
func (fs *memFS) SetInodeAttributes(
|
func (fs *memFS) SetInodeAttributes(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
op *fuseops.SetInodeAttributesOp) error {
|
op *fuseops.SetInodeAttributesOp) error {
|
||||||
if op.OpContext.Pid == 0 {
|
|
||||||
return fuse.EINVAL
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.mu.Lock()
|
fs.mu.Lock()
|
||||||
defer fs.mu.Unlock()
|
defer fs.mu.Unlock()
|
||||||
|
|
||||||
|
@ -284,10 +278,6 @@ func (fs *memFS) SetInodeAttributes(
|
||||||
func (fs *memFS) MkDir(
|
func (fs *memFS) MkDir(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
op *fuseops.MkDirOp) error {
|
op *fuseops.MkDirOp) error {
|
||||||
if op.OpContext.Pid == 0 {
|
|
||||||
return fuse.EINVAL
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.mu.Lock()
|
fs.mu.Lock()
|
||||||
defer fs.mu.Unlock()
|
defer fs.mu.Unlock()
|
||||||
|
|
||||||
|
@ -310,7 +300,7 @@ func (fs *memFS) MkDir(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allocate a child.
|
// Allocate a child.
|
||||||
childID, child := fs.allocateInode(childAttrs)
|
childID, child := fs.allocateInode(childAttrs, op.Name)
|
||||||
|
|
||||||
// Add an entry in the parent.
|
// Add an entry in the parent.
|
||||||
parent.AddChild(childID, op.Name, fuseutil.DT_Directory)
|
parent.AddChild(childID, op.Name, fuseutil.DT_Directory)
|
||||||
|
@ -330,10 +320,6 @@ func (fs *memFS) MkDir(
|
||||||
func (fs *memFS) MkNode(
|
func (fs *memFS) MkNode(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
op *fuseops.MkNodeOp) error {
|
op *fuseops.MkNodeOp) error {
|
||||||
if op.OpContext.Pid == 0 {
|
|
||||||
return fuse.EINVAL
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.mu.Lock()
|
fs.mu.Lock()
|
||||||
defer fs.mu.Unlock()
|
defer fs.mu.Unlock()
|
||||||
|
|
||||||
|
@ -371,7 +357,7 @@ func (fs *memFS) createFile(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allocate a child.
|
// Allocate a child.
|
||||||
childID, child := fs.allocateInode(childAttrs)
|
childID, child := fs.allocateInode(childAttrs, name)
|
||||||
|
|
||||||
// Add an entry in the parent.
|
// Add an entry in the parent.
|
||||||
parent.AddChild(childID, name, fuseutil.DT_File)
|
parent.AddChild(childID, name, fuseutil.DT_File)
|
||||||
|
@ -392,11 +378,6 @@ func (fs *memFS) createFile(
|
||||||
func (fs *memFS) CreateFile(
|
func (fs *memFS) CreateFile(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
op *fuseops.CreateFileOp) (err error) {
|
op *fuseops.CreateFileOp) (err error) {
|
||||||
if op.OpContext.Pid == 0 {
|
|
||||||
// CreateFileOp should have a valid pid in context.
|
|
||||||
return fuse.EINVAL
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.mu.Lock()
|
fs.mu.Lock()
|
||||||
defer fs.mu.Unlock()
|
defer fs.mu.Unlock()
|
||||||
|
|
||||||
|
@ -407,10 +388,6 @@ func (fs *memFS) CreateFile(
|
||||||
func (fs *memFS) CreateSymlink(
|
func (fs *memFS) CreateSymlink(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
op *fuseops.CreateSymlinkOp) error {
|
op *fuseops.CreateSymlinkOp) error {
|
||||||
if op.OpContext.Pid == 0 {
|
|
||||||
return fuse.EINVAL
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.mu.Lock()
|
fs.mu.Lock()
|
||||||
defer fs.mu.Unlock()
|
defer fs.mu.Unlock()
|
||||||
|
|
||||||
|
@ -438,7 +415,7 @@ func (fs *memFS) CreateSymlink(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allocate a child.
|
// Allocate a child.
|
||||||
childID, child := fs.allocateInode(childAttrs)
|
childID, child := fs.allocateInode(childAttrs, op.Name)
|
||||||
|
|
||||||
// Set up its target.
|
// Set up its target.
|
||||||
child.target = op.Target
|
child.target = op.Target
|
||||||
|
@ -461,10 +438,6 @@ func (fs *memFS) CreateSymlink(
|
||||||
func (fs *memFS) CreateLink(
|
func (fs *memFS) CreateLink(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
op *fuseops.CreateLinkOp) error {
|
op *fuseops.CreateLinkOp) error {
|
||||||
if op.OpContext.Pid == 0 {
|
|
||||||
return fuse.EINVAL
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.mu.Lock()
|
fs.mu.Lock()
|
||||||
defer fs.mu.Unlock()
|
defer fs.mu.Unlock()
|
||||||
|
|
||||||
|
@ -504,10 +477,6 @@ func (fs *memFS) CreateLink(
|
||||||
func (fs *memFS) Rename(
|
func (fs *memFS) Rename(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
op *fuseops.RenameOp) error {
|
op *fuseops.RenameOp) error {
|
||||||
if op.OpContext.Pid == 0 {
|
|
||||||
return fuse.EINVAL
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.mu.Lock()
|
fs.mu.Lock()
|
||||||
defer fs.mu.Unlock()
|
defer fs.mu.Unlock()
|
||||||
|
|
||||||
|
@ -549,10 +518,6 @@ func (fs *memFS) Rename(
|
||||||
func (fs *memFS) RmDir(
|
func (fs *memFS) RmDir(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
op *fuseops.RmDirOp) error {
|
op *fuseops.RmDirOp) error {
|
||||||
if op.OpContext.Pid == 0 {
|
|
||||||
return fuse.EINVAL
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.mu.Lock()
|
fs.mu.Lock()
|
||||||
defer fs.mu.Unlock()
|
defer fs.mu.Unlock()
|
||||||
|
|
||||||
|
@ -585,10 +550,6 @@ func (fs *memFS) RmDir(
|
||||||
func (fs *memFS) Unlink(
|
func (fs *memFS) Unlink(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
op *fuseops.UnlinkOp) error {
|
op *fuseops.UnlinkOp) error {
|
||||||
if op.OpContext.Pid == 0 {
|
|
||||||
return fuse.EINVAL
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.mu.Lock()
|
fs.mu.Lock()
|
||||||
defer fs.mu.Unlock()
|
defer fs.mu.Unlock()
|
||||||
|
|
||||||
|
@ -616,10 +577,6 @@ func (fs *memFS) Unlink(
|
||||||
func (fs *memFS) OpenDir(
|
func (fs *memFS) OpenDir(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
op *fuseops.OpenDirOp) error {
|
op *fuseops.OpenDirOp) error {
|
||||||
if op.OpContext.Pid == 0 {
|
|
||||||
return fuse.EINVAL
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.mu.Lock()
|
fs.mu.Lock()
|
||||||
defer fs.mu.Unlock()
|
defer fs.mu.Unlock()
|
||||||
|
|
||||||
|
@ -638,10 +595,6 @@ func (fs *memFS) OpenDir(
|
||||||
func (fs *memFS) ReadDir(
|
func (fs *memFS) ReadDir(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
op *fuseops.ReadDirOp) error {
|
op *fuseops.ReadDirOp) error {
|
||||||
if op.OpContext.Pid == 0 {
|
|
||||||
return fuse.EINVAL
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.mu.Lock()
|
fs.mu.Lock()
|
||||||
defer fs.mu.Unlock()
|
defer fs.mu.Unlock()
|
||||||
|
|
||||||
|
@ -657,11 +610,6 @@ func (fs *memFS) ReadDir(
|
||||||
func (fs *memFS) OpenFile(
|
func (fs *memFS) OpenFile(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
op *fuseops.OpenFileOp) error {
|
op *fuseops.OpenFileOp) error {
|
||||||
if op.OpContext.Pid == 0 {
|
|
||||||
// OpenFileOp should have a valid pid in context.
|
|
||||||
return fuse.EINVAL
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.mu.Lock()
|
fs.mu.Lock()
|
||||||
defer fs.mu.Unlock()
|
defer fs.mu.Unlock()
|
||||||
|
|
||||||
|
@ -674,16 +622,27 @@ func (fs *memFS) OpenFile(
|
||||||
panic("Found non-file.")
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fs *memFS) ReadFile(
|
func (fs *memFS) ReadFile(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
op *fuseops.ReadFileOp) error {
|
op *fuseops.ReadFileOp) error {
|
||||||
if op.OpContext.Pid == 0 {
|
|
||||||
return fuse.EINVAL
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.mu.Lock()
|
fs.mu.Lock()
|
||||||
defer fs.mu.Unlock()
|
defer fs.mu.Unlock()
|
||||||
|
|
||||||
|
@ -705,10 +664,6 @@ func (fs *memFS) ReadFile(
|
||||||
func (fs *memFS) WriteFile(
|
func (fs *memFS) WriteFile(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
op *fuseops.WriteFileOp) error {
|
op *fuseops.WriteFileOp) error {
|
||||||
if op.OpContext.Pid == 0 {
|
|
||||||
return fuse.EINVAL
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.mu.Lock()
|
fs.mu.Lock()
|
||||||
defer fs.mu.Unlock()
|
defer fs.mu.Unlock()
|
||||||
|
|
||||||
|
@ -724,20 +679,12 @@ func (fs *memFS) WriteFile(
|
||||||
func (fs *memFS) FlushFile(
|
func (fs *memFS) FlushFile(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
op *fuseops.FlushFileOp) (err error) {
|
op *fuseops.FlushFileOp) (err error) {
|
||||||
if op.OpContext.Pid == 0 {
|
|
||||||
// FlushFileOp should have a valid pid in context.
|
|
||||||
return fuse.EINVAL
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fs *memFS) ReadSymlink(
|
func (fs *memFS) ReadSymlink(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
op *fuseops.ReadSymlinkOp) error {
|
op *fuseops.ReadSymlinkOp) error {
|
||||||
if op.OpContext.Pid == 0 {
|
|
||||||
return fuse.EINVAL
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.mu.Lock()
|
fs.mu.Lock()
|
||||||
defer fs.mu.Unlock()
|
defer fs.mu.Unlock()
|
||||||
|
|
||||||
|
@ -752,10 +699,6 @@ func (fs *memFS) ReadSymlink(
|
||||||
|
|
||||||
func (fs *memFS) GetXattr(ctx context.Context,
|
func (fs *memFS) GetXattr(ctx context.Context,
|
||||||
op *fuseops.GetXattrOp) error {
|
op *fuseops.GetXattrOp) error {
|
||||||
if op.OpContext.Pid == 0 {
|
|
||||||
return fuse.EINVAL
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.mu.Lock()
|
fs.mu.Lock()
|
||||||
defer fs.mu.Unlock()
|
defer fs.mu.Unlock()
|
||||||
|
|
||||||
|
@ -776,10 +719,6 @@ func (fs *memFS) GetXattr(ctx context.Context,
|
||||||
|
|
||||||
func (fs *memFS) ListXattr(ctx context.Context,
|
func (fs *memFS) ListXattr(ctx context.Context,
|
||||||
op *fuseops.ListXattrOp) error {
|
op *fuseops.ListXattrOp) error {
|
||||||
if op.OpContext.Pid == 0 {
|
|
||||||
return fuse.EINVAL
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.mu.Lock()
|
fs.mu.Lock()
|
||||||
defer fs.mu.Unlock()
|
defer fs.mu.Unlock()
|
||||||
|
|
||||||
|
@ -803,10 +742,6 @@ func (fs *memFS) ListXattr(ctx context.Context,
|
||||||
|
|
||||||
func (fs *memFS) RemoveXattr(ctx context.Context,
|
func (fs *memFS) RemoveXattr(ctx context.Context,
|
||||||
op *fuseops.RemoveXattrOp) error {
|
op *fuseops.RemoveXattrOp) error {
|
||||||
if op.OpContext.Pid == 0 {
|
|
||||||
return fuse.EINVAL
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.mu.Lock()
|
fs.mu.Lock()
|
||||||
defer fs.mu.Unlock()
|
defer fs.mu.Unlock()
|
||||||
inode := fs.getInodeOrDie(op.Inode)
|
inode := fs.getInodeOrDie(op.Inode)
|
||||||
|
@ -821,14 +756,15 @@ func (fs *memFS) RemoveXattr(ctx context.Context,
|
||||||
|
|
||||||
func (fs *memFS) SetXattr(ctx context.Context,
|
func (fs *memFS) SetXattr(ctx context.Context,
|
||||||
op *fuseops.SetXattrOp) error {
|
op *fuseops.SetXattrOp) error {
|
||||||
if op.OpContext.Pid == 0 {
|
|
||||||
return fuse.EINVAL
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.mu.Lock()
|
fs.mu.Lock()
|
||||||
defer fs.mu.Unlock()
|
defer fs.mu.Unlock()
|
||||||
inode := fs.getInodeOrDie(op.Inode)
|
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]
|
_, ok := inode.xattrs[op.Name]
|
||||||
|
|
||||||
switch op.Flags {
|
switch op.Flags {
|
||||||
|
@ -850,10 +786,6 @@ func (fs *memFS) SetXattr(ctx context.Context,
|
||||||
|
|
||||||
func (fs *memFS) Fallocate(ctx context.Context,
|
func (fs *memFS) Fallocate(ctx context.Context,
|
||||||
op *fuseops.FallocateOp) error {
|
op *fuseops.FallocateOp) error {
|
||||||
if op.OpContext.Pid == 0 {
|
|
||||||
return fuse.EINVAL
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.mu.Lock()
|
fs.mu.Lock()
|
||||||
defer fs.mu.Unlock()
|
defer fs.mu.Unlock()
|
||||||
inode := fs.getInodeOrDie(op.Inode)
|
inode := fs.getInodeOrDie(op.Inode)
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
//go:build go1.8
|
||||||
// +build go1.8
|
// +build go1.8
|
||||||
|
|
||||||
package memfs_test
|
package memfs_test
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
//go:build !go1.8
|
||||||
// +build !go1.8
|
// +build !go1.8
|
||||||
|
|
||||||
package memfs_test
|
package memfs_test
|
||||||
|
|
|
@ -16,6 +16,7 @@ package memfs_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
@ -31,6 +32,7 @@ import (
|
||||||
fallocate "github.com/detailyang/go-fallocate"
|
fallocate "github.com/detailyang/go-fallocate"
|
||||||
"github.com/jacobsa/fuse"
|
"github.com/jacobsa/fuse"
|
||||||
"github.com/jacobsa/fuse/fusetesting"
|
"github.com/jacobsa/fuse/fusetesting"
|
||||||
|
"github.com/jacobsa/fuse/internal/fusekernel"
|
||||||
"github.com/jacobsa/fuse/samples"
|
"github.com/jacobsa/fuse/samples"
|
||||||
"github.com/jacobsa/fuse/samples/memfs"
|
"github.com/jacobsa/fuse/samples/memfs"
|
||||||
. "github.com/jacobsa/oglematchers"
|
. "github.com/jacobsa/oglematchers"
|
||||||
|
@ -88,6 +90,15 @@ func applyUmask(m os.FileMode) os.FileMode {
|
||||||
return m &^ os.FileMode(umask)
|
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
|
// Boilerplate
|
||||||
////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -292,7 +303,7 @@ func (t *MemFSTest) CreateNewFile_InRoot() {
|
||||||
var stat *syscall.Stat_t
|
var stat *syscall.Stat_t
|
||||||
|
|
||||||
// Write a file.
|
// Write a file.
|
||||||
fileName := path.Join(t.Dir, "foo")
|
fileName := path.Join(t.Dir, memfs.CheckFileOpenFlagsFileName)
|
||||||
const contents = "Hello\x00world"
|
const contents = "Hello\x00world"
|
||||||
|
|
||||||
createTime := time.Now()
|
createTime := time.Now()
|
||||||
|
@ -304,7 +315,7 @@ func (t *MemFSTest) CreateNewFile_InRoot() {
|
||||||
stat = fi.Sys().(*syscall.Stat_t)
|
stat = fi.Sys().(*syscall.Stat_t)
|
||||||
|
|
||||||
AssertEq(nil, err)
|
AssertEq(nil, err)
|
||||||
ExpectEq("foo", fi.Name())
|
ExpectEq(memfs.CheckFileOpenFlagsFileName, fi.Name())
|
||||||
ExpectEq(len(contents), fi.Size())
|
ExpectEq(len(contents), fi.Size())
|
||||||
ExpectEq(applyUmask(0400), fi.Mode())
|
ExpectEq(applyUmask(0400), fi.Mode())
|
||||||
ExpectThat(fi, fusetesting.MtimeIsWithin(createTime, timeSlop))
|
ExpectThat(fi, fusetesting.MtimeIsWithin(createTime, timeSlop))
|
||||||
|
@ -321,6 +332,7 @@ func (t *MemFSTest) CreateNewFile_InRoot() {
|
||||||
slice, err := ioutil.ReadFile(fileName)
|
slice, err := ioutil.ReadFile(fileName)
|
||||||
AssertEq(nil, err)
|
AssertEq(nil, err)
|
||||||
ExpectEq(contents, string(slice))
|
ExpectEq(contents, string(slice))
|
||||||
|
t.checkOpenFlagsXattr(fileName, fusekernel.OpenReadOnly)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *MemFSTest) CreateNewFile_InSubDir() {
|
func (t *MemFSTest) CreateNewFile_InSubDir() {
|
||||||
|
@ -372,7 +384,7 @@ func (t *MemFSTest) ModifyExistingFile_InRoot() {
|
||||||
var stat *syscall.Stat_t
|
var stat *syscall.Stat_t
|
||||||
|
|
||||||
// Write a file.
|
// Write a file.
|
||||||
fileName := path.Join(t.Dir, "foo")
|
fileName := path.Join(t.Dir, memfs.CheckFileOpenFlagsFileName)
|
||||||
|
|
||||||
createTime := time.Now()
|
createTime := time.Now()
|
||||||
err = ioutil.WriteFile(fileName, []byte("Hello, world!"), 0600)
|
err = ioutil.WriteFile(fileName, []byte("Hello, world!"), 0600)
|
||||||
|
@ -382,6 +394,7 @@ func (t *MemFSTest) ModifyExistingFile_InRoot() {
|
||||||
f, err := os.OpenFile(fileName, os.O_WRONLY, 0400)
|
f, err := os.OpenFile(fileName, os.O_WRONLY, 0400)
|
||||||
t.ToClose = append(t.ToClose, f)
|
t.ToClose = append(t.ToClose, f)
|
||||||
AssertEq(nil, err)
|
AssertEq(nil, err)
|
||||||
|
t.checkOpenFlagsXattr(fileName, fusekernel.OpenWriteOnly)
|
||||||
|
|
||||||
modifyTime := time.Now()
|
modifyTime := time.Now()
|
||||||
n, err = f.WriteAt([]byte("H"), 0)
|
n, err = f.WriteAt([]byte("H"), 0)
|
||||||
|
@ -393,7 +406,7 @@ func (t *MemFSTest) ModifyExistingFile_InRoot() {
|
||||||
stat = fi.Sys().(*syscall.Stat_t)
|
stat = fi.Sys().(*syscall.Stat_t)
|
||||||
|
|
||||||
AssertEq(nil, err)
|
AssertEq(nil, err)
|
||||||
ExpectEq("foo", fi.Name())
|
ExpectEq(memfs.CheckFileOpenFlagsFileName, fi.Name())
|
||||||
ExpectEq(len("Hello, world!"), fi.Size())
|
ExpectEq(len("Hello, world!"), fi.Size())
|
||||||
ExpectEq(applyUmask(0600), fi.Mode())
|
ExpectEq(applyUmask(0600), fi.Mode())
|
||||||
ExpectThat(fi, fusetesting.MtimeIsWithin(modifyTime, timeSlop))
|
ExpectThat(fi, fusetesting.MtimeIsWithin(modifyTime, timeSlop))
|
||||||
|
@ -410,6 +423,7 @@ func (t *MemFSTest) ModifyExistingFile_InRoot() {
|
||||||
slice, err := ioutil.ReadFile(fileName)
|
slice, err := ioutil.ReadFile(fileName)
|
||||||
AssertEq(nil, err)
|
AssertEq(nil, err)
|
||||||
ExpectEq("Hello, world!", string(slice))
|
ExpectEq("Hello, world!", string(slice))
|
||||||
|
t.checkOpenFlagsXattr(fileName, fusekernel.OpenReadOnly)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *MemFSTest) ModifyExistingFile_InSubDir() {
|
func (t *MemFSTest) ModifyExistingFile_InSubDir() {
|
||||||
|
@ -832,7 +846,7 @@ func (t *MemFSTest) AppendMode() {
|
||||||
buf := make([]byte, 1024)
|
buf := make([]byte, 1024)
|
||||||
|
|
||||||
// Create a file with some contents.
|
// Create a file with some contents.
|
||||||
fileName := path.Join(t.Dir, "foo")
|
fileName := path.Join(t.Dir, memfs.CheckFileOpenFlagsFileName)
|
||||||
err = ioutil.WriteFile(fileName, []byte("Jello, "), 0600)
|
err = ioutil.WriteFile(fileName, []byte("Jello, "), 0600)
|
||||||
AssertEq(nil, err)
|
AssertEq(nil, err)
|
||||||
|
|
||||||
|
@ -840,6 +854,7 @@ func (t *MemFSTest) AppendMode() {
|
||||||
f, err := os.OpenFile(fileName, os.O_RDWR|os.O_APPEND, 0600)
|
f, err := os.OpenFile(fileName, os.O_RDWR|os.O_APPEND, 0600)
|
||||||
t.ToClose = append(t.ToClose, f)
|
t.ToClose = append(t.ToClose, f)
|
||||||
AssertEq(nil, err)
|
AssertEq(nil, err)
|
||||||
|
t.checkOpenFlagsXattr(fileName, fusekernel.OpenReadWrite)
|
||||||
|
|
||||||
// Seek to somewhere silly and then write.
|
// Seek to somewhere silly and then write.
|
||||||
off, err = f.Seek(2, 0)
|
off, err = f.Seek(2, 0)
|
||||||
|
@ -855,10 +870,6 @@ func (t *MemFSTest) AppendMode() {
|
||||||
AssertEq(nil, err)
|
AssertEq(nil, err)
|
||||||
ExpectEq(13, off)
|
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
|
// Read back the contents of the file, which should be correct even though we
|
||||||
// seeked to a silly place before writing the world part.
|
// seeked to a silly place before writing the world part.
|
||||||
//
|
//
|
||||||
|
@ -911,7 +922,7 @@ func (t *MemFSTest) ReadsPastEndOfFile() {
|
||||||
|
|
||||||
func (t *MemFSTest) Truncate_Smaller() {
|
func (t *MemFSTest) Truncate_Smaller() {
|
||||||
var err error
|
var err error
|
||||||
fileName := path.Join(t.Dir, "foo")
|
fileName := path.Join(t.Dir, memfs.CheckFileOpenFlagsFileName)
|
||||||
|
|
||||||
// Create a file.
|
// Create a file.
|
||||||
err = ioutil.WriteFile(fileName, []byte("taco"), 0600)
|
err = ioutil.WriteFile(fileName, []byte("taco"), 0600)
|
||||||
|
@ -921,6 +932,7 @@ func (t *MemFSTest) Truncate_Smaller() {
|
||||||
f, err := os.OpenFile(fileName, os.O_RDWR, 0)
|
f, err := os.OpenFile(fileName, os.O_RDWR, 0)
|
||||||
t.ToClose = append(t.ToClose, f)
|
t.ToClose = append(t.ToClose, f)
|
||||||
AssertEq(nil, err)
|
AssertEq(nil, err)
|
||||||
|
t.checkOpenFlagsXattr(fileName, fusekernel.OpenReadWrite)
|
||||||
|
|
||||||
// Truncate it.
|
// Truncate it.
|
||||||
err = f.Truncate(2)
|
err = f.Truncate(2)
|
||||||
|
@ -935,6 +947,7 @@ func (t *MemFSTest) Truncate_Smaller() {
|
||||||
contents, err := ioutil.ReadFile(fileName)
|
contents, err := ioutil.ReadFile(fileName)
|
||||||
AssertEq(nil, err)
|
AssertEq(nil, err)
|
||||||
ExpectEq("ta", string(contents))
|
ExpectEq("ta", string(contents))
|
||||||
|
t.checkOpenFlagsXattr(fileName, fusekernel.OpenReadOnly)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *MemFSTest) Truncate_SameSize() {
|
func (t *MemFSTest) Truncate_SameSize() {
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,21 +3,31 @@ package main
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"flag"
|
"flag"
|
||||||
"log"
|
"fmt"
|
||||||
"os"
|
|
||||||
"github.com/jacobsa/fuse"
|
"github.com/jacobsa/fuse"
|
||||||
"github.com/jacobsa/fuse/samples/readbenchfs"
|
"github.com/jacobsa/fuse/samples/readbenchfs"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
_ "net/http/pprof"
|
||||||
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
var fMountPoint = flag.String("mount_point", "", "Path to mount point.")
|
var fMountPoint = flag.String("mount_point", "", "Path to mount point.")
|
||||||
var fReadOnly = flag.Bool("read_only", false, "Mount in read-only mode.")
|
var fReadOnly = flag.Bool("read_only", false, "Mount in read-only mode.")
|
||||||
var fVectored = flag.Bool("vectored", false, "Use vectored read.")
|
var fVectored = flag.Bool("vectored", false, "Use vectored read.")
|
||||||
var fDebug = flag.Bool("debug", false, "Enable debug logging.")
|
var fDebug = flag.Bool("debug", false, "Enable debug logging.")
|
||||||
|
var fPprof = flag.Int("pprof", 0, "Enable pprof profiling on the specified port.")
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
server, err := readbenchfs.NewReadBenchServer()
|
if *fPprof != 0 {
|
||||||
|
go func() {
|
||||||
|
fmt.Printf("%v", http.ListenAndServe(fmt.Sprintf("localhost:%v", *fPprof), nil))
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
server, err := readbenchfs.NewReadBenchServer(*fVectored)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("makeFS: %v", err)
|
log.Fatalf("makeFS: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -28,7 +38,7 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg := &fuse.MountConfig{
|
cfg := &fuse.MountConfig{
|
||||||
ReadOnly: *fReadOnly,
|
ReadOnly: *fReadOnly,
|
||||||
UseVectoredRead: *fVectored,
|
UseVectoredRead: *fVectored,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,9 +16,9 @@ package readbenchfs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
"os"
|
|
||||||
"io"
|
"io"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/jacobsa/fuse"
|
"github.com/jacobsa/fuse"
|
||||||
"github.com/jacobsa/fuse/fuseops"
|
"github.com/jacobsa/fuse/fuseops"
|
||||||
|
@ -27,39 +27,38 @@ import (
|
||||||
|
|
||||||
type readBenchFS struct {
|
type readBenchFS struct {
|
||||||
fuseutil.NotImplementedFileSystem
|
fuseutil.NotImplementedFileSystem
|
||||||
buf []byte
|
buf []byte
|
||||||
|
useVectoredRead bool
|
||||||
}
|
}
|
||||||
|
|
||||||
const FILE_SIZE = 1024*1024*1024*1024
|
// 1 TB
|
||||||
|
const fileSize = 1024 * 1024 * 1024 * 1024
|
||||||
|
|
||||||
var _ fuseutil.FileSystem = &readBenchFS{}
|
var _ fuseutil.FileSystem = &readBenchFS{}
|
||||||
|
|
||||||
func NewReadBenchServer() (server fuse.Server, err error) {
|
func NewReadBenchServer(useVectoredRead bool) (server fuse.Server, err error) {
|
||||||
// 1 GB of random data to exceed CPU cache
|
// 1 GB of random data to exceed CPU cache
|
||||||
buf := make([]byte, 1024*1024*1024)
|
buf := make([]byte, 1024*1024*1024)
|
||||||
rand.Read(buf)
|
rand.Read(buf)
|
||||||
server = fuseutil.NewFileSystemServer(&readBenchFS{
|
server = fuseutil.NewFileSystemServer(&readBenchFS{
|
||||||
buf: buf,
|
buf: buf,
|
||||||
|
useVectoredRead: useVectoredRead,
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fs *readBenchFS) StatFS(
|
func (fs *readBenchFS) StatFS(ctx context.Context, op *fuseops.StatFSOp) error {
|
||||||
ctx context.Context,
|
|
||||||
op *fuseops.StatFSOp) error {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fs *readBenchFS) LookUpInode(
|
func (fs *readBenchFS) LookUpInode(ctx context.Context, op *fuseops.LookUpInodeOp) error {
|
||||||
ctx context.Context,
|
|
||||||
op *fuseops.LookUpInodeOp) error {
|
|
||||||
if op.Name == "test" {
|
if op.Name == "test" {
|
||||||
op.Entry = fuseops.ChildInodeEntry{
|
op.Entry = fuseops.ChildInodeEntry{
|
||||||
Child: 2,
|
Child: 2,
|
||||||
Attributes: fuseops.InodeAttributes{
|
Attributes: fuseops.InodeAttributes{
|
||||||
Size: FILE_SIZE,
|
Size: fileSize,
|
||||||
Nlink: 1,
|
Nlink: 1,
|
||||||
Mode: 0444,
|
Mode: 0444,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -67,36 +66,30 @@ func (fs *readBenchFS) LookUpInode(
|
||||||
return fuse.ENOENT
|
return fuse.ENOENT
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fs *readBenchFS) GetInodeAttributes(
|
func (fs *readBenchFS) GetInodeAttributes(ctx context.Context, op *fuseops.GetInodeAttributesOp) error {
|
||||||
ctx context.Context,
|
|
||||||
op *fuseops.GetInodeAttributesOp) error {
|
|
||||||
if op.Inode == 1 {
|
if op.Inode == 1 {
|
||||||
op.Attributes = fuseops.InodeAttributes{
|
op.Attributes = fuseops.InodeAttributes{
|
||||||
Nlink: 1,
|
Nlink: 1,
|
||||||
Mode: 0755 | os.ModeDir,
|
Mode: 0755 | os.ModeDir,
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
} else if op.Inode == 2 {
|
} else if op.Inode == 2 {
|
||||||
op.Attributes = fuseops.InodeAttributes{
|
op.Attributes = fuseops.InodeAttributes{
|
||||||
Size: FILE_SIZE,
|
Size: fileSize,
|
||||||
Nlink: 1,
|
Nlink: 1,
|
||||||
Mode: 0444,
|
Mode: 0444,
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return fuse.ENOENT
|
return fuse.ENOENT
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fs *readBenchFS) OpenDir(
|
func (fs *readBenchFS) OpenDir(ctx context.Context, op *fuseops.OpenDirOp) error {
|
||||||
ctx context.Context,
|
|
||||||
op *fuseops.OpenDirOp) error {
|
|
||||||
// Allow opening any directory.
|
// Allow opening any directory.
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fs *readBenchFS) ReadDir(
|
func (fs *readBenchFS) ReadDir(ctx context.Context, op *fuseops.ReadDirOp) error {
|
||||||
ctx context.Context,
|
|
||||||
op *fuseops.ReadDirOp) error {
|
|
||||||
if op.Inode != 1 {
|
if op.Inode != 1 {
|
||||||
return fuse.ENOENT
|
return fuse.ENOENT
|
||||||
}
|
}
|
||||||
|
@ -121,93 +114,57 @@ func (fs *readBenchFS) ReadDir(
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fs *readBenchFS) OpenFile(
|
func (fs *readBenchFS) OpenFile(ctx context.Context, op *fuseops.OpenFileOp) error {
|
||||||
ctx context.Context,
|
|
||||||
op *fuseops.OpenFileOp) error {
|
|
||||||
// Allow opening any file.
|
// Allow opening any file.
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fs *readBenchFS) ReadFile(
|
func (fs *readBenchFS) ReadFile(ctx context.Context, op *fuseops.ReadFileOp) error {
|
||||||
ctx context.Context,
|
if op.Offset > fileSize {
|
||||||
op *fuseops.ReadFileOp) error {
|
|
||||||
if op.Offset > FILE_SIZE {
|
|
||||||
return io.EOF
|
return io.EOF
|
||||||
}
|
}
|
||||||
end := op.Offset+int64(len(op.Dst))
|
end := op.Offset + op.Size
|
||||||
if end > FILE_SIZE {
|
if end > fileSize {
|
||||||
end = FILE_SIZE
|
end = fileSize
|
||||||
}
|
}
|
||||||
buflen := int64(len(fs.buf))
|
buflen := int64(len(fs.buf))
|
||||||
for pos := op.Offset; pos < end; {
|
for pos := op.Offset; pos < end; {
|
||||||
s := pos % buflen
|
s := pos % buflen
|
||||||
e := buflen
|
e := buflen
|
||||||
if e-s > end-pos {
|
if e-s > end-pos {
|
||||||
e = s+end-pos
|
e = s + end - pos
|
||||||
}
|
}
|
||||||
copy(op.Dst[pos-op.Offset : ], fs.buf[s : ])
|
if fs.useVectoredRead {
|
||||||
pos = op.Offset+e
|
op.Data = append(op.Data, fs.buf[s:e])
|
||||||
}
|
} else {
|
||||||
op.BytesRead = int(end-op.Offset)
|
copy(op.Dst[pos-op.Offset:], fs.buf[s:])
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
||||||
pos = op.Offset+e
|
|
||||||
}
|
}
|
||||||
op.BytesRead = int(end-op.Offset)
|
op.BytesRead = int(end - op.Offset)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fs *readBenchFS) ReleaseDirHandle(
|
func (fs *readBenchFS) ReleaseDirHandle(ctx context.Context, op *fuseops.ReleaseDirHandleOp) error {
|
||||||
ctx context.Context,
|
|
||||||
op *fuseops.ReleaseDirHandleOp) error {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fs *readBenchFS) GetXattr(
|
func (fs *readBenchFS) GetXattr(ctx context.Context, op *fuseops.GetXattrOp) error {
|
||||||
ctx context.Context,
|
|
||||||
op *fuseops.GetXattrOp) error {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fs *readBenchFS) ListXattr(
|
func (fs *readBenchFS) ListXattr(ctx context.Context, op *fuseops.ListXattrOp) error {
|
||||||
ctx context.Context,
|
|
||||||
op *fuseops.ListXattrOp) error {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fs *readBenchFS) ForgetInode(
|
func (fs *readBenchFS) ForgetInode(ctx context.Context, op *fuseops.ForgetInodeOp) error {
|
||||||
ctx context.Context,
|
|
||||||
op *fuseops.ForgetInodeOp) error {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fs *readBenchFS) ReleaseFileHandle(
|
func (fs *readBenchFS) ReleaseFileHandle(ctx context.Context, op *fuseops.ReleaseFileHandleOp) error {
|
||||||
ctx context.Context,
|
|
||||||
op *fuseops.ReleaseFileHandleOp) error {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fs *readBenchFS) FlushFile(
|
func (fs *readBenchFS) FlushFile(ctx context.Context, op *fuseops.FlushFileOp) error {
|
||||||
ctx context.Context,
|
|
||||||
op *fuseops.FlushFileOp) error {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,9 +26,8 @@ import (
|
||||||
|
|
||||||
// Sample output:
|
// Sample output:
|
||||||
//
|
//
|
||||||
// Filesystem 1024-blocks Used Available Capacity iused ifree %iused Mounted on
|
// Filesystem 1024-blocks Used Available Capacity iused ifree %iused Mounted on
|
||||||
// fake@bucket 32 16 16 50% 0 0 100% /Users/jacobsa/tmp/mp
|
// 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+%.*$`)
|
var gDfOutputRegexp = regexp.MustCompile(`^\S+\s+(\d+)\s+(\d+)\s+(\d+)\s+\d+%\s+\d+\s+\d+\s+\d+%.*$`)
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
|
|
@ -26,9 +26,8 @@ import (
|
||||||
|
|
||||||
// Sample output:
|
// Sample output:
|
||||||
//
|
//
|
||||||
// Filesystem 1K-blocks Used Available Use% Mounted on
|
// Filesystem 1K-blocks Used Available Use% Mounted on
|
||||||
// some_fuse_file_system 512 64 384 15% /tmp/sample_test001288095
|
// some_fuse_file_system 512 64 384 15% /tmp/sample_test001288095
|
||||||
//
|
|
||||||
var gDfOutputRegexp = regexp.MustCompile(`^\S+\s+(\d+)\s+(\d+)\s+(\d+)\s+\d+%.*$`)
|
var gDfOutputRegexp = regexp.MustCompile(`^\S+\s+(\d+)\s+(\d+)\s+(\d+)\s+\d+%.*$`)
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
//go:build !linux
|
||||||
// +build !linux
|
// +build !linux
|
||||||
|
|
||||||
package fuse
|
package fuse
|
||||||
|
|
Loading…
Reference in New Issue