mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Merge pull request #3934 from avagin:feature/fuse
PiperOrigin-RevId: 332122081
This commit is contained in:
+640
-70
File diff suppressed because it is too large
Load Diff
@@ -44,7 +44,7 @@ func (StubMarshallable) MarshalBytes(dst []byte) {
|
||||
|
||||
// UnmarshalBytes implements Marshallable.UnmarshalBytes.
|
||||
func (StubMarshallable) UnmarshalBytes(src []byte) {
|
||||
panic("Please implement your own UnMarshalBytes function")
|
||||
panic("Please implement your own UnmarshalBytes function")
|
||||
}
|
||||
|
||||
// Packed implements Marshallable.Packed.
|
||||
|
||||
@@ -95,6 +95,52 @@ ops can be implemented in parallel.
|
||||
- Implement the remaining FUSE ops and decide if we can omit rarely used
|
||||
operations like ioctl.
|
||||
|
||||
### Design Details
|
||||
|
||||
#### Lifecycle for a FUSE Request
|
||||
|
||||
- User invokes a syscall
|
||||
- Sentry prepares corresponding request
|
||||
- If FUSE device is available
|
||||
- Write the request in binary
|
||||
- If FUSE device is full
|
||||
- Kernel task blocked until available
|
||||
- Sentry notifies the readers of fuse device that it's ready for read
|
||||
- FUSE daemon reads the request and processes it
|
||||
- Sentry waits until a reply is written to the FUSE device
|
||||
- but returns directly for async requests
|
||||
- FUSE daemon writes to the fuse device
|
||||
- Sentry processes the reply
|
||||
- For sync requests, unblock blocked kernel task
|
||||
- For async requests, execute pre-specified callback if any
|
||||
- Sentry returns the syscall to the user
|
||||
|
||||
#### Channels and Queues for Requests in Different Stages
|
||||
|
||||
`connection.initializedChan`
|
||||
|
||||
- a channel that the requests issued before connection initialization blocks
|
||||
on.
|
||||
|
||||
`fd.queue`
|
||||
|
||||
- a queue of requests that haven’t been read by the FUSE daemon yet.
|
||||
|
||||
`fd.completions`
|
||||
|
||||
- a map of the requests that have been prepared but not yet received a
|
||||
response, including the ones on the `fd.queue`.
|
||||
|
||||
`fd.waitQueue`
|
||||
|
||||
- a queue of waiters that is waiting for the fuse device fd to be available,
|
||||
such as the FUSE daemon.
|
||||
|
||||
`fd.fullQueueCh`
|
||||
|
||||
- a channel that the kernel task will be blocked on when the fd is not
|
||||
available.
|
||||
|
||||
# Appendix
|
||||
|
||||
## FUSE Protocol
|
||||
|
||||
@@ -30,12 +30,17 @@ go_library(
|
||||
name = "fuse",
|
||||
srcs = [
|
||||
"connection.go",
|
||||
"connection_control.go",
|
||||
"dev.go",
|
||||
"directory.go",
|
||||
"file.go",
|
||||
"fusefs.go",
|
||||
"init.go",
|
||||
"inode_refs.go",
|
||||
"read_write.go",
|
||||
"register.go",
|
||||
"regular_file.go",
|
||||
"request_list.go",
|
||||
"request_response.go",
|
||||
],
|
||||
visibility = ["//pkg/sentry:internal"],
|
||||
deps = [
|
||||
@@ -44,6 +49,7 @@ go_library(
|
||||
"//pkg/log",
|
||||
"//pkg/marshal",
|
||||
"//pkg/refs",
|
||||
"//pkg/safemem",
|
||||
"//pkg/sentry/fsimpl/devtmpfs",
|
||||
"//pkg/sentry/fsimpl/kernfs",
|
||||
"//pkg/sentry/kernel",
|
||||
@@ -60,7 +66,11 @@ go_library(
|
||||
go_test(
|
||||
name = "fuse_test",
|
||||
size = "small",
|
||||
srcs = ["dev_test.go"],
|
||||
srcs = [
|
||||
"connection_test.go",
|
||||
"dev_test.go",
|
||||
"utils_test.go",
|
||||
],
|
||||
library = ":fuse",
|
||||
deps = [
|
||||
"//pkg/abi/linux",
|
||||
|
||||
@@ -15,31 +15,17 @@
|
||||
package fuse
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/context"
|
||||
"gvisor.dev/gvisor/pkg/log"
|
||||
"gvisor.dev/gvisor/pkg/marshal"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
||||
"gvisor.dev/gvisor/pkg/sentry/vfs"
|
||||
"gvisor.dev/gvisor/pkg/syserror"
|
||||
"gvisor.dev/gvisor/pkg/waiter"
|
||||
)
|
||||
|
||||
// maxActiveRequestsDefault is the default setting controlling the upper bound
|
||||
// on the number of active requests at any given time.
|
||||
const maxActiveRequestsDefault = 10000
|
||||
|
||||
// Ordinary requests have even IDs, while interrupts IDs are odd.
|
||||
// Used to increment the unique ID for each FUSE request.
|
||||
var reqIDStep uint64 = 2
|
||||
|
||||
const (
|
||||
// fuseDefaultMaxBackground is the default value for MaxBackground.
|
||||
fuseDefaultMaxBackground = 12
|
||||
@@ -52,43 +38,36 @@ const (
|
||||
fuseDefaultMaxPagesPerReq = 32
|
||||
)
|
||||
|
||||
// Request represents a FUSE operation request that hasn't been sent to the
|
||||
// server yet.
|
||||
//
|
||||
// +stateify savable
|
||||
type Request struct {
|
||||
requestEntry
|
||||
|
||||
id linux.FUSEOpID
|
||||
hdr *linux.FUSEHeaderIn
|
||||
data []byte
|
||||
}
|
||||
|
||||
// Response represents an actual response from the server, including the
|
||||
// response payload.
|
||||
//
|
||||
// +stateify savable
|
||||
type Response struct {
|
||||
opcode linux.FUSEOpcode
|
||||
hdr linux.FUSEHeaderOut
|
||||
data []byte
|
||||
}
|
||||
|
||||
// connection is the struct by which the sentry communicates with the FUSE server daemon.
|
||||
// Lock order:
|
||||
// - conn.fd.mu
|
||||
// - conn.mu
|
||||
// - conn.asyncMu
|
||||
type connection struct {
|
||||
fd *DeviceFD
|
||||
|
||||
// mu protects access to struct memebers.
|
||||
mu sync.Mutex
|
||||
|
||||
// attributeVersion is the version of connection's attributes.
|
||||
attributeVersion uint64
|
||||
|
||||
// We target FUSE 7.23.
|
||||
// The following FUSE_INIT flags are currently unsupported by this implementation:
|
||||
// - FUSE_ATOMIC_O_TRUNC: requires open(..., O_TRUNC)
|
||||
// - FUSE_EXPORT_SUPPORT
|
||||
// - FUSE_HANDLE_KILLPRIV
|
||||
// - FUSE_POSIX_LOCKS: requires POSIX locks
|
||||
// - FUSE_FLOCK_LOCKS: requires POSIX locks
|
||||
// - FUSE_AUTO_INVAL_DATA: requires page caching eviction
|
||||
// - FUSE_EXPLICIT_INVAL_DATA: requires page caching eviction
|
||||
// - FUSE_DO_READDIRPLUS/FUSE_READDIRPLUS_AUTO: requires FUSE_READDIRPLUS implementation
|
||||
// - FUSE_ASYNC_DIO
|
||||
// - FUSE_POSIX_ACL: affects defaultPermissions, posixACL, xattr handler
|
||||
// - FUSE_PARALLEL_DIROPS (7.25)
|
||||
// - FUSE_HANDLE_KILLPRIV (7.26)
|
||||
// - FUSE_POSIX_ACL: affects defaultPermissions, posixACL, xattr handler (7.26)
|
||||
// - FUSE_ABORT_ERROR (7.27)
|
||||
// - FUSE_CACHE_SYMLINKS (7.28)
|
||||
// - FUSE_NO_OPENDIR_SUPPORT (7.29)
|
||||
// - FUSE_EXPLICIT_INVAL_DATA: requires page caching eviction (7.30)
|
||||
// - FUSE_MAP_ALIGNMENT (7.31)
|
||||
|
||||
// initialized after receiving FUSE_INIT reply.
|
||||
// Until it's set, suspend sending FUSE requests.
|
||||
@@ -98,10 +77,6 @@ type connection struct {
|
||||
// initializedChan is used to block requests before initialization.
|
||||
initializedChan chan struct{}
|
||||
|
||||
// blocked when there are too many outstading backgrounds requests (NumBackground == MaxBackground).
|
||||
// TODO(gvisor.dev/issue/3185): update the numBackground accordingly; use a channel to block.
|
||||
blocked bool
|
||||
|
||||
// connected (connection established) when a new FUSE file system is created.
|
||||
// Set to false when:
|
||||
// umount,
|
||||
@@ -109,48 +84,55 @@ type connection struct {
|
||||
// device release.
|
||||
connected bool
|
||||
|
||||
// aborted via sysfs.
|
||||
// TODO(gvisor.dev/issue/3185): abort all queued requests.
|
||||
aborted bool
|
||||
|
||||
// connInitError if FUSE_INIT encountered error (major version mismatch).
|
||||
// Only set in INIT.
|
||||
connInitError bool
|
||||
|
||||
// connInitSuccess if FUSE_INIT is successful.
|
||||
// Only set in INIT.
|
||||
// Used for destory.
|
||||
// Used for destory (not yet implemented).
|
||||
connInitSuccess bool
|
||||
|
||||
// TODO(gvisor.dev/issue/3185): All the queue logic are working in progress.
|
||||
// aborted via sysfs, and will send ECONNABORTED to read after disconnection (instead of ENODEV).
|
||||
// Set only if abortErr is true and via fuse control fs (not yet implemented).
|
||||
// TODO(gvisor.dev/issue/3525): set this to true when user aborts.
|
||||
aborted bool
|
||||
|
||||
// NumberBackground is the number of requests in the background.
|
||||
numBackground uint16
|
||||
|
||||
// congestionThreshold for NumBackground.
|
||||
// Negotiated in FUSE_INIT.
|
||||
congestionThreshold uint16
|
||||
|
||||
// maxBackground is the maximum number of NumBackground.
|
||||
// Block connection when it is reached.
|
||||
// Negotiated in FUSE_INIT.
|
||||
maxBackground uint16
|
||||
|
||||
// numActiveBackground is the number of requests in background and has being marked as active.
|
||||
numActiveBackground uint16
|
||||
|
||||
// numWating is the number of requests waiting for completion.
|
||||
// numWating is the number of requests waiting to be
|
||||
// sent to FUSE device or being processed by FUSE daemon.
|
||||
numWaiting uint32
|
||||
|
||||
// TODO(gvisor.dev/issue/3185): BgQueue
|
||||
// some queue for background queued requests.
|
||||
// Terminology note:
|
||||
//
|
||||
// - `asyncNumMax` is the `MaxBackground` in the FUSE_INIT_IN struct.
|
||||
//
|
||||
// - `asyncCongestionThreshold` is the `CongestionThreshold` in the FUSE_INIT_IN struct.
|
||||
//
|
||||
// We call the "background" requests in unix term as async requests.
|
||||
// The "async requests" in unix term is our async requests that expect a reply,
|
||||
// i.e. `!request.noReply`
|
||||
|
||||
// bgLock protects:
|
||||
// MaxBackground, CongestionThreshold, NumBackground,
|
||||
// NumActiveBackground, BgQueue, Blocked.
|
||||
bgLock sync.Mutex
|
||||
// asyncMu protects the async request fields.
|
||||
asyncMu sync.Mutex
|
||||
|
||||
// asyncNum is the number of async requests.
|
||||
// Protected by asyncMu.
|
||||
asyncNum uint16
|
||||
|
||||
// asyncCongestionThreshold the number of async requests.
|
||||
// Negotiated in FUSE_INIT as "CongestionThreshold".
|
||||
// TODO(gvisor.dev/issue/3529): add congestion control.
|
||||
// Protected by asyncMu.
|
||||
asyncCongestionThreshold uint16
|
||||
|
||||
// asyncNumMax is the maximum number of asyncNum.
|
||||
// Connection blocks the async requests when it is reached.
|
||||
// Negotiated in FUSE_INIT as "MaxBackground".
|
||||
// Protected by asyncMu.
|
||||
asyncNumMax uint16
|
||||
|
||||
// maxRead is the maximum size of a read buffer in in bytes.
|
||||
// Initialized from a fuse fs parameter.
|
||||
maxRead uint32
|
||||
|
||||
// maxWrite is the maximum size of a write buffer in bytes.
|
||||
@@ -165,23 +147,20 @@ type connection struct {
|
||||
// Negotiated and only set in INIT.
|
||||
minor uint32
|
||||
|
||||
// atomicOTrunc is true when FUSE does not send a separate SETATTR request
|
||||
// before open with O_TRUNC flag.
|
||||
// Negotiated and only set in INIT.
|
||||
atomicOTrunc bool
|
||||
|
||||
// asyncRead if read pages asynchronously.
|
||||
// Negotiated and only set in INIT.
|
||||
asyncRead bool
|
||||
|
||||
// abortErr is true if kernel need to return an unique read error after abort.
|
||||
// Negotiated and only set in INIT.
|
||||
abortErr bool
|
||||
|
||||
// writebackCache is true for write-back cache policy,
|
||||
// false for write-through policy.
|
||||
// Negotiated and only set in INIT.
|
||||
writebackCache bool
|
||||
|
||||
// cacheSymlinks if filesystem needs to cache READLINK responses in page cache.
|
||||
// Negotiated and only set in INIT.
|
||||
cacheSymlinks bool
|
||||
|
||||
// bigWrites if doing multi-page cached writes.
|
||||
// Negotiated and only set in INIT.
|
||||
bigWrites bool
|
||||
@@ -189,116 +168,70 @@ type connection struct {
|
||||
// dontMask if filestestem does not apply umask to creation modes.
|
||||
// Negotiated in INIT.
|
||||
dontMask bool
|
||||
|
||||
// noOpen if FUSE server doesn't support open operation.
|
||||
// This flag only influence performance, not correctness of the program.
|
||||
noOpen bool
|
||||
}
|
||||
|
||||
// newFUSEConnection creates a FUSE connection to fd.
|
||||
func newFUSEConnection(_ context.Context, fd *vfs.FileDescription, maxInFlightRequests uint64) (*connection, error) {
|
||||
func newFUSEConnection(_ context.Context, fd *vfs.FileDescription, opts *filesystemOptions) (*connection, error) {
|
||||
// Mark the device as ready so it can be used. /dev/fuse can only be used if the FD was used to
|
||||
// mount a FUSE filesystem.
|
||||
fuseFD := fd.Impl().(*DeviceFD)
|
||||
fuseFD.mounted = true
|
||||
|
||||
// Create the writeBuf for the header to be stored in.
|
||||
hdrLen := uint32((*linux.FUSEHeaderOut)(nil).SizeBytes())
|
||||
fuseFD.writeBuf = make([]byte, hdrLen)
|
||||
fuseFD.completions = make(map[linux.FUSEOpID]*futureResponse)
|
||||
fuseFD.fullQueueCh = make(chan struct{}, maxInFlightRequests)
|
||||
fuseFD.fullQueueCh = make(chan struct{}, opts.maxActiveRequests)
|
||||
fuseFD.writeCursor = 0
|
||||
|
||||
return &connection{
|
||||
fd: fuseFD,
|
||||
maxBackground: fuseDefaultMaxBackground,
|
||||
congestionThreshold: fuseDefaultCongestionThreshold,
|
||||
maxPages: fuseDefaultMaxPagesPerReq,
|
||||
initializedChan: make(chan struct{}),
|
||||
connected: true,
|
||||
fd: fuseFD,
|
||||
asyncNumMax: fuseDefaultMaxBackground,
|
||||
asyncCongestionThreshold: fuseDefaultCongestionThreshold,
|
||||
maxRead: opts.maxRead,
|
||||
maxPages: fuseDefaultMaxPagesPerReq,
|
||||
initializedChan: make(chan struct{}),
|
||||
connected: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SetInitialized atomically sets the connection as initialized.
|
||||
func (conn *connection) SetInitialized() {
|
||||
// Unblock the requests sent before INIT.
|
||||
close(conn.initializedChan)
|
||||
|
||||
// Close the channel first to avoid the non-atomic situation
|
||||
// where conn.initialized is true but there are
|
||||
// tasks being blocked on the channel.
|
||||
// And it prevents the newer tasks from gaining
|
||||
// unnecessary higher chance to be issued before the blocked one.
|
||||
|
||||
atomic.StoreInt32(&(conn.initialized), int32(1))
|
||||
// CallAsync makes an async (aka background) request.
|
||||
// It's a simple wrapper around Call().
|
||||
func (conn *connection) CallAsync(t *kernel.Task, r *Request) error {
|
||||
r.async = true
|
||||
_, err := conn.Call(t, r)
|
||||
return err
|
||||
}
|
||||
|
||||
// IsInitialized atomically check if the connection is initialized.
|
||||
// pairs with SetInitialized().
|
||||
func (conn *connection) Initialized() bool {
|
||||
return atomic.LoadInt32(&(conn.initialized)) != 0
|
||||
}
|
||||
|
||||
// NewRequest creates a new request that can be sent to the FUSE server.
|
||||
func (conn *connection) NewRequest(creds *auth.Credentials, pid uint32, ino uint64, opcode linux.FUSEOpcode, payload marshal.Marshallable) (*Request, error) {
|
||||
conn.fd.mu.Lock()
|
||||
defer conn.fd.mu.Unlock()
|
||||
conn.fd.nextOpID += linux.FUSEOpID(reqIDStep)
|
||||
|
||||
hdrLen := (*linux.FUSEHeaderIn)(nil).SizeBytes()
|
||||
hdr := linux.FUSEHeaderIn{
|
||||
Len: uint32(hdrLen + payload.SizeBytes()),
|
||||
Opcode: opcode,
|
||||
Unique: conn.fd.nextOpID,
|
||||
NodeID: ino,
|
||||
UID: uint32(creds.EffectiveKUID),
|
||||
GID: uint32(creds.EffectiveKGID),
|
||||
PID: pid,
|
||||
}
|
||||
|
||||
buf := make([]byte, hdr.Len)
|
||||
hdr.MarshalUnsafe(buf[:hdrLen])
|
||||
payload.MarshalUnsafe(buf[hdrLen:])
|
||||
|
||||
return &Request{
|
||||
id: hdr.Unique,
|
||||
hdr: &hdr,
|
||||
data: buf,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Call makes a request to the server and blocks the invoking task until a
|
||||
// server responds with a response. Task should never be nil.
|
||||
// Requests will not be sent before the connection is initialized.
|
||||
// For async tasks, use CallAsync().
|
||||
// Call makes a request to the server.
|
||||
// Block before the connection is initialized.
|
||||
// When the Request is FUSE_INIT, it will not be blocked before initialization.
|
||||
// Task should never be nil.
|
||||
//
|
||||
// For a sync request, it blocks the invoking task until
|
||||
// a server responds with a response.
|
||||
//
|
||||
// For an async request (that do not expect a response immediately),
|
||||
// it returns directly unless being blocked either before initialization
|
||||
// or when there are too many async requests ongoing.
|
||||
//
|
||||
// Example for async request:
|
||||
// init, readahead, write, async read/write, fuse_notify_reply,
|
||||
// non-sync release, interrupt, forget.
|
||||
//
|
||||
// The forget request does not have a reply,
|
||||
// as documented in include/uapi/linux/fuse.h:FUSE_FORGET.
|
||||
func (conn *connection) Call(t *kernel.Task, r *Request) (*Response, error) {
|
||||
// Block requests sent before connection is initalized.
|
||||
if !conn.Initialized() {
|
||||
if !conn.Initialized() && r.hdr.Opcode != linux.FUSE_INIT {
|
||||
if err := t.Block(conn.initializedChan); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return conn.call(t, r)
|
||||
}
|
||||
|
||||
// CallAsync makes an async (aka background) request.
|
||||
// Those requests either do not expect a response (e.g. release) or
|
||||
// the response should be handled by others (e.g. init).
|
||||
// Return immediately unless the connection is blocked (before initialization).
|
||||
// Async call example: init, release, forget, aio, interrupt.
|
||||
// When the Request is FUSE_INIT, it will not be blocked before initialization.
|
||||
func (conn *connection) CallAsync(t *kernel.Task, r *Request) error {
|
||||
// Block requests sent before connection is initalized.
|
||||
if !conn.Initialized() && r.hdr.Opcode != linux.FUSE_INIT {
|
||||
if err := t.Block(conn.initializedChan); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// This should be the only place that invokes call() with a nil task.
|
||||
_, err := conn.call(nil, r)
|
||||
return err
|
||||
}
|
||||
|
||||
// call makes a call without blocking checks.
|
||||
func (conn *connection) call(t *kernel.Task, r *Request) (*Response, error) {
|
||||
if !conn.connected {
|
||||
return nil, syserror.ENOTCONN
|
||||
}
|
||||
@@ -315,31 +248,6 @@ func (conn *connection) call(t *kernel.Task, r *Request) (*Response, error) {
|
||||
return fut.resolve(t)
|
||||
}
|
||||
|
||||
// Error returns the error of the FUSE call.
|
||||
func (r *Response) Error() error {
|
||||
errno := r.hdr.Error
|
||||
if errno >= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
sysErrNo := syscall.Errno(-errno)
|
||||
return error(sysErrNo)
|
||||
}
|
||||
|
||||
// UnmarshalPayload unmarshals the response data into m.
|
||||
func (r *Response) UnmarshalPayload(m marshal.Marshallable) error {
|
||||
hdrLen := r.hdr.SizeBytes()
|
||||
haveDataLen := r.hdr.Len - uint32(hdrLen)
|
||||
wantDataLen := uint32(m.SizeBytes())
|
||||
|
||||
if haveDataLen < wantDataLen {
|
||||
return fmt.Errorf("payload too small. Minimum data lenth required: %d, but got data length %d", wantDataLen, haveDataLen)
|
||||
}
|
||||
|
||||
m.UnmarshalUnsafe(r.data[hdrLen:])
|
||||
return nil
|
||||
}
|
||||
|
||||
// callFuture makes a request to the server and returns a future response.
|
||||
// Call resolve() when the response needs to be fulfilled.
|
||||
func (conn *connection) callFuture(t *kernel.Task, r *Request) (*futureResponse, error) {
|
||||
@@ -358,11 +266,6 @@ func (conn *connection) callFuture(t *kernel.Task, r *Request) (*futureResponse,
|
||||
// if there are always too many ongoing requests all the time. The
|
||||
// supported maxActiveRequests setting should be really high to avoid this.
|
||||
for conn.fd.numActiveRequests == conn.fd.fs.opts.maxActiveRequests {
|
||||
if t == nil {
|
||||
// Since there is no task that is waiting. We must error out.
|
||||
return nil, errors.New("FUSE request queue full")
|
||||
}
|
||||
|
||||
log.Infof("Blocking request %v from being queued. Too many active requests: %v",
|
||||
r.id, conn.fd.numActiveRequests)
|
||||
conn.fd.mu.Unlock()
|
||||
@@ -378,9 +281,19 @@ func (conn *connection) callFuture(t *kernel.Task, r *Request) (*futureResponse,
|
||||
|
||||
// callFutureLocked makes a request to the server and returns a future response.
|
||||
func (conn *connection) callFutureLocked(t *kernel.Task, r *Request) (*futureResponse, error) {
|
||||
// Check connected again holding conn.mu.
|
||||
conn.mu.Lock()
|
||||
if !conn.connected {
|
||||
conn.mu.Unlock()
|
||||
// we checked connected before,
|
||||
// this must be due to aborted connection.
|
||||
return nil, syserror.ECONNABORTED
|
||||
}
|
||||
conn.mu.Unlock()
|
||||
|
||||
conn.fd.queue.PushBack(r)
|
||||
conn.fd.numActiveRequests += 1
|
||||
fut := newFutureResponse(r.hdr.Opcode)
|
||||
conn.fd.numActiveRequests++
|
||||
fut := newFutureResponse(r)
|
||||
conn.fd.completions[r.id] = fut
|
||||
|
||||
// Signal the readers that there is something to read.
|
||||
@@ -388,50 +301,3 @@ func (conn *connection) callFutureLocked(t *kernel.Task, r *Request) (*futureRes
|
||||
|
||||
return fut, nil
|
||||
}
|
||||
|
||||
// futureResponse represents an in-flight request, that may or may not have
|
||||
// completed yet. Convert it to a resolved Response by calling Resolve, but note
|
||||
// that this may block.
|
||||
//
|
||||
// +stateify savable
|
||||
type futureResponse struct {
|
||||
opcode linux.FUSEOpcode
|
||||
ch chan struct{}
|
||||
hdr *linux.FUSEHeaderOut
|
||||
data []byte
|
||||
}
|
||||
|
||||
// newFutureResponse creates a future response to a FUSE request.
|
||||
func newFutureResponse(opcode linux.FUSEOpcode) *futureResponse {
|
||||
return &futureResponse{
|
||||
opcode: opcode,
|
||||
ch: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// resolve blocks the task until the server responds to its corresponding request,
|
||||
// then returns a resolved response.
|
||||
func (f *futureResponse) resolve(t *kernel.Task) (*Response, error) {
|
||||
// If there is no Task associated with this request - then we don't try to resolve
|
||||
// the response. Instead, the task writing the response (proxy to the server) will
|
||||
// process the response on our behalf.
|
||||
if t == nil {
|
||||
log.Infof("fuse.Response.resolve: Not waiting on a response from server.")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if err := t.Block(f.ch); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return f.getResponse(), nil
|
||||
}
|
||||
|
||||
// getResponse creates a Response from the data the futureResponse has.
|
||||
func (f *futureResponse) getResponse() *Response {
|
||||
return &Response{
|
||||
opcode: f.opcode,
|
||||
hdr: *f.hdr,
|
||||
data: f.data,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,11 @@
|
||||
package fuse
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/context"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
||||
)
|
||||
|
||||
@@ -29,9 +33,10 @@ const (
|
||||
// Follow the same behavior as unix fuse implementation.
|
||||
fuseMaxTimeGranNs = 1000000000
|
||||
|
||||
// Minimum value for MaxWrite.
|
||||
// Minimum value for MaxWrite and MaxRead.
|
||||
// Follow the same behavior as unix fuse implementation.
|
||||
fuseMinMaxWrite = 4096
|
||||
fuseMinMaxRead = 4096
|
||||
|
||||
// Temporary default value for max readahead, 128kb.
|
||||
fuseDefaultMaxReadahead = 131072
|
||||
@@ -49,6 +54,26 @@ var (
|
||||
MaxUserCongestionThreshold uint16 = fuseDefaultCongestionThreshold
|
||||
)
|
||||
|
||||
// SetInitialized atomically sets the connection as initialized.
|
||||
func (conn *connection) SetInitialized() {
|
||||
// Unblock the requests sent before INIT.
|
||||
close(conn.initializedChan)
|
||||
|
||||
// Close the channel first to avoid the non-atomic situation
|
||||
// where conn.initialized is true but there are
|
||||
// tasks being blocked on the channel.
|
||||
// And it prevents the newer tasks from gaining
|
||||
// unnecessary higher chance to be issued before the blocked one.
|
||||
|
||||
atomic.StoreInt32(&(conn.initialized), int32(1))
|
||||
}
|
||||
|
||||
// IsInitialized atomically check if the connection is initialized.
|
||||
// pairs with SetInitialized().
|
||||
func (conn *connection) Initialized() bool {
|
||||
return atomic.LoadInt32(&(conn.initialized)) != 0
|
||||
}
|
||||
|
||||
// InitSend sends a FUSE_INIT request.
|
||||
func (conn *connection) InitSend(creds *auth.Credentials, pid uint32) error {
|
||||
in := linux.FUSEInitIn{
|
||||
@@ -70,29 +95,31 @@ func (conn *connection) InitSend(creds *auth.Credentials, pid uint32) error {
|
||||
}
|
||||
|
||||
// InitRecv receives a FUSE_INIT reply and process it.
|
||||
//
|
||||
// Preconditions: conn.asyncMu must not be held if minor verion is newer than 13.
|
||||
func (conn *connection) InitRecv(res *Response, hasSysAdminCap bool) error {
|
||||
if err := res.Error(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var out linux.FUSEInitOut
|
||||
if err := res.UnmarshalPayload(&out); err != nil {
|
||||
initRes := fuseInitRes{initLen: res.DataLen()}
|
||||
if err := res.UnmarshalPayload(&initRes); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return conn.initProcessReply(&out, hasSysAdminCap)
|
||||
return conn.initProcessReply(&initRes.initOut, hasSysAdminCap)
|
||||
}
|
||||
|
||||
// Process the FUSE_INIT reply from the FUSE server.
|
||||
// It tries to acquire the conn.asyncMu lock if minor version is newer than 13.
|
||||
func (conn *connection) initProcessReply(out *linux.FUSEInitOut, hasSysAdminCap bool) error {
|
||||
// No matter error or not, always set initialzied.
|
||||
// to unblock the blocked requests.
|
||||
defer conn.SetInitialized()
|
||||
|
||||
// No support for old major fuse versions.
|
||||
if out.Major != linux.FUSE_KERNEL_VERSION {
|
||||
conn.connInitError = true
|
||||
|
||||
// Set the connection as initialized and unblock the blocked requests
|
||||
// (i.e. return error for them).
|
||||
conn.SetInitialized()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -100,29 +127,14 @@ func (conn *connection) initProcessReply(out *linux.FUSEInitOut, hasSysAdminCap
|
||||
conn.connInitSuccess = true
|
||||
conn.minor = out.Minor
|
||||
|
||||
// No support for limits before minor version 13.
|
||||
if out.Minor >= 13 {
|
||||
conn.bgLock.Lock()
|
||||
|
||||
if out.MaxBackground > 0 {
|
||||
conn.maxBackground = out.MaxBackground
|
||||
|
||||
if !hasSysAdminCap &&
|
||||
conn.maxBackground > MaxUserBackgroundRequest {
|
||||
conn.maxBackground = MaxUserBackgroundRequest
|
||||
}
|
||||
}
|
||||
|
||||
if out.CongestionThreshold > 0 {
|
||||
conn.congestionThreshold = out.CongestionThreshold
|
||||
|
||||
if !hasSysAdminCap &&
|
||||
conn.congestionThreshold > MaxUserCongestionThreshold {
|
||||
conn.congestionThreshold = MaxUserCongestionThreshold
|
||||
}
|
||||
}
|
||||
|
||||
conn.bgLock.Unlock()
|
||||
// No support for negotiating MaxWrite before minor version 5.
|
||||
if out.Minor >= 5 {
|
||||
conn.maxWrite = out.MaxWrite
|
||||
} else {
|
||||
conn.maxWrite = fuseMinMaxWrite
|
||||
}
|
||||
if conn.maxWrite < fuseMinMaxWrite {
|
||||
conn.maxWrite = fuseMinMaxWrite
|
||||
}
|
||||
|
||||
// No support for the following flags before minor version 6.
|
||||
@@ -131,8 +143,6 @@ func (conn *connection) initProcessReply(out *linux.FUSEInitOut, hasSysAdminCap
|
||||
conn.bigWrites = out.Flags&linux.FUSE_BIG_WRITES != 0
|
||||
conn.dontMask = out.Flags&linux.FUSE_DONT_MASK != 0
|
||||
conn.writebackCache = out.Flags&linux.FUSE_WRITEBACK_CACHE != 0
|
||||
conn.cacheSymlinks = out.Flags&linux.FUSE_CACHE_SYMLINKS != 0
|
||||
conn.abortErr = out.Flags&linux.FUSE_ABORT_ERROR != 0
|
||||
|
||||
// TODO(gvisor.dev/issue/3195): figure out how to use TimeGran (0 < TimeGran <= fuseMaxTimeGranNs).
|
||||
|
||||
@@ -148,19 +158,90 @@ func (conn *connection) initProcessReply(out *linux.FUSEInitOut, hasSysAdminCap
|
||||
}
|
||||
}
|
||||
|
||||
// No support for negotiating MaxWrite before minor version 5.
|
||||
if out.Minor >= 5 {
|
||||
conn.maxWrite = out.MaxWrite
|
||||
} else {
|
||||
conn.maxWrite = fuseMinMaxWrite
|
||||
}
|
||||
if conn.maxWrite < fuseMinMaxWrite {
|
||||
conn.maxWrite = fuseMinMaxWrite
|
||||
}
|
||||
// No support for limits before minor version 13.
|
||||
if out.Minor >= 13 {
|
||||
conn.asyncMu.Lock()
|
||||
|
||||
// Set connection as initialized and unblock the requests
|
||||
// issued before init.
|
||||
conn.SetInitialized()
|
||||
if out.MaxBackground > 0 {
|
||||
conn.asyncNumMax = out.MaxBackground
|
||||
|
||||
if !hasSysAdminCap &&
|
||||
conn.asyncNumMax > MaxUserBackgroundRequest {
|
||||
conn.asyncNumMax = MaxUserBackgroundRequest
|
||||
}
|
||||
}
|
||||
|
||||
if out.CongestionThreshold > 0 {
|
||||
conn.asyncCongestionThreshold = out.CongestionThreshold
|
||||
|
||||
if !hasSysAdminCap &&
|
||||
conn.asyncCongestionThreshold > MaxUserCongestionThreshold {
|
||||
conn.asyncCongestionThreshold = MaxUserCongestionThreshold
|
||||
}
|
||||
}
|
||||
|
||||
conn.asyncMu.Unlock()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Abort this FUSE connection.
|
||||
// It tries to acquire conn.fd.mu, conn.lock, conn.bgLock in order.
|
||||
// All possible requests waiting or blocking will be aborted.
|
||||
//
|
||||
// Preconditions: conn.fd.mu is locked.
|
||||
func (conn *connection) Abort(ctx context.Context) {
|
||||
conn.mu.Lock()
|
||||
conn.asyncMu.Lock()
|
||||
|
||||
if !conn.connected {
|
||||
conn.asyncMu.Unlock()
|
||||
conn.mu.Unlock()
|
||||
conn.fd.mu.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
conn.connected = false
|
||||
|
||||
// Empty the `fd.queue` that holds the requests
|
||||
// not yet read by the FUSE daemon yet.
|
||||
// These are a subset of the requests in `fuse.completion` map.
|
||||
for !conn.fd.queue.Empty() {
|
||||
req := conn.fd.queue.Front()
|
||||
conn.fd.queue.Remove(req)
|
||||
}
|
||||
|
||||
var terminate []linux.FUSEOpID
|
||||
|
||||
// 2. Collect the requests have not been sent to FUSE daemon,
|
||||
// or have not received a reply.
|
||||
for unique := range conn.fd.completions {
|
||||
terminate = append(terminate, unique)
|
||||
}
|
||||
|
||||
// Release locks to avoid deadlock.
|
||||
conn.asyncMu.Unlock()
|
||||
conn.mu.Unlock()
|
||||
|
||||
// 1. The requets blocked before initialization.
|
||||
// Will reach call() `connected` check and return.
|
||||
if !conn.Initialized() {
|
||||
conn.SetInitialized()
|
||||
}
|
||||
|
||||
// 2. Terminate the requests collected above.
|
||||
// Set ECONNABORTED error.
|
||||
// sendError() will remove them from `fd.completion` map.
|
||||
// Will enter the path of a normally received error.
|
||||
for _, toTerminate := range terminate {
|
||||
conn.fd.sendError(ctx, -int32(syscall.ECONNABORTED), toTerminate)
|
||||
}
|
||||
|
||||
// 3. The requests not yet written to FUSE device.
|
||||
// Early terminate.
|
||||
// Will reach callFutureLocked() `connected` check and return.
|
||||
close(conn.fd.fullQueueCh)
|
||||
|
||||
// TODO(gvisor.dev/issue/3528): Forget all pending forget reqs.
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
// Copyright 2020 The gVisor Authors.
|
||||
//
|
||||
// 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 fuse
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
||||
"gvisor.dev/gvisor/pkg/syserror"
|
||||
)
|
||||
|
||||
// TestConnectionInitBlock tests if initialization
|
||||
// correctly blocks and unblocks the connection.
|
||||
// Since it's unfeasible to test kernelTask.Block() in unit test,
|
||||
// the code in Call() are not tested here.
|
||||
func TestConnectionInitBlock(t *testing.T) {
|
||||
s := setup(t)
|
||||
defer s.Destroy()
|
||||
|
||||
k := kernel.KernelFromContext(s.Ctx)
|
||||
|
||||
conn, _, err := newTestConnection(s, k, maxActiveRequestsDefault)
|
||||
if err != nil {
|
||||
t.Fatalf("newTestConnection: %v", err)
|
||||
}
|
||||
|
||||
select {
|
||||
case <-conn.initializedChan:
|
||||
t.Fatalf("initializedChan should be blocking before SetInitialized")
|
||||
default:
|
||||
}
|
||||
|
||||
conn.SetInitialized()
|
||||
|
||||
select {
|
||||
case <-conn.initializedChan:
|
||||
default:
|
||||
t.Fatalf("initializedChan should not be blocking after SetInitialized")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConnectionAbort(t *testing.T) {
|
||||
s := setup(t)
|
||||
defer s.Destroy()
|
||||
|
||||
k := kernel.KernelFromContext(s.Ctx)
|
||||
creds := auth.CredentialsFromContext(s.Ctx)
|
||||
task := kernel.TaskFromContext(s.Ctx)
|
||||
|
||||
const numRequests uint64 = 256
|
||||
|
||||
conn, _, err := newTestConnection(s, k, numRequests)
|
||||
if err != nil {
|
||||
t.Fatalf("newTestConnection: %v", err)
|
||||
}
|
||||
|
||||
testObj := &testPayload{
|
||||
data: rand.Uint32(),
|
||||
}
|
||||
|
||||
var futNormal []*futureResponse
|
||||
|
||||
for i := 0; i < int(numRequests); i++ {
|
||||
req, err := conn.NewRequest(creds, uint32(i), uint64(i), 0, testObj)
|
||||
if err != nil {
|
||||
t.Fatalf("NewRequest creation failed: %v", err)
|
||||
}
|
||||
fut, err := conn.callFutureLocked(task, req)
|
||||
if err != nil {
|
||||
t.Fatalf("callFutureLocked failed: %v", err)
|
||||
}
|
||||
futNormal = append(futNormal, fut)
|
||||
}
|
||||
|
||||
conn.Abort(s.Ctx)
|
||||
|
||||
// Abort should unblock the initialization channel.
|
||||
// Note: no test requests are actually blocked on `conn.initializedChan`.
|
||||
select {
|
||||
case <-conn.initializedChan:
|
||||
default:
|
||||
t.Fatalf("initializedChan should not be blocking after SetInitialized")
|
||||
}
|
||||
|
||||
// Abort will return ECONNABORTED error to unblocked requests.
|
||||
for _, fut := range futNormal {
|
||||
if fut.getResponse().hdr.Error != -int32(syscall.ECONNABORTED) {
|
||||
t.Fatalf("Incorrect error code received for aborted connection: %v", fut.getResponse().hdr.Error)
|
||||
}
|
||||
}
|
||||
|
||||
// After abort, Call() should return directly with ENOTCONN.
|
||||
req, err := conn.NewRequest(creds, 0, 0, 0, testObj)
|
||||
if err != nil {
|
||||
t.Fatalf("NewRequest creation failed: %v", err)
|
||||
}
|
||||
_, err = conn.Call(task, req)
|
||||
if err != syserror.ENOTCONN {
|
||||
t.Fatalf("Incorrect error code received for Call() after connection aborted")
|
||||
}
|
||||
|
||||
}
|
||||
+118
-69
@@ -19,7 +19,6 @@ import (
|
||||
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/context"
|
||||
"gvisor.dev/gvisor/pkg/log"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
||||
"gvisor.dev/gvisor/pkg/sentry/vfs"
|
||||
@@ -56,9 +55,6 @@ type DeviceFD struct {
|
||||
vfs.DentryMetadataFileDescriptionImpl
|
||||
vfs.NoLockFD
|
||||
|
||||
// mounted specifies whether a FUSE filesystem was mounted using the DeviceFD.
|
||||
mounted bool
|
||||
|
||||
// nextOpID is used to create new requests.
|
||||
nextOpID linux.FUSEOpID
|
||||
|
||||
@@ -100,13 +96,15 @@ type DeviceFD struct {
|
||||
|
||||
// Release implements vfs.FileDescriptionImpl.Release.
|
||||
func (fd *DeviceFD) Release(context.Context) {
|
||||
fd.fs.conn.connected = false
|
||||
if fd.fs != nil {
|
||||
fd.fs.conn.connected = false
|
||||
}
|
||||
}
|
||||
|
||||
// PRead implements vfs.FileDescriptionImpl.PRead.
|
||||
func (fd *DeviceFD) PRead(ctx context.Context, dst usermem.IOSequence, offset int64, opts vfs.ReadOptions) (int64, error) {
|
||||
// Operations on /dev/fuse don't make sense until a FUSE filesystem is mounted.
|
||||
if !fd.mounted {
|
||||
if fd.fs == nil {
|
||||
return 0, syserror.EPERM
|
||||
}
|
||||
|
||||
@@ -116,10 +114,16 @@ func (fd *DeviceFD) PRead(ctx context.Context, dst usermem.IOSequence, offset in
|
||||
// Read implements vfs.FileDescriptionImpl.Read.
|
||||
func (fd *DeviceFD) Read(ctx context.Context, dst usermem.IOSequence, opts vfs.ReadOptions) (int64, error) {
|
||||
// Operations on /dev/fuse don't make sense until a FUSE filesystem is mounted.
|
||||
if !fd.mounted {
|
||||
if fd.fs == nil {
|
||||
return 0, syserror.EPERM
|
||||
}
|
||||
|
||||
// Return ENODEV if the filesystem is umounted.
|
||||
if fd.fs.umounted {
|
||||
// TODO(gvisor.dev/issue/3525): return ECONNABORTED if aborted via fuse control fs.
|
||||
return 0, syserror.ENODEV
|
||||
}
|
||||
|
||||
// We require that any Read done on this filesystem have a sane minimum
|
||||
// read buffer. It must have the capacity for the fixed parts of any request
|
||||
// header (Linux uses the request header and the FUSEWriteIn header for this
|
||||
@@ -143,58 +147,82 @@ func (fd *DeviceFD) Read(ctx context.Context, dst usermem.IOSequence, opts vfs.R
|
||||
}
|
||||
|
||||
// readLocked implements the reading of the fuse device while locked with DeviceFD.mu.
|
||||
//
|
||||
// Preconditions: dst is large enough for any reasonable request.
|
||||
func (fd *DeviceFD) readLocked(ctx context.Context, dst usermem.IOSequence, opts vfs.ReadOptions) (int64, error) {
|
||||
if fd.queue.Empty() {
|
||||
var req *Request
|
||||
|
||||
// Find the first valid request.
|
||||
// For the normal case this loop only execute once.
|
||||
for !fd.queue.Empty() {
|
||||
req = fd.queue.Front()
|
||||
|
||||
if int64(req.hdr.Len)+int64(len(req.payload)) <= dst.NumBytes() {
|
||||
break
|
||||
}
|
||||
|
||||
// The request is too large. Cannot process it. All requests must be smaller than the
|
||||
// negotiated size as specified by Connection.MaxWrite set as part of the FUSE_INIT
|
||||
// handshake.
|
||||
errno := -int32(syscall.EIO)
|
||||
if req.hdr.Opcode == linux.FUSE_SETXATTR {
|
||||
errno = -int32(syscall.E2BIG)
|
||||
}
|
||||
|
||||
// Return the error to the calling task.
|
||||
if err := fd.sendError(ctx, errno, req.hdr.Unique); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// We're done with this request.
|
||||
fd.queue.Remove(req)
|
||||
req = nil
|
||||
}
|
||||
|
||||
if req == nil {
|
||||
return 0, syserror.ErrWouldBlock
|
||||
}
|
||||
|
||||
var readCursor uint32
|
||||
var bytesRead int64
|
||||
for {
|
||||
req := fd.queue.Front()
|
||||
if dst.NumBytes() < int64(req.hdr.Len) {
|
||||
// The request is too large. Cannot process it. All requests must be smaller than the
|
||||
// negotiated size as specified by Connection.MaxWrite set as part of the FUSE_INIT
|
||||
// handshake.
|
||||
errno := -int32(syscall.EIO)
|
||||
if req.hdr.Opcode == linux.FUSE_SETXATTR {
|
||||
errno = -int32(syscall.E2BIG)
|
||||
}
|
||||
// We already checked the size: dst must be able to fit the whole request.
|
||||
// Now we write the marshalled header, the payload,
|
||||
// and the potential additional payload
|
||||
// to the user memory IOSequence.
|
||||
|
||||
// Return the error to the calling task.
|
||||
if err := fd.sendError(ctx, errno, req); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
n, err := dst.CopyOut(ctx, req.data)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if n != len(req.data) {
|
||||
return 0, syserror.EIO
|
||||
}
|
||||
|
||||
// We're done with this request.
|
||||
fd.queue.Remove(req)
|
||||
|
||||
// Restart the read as this request was invalid.
|
||||
log.Warningf("fuse.DeviceFD.Read: request found was too large. Restarting read.")
|
||||
return fd.readLocked(ctx, dst, opts)
|
||||
}
|
||||
|
||||
n, err := dst.CopyOut(ctx, req.data[readCursor:])
|
||||
if req.hdr.Opcode == linux.FUSE_WRITE {
|
||||
written, err := dst.DropFirst(n).CopyOut(ctx, req.payload)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
readCursor += uint32(n)
|
||||
bytesRead += int64(n)
|
||||
|
||||
if readCursor >= req.hdr.Len {
|
||||
// Fully done with this req, remove it from the queue.
|
||||
fd.queue.Remove(req)
|
||||
break
|
||||
if written != len(req.payload) {
|
||||
return 0, syserror.EIO
|
||||
}
|
||||
n += int(written)
|
||||
}
|
||||
|
||||
return bytesRead, nil
|
||||
// Fully done with this req, remove it from the queue.
|
||||
fd.queue.Remove(req)
|
||||
|
||||
// Remove noReply ones from map of requests expecting a reply.
|
||||
if req.noReply {
|
||||
fd.numActiveRequests -= 1
|
||||
delete(fd.completions, req.hdr.Unique)
|
||||
}
|
||||
|
||||
return int64(n), nil
|
||||
}
|
||||
|
||||
// PWrite implements vfs.FileDescriptionImpl.PWrite.
|
||||
func (fd *DeviceFD) PWrite(ctx context.Context, src usermem.IOSequence, offset int64, opts vfs.WriteOptions) (int64, error) {
|
||||
// Operations on /dev/fuse don't make sense until a FUSE filesystem is mounted.
|
||||
if !fd.mounted {
|
||||
if fd.fs == nil {
|
||||
return 0, syserror.EPERM
|
||||
}
|
||||
|
||||
@@ -211,10 +239,15 @@ func (fd *DeviceFD) Write(ctx context.Context, src usermem.IOSequence, opts vfs.
|
||||
// writeLocked implements writing to the fuse device while locked with DeviceFD.mu.
|
||||
func (fd *DeviceFD) writeLocked(ctx context.Context, src usermem.IOSequence, opts vfs.WriteOptions) (int64, error) {
|
||||
// Operations on /dev/fuse don't make sense until a FUSE filesystem is mounted.
|
||||
if !fd.mounted {
|
||||
if fd.fs == nil {
|
||||
return 0, syserror.EPERM
|
||||
}
|
||||
|
||||
// Return ENODEV if the filesystem is umounted.
|
||||
if fd.fs.umounted {
|
||||
return 0, syserror.ENODEV
|
||||
}
|
||||
|
||||
var cn, n int64
|
||||
hdrLen := uint32((*linux.FUSEHeaderOut)(nil).SizeBytes())
|
||||
|
||||
@@ -276,7 +309,8 @@ func (fd *DeviceFD) writeLocked(ctx context.Context, src usermem.IOSequence, opt
|
||||
|
||||
fut, ok := fd.completions[hdr.Unique]
|
||||
if !ok {
|
||||
// Server sent us a response for a request we never sent?
|
||||
// Server sent us a response for a request we never sent,
|
||||
// or for which we already received a reply (e.g. aborted), an unlikely event.
|
||||
return 0, syserror.EINVAL
|
||||
}
|
||||
|
||||
@@ -307,8 +341,23 @@ func (fd *DeviceFD) writeLocked(ctx context.Context, src usermem.IOSequence, opt
|
||||
|
||||
// Readiness implements vfs.FileDescriptionImpl.Readiness.
|
||||
func (fd *DeviceFD) Readiness(mask waiter.EventMask) waiter.EventMask {
|
||||
fd.mu.Lock()
|
||||
defer fd.mu.Unlock()
|
||||
return fd.readinessLocked(mask)
|
||||
}
|
||||
|
||||
// readinessLocked implements checking the readiness of the fuse device while
|
||||
// locked with DeviceFD.mu.
|
||||
func (fd *DeviceFD) readinessLocked(mask waiter.EventMask) waiter.EventMask {
|
||||
var ready waiter.EventMask
|
||||
ready |= waiter.EventOut // FD is always writable
|
||||
|
||||
if fd.fs.umounted {
|
||||
ready |= waiter.EventErr
|
||||
return ready & mask
|
||||
}
|
||||
|
||||
// FD is always writable.
|
||||
ready |= waiter.EventOut
|
||||
if !fd.queue.Empty() {
|
||||
// Have reqs available, FD is readable.
|
||||
ready |= waiter.EventIn
|
||||
@@ -330,7 +379,7 @@ func (fd *DeviceFD) EventUnregister(e *waiter.Entry) {
|
||||
// Seek implements vfs.FileDescriptionImpl.Seek.
|
||||
func (fd *DeviceFD) Seek(ctx context.Context, offset int64, whence int32) (int64, error) {
|
||||
// Operations on /dev/fuse don't make sense until a FUSE filesystem is mounted.
|
||||
if !fd.mounted {
|
||||
if fd.fs == nil {
|
||||
return 0, syserror.EPERM
|
||||
}
|
||||
|
||||
@@ -338,59 +387,59 @@ func (fd *DeviceFD) Seek(ctx context.Context, offset int64, whence int32) (int64
|
||||
}
|
||||
|
||||
// sendResponse sends a response to the waiting task (if any).
|
||||
//
|
||||
// Preconditions: fd.mu must be held.
|
||||
func (fd *DeviceFD) sendResponse(ctx context.Context, fut *futureResponse) error {
|
||||
// See if the running task need to perform some action before returning.
|
||||
// Since we just finished writing the future, we can be sure that
|
||||
// getResponse generates a populated response.
|
||||
if err := fd.noReceiverAction(ctx, fut.getResponse()); err != nil {
|
||||
return err
|
||||
}
|
||||
// Signal the task waiting on a response if any.
|
||||
defer close(fut.ch)
|
||||
|
||||
// Signal that the queue is no longer full.
|
||||
select {
|
||||
case fd.fullQueueCh <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
fd.numActiveRequests -= 1
|
||||
fd.numActiveRequests--
|
||||
|
||||
if fut.async {
|
||||
return fd.asyncCallBack(ctx, fut.getResponse())
|
||||
}
|
||||
|
||||
// Signal the task waiting on a response.
|
||||
close(fut.ch)
|
||||
return nil
|
||||
}
|
||||
|
||||
// sendError sends an error response to the waiting task (if any).
|
||||
func (fd *DeviceFD) sendError(ctx context.Context, errno int32, req *Request) error {
|
||||
// sendError sends an error response to the waiting task (if any) by calling sendResponse().
|
||||
//
|
||||
// Preconditions: fd.mu must be held.
|
||||
func (fd *DeviceFD) sendError(ctx context.Context, errno int32, unique linux.FUSEOpID) error {
|
||||
// Return the error to the calling task.
|
||||
outHdrLen := uint32((*linux.FUSEHeaderOut)(nil).SizeBytes())
|
||||
respHdr := linux.FUSEHeaderOut{
|
||||
Len: outHdrLen,
|
||||
Error: errno,
|
||||
Unique: req.hdr.Unique,
|
||||
Unique: unique,
|
||||
}
|
||||
|
||||
fut, ok := fd.completions[respHdr.Unique]
|
||||
if !ok {
|
||||
// Server sent us a response for a request we never sent?
|
||||
// A response for a request we never sent,
|
||||
// or for which we already received a reply (e.g. aborted).
|
||||
return syserror.EINVAL
|
||||
}
|
||||
delete(fd.completions, respHdr.Unique)
|
||||
|
||||
fut.hdr = &respHdr
|
||||
if err := fd.sendResponse(ctx, fut); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return fd.sendResponse(ctx, fut)
|
||||
}
|
||||
|
||||
// noReceiverAction has the calling kernel.Task do some action if its known that no
|
||||
// receiver is going to be waiting on the future channel. This is to be used by:
|
||||
// FUSE_INIT.
|
||||
func (fd *DeviceFD) noReceiverAction(ctx context.Context, r *Response) error {
|
||||
if r.opcode == linux.FUSE_INIT {
|
||||
// asyncCallBack executes pre-defined callback function for async requests.
|
||||
// Currently used by: FUSE_INIT.
|
||||
func (fd *DeviceFD) asyncCallBack(ctx context.Context, r *Response) error {
|
||||
switch r.opcode {
|
||||
case linux.FUSE_INIT:
|
||||
creds := auth.CredentialsFromContext(ctx)
|
||||
rootUserNs := kernel.KernelFromContext(ctx).RootUserNamespace()
|
||||
return fd.fs.conn.InitRecv(r, creds.HasCapabilityIn(linux.CAP_SYS_ADMIN, rootUserNs))
|
||||
// TODO(gvisor.dev/issue/3247): support async read: correctly process the response.
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -16,12 +16,10 @@ package fuse
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"testing"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/marshal"
|
||||
"gvisor.dev/gvisor/pkg/sentry/fsimpl/testutil"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
||||
@@ -35,10 +33,6 @@ import (
|
||||
// will simply echo the payload back with the appropriate headers.
|
||||
const echoTestOpcode linux.FUSEOpcode = 1000
|
||||
|
||||
type testPayload struct {
|
||||
data uint32
|
||||
}
|
||||
|
||||
// TestFUSECommunication tests that the communication layer between the Sentry and the
|
||||
// FUSE server daemon works as expected.
|
||||
func TestFUSECommunication(t *testing.T) {
|
||||
@@ -327,102 +321,3 @@ func fuseServerRun(t *testing.T, s *testutil.System, k *kernel.Kernel, fd *vfs.F
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func setup(t *testing.T) *testutil.System {
|
||||
k, err := testutil.Boot()
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating kernel: %v", err)
|
||||
}
|
||||
|
||||
ctx := k.SupervisorContext()
|
||||
creds := auth.CredentialsFromContext(ctx)
|
||||
|
||||
k.VFS().MustRegisterFilesystemType(Name, &FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{
|
||||
AllowUserList: true,
|
||||
AllowUserMount: true,
|
||||
})
|
||||
|
||||
mntns, err := k.VFS().NewMountNamespace(ctx, creds, "", "tmpfs", &vfs.MountOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("NewMountNamespace(): %v", err)
|
||||
}
|
||||
|
||||
return testutil.NewSystem(ctx, t, k.VFS(), mntns)
|
||||
}
|
||||
|
||||
// newTestConnection creates a fuse connection that the sentry can communicate with
|
||||
// and the FD for the server to communicate with.
|
||||
func newTestConnection(system *testutil.System, k *kernel.Kernel, maxActiveRequests uint64) (*connection, *vfs.FileDescription, error) {
|
||||
vfsObj := &vfs.VirtualFilesystem{}
|
||||
fuseDev := &DeviceFD{}
|
||||
|
||||
if err := vfsObj.Init(system.Ctx); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
vd := vfsObj.NewAnonVirtualDentry("genCountFD")
|
||||
defer vd.DecRef(system.Ctx)
|
||||
if err := fuseDev.vfsfd.Init(fuseDev, linux.O_RDWR|linux.O_CREAT, vd.Mount(), vd.Dentry(), &vfs.FileDescriptionOptions{}); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
fsopts := filesystemOptions{
|
||||
maxActiveRequests: maxActiveRequests,
|
||||
}
|
||||
fs, err := NewFUSEFilesystem(system.Ctx, 0, &fsopts, &fuseDev.vfsfd)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return fs.conn, &fuseDev.vfsfd, nil
|
||||
}
|
||||
|
||||
// SizeBytes implements marshal.Marshallable.SizeBytes.
|
||||
func (t *testPayload) SizeBytes() int {
|
||||
return 4
|
||||
}
|
||||
|
||||
// MarshalBytes implements marshal.Marshallable.MarshalBytes.
|
||||
func (t *testPayload) MarshalBytes(dst []byte) {
|
||||
usermem.ByteOrder.PutUint32(dst[:4], t.data)
|
||||
}
|
||||
|
||||
// UnmarshalBytes implements marshal.Marshallable.UnmarshalBytes.
|
||||
func (t *testPayload) UnmarshalBytes(src []byte) {
|
||||
*t = testPayload{data: usermem.ByteOrder.Uint32(src[:4])}
|
||||
}
|
||||
|
||||
// Packed implements marshal.Marshallable.Packed.
|
||||
func (t *testPayload) Packed() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// MarshalUnsafe implements marshal.Marshallable.MarshalUnsafe.
|
||||
func (t *testPayload) MarshalUnsafe(dst []byte) {
|
||||
t.MarshalBytes(dst)
|
||||
}
|
||||
|
||||
// UnmarshalUnsafe implements marshal.Marshallable.UnmarshalUnsafe.
|
||||
func (t *testPayload) UnmarshalUnsafe(src []byte) {
|
||||
t.UnmarshalBytes(src)
|
||||
}
|
||||
|
||||
// CopyOutN implements marshal.Marshallable.CopyOutN.
|
||||
func (t *testPayload) CopyOutN(cc marshal.CopyContext, addr usermem.Addr, limit int) (int, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
// CopyOut implements marshal.Marshallable.CopyOut.
|
||||
func (t *testPayload) CopyOut(cc marshal.CopyContext, addr usermem.Addr) (int, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
// CopyIn implements marshal.Marshallable.CopyIn.
|
||||
func (t *testPayload) CopyIn(cc marshal.CopyContext, addr usermem.Addr) (int, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
// WriteTo implements io.WriterTo.WriteTo.
|
||||
func (t *testPayload) WriteTo(w io.Writer) (int64, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
// Copyright 2020 The gVisor Authors.
|
||||
//
|
||||
// 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 fuse
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/context"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
||||
"gvisor.dev/gvisor/pkg/sentry/vfs"
|
||||
"gvisor.dev/gvisor/pkg/syserror"
|
||||
"gvisor.dev/gvisor/pkg/usermem"
|
||||
)
|
||||
|
||||
type directoryFD struct {
|
||||
fileDescription
|
||||
}
|
||||
|
||||
// Allocate implements directoryFD.Allocate.
|
||||
func (*directoryFD) Allocate(ctx context.Context, mode, offset, length uint64) error {
|
||||
return syserror.EISDIR
|
||||
}
|
||||
|
||||
// PRead implements FileDescriptionImpl.PRead.
|
||||
func (*directoryFD) PRead(ctx context.Context, dst usermem.IOSequence, offset int64, opts vfs.ReadOptions) (int64, error) {
|
||||
return 0, syserror.EISDIR
|
||||
}
|
||||
|
||||
// Read implements FileDescriptionImpl.Read.
|
||||
func (*directoryFD) Read(ctx context.Context, dst usermem.IOSequence, opts vfs.ReadOptions) (int64, error) {
|
||||
return 0, syserror.EISDIR
|
||||
}
|
||||
|
||||
// PWrite implements FileDescriptionImpl.PWrite.
|
||||
func (*directoryFD) PWrite(ctx context.Context, src usermem.IOSequence, offset int64, opts vfs.WriteOptions) (int64, error) {
|
||||
return 0, syserror.EISDIR
|
||||
}
|
||||
|
||||
// Write implements FileDescriptionImpl.Write.
|
||||
func (*directoryFD) Write(ctx context.Context, src usermem.IOSequence, opts vfs.WriteOptions) (int64, error) {
|
||||
return 0, syserror.EISDIR
|
||||
}
|
||||
|
||||
// IterDirents implements FileDescriptionImpl.IterDirents.
|
||||
func (dir *directoryFD) IterDirents(ctx context.Context, callback vfs.IterDirentsCallback) error {
|
||||
fusefs := dir.inode().fs
|
||||
task, creds := kernel.TaskFromContext(ctx), auth.CredentialsFromContext(ctx)
|
||||
|
||||
in := linux.FUSEReadIn{
|
||||
Fh: dir.Fh,
|
||||
Offset: uint64(atomic.LoadInt64(&dir.off)),
|
||||
Size: linux.FUSE_PAGE_SIZE,
|
||||
Flags: dir.statusFlags(),
|
||||
}
|
||||
|
||||
// TODO(gVisor.dev/issue/3404): Support FUSE_READDIRPLUS.
|
||||
req, err := fusefs.conn.NewRequest(creds, uint32(task.ThreadID()), dir.inode().nodeID, linux.FUSE_READDIR, &in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res, err := fusefs.conn.Call(task, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := res.Error(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var out linux.FUSEDirents
|
||||
if err := res.UnmarshalPayload(&out); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, fuseDirent := range out.Dirents {
|
||||
nextOff := int64(fuseDirent.Meta.Off)
|
||||
dirent := vfs.Dirent{
|
||||
Name: fuseDirent.Name,
|
||||
Type: uint8(fuseDirent.Meta.Type),
|
||||
Ino: fuseDirent.Meta.Ino,
|
||||
NextOff: nextOff,
|
||||
}
|
||||
|
||||
if err := callback.Handle(dirent); err != nil {
|
||||
return err
|
||||
}
|
||||
atomic.StoreInt64(&dir.off, nextOff)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
// Copyright 2020 The gVisor Authors.
|
||||
//
|
||||
// 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 fuse
|
||||
|
||||
import (
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/context"
|
||||
"gvisor.dev/gvisor/pkg/sentry/fsimpl/kernfs"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
||||
"gvisor.dev/gvisor/pkg/sentry/vfs"
|
||||
"gvisor.dev/gvisor/pkg/usermem"
|
||||
)
|
||||
|
||||
// fileDescription implements vfs.FileDescriptionImpl for fuse.
|
||||
type fileDescription struct {
|
||||
vfsfd vfs.FileDescription
|
||||
vfs.FileDescriptionDefaultImpl
|
||||
vfs.DentryMetadataFileDescriptionImpl
|
||||
vfs.NoLockFD
|
||||
|
||||
// the file handle used in userspace.
|
||||
Fh uint64
|
||||
|
||||
// Nonseekable is indicate cannot perform seek on a file.
|
||||
Nonseekable bool
|
||||
|
||||
// DirectIO suggest fuse to use direct io operation.
|
||||
DirectIO bool
|
||||
|
||||
// OpenFlag is the flag returned by open.
|
||||
OpenFlag uint32
|
||||
|
||||
// off is the file offset.
|
||||
off int64
|
||||
}
|
||||
|
||||
func (fd *fileDescription) dentry() *kernfs.Dentry {
|
||||
return fd.vfsfd.Dentry().Impl().(*kernfs.Dentry)
|
||||
}
|
||||
|
||||
func (fd *fileDescription) inode() *inode {
|
||||
return fd.dentry().Inode().(*inode)
|
||||
}
|
||||
|
||||
func (fd *fileDescription) filesystem() *vfs.Filesystem {
|
||||
return fd.vfsfd.VirtualDentry().Mount().Filesystem()
|
||||
}
|
||||
|
||||
func (fd *fileDescription) statusFlags() uint32 {
|
||||
return fd.vfsfd.StatusFlags()
|
||||
}
|
||||
|
||||
// Release implements vfs.FileDescriptionImpl.Release.
|
||||
func (fd *fileDescription) Release(ctx context.Context) {
|
||||
// no need to release if FUSE server doesn't implement Open.
|
||||
conn := fd.inode().fs.conn
|
||||
if conn.noOpen {
|
||||
return
|
||||
}
|
||||
|
||||
in := linux.FUSEReleaseIn{
|
||||
Fh: fd.Fh,
|
||||
Flags: fd.statusFlags(),
|
||||
}
|
||||
// TODO(gvisor.dev/issue/3245): add logic when we support file lock owner.
|
||||
var opcode linux.FUSEOpcode
|
||||
if fd.inode().Mode().IsDir() {
|
||||
opcode = linux.FUSE_RELEASEDIR
|
||||
} else {
|
||||
opcode = linux.FUSE_RELEASE
|
||||
}
|
||||
kernelTask := kernel.TaskFromContext(ctx)
|
||||
// ignoring errors and FUSE server reply is analogous to Linux's behavior.
|
||||
req, err := conn.NewRequest(auth.CredentialsFromContext(ctx), uint32(kernelTask.ThreadID()), fd.inode().nodeID, opcode, &in)
|
||||
if err != nil {
|
||||
// No way to invoke Call() with an errored request.
|
||||
return
|
||||
}
|
||||
// The reply will be ignored since no callback is defined in asyncCallBack().
|
||||
conn.CallAsync(kernelTask, req)
|
||||
}
|
||||
|
||||
// PRead implements vfs.FileDescriptionImpl.PRead.
|
||||
func (fd *fileDescription) PRead(ctx context.Context, dst usermem.IOSequence, offset int64, opts vfs.ReadOptions) (int64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// Read implements vfs.FileDescriptionImpl.Read.
|
||||
func (fd *fileDescription) Read(ctx context.Context, dst usermem.IOSequence, opts vfs.ReadOptions) (int64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// PWrite implements vfs.FileDescriptionImpl.PWrite.
|
||||
func (fd *fileDescription) PWrite(ctx context.Context, src usermem.IOSequence, offset int64, opts vfs.WriteOptions) (int64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// Write implements vfs.FileDescriptionImpl.Write.
|
||||
func (fd *fileDescription) Write(ctx context.Context, src usermem.IOSequence, opts vfs.WriteOptions) (int64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// Seek implements vfs.FileDescriptionImpl.Seek.
|
||||
func (fd *fileDescription) Seek(ctx context.Context, offset int64, whence int32) (int64, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// Stat implements vfs.FileDescriptionImpl.Stat.
|
||||
func (fd *fileDescription) Stat(ctx context.Context, opts vfs.StatOptions) (linux.Statx, error) {
|
||||
fs := fd.filesystem()
|
||||
inode := fd.inode()
|
||||
return inode.Stat(ctx, fs, opts)
|
||||
}
|
||||
|
||||
// SetStat implements vfs.FileDescriptionImpl.SetStat.
|
||||
func (fd *fileDescription) SetStat(ctx context.Context, opts vfs.SetStatOptions) error {
|
||||
fs := fd.filesystem()
|
||||
creds := auth.CredentialsFromContext(ctx)
|
||||
return fd.inode().setAttr(ctx, fs, creds, opts, true, fd.Fh)
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,242 @@
|
||||
// Copyright 2020 The gVisor Authors.
|
||||
//
|
||||
// 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 fuse
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync/atomic"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/context"
|
||||
"gvisor.dev/gvisor/pkg/log"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
||||
"gvisor.dev/gvisor/pkg/syserror"
|
||||
"gvisor.dev/gvisor/pkg/usermem"
|
||||
)
|
||||
|
||||
// ReadInPages sends FUSE_READ requests for the size after round it up to
|
||||
// a multiple of page size, blocks on it for reply, processes the reply
|
||||
// and returns the payload (or joined payloads) as a byte slice.
|
||||
// This is used for the general purpose reading.
|
||||
// We do not support direct IO (which read the exact number of bytes)
|
||||
// at this moment.
|
||||
func (fs *filesystem) ReadInPages(ctx context.Context, fd *regularFileFD, off uint64, size uint32) ([][]byte, uint32, error) {
|
||||
attributeVersion := atomic.LoadUint64(&fs.conn.attributeVersion)
|
||||
|
||||
t := kernel.TaskFromContext(ctx)
|
||||
if t == nil {
|
||||
log.Warningf("fusefs.Read: couldn't get kernel task from context")
|
||||
return nil, 0, syserror.EINVAL
|
||||
}
|
||||
|
||||
// Round up to a multiple of page size.
|
||||
readSize, _ := usermem.PageRoundUp(uint64(size))
|
||||
|
||||
// One request cannnot exceed either maxRead or maxPages.
|
||||
maxPages := fs.conn.maxRead >> usermem.PageShift
|
||||
if maxPages > uint32(fs.conn.maxPages) {
|
||||
maxPages = uint32(fs.conn.maxPages)
|
||||
}
|
||||
|
||||
var outs [][]byte
|
||||
var sizeRead uint32
|
||||
|
||||
// readSize is a multiple of usermem.PageSize.
|
||||
// Always request bytes as a multiple of pages.
|
||||
pagesRead, pagesToRead := uint32(0), uint32(readSize>>usermem.PageShift)
|
||||
|
||||
// Reuse the same struct for unmarshalling to avoid unnecessary memory allocation.
|
||||
in := linux.FUSEReadIn{
|
||||
Fh: fd.Fh,
|
||||
LockOwner: 0, // TODO(gvisor.dev/issue/3245): file lock
|
||||
ReadFlags: 0, // TODO(gvisor.dev/issue/3245): |= linux.FUSE_READ_LOCKOWNER
|
||||
Flags: fd.statusFlags(),
|
||||
}
|
||||
|
||||
// This loop is intended for fragmented read where the bytes to read is
|
||||
// larger than either the maxPages or maxRead.
|
||||
// For the majority of reads with normal size, this loop should only
|
||||
// execute once.
|
||||
for pagesRead < pagesToRead {
|
||||
pagesCanRead := pagesToRead - pagesRead
|
||||
if pagesCanRead > maxPages {
|
||||
pagesCanRead = maxPages
|
||||
}
|
||||
|
||||
in.Offset = off + (uint64(pagesRead) << usermem.PageShift)
|
||||
in.Size = pagesCanRead << usermem.PageShift
|
||||
|
||||
req, err := fs.conn.NewRequest(auth.CredentialsFromContext(ctx), uint32(t.ThreadID()), fd.inode().nodeID, linux.FUSE_READ, &in)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// TODO(gvisor.dev/issue/3247): support async read.
|
||||
|
||||
res, err := fs.conn.Call(t, req)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
if err := res.Error(); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
// Not enough bytes in response,
|
||||
// either we reached EOF,
|
||||
// or the FUSE server sends back a response
|
||||
// that cannot even fit the hdr.
|
||||
if len(res.data) <= res.hdr.SizeBytes() {
|
||||
// We treat both case as EOF here for now
|
||||
// since there is no reliable way to detect
|
||||
// the over-short hdr case.
|
||||
break
|
||||
}
|
||||
|
||||
// Directly using the slice to avoid extra copy.
|
||||
out := res.data[res.hdr.SizeBytes():]
|
||||
|
||||
outs = append(outs, out)
|
||||
sizeRead += uint32(len(out))
|
||||
|
||||
pagesRead += pagesCanRead
|
||||
}
|
||||
|
||||
defer fs.ReadCallback(ctx, fd, off, size, sizeRead, attributeVersion)
|
||||
|
||||
// No bytes returned: offset >= EOF.
|
||||
if len(outs) == 0 {
|
||||
return nil, 0, io.EOF
|
||||
}
|
||||
|
||||
return outs, sizeRead, nil
|
||||
}
|
||||
|
||||
// ReadCallback updates several information after receiving a read response.
|
||||
// Due to readahead, sizeRead can be larger than size.
|
||||
func (fs *filesystem) ReadCallback(ctx context.Context, fd *regularFileFD, off uint64, size uint32, sizeRead uint32, attributeVersion uint64) {
|
||||
// TODO(gvisor.dev/issue/3247): support async read.
|
||||
// If this is called by an async read, correctly process it.
|
||||
// May need to update the signature.
|
||||
|
||||
i := fd.inode()
|
||||
// TODO(gvisor.dev/issue/1193): Invalidate or update atime.
|
||||
|
||||
// Reached EOF.
|
||||
if sizeRead < size {
|
||||
// TODO(gvisor.dev/issue/3630): If we have writeback cache, then we need to fill this hole.
|
||||
// Might need to update the buf to be returned from the Read().
|
||||
|
||||
// Update existing size.
|
||||
newSize := off + uint64(sizeRead)
|
||||
fs.conn.mu.Lock()
|
||||
if attributeVersion == i.attributeVersion && newSize < atomic.LoadUint64(&i.size) {
|
||||
fs.conn.attributeVersion++
|
||||
i.attributeVersion = i.fs.conn.attributeVersion
|
||||
atomic.StoreUint64(&i.size, newSize)
|
||||
}
|
||||
fs.conn.mu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// Write sends FUSE_WRITE requests and return the bytes
|
||||
// written according to the response.
|
||||
//
|
||||
// Preconditions: len(data) == size.
|
||||
func (fs *filesystem) Write(ctx context.Context, fd *regularFileFD, off uint64, size uint32, data []byte) (uint32, error) {
|
||||
t := kernel.TaskFromContext(ctx)
|
||||
if t == nil {
|
||||
log.Warningf("fusefs.Read: couldn't get kernel task from context")
|
||||
return 0, syserror.EINVAL
|
||||
}
|
||||
|
||||
// One request cannnot exceed either maxWrite or maxPages.
|
||||
maxWrite := uint32(fs.conn.maxPages) << usermem.PageShift
|
||||
if maxWrite > fs.conn.maxWrite {
|
||||
maxWrite = fs.conn.maxWrite
|
||||
}
|
||||
|
||||
// Reuse the same struct for unmarshalling to avoid unnecessary memory allocation.
|
||||
in := linux.FUSEWriteIn{
|
||||
Fh: fd.Fh,
|
||||
// TODO(gvisor.dev/issue/3245): file lock
|
||||
LockOwner: 0,
|
||||
// TODO(gvisor.dev/issue/3245): |= linux.FUSE_READ_LOCKOWNER
|
||||
// TODO(gvisor.dev/issue/3237): |= linux.FUSE_WRITE_CACHE (not added yet)
|
||||
WriteFlags: 0,
|
||||
Flags: fd.statusFlags(),
|
||||
}
|
||||
|
||||
var written uint32
|
||||
|
||||
// This loop is intended for fragmented write where the bytes to write is
|
||||
// larger than either the maxWrite or maxPages or when bigWrites is false.
|
||||
// Unless a small value for max_write is explicitly used, this loop
|
||||
// is expected to execute only once for the majority of the writes.
|
||||
for written < size {
|
||||
toWrite := size - written
|
||||
|
||||
// Limit the write size to one page.
|
||||
// Note that the bigWrites flag is obsolete,
|
||||
// latest libfuse always sets it on.
|
||||
if !fs.conn.bigWrites && toWrite > usermem.PageSize {
|
||||
toWrite = usermem.PageSize
|
||||
}
|
||||
|
||||
// Limit the write size to maxWrite.
|
||||
if toWrite > maxWrite {
|
||||
toWrite = maxWrite
|
||||
}
|
||||
|
||||
in.Offset = off + uint64(written)
|
||||
in.Size = toWrite
|
||||
|
||||
req, err := fs.conn.NewRequest(auth.CredentialsFromContext(ctx), uint32(t.ThreadID()), fd.inode().nodeID, linux.FUSE_WRITE, &in)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
req.payload = data[written : written+toWrite]
|
||||
|
||||
// TODO(gvisor.dev/issue/3247): support async write.
|
||||
|
||||
res, err := fs.conn.Call(t, req)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if err := res.Error(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
out := linux.FUSEWriteOut{}
|
||||
if err := res.UnmarshalPayload(&out); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// Write more than requested? EIO.
|
||||
if out.Size > toWrite {
|
||||
return 0, syserror.EIO
|
||||
}
|
||||
|
||||
written += out.Size
|
||||
|
||||
// Break if short write. Not necessarily an error.
|
||||
if out.Size != toWrite {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return written, nil
|
||||
}
|
||||
@@ -0,0 +1,230 @@
|
||||
// Copyright 2020 The gVisor Authors.
|
||||
//
|
||||
// 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 fuse
|
||||
|
||||
import (
|
||||
"io"
|
||||
"math"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/context"
|
||||
"gvisor.dev/gvisor/pkg/sentry/vfs"
|
||||
"gvisor.dev/gvisor/pkg/syserror"
|
||||
"gvisor.dev/gvisor/pkg/usermem"
|
||||
)
|
||||
|
||||
type regularFileFD struct {
|
||||
fileDescription
|
||||
|
||||
// off is the file offset.
|
||||
off int64
|
||||
// offMu protects off.
|
||||
offMu sync.Mutex
|
||||
}
|
||||
|
||||
// PRead implements vfs.FileDescriptionImpl.PRead.
|
||||
func (fd *regularFileFD) PRead(ctx context.Context, dst usermem.IOSequence, offset int64, opts vfs.ReadOptions) (int64, error) {
|
||||
if offset < 0 {
|
||||
return 0, syserror.EINVAL
|
||||
}
|
||||
|
||||
// Check that flags are supported.
|
||||
//
|
||||
// TODO(gvisor.dev/issue/2601): Support select preadv2 flags.
|
||||
if opts.Flags&^linux.RWF_HIPRI != 0 {
|
||||
return 0, syserror.EOPNOTSUPP
|
||||
}
|
||||
|
||||
size := dst.NumBytes()
|
||||
if size == 0 {
|
||||
// Early return if count is 0.
|
||||
return 0, nil
|
||||
} else if size > math.MaxUint32 {
|
||||
// FUSE only supports uint32 for size.
|
||||
// Overflow.
|
||||
return 0, syserror.EINVAL
|
||||
}
|
||||
|
||||
// TODO(gvisor.dev/issue/3678): Add direct IO support.
|
||||
|
||||
inode := fd.inode()
|
||||
|
||||
// Reading beyond EOF, update file size if outdated.
|
||||
if uint64(offset+size) > atomic.LoadUint64(&inode.size) {
|
||||
if err := inode.reviseAttr(ctx, linux.FUSE_GETATTR_FH, fd.Fh); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
// If the offset after update is still too large, return error.
|
||||
if uint64(offset) >= atomic.LoadUint64(&inode.size) {
|
||||
return 0, io.EOF
|
||||
}
|
||||
}
|
||||
|
||||
// Truncate the read with updated file size.
|
||||
fileSize := atomic.LoadUint64(&inode.size)
|
||||
if uint64(offset+size) > fileSize {
|
||||
size = int64(fileSize) - offset
|
||||
}
|
||||
|
||||
buffers, n, err := inode.fs.ReadInPages(ctx, fd, uint64(offset), uint32(size))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// TODO(gvisor.dev/issue/3237): support indirect IO (e.g. caching),
|
||||
// store the bytes that were read ahead.
|
||||
|
||||
// Update the number of bytes to copy for short read.
|
||||
if n < uint32(size) {
|
||||
size = int64(n)
|
||||
}
|
||||
|
||||
// Copy the bytes read to the dst.
|
||||
// This loop is intended for fragmented reads.
|
||||
// For the majority of reads, this loop only execute once.
|
||||
var copied int64
|
||||
for _, buffer := range buffers {
|
||||
toCopy := int64(len(buffer))
|
||||
if copied+toCopy > size {
|
||||
toCopy = size - copied
|
||||
}
|
||||
cp, err := dst.DropFirst64(copied).CopyOut(ctx, buffer[:toCopy])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if int64(cp) != toCopy {
|
||||
return 0, syserror.EIO
|
||||
}
|
||||
copied += toCopy
|
||||
}
|
||||
|
||||
return copied, nil
|
||||
}
|
||||
|
||||
// Read implements vfs.FileDescriptionImpl.Read.
|
||||
func (fd *regularFileFD) Read(ctx context.Context, dst usermem.IOSequence, opts vfs.ReadOptions) (int64, error) {
|
||||
fd.offMu.Lock()
|
||||
n, err := fd.PRead(ctx, dst, fd.off, opts)
|
||||
fd.off += n
|
||||
fd.offMu.Unlock()
|
||||
return n, err
|
||||
}
|
||||
|
||||
// PWrite implements vfs.FileDescriptionImpl.PWrite.
|
||||
func (fd *regularFileFD) PWrite(ctx context.Context, src usermem.IOSequence, offset int64, opts vfs.WriteOptions) (int64, error) {
|
||||
n, _, err := fd.pwrite(ctx, src, offset, opts)
|
||||
return n, err
|
||||
}
|
||||
|
||||
// Write implements vfs.FileDescriptionImpl.Write.
|
||||
func (fd *regularFileFD) Write(ctx context.Context, src usermem.IOSequence, opts vfs.WriteOptions) (int64, error) {
|
||||
fd.offMu.Lock()
|
||||
n, off, err := fd.pwrite(ctx, src, fd.off, opts)
|
||||
fd.off = off
|
||||
fd.offMu.Unlock()
|
||||
return n, err
|
||||
}
|
||||
|
||||
// pwrite returns the number of bytes written, final offset and error. The
|
||||
// final offset should be ignored by PWrite.
|
||||
func (fd *regularFileFD) pwrite(ctx context.Context, src usermem.IOSequence, offset int64, opts vfs.WriteOptions) (written, finalOff int64, err error) {
|
||||
if offset < 0 {
|
||||
return 0, offset, syserror.EINVAL
|
||||
}
|
||||
|
||||
// Check that flags are supported.
|
||||
//
|
||||
// TODO(gvisor.dev/issue/2601): Support select preadv2 flags.
|
||||
if opts.Flags&^linux.RWF_HIPRI != 0 {
|
||||
return 0, offset, syserror.EOPNOTSUPP
|
||||
}
|
||||
|
||||
inode := fd.inode()
|
||||
inode.metadataMu.Lock()
|
||||
defer inode.metadataMu.Unlock()
|
||||
|
||||
// If the file is opened with O_APPEND, update offset to file size.
|
||||
// Note: since our Open() implements the interface of kernfs,
|
||||
// and kernfs currently does not support O_APPEND, this will never
|
||||
// be true before we switch out from kernfs.
|
||||
if fd.vfsfd.StatusFlags()&linux.O_APPEND != 0 {
|
||||
// Locking inode.metadataMu is sufficient for reading size
|
||||
offset = int64(inode.size)
|
||||
}
|
||||
|
||||
srclen := src.NumBytes()
|
||||
|
||||
if srclen > math.MaxUint32 {
|
||||
// FUSE only supports uint32 for size.
|
||||
// Overflow.
|
||||
return 0, offset, syserror.EINVAL
|
||||
}
|
||||
if end := offset + srclen; end < offset {
|
||||
// Overflow.
|
||||
return 0, offset, syserror.EINVAL
|
||||
}
|
||||
|
||||
srclen, err = vfs.CheckLimit(ctx, offset, srclen)
|
||||
if err != nil {
|
||||
return 0, offset, err
|
||||
}
|
||||
|
||||
if srclen == 0 {
|
||||
// Return before causing any side effects.
|
||||
return 0, offset, nil
|
||||
}
|
||||
|
||||
src = src.TakeFirst64(srclen)
|
||||
|
||||
// TODO(gvisor.dev/issue/3237): Add cache support:
|
||||
// buffer cache. Ideally we write from src to our buffer cache first.
|
||||
// The slice passed to fs.Write() should be a slice from buffer cache.
|
||||
data := make([]byte, srclen)
|
||||
// Reason for making a copy here: connection.Call() blocks on kerneltask,
|
||||
// which in turn acquires mm.activeMu lock. Functions like CopyInTo() will
|
||||
// attemp to acquire the mm.activeMu lock as well -> deadlock.
|
||||
// We must finish reading from the userspace memory before
|
||||
// t.Block() deactivates it.
|
||||
cp, err := src.CopyIn(ctx, data)
|
||||
if err != nil {
|
||||
return 0, offset, err
|
||||
}
|
||||
if int64(cp) != srclen {
|
||||
return 0, offset, syserror.EIO
|
||||
}
|
||||
|
||||
n, err := fd.inode().fs.Write(ctx, fd, uint64(offset), uint32(srclen), data)
|
||||
if err != nil {
|
||||
return 0, offset, err
|
||||
}
|
||||
|
||||
if n == 0 {
|
||||
// We have checked srclen != 0 previously.
|
||||
// If err == nil, then it's a short write and we return EIO.
|
||||
return 0, offset, syserror.EIO
|
||||
}
|
||||
|
||||
written = int64(n)
|
||||
finalOff = offset + written
|
||||
|
||||
if finalOff > int64(inode.size) {
|
||||
atomic.StoreUint64(&inode.size, uint64(finalOff))
|
||||
atomic.AddUint64(&inode.fs.conn.attributeVersion, 1)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
@@ -0,0 +1,229 @@
|
||||
// Copyright 2020 The gVisor Authors.
|
||||
//
|
||||
// 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 fuse
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"syscall"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/marshal"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
||||
"gvisor.dev/gvisor/pkg/usermem"
|
||||
)
|
||||
|
||||
// fuseInitRes is a variable-length wrapper of linux.FUSEInitOut. The FUSE
|
||||
// server may implement an older version of FUSE protocol, which contains a
|
||||
// linux.FUSEInitOut with less attributes.
|
||||
//
|
||||
// Dynamically-sized objects cannot be marshalled.
|
||||
type fuseInitRes struct {
|
||||
marshal.StubMarshallable
|
||||
|
||||
// initOut contains the response from the FUSE server.
|
||||
initOut linux.FUSEInitOut
|
||||
|
||||
// initLen is the total length of bytes of the response.
|
||||
initLen uint32
|
||||
}
|
||||
|
||||
// UnmarshalBytes deserializes src to the initOut attribute in a fuseInitRes.
|
||||
func (r *fuseInitRes) UnmarshalBytes(src []byte) {
|
||||
out := &r.initOut
|
||||
|
||||
// Introduced before FUSE kernel version 7.13.
|
||||
out.Major = uint32(usermem.ByteOrder.Uint32(src[:4]))
|
||||
src = src[4:]
|
||||
out.Minor = uint32(usermem.ByteOrder.Uint32(src[:4]))
|
||||
src = src[4:]
|
||||
out.MaxReadahead = uint32(usermem.ByteOrder.Uint32(src[:4]))
|
||||
src = src[4:]
|
||||
out.Flags = uint32(usermem.ByteOrder.Uint32(src[:4]))
|
||||
src = src[4:]
|
||||
out.MaxBackground = uint16(usermem.ByteOrder.Uint16(src[:2]))
|
||||
src = src[2:]
|
||||
out.CongestionThreshold = uint16(usermem.ByteOrder.Uint16(src[:2]))
|
||||
src = src[2:]
|
||||
out.MaxWrite = uint32(usermem.ByteOrder.Uint32(src[:4]))
|
||||
src = src[4:]
|
||||
|
||||
// Introduced in FUSE kernel version 7.23.
|
||||
if len(src) >= 4 {
|
||||
out.TimeGran = uint32(usermem.ByteOrder.Uint32(src[:4]))
|
||||
src = src[4:]
|
||||
}
|
||||
// Introduced in FUSE kernel version 7.28.
|
||||
if len(src) >= 2 {
|
||||
out.MaxPages = uint16(usermem.ByteOrder.Uint16(src[:2]))
|
||||
src = src[2:]
|
||||
}
|
||||
}
|
||||
|
||||
// SizeBytes is the size of the payload of the FUSE_INIT response.
|
||||
func (r *fuseInitRes) SizeBytes() int {
|
||||
return int(r.initLen)
|
||||
}
|
||||
|
||||
// Ordinary requests have even IDs, while interrupts IDs are odd.
|
||||
// Used to increment the unique ID for each FUSE request.
|
||||
var reqIDStep uint64 = 2
|
||||
|
||||
// Request represents a FUSE operation request that hasn't been sent to the
|
||||
// server yet.
|
||||
//
|
||||
// +stateify savable
|
||||
type Request struct {
|
||||
requestEntry
|
||||
|
||||
id linux.FUSEOpID
|
||||
hdr *linux.FUSEHeaderIn
|
||||
data []byte
|
||||
|
||||
// payload for this request: extra bytes to write after
|
||||
// the data slice. Used by FUSE_WRITE.
|
||||
payload []byte
|
||||
|
||||
// If this request is async.
|
||||
async bool
|
||||
// If we don't care its response.
|
||||
// Manually set by the caller.
|
||||
noReply bool
|
||||
}
|
||||
|
||||
// NewRequest creates a new request that can be sent to the FUSE server.
|
||||
func (conn *connection) NewRequest(creds *auth.Credentials, pid uint32, ino uint64, opcode linux.FUSEOpcode, payload marshal.Marshallable) (*Request, error) {
|
||||
conn.fd.mu.Lock()
|
||||
defer conn.fd.mu.Unlock()
|
||||
conn.fd.nextOpID += linux.FUSEOpID(reqIDStep)
|
||||
|
||||
hdrLen := (*linux.FUSEHeaderIn)(nil).SizeBytes()
|
||||
hdr := linux.FUSEHeaderIn{
|
||||
Len: uint32(hdrLen + payload.SizeBytes()),
|
||||
Opcode: opcode,
|
||||
Unique: conn.fd.nextOpID,
|
||||
NodeID: ino,
|
||||
UID: uint32(creds.EffectiveKUID),
|
||||
GID: uint32(creds.EffectiveKGID),
|
||||
PID: pid,
|
||||
}
|
||||
|
||||
buf := make([]byte, hdr.Len)
|
||||
|
||||
// TODO(gVisor.dev/issue/3698): Use the unsafe version once go_marshal is safe to use again.
|
||||
hdr.MarshalBytes(buf[:hdrLen])
|
||||
payload.MarshalBytes(buf[hdrLen:])
|
||||
|
||||
return &Request{
|
||||
id: hdr.Unique,
|
||||
hdr: &hdr,
|
||||
data: buf,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// futureResponse represents an in-flight request, that may or may not have
|
||||
// completed yet. Convert it to a resolved Response by calling Resolve, but note
|
||||
// that this may block.
|
||||
//
|
||||
// +stateify savable
|
||||
type futureResponse struct {
|
||||
opcode linux.FUSEOpcode
|
||||
ch chan struct{}
|
||||
hdr *linux.FUSEHeaderOut
|
||||
data []byte
|
||||
|
||||
// If this request is async.
|
||||
async bool
|
||||
}
|
||||
|
||||
// newFutureResponse creates a future response to a FUSE request.
|
||||
func newFutureResponse(req *Request) *futureResponse {
|
||||
return &futureResponse{
|
||||
opcode: req.hdr.Opcode,
|
||||
ch: make(chan struct{}),
|
||||
async: req.async,
|
||||
}
|
||||
}
|
||||
|
||||
// resolve blocks the task until the server responds to its corresponding request,
|
||||
// then returns a resolved response.
|
||||
func (f *futureResponse) resolve(t *kernel.Task) (*Response, error) {
|
||||
// Return directly for async requests.
|
||||
if f.async {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if err := t.Block(f.ch); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return f.getResponse(), nil
|
||||
}
|
||||
|
||||
// getResponse creates a Response from the data the futureResponse has.
|
||||
func (f *futureResponse) getResponse() *Response {
|
||||
return &Response{
|
||||
opcode: f.opcode,
|
||||
hdr: *f.hdr,
|
||||
data: f.data,
|
||||
}
|
||||
}
|
||||
|
||||
// Response represents an actual response from the server, including the
|
||||
// response payload.
|
||||
//
|
||||
// +stateify savable
|
||||
type Response struct {
|
||||
opcode linux.FUSEOpcode
|
||||
hdr linux.FUSEHeaderOut
|
||||
data []byte
|
||||
}
|
||||
|
||||
// Error returns the error of the FUSE call.
|
||||
func (r *Response) Error() error {
|
||||
errno := r.hdr.Error
|
||||
if errno >= 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
sysErrNo := syscall.Errno(-errno)
|
||||
return error(sysErrNo)
|
||||
}
|
||||
|
||||
// DataLen returns the size of the response without the header.
|
||||
func (r *Response) DataLen() uint32 {
|
||||
return r.hdr.Len - uint32(r.hdr.SizeBytes())
|
||||
}
|
||||
|
||||
// UnmarshalPayload unmarshals the response data into m.
|
||||
func (r *Response) UnmarshalPayload(m marshal.Marshallable) error {
|
||||
hdrLen := r.hdr.SizeBytes()
|
||||
haveDataLen := r.hdr.Len - uint32(hdrLen)
|
||||
wantDataLen := uint32(m.SizeBytes())
|
||||
|
||||
if haveDataLen < wantDataLen {
|
||||
return fmt.Errorf("payload too small. Minimum data lenth required: %d, but got data length %d", wantDataLen, haveDataLen)
|
||||
}
|
||||
|
||||
// The response data is empty unless there is some payload. And so, doesn't
|
||||
// need to be unmarshalled.
|
||||
if r.data == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO(gVisor.dev/issue/3698): Use the unsafe version once go_marshal is safe to use again.
|
||||
m.UnmarshalBytes(r.data[hdrLen:])
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
// Copyright 2020 The gVisor Authors.
|
||||
//
|
||||
// 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 fuse
|
||||
|
||||
import (
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
"gvisor.dev/gvisor/pkg/abi/linux"
|
||||
"gvisor.dev/gvisor/pkg/marshal"
|
||||
"gvisor.dev/gvisor/pkg/sentry/fsimpl/testutil"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel"
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
||||
"gvisor.dev/gvisor/pkg/sentry/vfs"
|
||||
"gvisor.dev/gvisor/pkg/usermem"
|
||||
)
|
||||
|
||||
func setup(t *testing.T) *testutil.System {
|
||||
k, err := testutil.Boot()
|
||||
if err != nil {
|
||||
t.Fatalf("Error creating kernel: %v", err)
|
||||
}
|
||||
|
||||
ctx := k.SupervisorContext()
|
||||
creds := auth.CredentialsFromContext(ctx)
|
||||
|
||||
k.VFS().MustRegisterFilesystemType(Name, &FilesystemType{}, &vfs.RegisterFilesystemTypeOptions{
|
||||
AllowUserList: true,
|
||||
AllowUserMount: true,
|
||||
})
|
||||
|
||||
mntns, err := k.VFS().NewMountNamespace(ctx, creds, "", "tmpfs", &vfs.MountOptions{})
|
||||
if err != nil {
|
||||
t.Fatalf("NewMountNamespace(): %v", err)
|
||||
}
|
||||
|
||||
return testutil.NewSystem(ctx, t, k.VFS(), mntns)
|
||||
}
|
||||
|
||||
// newTestConnection creates a fuse connection that the sentry can communicate with
|
||||
// and the FD for the server to communicate with.
|
||||
func newTestConnection(system *testutil.System, k *kernel.Kernel, maxActiveRequests uint64) (*connection, *vfs.FileDescription, error) {
|
||||
vfsObj := &vfs.VirtualFilesystem{}
|
||||
fuseDev := &DeviceFD{}
|
||||
|
||||
if err := vfsObj.Init(system.Ctx); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
vd := vfsObj.NewAnonVirtualDentry("genCountFD")
|
||||
defer vd.DecRef(system.Ctx)
|
||||
if err := fuseDev.vfsfd.Init(fuseDev, linux.O_RDWR|linux.O_CREAT, vd.Mount(), vd.Dentry(), &vfs.FileDescriptionOptions{}); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
fsopts := filesystemOptions{
|
||||
maxActiveRequests: maxActiveRequests,
|
||||
}
|
||||
fs, err := newFUSEFilesystem(system.Ctx, 0, &fsopts, &fuseDev.vfsfd)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return fs.conn, &fuseDev.vfsfd, nil
|
||||
}
|
||||
|
||||
type testPayload struct {
|
||||
marshal.StubMarshallable
|
||||
data uint32
|
||||
}
|
||||
|
||||
// SizeBytes implements marshal.Marshallable.SizeBytes.
|
||||
func (t *testPayload) SizeBytes() int {
|
||||
return 4
|
||||
}
|
||||
|
||||
// MarshalBytes implements marshal.Marshallable.MarshalBytes.
|
||||
func (t *testPayload) MarshalBytes(dst []byte) {
|
||||
usermem.ByteOrder.PutUint32(dst[:4], t.data)
|
||||
}
|
||||
|
||||
// UnmarshalBytes implements marshal.Marshallable.UnmarshalBytes.
|
||||
func (t *testPayload) UnmarshalBytes(src []byte) {
|
||||
*t = testPayload{data: usermem.ByteOrder.Uint32(src[:4])}
|
||||
}
|
||||
|
||||
// Packed implements marshal.Marshallable.Packed.
|
||||
func (t *testPayload) Packed() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// MarshalUnsafe implements marshal.Marshallable.MarshalUnsafe.
|
||||
func (t *testPayload) MarshalUnsafe(dst []byte) {
|
||||
t.MarshalBytes(dst)
|
||||
}
|
||||
|
||||
// UnmarshalUnsafe implements marshal.Marshallable.UnmarshalUnsafe.
|
||||
func (t *testPayload) UnmarshalUnsafe(src []byte) {
|
||||
t.UnmarshalBytes(src)
|
||||
}
|
||||
|
||||
// CopyOutN implements marshal.Marshallable.CopyOutN.
|
||||
func (t *testPayload) CopyOutN(task marshal.CopyContext, addr usermem.Addr, limit int) (int, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
// CopyOut implements marshal.Marshallable.CopyOut.
|
||||
func (t *testPayload) CopyOut(task marshal.CopyContext, addr usermem.Addr) (int, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
// CopyIn implements marshal.Marshallable.CopyIn.
|
||||
func (t *testPayload) CopyIn(task marshal.CopyContext, addr usermem.Addr) (int, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
// WriteTo implements io.WriterTo.WriteTo.
|
||||
func (t *testPayload) WriteTo(w io.Writer) (int64, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
@@ -140,7 +140,7 @@ func (fs *Filesystem) revalidateChildLocked(ctx context.Context, vfsObj *vfs.Vir
|
||||
}
|
||||
// Reference on childVFSD dropped by a corresponding Valid.
|
||||
child = childVFSD.Impl().(*Dentry)
|
||||
parent.insertChildLocked(name, child)
|
||||
parent.InsertChildLocked(name, child)
|
||||
}
|
||||
return child, nil
|
||||
}
|
||||
@@ -548,7 +548,7 @@ func (fs *Filesystem) ReadlinkAt(ctx context.Context, rp *vfs.ResolvingPath) (st
|
||||
if !d.Impl().(*Dentry).isSymlink() {
|
||||
return "", syserror.EINVAL
|
||||
}
|
||||
return inode.Readlink(ctx)
|
||||
return inode.Readlink(ctx, rp.Mount())
|
||||
}
|
||||
|
||||
// RenameAt implements vfs.FilesystemImpl.RenameAt.
|
||||
@@ -657,6 +657,10 @@ func (fs *Filesystem) RenameAt(ctx context.Context, rp *vfs.ResolvingPath, oldPa
|
||||
func (fs *Filesystem) RmdirAt(ctx context.Context, rp *vfs.ResolvingPath) error {
|
||||
fs.mu.Lock()
|
||||
defer fs.mu.Unlock()
|
||||
|
||||
// Store the name before walkExistingLocked as rp will be advanced past the
|
||||
// name in the following call.
|
||||
name := rp.Component()
|
||||
vfsd, inode, err := fs.walkExistingLocked(ctx, rp)
|
||||
fs.processDeferredDecRefsLocked(ctx)
|
||||
if err != nil {
|
||||
@@ -686,7 +690,8 @@ func (fs *Filesystem) RmdirAt(ctx context.Context, rp *vfs.ResolvingPath) error
|
||||
if err := virtfs.PrepareDeleteDentry(mntns, vfsd); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := parentDentry.inode.RmDir(ctx, rp.Component(), vfsd); err != nil {
|
||||
|
||||
if err := parentDentry.inode.RmDir(ctx, name, vfsd); err != nil {
|
||||
virtfs.AbortDeleteDentry(vfsd)
|
||||
return err
|
||||
}
|
||||
@@ -765,6 +770,10 @@ func (fs *Filesystem) SymlinkAt(ctx context.Context, rp *vfs.ResolvingPath, targ
|
||||
func (fs *Filesystem) UnlinkAt(ctx context.Context, rp *vfs.ResolvingPath) error {
|
||||
fs.mu.Lock()
|
||||
defer fs.mu.Unlock()
|
||||
|
||||
// Store the name before walkExistingLocked as rp will be advanced past the
|
||||
// name in the following call.
|
||||
name := rp.Component()
|
||||
vfsd, _, err := fs.walkExistingLocked(ctx, rp)
|
||||
fs.processDeferredDecRefsLocked(ctx)
|
||||
if err != nil {
|
||||
@@ -790,7 +799,7 @@ func (fs *Filesystem) UnlinkAt(ctx context.Context, rp *vfs.ResolvingPath) error
|
||||
if err := virtfs.PrepareDeleteDentry(mntns, vfsd); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := parentDentry.inode.Unlink(ctx, rp.Component(), vfsd); err != nil {
|
||||
if err := parentDentry.inode.Unlink(ctx, name, vfsd); err != nil {
|
||||
virtfs.AbortDeleteDentry(vfsd)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -172,7 +172,7 @@ func (InodeNoDynamicLookup) Valid(ctx context.Context) bool {
|
||||
type InodeNotSymlink struct{}
|
||||
|
||||
// Readlink implements Inode.Readlink.
|
||||
func (InodeNotSymlink) Readlink(context.Context) (string, error) {
|
||||
func (InodeNotSymlink) Readlink(context.Context, *vfs.Mount) (string, error) {
|
||||
return "", syserror.EINVAL
|
||||
}
|
||||
|
||||
@@ -256,6 +256,13 @@ func (a *InodeAttrs) Stat(context.Context, *vfs.Filesystem, vfs.StatOptions) (li
|
||||
|
||||
// SetStat implements Inode.SetStat.
|
||||
func (a *InodeAttrs) SetStat(ctx context.Context, fs *vfs.Filesystem, creds *auth.Credentials, opts vfs.SetStatOptions) error {
|
||||
return a.SetInodeStat(ctx, fs, creds, opts)
|
||||
}
|
||||
|
||||
// SetInodeStat sets the corresponding attributes from opts to InodeAttrs.
|
||||
// This function can be used by other kernfs-based filesystem implementation to
|
||||
// sets the unexported attributes into kernfs.InodeAttrs.
|
||||
func (a *InodeAttrs) SetInodeStat(ctx context.Context, fs *vfs.Filesystem, creds *auth.Credentials, opts vfs.SetStatOptions) error {
|
||||
if opts.Stat.Mask == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@ import (
|
||||
"gvisor.dev/gvisor/pkg/sentry/kernel/auth"
|
||||
"gvisor.dev/gvisor/pkg/sentry/vfs"
|
||||
"gvisor.dev/gvisor/pkg/sync"
|
||||
"gvisor.dev/gvisor/pkg/syserror"
|
||||
)
|
||||
|
||||
// Filesystem mostly implements vfs.FilesystemImpl for a generic in-memory
|
||||
@@ -246,15 +247,15 @@ func (d *Dentry) OnZeroWatches(context.Context) {}
|
||||
// Precondition: d must represent a directory inode.
|
||||
func (d *Dentry) InsertChild(name string, child *Dentry) {
|
||||
d.dirMu.Lock()
|
||||
d.insertChildLocked(name, child)
|
||||
d.InsertChildLocked(name, child)
|
||||
d.dirMu.Unlock()
|
||||
}
|
||||
|
||||
// insertChildLocked is equivalent to InsertChild, with additional
|
||||
// InsertChildLocked is equivalent to InsertChild, with additional
|
||||
// preconditions.
|
||||
//
|
||||
// Precondition: d.dirMu must be locked.
|
||||
func (d *Dentry) insertChildLocked(name string, child *Dentry) {
|
||||
func (d *Dentry) InsertChildLocked(name string, child *Dentry) {
|
||||
if !d.isDir() {
|
||||
panic(fmt.Sprintf("InsertChild called on non-directory Dentry: %+v.", d))
|
||||
}
|
||||
@@ -267,6 +268,36 @@ func (d *Dentry) insertChildLocked(name string, child *Dentry) {
|
||||
d.children[name] = child
|
||||
}
|
||||
|
||||
// RemoveChild removes child from the vfs dentry cache. This does not update the
|
||||
// directory inode or modify the inode to be unlinked. So calling this on its own
|
||||
// isn't sufficient to remove a child from a directory.
|
||||
//
|
||||
// Precondition: d must represent a directory inode.
|
||||
func (d *Dentry) RemoveChild(name string, child *vfs.Dentry) error {
|
||||
d.dirMu.Lock()
|
||||
defer d.dirMu.Unlock()
|
||||
return d.RemoveChildLocked(name, child)
|
||||
}
|
||||
|
||||
// RemoveChildLocked is equivalent to RemoveChild, with additional
|
||||
// preconditions.
|
||||
//
|
||||
// Precondition: d.dirMu must be locked.
|
||||
func (d *Dentry) RemoveChildLocked(name string, child *vfs.Dentry) error {
|
||||
if !d.isDir() {
|
||||
panic(fmt.Sprintf("RemoveChild called on non-directory Dentry: %+v.", d))
|
||||
}
|
||||
c, ok := d.children[name]
|
||||
if !ok {
|
||||
return syserror.ENOENT
|
||||
}
|
||||
if &c.vfsd != child {
|
||||
panic(fmt.Sprintf("Dentry hashed into inode doesn't match what vfs thinks! Child: %+v, vfs: %+v", c, child))
|
||||
}
|
||||
delete(d.children, name)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Inode returns the dentry's inode.
|
||||
func (d *Dentry) Inode() Inode {
|
||||
return d.inode
|
||||
@@ -425,7 +456,7 @@ type inodeDynamicLookup interface {
|
||||
Valid(ctx context.Context) bool
|
||||
|
||||
// IterDirents is used to iterate over dynamically created entries. It invokes
|
||||
// cb on each entry in the directory represented by the FileDescription.
|
||||
// cb on each entry in the directory represented by the Inode.
|
||||
// 'offset' is the offset for the entire IterDirents call, which may include
|
||||
// results from the caller (e.g. "." and ".."). 'relOffset' is the offset
|
||||
// inside the entries returned by this IterDirents invocation. In other words,
|
||||
@@ -437,7 +468,7 @@ type inodeDynamicLookup interface {
|
||||
type inodeSymlink interface {
|
||||
// Readlink returns the target of a symbolic link. If an inode is not a
|
||||
// symlink, the implementation should return EINVAL.
|
||||
Readlink(ctx context.Context) (string, error)
|
||||
Readlink(ctx context.Context, mnt *vfs.Mount) (string, error)
|
||||
|
||||
// Getlink returns the target of a symbolic link, as used by path
|
||||
// resolution:
|
||||
|
||||
@@ -52,7 +52,7 @@ func (s *StaticSymlink) Init(creds *auth.Credentials, devMajor uint32, devMinor
|
||||
}
|
||||
|
||||
// Readlink implements Inode.
|
||||
func (s *StaticSymlink) Readlink(_ context.Context) (string, error) {
|
||||
func (s *StaticSymlink) Readlink(_ context.Context, _ *vfs.Mount) (string, error) {
|
||||
return s.target, nil
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user