// 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 }