mirror of
https://github.com/netbirdio/gvisor.git
synced 2026-05-22 17:12:49 -07:00
Fix epoll_ctl(2) regular files and dirs
Linux behaves differently for regular files and dirs for poll(2)/select(2) compared to epoll_ctl(2). The latter returns EPERM for file and dirs. I've also changed host FDs to behave like the underlying FD in regards to epoll to keep it compatible with docker. Fixes #7134 PiperOrigin-RevId: 429412692
This commit is contained in:
committed by
gVisor bot
parent
a9b5dcd31b
commit
dfcf798425
@@ -14,8 +14,10 @@
|
||||
|
||||
#include <err.h>
|
||||
#include <errno.h>
|
||||
#include <stddef.h>
|
||||
#include <sys/epoll.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/select.h>
|
||||
#include <unistd.h>
|
||||
|
||||
// Tests that FIONREAD is supported with host FD.
|
||||
@@ -49,8 +51,24 @@ void testEpoll() {
|
||||
}
|
||||
}
|
||||
|
||||
// Docker maps stdin to /dev/null. Check that select(2) works with stdin.
|
||||
void testSelect() {
|
||||
fd_set rfds;
|
||||
struct timeval tv;
|
||||
int res;
|
||||
FD_ZERO(&rfds);
|
||||
FD_SET(0, &rfds);
|
||||
tv.tv_sec = 0;
|
||||
tv.tv_usec = 1;
|
||||
res = select(1, &rfds, NULL, NULL, &tv);
|
||||
if (res == -1) {
|
||||
err(1, "select(1, [STDIN], NULL, NULL) returned error");
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
testFionread();
|
||||
testEpoll();
|
||||
testSelect();
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -166,6 +166,11 @@ func (fd *tunFD) EventUnregister(e *waiter.Entry) {
|
||||
fd.device.EventUnregister(e)
|
||||
}
|
||||
|
||||
// Epollable implements FileDescriptionImpl.Epollable.
|
||||
func (fd *tunFD) Epollable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// IsNetTunSupported returns whether /dev/net/tun device is supported for s.
|
||||
func IsNetTunSupported(s inet.Stack) bool {
|
||||
_, ok := s.(*netstack.Stack)
|
||||
|
||||
@@ -118,6 +118,11 @@ func (mfd *masterFileDescription) Readiness(mask waiter.EventMask) waiter.EventM
|
||||
return mfd.t.ld.masterReadiness()
|
||||
}
|
||||
|
||||
// Epollable implements FileDescriptionImpl.Epollable.
|
||||
func (mfd *masterFileDescription) Epollable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Read implements vfs.FileDescriptionImpl.Read.
|
||||
func (mfd *masterFileDescription) Read(ctx context.Context, dst usermem.IOSequence, _ vfs.ReadOptions) (int64, error) {
|
||||
return mfd.t.ld.outputQueueRead(ctx, dst)
|
||||
|
||||
@@ -127,6 +127,11 @@ func (rfd *replicaFileDescription) Readiness(mask waiter.EventMask) waiter.Event
|
||||
return rfd.inode.t.ld.replicaReadiness()
|
||||
}
|
||||
|
||||
// Epollable implements FileDescriptionImpl.Epollable.
|
||||
func (rfd *replicaFileDescription) Epollable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Read implements vfs.FileDescriptionImpl.Read.
|
||||
func (rfd *replicaFileDescription) Read(ctx context.Context, dst usermem.IOSequence, _ vfs.ReadOptions) (int64, error) {
|
||||
return rfd.inode.t.ld.inputQueueRead(ctx, dst)
|
||||
|
||||
@@ -294,3 +294,8 @@ func (efd *EventFileDescription) EventUnregister(entry *waiter.Entry) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Epollable implements FileDescriptionImpl.Epollable.
|
||||
func (efd *EventFileDescription) Epollable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -389,6 +389,11 @@ func (fd *DeviceFD) EventUnregister(e *waiter.Entry) {
|
||||
fd.waitQueue.EventUnregister(e)
|
||||
}
|
||||
|
||||
// Epollable implements FileDescriptionImpl.Epollable.
|
||||
func (fd *DeviceFD) Epollable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Seek implements vfs.FileDescriptionImpl.Seek.
|
||||
func (fd *DeviceFD) Seek(ctx context.Context, offset int64, whence int32) (int64, error) {
|
||||
// Operations on /dev/fuse don't make sense until a FUSE filesystem is mounted.
|
||||
|
||||
@@ -190,6 +190,14 @@ func (fd *specialFileFD) EventUnregister(e *waiter.Entry) {
|
||||
fd.fileDescription.EventUnregister(e)
|
||||
}
|
||||
|
||||
// Epollable implements FileDescriptionImpl.Epollable.
|
||||
func (fd *specialFileFD) Epollable() bool {
|
||||
if fd.haveQueue {
|
||||
return true
|
||||
}
|
||||
return fd.fileDescription.Epollable()
|
||||
}
|
||||
|
||||
func (fd *specialFileFD) Allocate(ctx context.Context, mode, offset, length uint64) error {
|
||||
if fd.isRegularFile {
|
||||
d := fd.dentry()
|
||||
|
||||
@@ -74,6 +74,22 @@ func (v *virtualOwner) atomicMode() uint32 {
|
||||
return atomic.LoadUint32(&v.mode)
|
||||
}
|
||||
|
||||
func isEpollable(fd int) bool {
|
||||
epollfd, err := unix.EpollCreate1(0)
|
||||
if err != nil {
|
||||
// This shouldn't happen. If it does, just say file doesn't support epoll.
|
||||
return false
|
||||
}
|
||||
defer unix.Close(epollfd)
|
||||
|
||||
event := unix.EpollEvent{
|
||||
Fd: int32(fd),
|
||||
Events: unix.EPOLLIN,
|
||||
}
|
||||
err = unix.EpollCtl(epollfd, unix.EPOLL_CTL_ADD, fd, &event)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// inode implements kernfs.Inode.
|
||||
//
|
||||
// +stateify savable
|
||||
@@ -105,11 +121,11 @@ type inode struct {
|
||||
// This field is initialized at creation time and is immutable.
|
||||
ftype uint16
|
||||
|
||||
// mayBlock is true if hostFD is non-blocking, and operations on it may
|
||||
// return EAGAIN or EWOULDBLOCK instead of blocking.
|
||||
// epollable indicates whether the hostFD can be used with epoll_ctl(2). This
|
||||
// also indicates that hostFD has been set to non-blocking.
|
||||
//
|
||||
// This field is initialized at creation time and is immutable.
|
||||
mayBlock bool
|
||||
epollable bool
|
||||
|
||||
// seekable is false if lseek(hostFD) returns ESPIPE. We assume that file
|
||||
// offsets are meaningful iff seekable is true.
|
||||
@@ -156,20 +172,20 @@ func newInode(ctx context.Context, fs *filesystem, hostFD int, savable bool, fil
|
||||
}
|
||||
|
||||
i := &inode{
|
||||
hostFD: hostFD,
|
||||
ino: fs.NextIno(),
|
||||
ftype: uint16(fileType),
|
||||
mayBlock: fileType != unix.S_IFREG && fileType != unix.S_IFDIR,
|
||||
seekable: seekable,
|
||||
isTTY: isTTY,
|
||||
savable: savable,
|
||||
hostFD: hostFD,
|
||||
ino: fs.NextIno(),
|
||||
ftype: uint16(fileType),
|
||||
epollable: isEpollable(hostFD),
|
||||
seekable: seekable,
|
||||
isTTY: isTTY,
|
||||
savable: savable,
|
||||
}
|
||||
i.InitRefs()
|
||||
i.CachedMappable.Init(hostFD)
|
||||
|
||||
// If the hostFD can return EWOULDBLOCK when set to non-blocking, do so and
|
||||
// handle blocking behavior in the sentry.
|
||||
if i.mayBlock {
|
||||
if i.epollable {
|
||||
if err := unix.SetNonblock(i.hostFD, true); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -551,7 +567,7 @@ func (i *inode) SetStat(ctx context.Context, fs *vfs.Filesystem, creds *auth.Cre
|
||||
// DecRef implements kernfs.Inode.DecRef.
|
||||
func (i *inode) DecRef(ctx context.Context) {
|
||||
i.inodeRefs.DecRef(func() {
|
||||
if i.mayBlock {
|
||||
if i.epollable {
|
||||
fdnotifier.RemoveFD(int32(i.hostFD))
|
||||
}
|
||||
if err := unix.Close(i.hostFD); err != nil {
|
||||
@@ -909,7 +925,7 @@ func (f *fileDescription) ConfigureMMap(_ context.Context, opts *memmap.MMapOpts
|
||||
// EventRegister implements waiter.Waitable.EventRegister.
|
||||
func (f *fileDescription) EventRegister(e *waiter.Entry) error {
|
||||
f.inode.queue.EventRegister(e)
|
||||
if f.inode.mayBlock {
|
||||
if f.inode.epollable {
|
||||
if err := fdnotifier.UpdateFD(int32(f.inode.hostFD)); err != nil {
|
||||
f.inode.queue.EventUnregister(e)
|
||||
return err
|
||||
@@ -921,7 +937,7 @@ func (f *fileDescription) EventRegister(e *waiter.Entry) error {
|
||||
// EventUnregister implements waiter.Waitable.EventUnregister.
|
||||
func (f *fileDescription) EventUnregister(e *waiter.Entry) {
|
||||
f.inode.queue.EventUnregister(e)
|
||||
if f.inode.mayBlock {
|
||||
if f.inode.epollable {
|
||||
if err := fdnotifier.UpdateFD(int32(f.inode.hostFD)); err != nil {
|
||||
panic(fmt.Sprint("UpdateFD:", err))
|
||||
}
|
||||
@@ -933,6 +949,11 @@ func (f *fileDescription) Readiness(mask waiter.EventMask) waiter.EventMask {
|
||||
return fdnotifier.NonBlockingPoll(int32(f.inode.hostFD), mask)
|
||||
}
|
||||
|
||||
// Epollable implements FileDescriptionImpl.Epollable.
|
||||
func (f *fileDescription) Epollable() bool {
|
||||
return f.inode.epollable
|
||||
}
|
||||
|
||||
// Ioctl queries the underlying FD for allowed ioctl commands.
|
||||
func (f *fileDescription) Ioctl(ctx context.Context, uio usermem.IO, args arch.SyscallArguments) (uintptr, error) {
|
||||
switch cmd := args[1].Int(); cmd {
|
||||
|
||||
@@ -59,7 +59,7 @@ func (i *inode) beforeSave() {
|
||||
|
||||
// afterLoad is invoked by stateify.
|
||||
func (i *inode) afterLoad() {
|
||||
if i.mayBlock {
|
||||
if i.epollable {
|
||||
if err := unix.SetNonblock(i.hostFD, true); err != nil {
|
||||
panic(fmt.Sprintf("host.inode.afterLoad: failed to set host FD %d non-blocking: %v", i.hostFD, err))
|
||||
}
|
||||
|
||||
@@ -143,3 +143,8 @@ func (fd *queueFD) EventRegister(e *waiter.Entry) error {
|
||||
func (fd *queueFD) EventUnregister(e *waiter.Entry) {
|
||||
fd.queue.EventUnregister(e)
|
||||
}
|
||||
|
||||
// Epollable implements FileDescriptionImpl.Epollable.
|
||||
func (fd *queueFD) Epollable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -261,6 +261,21 @@ func (fd *regularFileFD) EventUnregister(e *waiter.Entry) {
|
||||
fd.cachedFD.EventUnregister(e)
|
||||
}
|
||||
|
||||
// Epollable implements FileDescriptionImpl.Epollable.
|
||||
func (fd *regularFileFD) Epollable() bool {
|
||||
fd.mu.Lock()
|
||||
defer fd.mu.Unlock()
|
||||
wrappedFD, err := fd.currentFDLocked(context.Background())
|
||||
if err != nil {
|
||||
// TODO(b/171089913): Just use fd.cachedFD since EventRegister can't
|
||||
// return an error. This is obviously wrong, but at least consistent
|
||||
// with VFS1.
|
||||
log.Warningf("overlay.regularFileFD.Epollable: currentFDLocked failed: %v", err)
|
||||
wrappedFD = fd.cachedFD
|
||||
}
|
||||
return wrappedFD.Epollable()
|
||||
}
|
||||
|
||||
// PRead implements vfs.FileDescriptionImpl.PRead.
|
||||
func (fd *regularFileFD) PRead(ctx context.Context, dst usermem.IOSequence, offset int64, opts vfs.ReadOptions) (int64, error) {
|
||||
wrappedFD, err := fd.getCurrentFD(ctx)
|
||||
|
||||
@@ -146,6 +146,11 @@ func (sfd *SignalFileDescription) NotifyEvent(mask waiter.EventMask) {
|
||||
sfd.queue.Notify(waiter.EventIn) // Always notify data available.
|
||||
}
|
||||
|
||||
// Epollable implements FileDescriptionImpl.Epollable.
|
||||
func (sfd *SignalFileDescription) Epollable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Release implements vfs.FileDescriptionImpl.Release.
|
||||
func (sfd *SignalFileDescription) Release(context.Context) {
|
||||
sfd.target.SignalUnregister(&sfd.entry)
|
||||
|
||||
@@ -122,6 +122,11 @@ func (tfd *TimerFileDescription) EventUnregister(e *waiter.Entry) {
|
||||
tfd.events.EventUnregister(e)
|
||||
}
|
||||
|
||||
// Epollable implements FileDescriptionImpl.Epollable.
|
||||
func (tfd *TimerFileDescription) Epollable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// PauseTimer pauses the associated Timer.
|
||||
func (tfd *TimerFileDescription) PauseTimer() {
|
||||
tfd.timer.Pause()
|
||||
|
||||
@@ -220,6 +220,11 @@ func (fd *VFSPipeFD) EventUnregister(e *waiter.Entry) {
|
||||
fd.pipe.EventUnregister(e)
|
||||
}
|
||||
|
||||
// Epollable implements FileDescriptionImpl.Epollable.
|
||||
func (fd *VFSPipeFD) Epollable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Read implements vfs.FileDescriptionImpl.Read.
|
||||
func (fd *VFSPipeFD) Read(ctx context.Context, dst usermem.IOSequence, _ vfs.ReadOptions) (int64, error) {
|
||||
return fd.pipe.Read(ctx, dst)
|
||||
|
||||
@@ -98,6 +98,11 @@ func (s *socketVFS2) EventUnregister(e *waiter.Entry) {
|
||||
s.socketOpsCommon.EventUnregister(e)
|
||||
}
|
||||
|
||||
// Epollable implements FileDescriptionImpl.Epollable.
|
||||
func (s *socketVFS2) Epollable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Ioctl implements vfs.FileDescriptionImpl.
|
||||
func (s *socketVFS2) Ioctl(ctx context.Context, uio usermem.IO, args arch.SyscallArguments) (uintptr, error) {
|
||||
return ioctl(ctx, s.fd, uio, args)
|
||||
|
||||
@@ -105,6 +105,11 @@ func (s *SocketVFS2) EventUnregister(e *waiter.Entry) {
|
||||
s.socketOpsCommon.EventUnregister(e)
|
||||
}
|
||||
|
||||
// Epollable implements FileDescriptionImpl.Epollable.
|
||||
func (s *SocketVFS2) Epollable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Ioctl implements vfs.FileDescriptionImpl.
|
||||
func (*SocketVFS2) Ioctl(context.Context, usermem.IO, arch.SyscallArguments) (uintptr, error) {
|
||||
// TODO(b/68878065): no ioctls supported.
|
||||
|
||||
@@ -100,6 +100,11 @@ func (s *SocketVFS2) EventUnregister(e *waiter.Entry) {
|
||||
s.socketOpsCommon.EventUnregister(e)
|
||||
}
|
||||
|
||||
// Epollable implements FileDescriptionImpl.Epollable.
|
||||
func (s *SocketVFS2) Epollable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Read implements vfs.FileDescriptionImpl.
|
||||
func (s *SocketVFS2) Read(ctx context.Context, dst usermem.IOSequence, opts vfs.ReadOptions) (int64, error) {
|
||||
// All flags other than RWF_NOWAIT should be ignored.
|
||||
|
||||
@@ -324,6 +324,11 @@ func (s *SocketVFS2) EventUnregister(e *waiter.Entry) {
|
||||
s.socketOpsCommon.EventUnregister(e)
|
||||
}
|
||||
|
||||
// Epollable implements FileDescriptionImpl.Epollable.
|
||||
func (s *SocketVFS2) Epollable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// SetSockOpt implements the linux syscall setsockopt(2) for sockets backed by
|
||||
// a transport.Endpoint.
|
||||
func (s *SocketVFS2) SetSockOpt(t *kernel.Task, level int, name int, optVal []byte) *syserr.Error {
|
||||
|
||||
@@ -161,6 +161,11 @@ func (ep *EpollInstance) EventUnregister(e *waiter.Entry) {
|
||||
ep.q.EventUnregister(e)
|
||||
}
|
||||
|
||||
// Epollable implements FileDescriptionImpl.Epollable.
|
||||
func (ep *EpollInstance) Epollable() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Seek implements FileDescriptionImpl.Seek.
|
||||
func (ep *EpollInstance) Seek(ctx context.Context, offset int64, whence int32) (int64, error) {
|
||||
// Linux: fs/eventpoll.c:eventpoll_fops.llseek == noop_llseek
|
||||
@@ -171,6 +176,10 @@ func (ep *EpollInstance) Seek(ctx context.Context, offset int64, whence int32) (
|
||||
//
|
||||
// Preconditions: A reference must be held on file.
|
||||
func (ep *EpollInstance) AddInterest(file *FileDescription, num int32, event linux.EpollEvent) error {
|
||||
if !file.Epollable() {
|
||||
return linuxerr.EPERM
|
||||
}
|
||||
|
||||
// Check for cyclic polling if necessary.
|
||||
subep, _ := file.impl.(*EpollInstance)
|
||||
if subep != nil {
|
||||
|
||||
@@ -352,6 +352,9 @@ type FileDescriptionImpl interface {
|
||||
// waiter.Waitable methods may be used to poll for I/O events.
|
||||
waiter.Waitable
|
||||
|
||||
// Epollable indicates whether this file can be used with epoll_ctl(2).
|
||||
Epollable() bool
|
||||
|
||||
// PRead reads from the file into dst, starting at the given offset, and
|
||||
// returns the number of bytes read. PRead is permitted to return partial
|
||||
// reads with a nil error.
|
||||
@@ -597,6 +600,11 @@ func (fd *FileDescription) EventUnregister(e *waiter.Entry) {
|
||||
fd.impl.EventUnregister(e)
|
||||
}
|
||||
|
||||
// Epollable returns whether this file can be used with epoll_ctl(2).
|
||||
func (fd *FileDescription) Epollable() bool {
|
||||
return fd.impl.Epollable()
|
||||
}
|
||||
|
||||
// PRead reads from the file represented by fd into dst, starting at the given
|
||||
// offset, and returns the number of bytes read. PRead is permitted to return
|
||||
// partial reads with a nil error.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user