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:
Fabricio Voznika
2022-02-17 15:12:36 -08:00
committed by gVisor bot
parent a9b5dcd31b
commit dfcf798425
24 changed files with 184 additions and 15 deletions
+18
View File
@@ -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;
}
+5
View File
@@ -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)
+5
View File
@@ -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)
+5
View File
@@ -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)
+5
View File
@@ -294,3 +294,8 @@ func (efd *EventFileDescription) EventUnregister(entry *waiter.Entry) {
}
}
}
// Epollable implements FileDescriptionImpl.Epollable.
func (efd *EventFileDescription) Epollable() bool {
return true
}
+5
View File
@@ -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.
+8
View File
@@ -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()
+35 -14
View File
@@ -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 {
+1 -1
View File
@@ -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))
}
+5
View File
@@ -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
}
+15
View File
@@ -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)
+5
View File
@@ -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)
+5
View File
@@ -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()
+5
View File
@@ -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)
+5
View File
@@ -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.
+5
View File
@@ -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 {
+9
View File
@@ -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 {
+8
View File
@@ -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