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