Files
gvisor/pkg/sentry/socket/plugin/stack/notifier.go
T
2025-01-29 21:16:51 -08:00

166 lines
4.2 KiB
Go

// Copyright 2023 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 stack
import (
"fmt"
"runtime"
"sync"
"syscall"
"golang.org/x/sys/unix"
"gvisor.dev/gvisor/pkg/sentry/socket/plugin"
"gvisor.dev/gvisor/pkg/sentry/socket/plugin/cgo"
"gvisor.dev/gvisor/pkg/waiter"
)
// Notifier holds all the state necessary to issue notifications when
// IO events occur on the observed FDs in plugin stack.
type Notifier struct {
// the epoll FD used to register for io notifications.
epFD int32
// mu protects eventMap.
mu sync.Mutex
// eventMap maps file descriptors to their notification queues
// and waiting status.
eventMap map[uint32]*plugin.EventInfo
}
const (
MaxEpollEvents = 128
SleepInMsecond = 100
)
// NewNotifier initialize the event notifier for plugin stack.
// It will allocate a eventMap with fd as key and corresponding eventInfo
// as value and start a goroutine waiting the arrival of events.
func NewNotifier() *Notifier {
ioInit := make(chan int32)
n := &Notifier{
eventMap: make(map[uint32]*plugin.EventInfo),
}
go n.waitAndNotify(ioInit)
epFD := <-ioInit
if epFD < 0 {
return nil
}
return n
}
// AddFD implements plugin.PluginNotifier.AddFD.
func (n *Notifier) AddFD(fd uint32, eventInfo *plugin.EventInfo) {
n.mu.Lock()
defer n.mu.Unlock()
// Panic if we're already notifying on this FD.
if _, ok := n.eventMap[fd]; ok {
panic(fmt.Sprintf("File descriptor %d added twice", fd))
}
// We have nothing to wait for at the moment. Just add it to the map.
n.eventMap[fd] = eventInfo
}
// RemoveFD implements plugin.PluginNotifier.RemoveFD.
func (n *Notifier) RemoveFD(fd uint32) {
n.mu.Lock()
defer n.mu.Unlock()
delete(n.eventMap, fd)
}
// UpdateFD implements plugin.PluginNotifier.UpdateFD.
func (n *Notifier) UpdateFD(fd uint32) {
n.mu.Lock()
defer n.mu.Unlock()
if eventInfo, ok := n.eventMap[fd]; ok {
n.waitFD(fd, eventInfo)
}
}
// waitAndNotify loops waiting for io event notifications from the epoll
// object. Once notifications arrive, they are dispatched to the
// registered queue.
func (n *Notifier) waitAndNotify(ioInit chan int32) error {
// plugin stack leverages TLS variables, so bind this goroutine with
// one specific OS thread
runtime.LockOSThread()
// If current thread is not the main thread, change the thread name.
if syscall.Getpid() != syscall.Gettid() {
threadName := []byte("io-thread\x00")
if err := unix.Prctl(unix.PR_SET_NAME, uintptr(cgo.GetPtr(threadName)), 0, 0, 0); err != nil {
return err
}
}
n.epFD = int32(cgo.EpollCreate())
ioInit <- n.epFD
var events [MaxEpollEvents]syscall.EpollEvent
for {
num := cgo.EpollWait(n.epFD, events[:], MaxEpollEvents, SleepInMsecond)
if num <= 0 {
continue
}
n.mu.Lock()
for i := 0; i < num; i++ {
h := uint32(events[i].Fd)
eventInfo, ok := n.eventMap[h]
if !ok {
continue
}
ev := waiter.EventMask(events[i].Events)
eventInfo.Ready |= ev & (eventInfo.Mask | waiter.EventErr | waiter.EventHUp)
// When an error occurred, invoke all events
if ev&(waiter.EventErr|waiter.EventHUp) != 0 {
ev |= waiter.EventIn | waiter.EventOut
}
eventInfo.Wq.Notify(ev)
}
n.mu.Unlock()
}
}
func (n *Notifier) waitFD(fd uint32, eventInfo *plugin.EventInfo) {
mask := eventInfo.Wq.Events()
eventInfo.Mask = mask
if !eventInfo.Waiting && mask == 0 {
return
}
switch {
case !eventInfo.Waiting && mask != 0:
cgo.EpollCtl(n.epFD, syscall.EPOLL_CTL_ADD, fd, uint32(mask))
eventInfo.Waiting = true
case eventInfo.Waiting && mask == 0:
cgo.EpollCtl(n.epFD, syscall.EPOLL_CTL_DEL, fd, uint32(mask))
eventInfo.Ready = 0
eventInfo.Waiting = false
case eventInfo.Waiting && mask != 0:
cgo.EpollCtl(n.epFD, syscall.EPOLL_CTL_MOD, fd, uint32(mask))
eventInfo.Ready &= mask
}
}