mirror of
https://github.com/hanzoai/visor.git
synced 2026-09-24 17:04:14 +00:00
This renames FDMap to FDTable and drops the kernel.FD type, which had an entire package to itself and didn't serve much use (it was freely cast between types, and served as more of an annoyance than providing any protection.) Based on BenchmarkFDLookupAndDecRef-12, we can expect 5-10 ns per lookup operation, and 10-15 ns per concurrent lookup operation of savings. This also fixes two tangential usage issues with the FDMap. Namely, non-atomic use of NewFDFrom and associated calls to Remove (that are both racy and fail to drop the reference on the underlying file.) PiperOrigin-RevId: 256285890
416 lines
10 KiB
Go
416 lines
10 KiB
Go
// Copyright 2018 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 linux
|
|
|
|
import (
|
|
"encoding/binary"
|
|
|
|
"gvisor.dev/gvisor/pkg/sentry/arch"
|
|
"gvisor.dev/gvisor/pkg/sentry/fs"
|
|
"gvisor.dev/gvisor/pkg/sentry/kernel"
|
|
"gvisor.dev/gvisor/pkg/sentry/kernel/eventfd"
|
|
ktime "gvisor.dev/gvisor/pkg/sentry/kernel/time"
|
|
"gvisor.dev/gvisor/pkg/sentry/mm"
|
|
"gvisor.dev/gvisor/pkg/sentry/usermem"
|
|
"gvisor.dev/gvisor/pkg/syserror"
|
|
)
|
|
|
|
// I/O commands.
|
|
const (
|
|
_IOCB_CMD_PREAD = 0
|
|
_IOCB_CMD_PWRITE = 1
|
|
_IOCB_CMD_FSYNC = 2
|
|
_IOCB_CMD_FDSYNC = 3
|
|
_IOCB_CMD_NOOP = 6
|
|
_IOCB_CMD_PREADV = 7
|
|
_IOCB_CMD_PWRITEV = 8
|
|
)
|
|
|
|
// I/O flags.
|
|
const (
|
|
_IOCB_FLAG_RESFD = 1
|
|
)
|
|
|
|
// ioCallback describes an I/O request.
|
|
//
|
|
// The priority field is currently ignored in the implementation below. Also
|
|
// note that the IOCB_FLAG_RESFD feature is not supported.
|
|
type ioCallback struct {
|
|
Data uint64
|
|
Key uint32
|
|
Reserved1 uint32
|
|
|
|
OpCode uint16
|
|
ReqPrio int16
|
|
FD int32
|
|
|
|
Buf uint64
|
|
Bytes uint64
|
|
Offset int64
|
|
|
|
Reserved2 uint64
|
|
Flags uint32
|
|
|
|
// eventfd to signal if IOCB_FLAG_RESFD is set in flags.
|
|
ResFD int32
|
|
}
|
|
|
|
// ioEvent describes an I/O result.
|
|
//
|
|
// +stateify savable
|
|
type ioEvent struct {
|
|
Data uint64
|
|
Obj uint64
|
|
Result int64
|
|
Result2 int64
|
|
}
|
|
|
|
// ioEventSize is the size of an ioEvent encoded.
|
|
var ioEventSize = binary.Size(ioEvent{})
|
|
|
|
// IoSetup implements linux syscall io_setup(2).
|
|
func IoSetup(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
|
|
nrEvents := args[0].Int()
|
|
idAddr := args[1].Pointer()
|
|
|
|
// Linux uses the native long as the aio ID.
|
|
//
|
|
// The context pointer _must_ be zero initially.
|
|
var idIn uint64
|
|
if _, err := t.CopyIn(idAddr, &idIn); err != nil {
|
|
return 0, nil, err
|
|
}
|
|
if idIn != 0 {
|
|
return 0, nil, syserror.EINVAL
|
|
}
|
|
|
|
id, err := t.MemoryManager().NewAIOContext(t, uint32(nrEvents))
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
|
|
// Copy out the new ID.
|
|
if _, err := t.CopyOut(idAddr, &id); err != nil {
|
|
t.MemoryManager().DestroyAIOContext(t, id)
|
|
return 0, nil, err
|
|
}
|
|
|
|
return 0, nil, nil
|
|
}
|
|
|
|
// IoDestroy implements linux syscall io_destroy(2).
|
|
func IoDestroy(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
|
|
id := args[0].Uint64()
|
|
|
|
// Destroy the given context.
|
|
if !t.MemoryManager().DestroyAIOContext(t, id) {
|
|
// Does not exist.
|
|
return 0, nil, syserror.EINVAL
|
|
}
|
|
// FIXME(fvoznika): Linux blocks until all AIO to the destroyed context is
|
|
// done.
|
|
return 0, nil, nil
|
|
}
|
|
|
|
// IoGetevents implements linux syscall io_getevents(2).
|
|
func IoGetevents(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
|
|
id := args[0].Uint64()
|
|
minEvents := args[1].Int()
|
|
events := args[2].Int()
|
|
eventsAddr := args[3].Pointer()
|
|
timespecAddr := args[4].Pointer()
|
|
|
|
// Sanity check arguments.
|
|
if minEvents < 0 || minEvents > events {
|
|
return 0, nil, syserror.EINVAL
|
|
}
|
|
|
|
ctx, ok := t.MemoryManager().LookupAIOContext(t, id)
|
|
if !ok {
|
|
return 0, nil, syserror.EINVAL
|
|
}
|
|
|
|
// Setup the timeout.
|
|
var haveDeadline bool
|
|
var deadline ktime.Time
|
|
if timespecAddr != 0 {
|
|
d, err := copyTimespecIn(t, timespecAddr)
|
|
if err != nil {
|
|
return 0, nil, err
|
|
}
|
|
if !d.Valid() {
|
|
return 0, nil, syserror.EINVAL
|
|
}
|
|
deadline = t.Kernel().MonotonicClock().Now().Add(d.ToDuration())
|
|
haveDeadline = true
|
|
}
|
|
|
|
// Loop over all requests.
|
|
for count := int32(0); count < events; count++ {
|
|
// Get a request, per semantics.
|
|
var v interface{}
|
|
if count >= minEvents {
|
|
var ok bool
|
|
v, ok = ctx.PopRequest()
|
|
if !ok {
|
|
return uintptr(count), nil, nil
|
|
}
|
|
} else {
|
|
var err error
|
|
v, err = waitForRequest(ctx, t, haveDeadline, deadline)
|
|
if err != nil {
|
|
if count > 0 || err == syserror.ETIMEDOUT {
|
|
return uintptr(count), nil, nil
|
|
}
|
|
return 0, nil, syserror.ConvertIntr(err, syserror.EINTR)
|
|
}
|
|
}
|
|
|
|
ev := v.(*ioEvent)
|
|
|
|
// Copy out the result.
|
|
if _, err := t.CopyOut(eventsAddr, ev); err != nil {
|
|
if count > 0 {
|
|
return uintptr(count), nil, nil
|
|
}
|
|
// Nothing done.
|
|
return 0, nil, err
|
|
}
|
|
|
|
// Keep rolling.
|
|
eventsAddr += usermem.Addr(ioEventSize)
|
|
}
|
|
|
|
// Everything finished.
|
|
return uintptr(events), nil, nil
|
|
}
|
|
|
|
func waitForRequest(ctx *mm.AIOContext, t *kernel.Task, haveDeadline bool, deadline ktime.Time) (interface{}, error) {
|
|
for {
|
|
if v, ok := ctx.PopRequest(); ok {
|
|
// Request was readly available. Just return it.
|
|
return v, nil
|
|
}
|
|
|
|
// Need to wait for request completion.
|
|
done, active := ctx.WaitChannel()
|
|
if !active {
|
|
// Context has been destroyed.
|
|
return nil, syserror.EINVAL
|
|
}
|
|
if err := t.BlockWithDeadline(done, haveDeadline, deadline); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
}
|
|
|
|
// memoryFor returns appropriate memory for the given callback.
|
|
func memoryFor(t *kernel.Task, cb *ioCallback) (usermem.IOSequence, error) {
|
|
bytes := int(cb.Bytes)
|
|
if bytes < 0 {
|
|
// Linux also requires that this field fit in ssize_t.
|
|
return usermem.IOSequence{}, syserror.EINVAL
|
|
}
|
|
|
|
// Since this I/O will be asynchronous with respect to t's task goroutine,
|
|
// we have no guarantee that t's AddressSpace will be active during the
|
|
// I/O.
|
|
switch cb.OpCode {
|
|
case _IOCB_CMD_PREAD, _IOCB_CMD_PWRITE:
|
|
return t.SingleIOSequence(usermem.Addr(cb.Buf), bytes, usermem.IOOpts{
|
|
AddressSpaceActive: false,
|
|
})
|
|
|
|
case _IOCB_CMD_PREADV, _IOCB_CMD_PWRITEV:
|
|
return t.IovecsIOSequence(usermem.Addr(cb.Buf), bytes, usermem.IOOpts{
|
|
AddressSpaceActive: false,
|
|
})
|
|
|
|
case _IOCB_CMD_FSYNC, _IOCB_CMD_FDSYNC, _IOCB_CMD_NOOP:
|
|
return usermem.IOSequence{}, nil
|
|
|
|
default:
|
|
// Not a supported command.
|
|
return usermem.IOSequence{}, syserror.EINVAL
|
|
}
|
|
}
|
|
|
|
func performCallback(t *kernel.Task, file *fs.File, cbAddr usermem.Addr, cb *ioCallback, ioseq usermem.IOSequence, ctx *mm.AIOContext, eventFile *fs.File) {
|
|
ev := &ioEvent{
|
|
Data: cb.Data,
|
|
Obj: uint64(cbAddr),
|
|
}
|
|
|
|
// Construct a context.Context that will not be interrupted if t is
|
|
// interrupted.
|
|
c := t.AsyncContext()
|
|
|
|
var err error
|
|
switch cb.OpCode {
|
|
case _IOCB_CMD_PREAD, _IOCB_CMD_PREADV:
|
|
ev.Result, err = file.Preadv(c, ioseq, cb.Offset)
|
|
case _IOCB_CMD_PWRITE, _IOCB_CMD_PWRITEV:
|
|
ev.Result, err = file.Pwritev(c, ioseq, cb.Offset)
|
|
case _IOCB_CMD_FSYNC:
|
|
err = file.Fsync(c, 0, fs.FileMaxOffset, fs.SyncAll)
|
|
case _IOCB_CMD_FDSYNC:
|
|
err = file.Fsync(c, 0, fs.FileMaxOffset, fs.SyncData)
|
|
}
|
|
|
|
// Update the result.
|
|
if err != nil {
|
|
err = handleIOError(t, ev.Result != 0 /* partial */, err, nil /* never interrupted */, "aio", file)
|
|
ev.Result = -int64(t.ExtractErrno(err, 0))
|
|
}
|
|
|
|
file.DecRef()
|
|
|
|
// Queue the result for delivery.
|
|
ctx.FinishRequest(ev)
|
|
|
|
// Notify the event file if one was specified. This needs to happen
|
|
// *after* queueing the result to avoid racing with the thread we may
|
|
// wake up.
|
|
if eventFile != nil {
|
|
eventFile.FileOperations.(*eventfd.EventOperations).Signal(1)
|
|
eventFile.DecRef()
|
|
}
|
|
}
|
|
|
|
// submitCallback processes a single callback.
|
|
func submitCallback(t *kernel.Task, id uint64, cb *ioCallback, cbAddr usermem.Addr) error {
|
|
file := t.GetFile(cb.FD)
|
|
if file == nil {
|
|
// File not found.
|
|
return syserror.EBADF
|
|
}
|
|
defer file.DecRef()
|
|
|
|
// Was there an eventFD? Extract it.
|
|
var eventFile *fs.File
|
|
if cb.Flags&_IOCB_FLAG_RESFD != 0 {
|
|
eventFile = t.GetFile(cb.ResFD)
|
|
if eventFile == nil {
|
|
// Bad FD.
|
|
return syserror.EBADF
|
|
}
|
|
defer eventFile.DecRef()
|
|
|
|
// Check that it is an eventfd.
|
|
if _, ok := eventFile.FileOperations.(*eventfd.EventOperations); !ok {
|
|
// Not an event FD.
|
|
return syserror.EINVAL
|
|
}
|
|
}
|
|
|
|
ioseq, err := memoryFor(t, cb)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Check offset for reads/writes.
|
|
switch cb.OpCode {
|
|
case _IOCB_CMD_PREAD, _IOCB_CMD_PREADV, _IOCB_CMD_PWRITE, _IOCB_CMD_PWRITEV:
|
|
if cb.Offset < 0 {
|
|
return syserror.EINVAL
|
|
}
|
|
}
|
|
|
|
// Prepare the request.
|
|
ctx, ok := t.MemoryManager().LookupAIOContext(t, id)
|
|
if !ok {
|
|
return syserror.EINVAL
|
|
}
|
|
if ready := ctx.Prepare(); !ready {
|
|
// Context is busy.
|
|
return syserror.EAGAIN
|
|
}
|
|
|
|
if eventFile != nil {
|
|
// The request is set. Make sure there's a ref on the file.
|
|
//
|
|
// This is necessary when the callback executes on completion,
|
|
// which is also what will release this reference.
|
|
eventFile.IncRef()
|
|
}
|
|
|
|
// Perform the request asynchronously.
|
|
file.IncRef()
|
|
fs.Async(func() { performCallback(t, file, cbAddr, cb, ioseq, ctx, eventFile) })
|
|
|
|
// All set.
|
|
return nil
|
|
}
|
|
|
|
// IoSubmit implements linux syscall io_submit(2).
|
|
func IoSubmit(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
|
|
id := args[0].Uint64()
|
|
nrEvents := args[1].Int()
|
|
addr := args[2].Pointer()
|
|
|
|
if nrEvents < 0 {
|
|
return 0, nil, syserror.EINVAL
|
|
}
|
|
|
|
for i := int32(0); i < nrEvents; i++ {
|
|
// Copy in the address.
|
|
cbAddrNative := t.Arch().Native(0)
|
|
if _, err := t.CopyIn(addr, cbAddrNative); err != nil {
|
|
if i > 0 {
|
|
// Some successful.
|
|
return uintptr(i), nil, nil
|
|
}
|
|
// Nothing done.
|
|
return 0, nil, err
|
|
}
|
|
|
|
// Copy in this callback.
|
|
var cb ioCallback
|
|
cbAddr := usermem.Addr(t.Arch().Value(cbAddrNative))
|
|
if _, err := t.CopyIn(cbAddr, &cb); err != nil {
|
|
|
|
if i > 0 {
|
|
// Some have been successful.
|
|
return uintptr(i), nil, nil
|
|
}
|
|
// Nothing done.
|
|
return 0, nil, err
|
|
}
|
|
|
|
// Process this callback.
|
|
if err := submitCallback(t, id, &cb, cbAddr); err != nil {
|
|
if i > 0 {
|
|
// Partial success.
|
|
return uintptr(i), nil, nil
|
|
}
|
|
// Nothing done.
|
|
return 0, nil, err
|
|
}
|
|
|
|
// Advance to the next one.
|
|
addr += usermem.Addr(t.Arch().Width())
|
|
}
|
|
|
|
return uintptr(nrEvents), nil, nil
|
|
}
|
|
|
|
// IoCancel implements linux syscall io_cancel(2).
|
|
//
|
|
// It is not presently supported (ENOSYS indicates no support on this
|
|
// architecture).
|
|
func IoCancel(t *kernel.Task, args arch.SyscallArguments) (uintptr, *kernel.SyscallControl, error) {
|
|
return 0, nil, syserror.ENOSYS
|
|
}
|