Files
visor/pkg/sentry/syscalls/linux/sys_aio.go
T
Adin ScannellandgVisor bot 753da9604e Remove map from fd_map, change to fd_table.
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
2019-07-02 19:28:59 -07:00

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
}