gvisor/pkg/sentry/syscalls/polling.go

138 lines
3.5 KiB
Go

// Copyright 2018 Google Inc.
//
// 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 syscalls
import (
"syscall"
"time"
"gvisor.googlesource.com/gvisor/pkg/sentry/fs"
"gvisor.googlesource.com/gvisor/pkg/sentry/kernel"
"gvisor.googlesource.com/gvisor/pkg/sentry/kernel/kdefs"
"gvisor.googlesource.com/gvisor/pkg/waiter"
)
// PollFD describes a pollable FD.
type PollFD struct {
FD kdefs.FD
Events waiter.EventMask
REvents waiter.EventMask
}
// pollState tracks the associated file descriptor and waiter of a PollFD.
type pollState struct {
file *fs.File
waiter waiter.Entry
}
// initReadiness gets the current ready mask for the file represented by the FD
// stored in pfd.FD. If a channel is passed in, the waiter entry in "state" is
// used to register with the file for event notifications, and a reference to
// the file is stored in "state".
func (pfd *PollFD) initReadiness(t *kernel.Task, state *pollState, ch chan struct{}) {
if pfd.FD < 0 {
pfd.REvents = 0
return
}
file := t.FDMap().GetFile(pfd.FD)
if file == nil {
pfd.REvents = waiter.EventNVal
return
}
if ch == nil {
defer file.DecRef()
} else {
state.file = file
state.waiter, _ = waiter.NewChannelEntry(ch)
file.EventRegister(&state.waiter, pfd.Events)
}
pfd.REvents = file.Readiness(pfd.Events) & pfd.Events
}
// releaseState releases all the pollState in "state".
func releaseState(state []pollState) {
for i := range state {
if state[i].file != nil {
state[i].file.EventUnregister(&state[i].waiter)
state[i].file.DecRef()
}
}
}
// Poll polls the PollFDs in "pfd" with a bounded time specified in "timeout"
// when "timeout" is greater than zero.
//
// Poll returns the remaining timeout, which is always 0 on a timeout; and 0 or
// positive if interrupted by a signal.
func Poll(t *kernel.Task, pfd []PollFD, timeout time.Duration) (time.Duration, uintptr, error) {
var ch chan struct{}
if timeout != 0 {
ch = make(chan struct{}, 1)
}
// Register for event notification in the files involved if we may
// block (timeout not zero). Once we find a file that has a non-zero
// result, we stop registering for events but still go through all files
// to get their ready masks.
state := make([]pollState, len(pfd))
defer releaseState(state)
n := uintptr(0)
for i := range pfd {
pfd[i].initReadiness(t, &state[i], ch)
if pfd[i].REvents != 0 {
n++
ch = nil
}
}
if timeout == 0 {
return timeout, n, nil
}
forever := timeout < 0
for n == 0 {
var err error
// Wait for a notification.
timeout, err = t.BlockWithTimeout(ch, !forever, timeout)
if err != nil {
if err == syscall.ETIMEDOUT {
err = nil
}
return timeout, 0, err
}
// We got notified, count how many files are ready. If none,
// then this was a spurious notification, and we just go back
// to sleep with the remaining timeout.
for i := range state {
if state[i].file == nil {
continue
}
ready := state[i].file.Readiness(pfd[i].Events) & pfd[i].Events
if ready != 0 {
pfd[i].REvents = ready
n++
}
}
}
return timeout, n, nil
}