Merge pull request #3934 from avagin:feature/fuse

PiperOrigin-RevId: 332122081
This commit is contained in:
gVisor bot
2020-09-16 17:12:57 -07:00
48 changed files with 6254 additions and 893 deletions
+640 -70
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -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.
+46
View File
@@ -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 havent 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
+12 -2
View File
@@ -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",
+109 -243
View File
@@ -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.
}
+117
View File
@@ -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
View File
@@ -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
-105
View File
@@ -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")
}
+105
View File
@@ -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
}
+133
View File
@@ -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
+242
View File
@@ -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
}
+230
View File
@@ -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
}
+229
View File
@@ -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
}
+132
View File
@@ -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")
}
+13 -4
View File
@@ -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
}
+8 -1
View File
@@ -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
}
+36 -5
View File
@@ -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:
+1 -1
View File
@@ -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