/*
* Copyright (c) 2008-2024 Jonathan Schleifer <js@nil.im>
*
* All rights reserved.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License version 3.0 only,
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* version 3.0 for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* version 3.0 along with this program. If not, see
* <https://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "platform.h"
#ifdef OF_WINDOWS
/* Win32 has a ridiculous default of 64, even though it supports much more. */
# define FD_SETSIZE 1024
#endif
#include <errno.h>
#include <string.h>
#include <sys/time.h>
#import "OFSelectKernelEventObserver.h"
#import "OFArray.h"
#import "OFSocket+Private.h"
#import "OFInitializationFailedException.h"
#import "OFObserveKernelEventsFailedException.h"
#import "OFOutOfRangeException.h"
#ifdef OF_AMIGAOS
# define Class IntuitionClass
# include <proto/exec.h>
# undef Class
#endif
#ifdef OF_HPUX
/* FD_SET causes warnings on HP-UX/IA64. */
# pragma GCC diagnostic ignored "-Wstrict-aliasing"
#endif
@implementation OFSelectKernelEventObserver
- (instancetype)init
{
self = [super init];
@try {
FD_ZERO(&_readFDs);
FD_ZERO(&_writeFDs);
#ifdef OF_AMIGAOS
_maxFD = -1;
#else
# ifndef OF_WINDOWS
if (_cancelFD[0] >= (int)FD_SETSIZE)
@throw [OFInitializationFailedException
exceptionWithClass: self.class];
# endif
FD_SET(_cancelFD[0], &_readFDs);
if (_cancelFD[0] > INT_MAX)
@throw [OFOutOfRangeException exception];
_maxFD = (int)_cancelFD[0];
#endif
} @catch (id e) {
[self release];
@throw e;
}
return self;
}
- (void)addObjectForReading: (id <OFReadyForReadingObserving>)object
{
int fd = object.fileDescriptorForReading;
if (fd < 0)
@throw [OFObserveKernelEventsFailedException
exceptionWithObserver: self
errNo: EBADF];
if (fd > INT_MAX - 1)
@throw [OFOutOfRangeException exception];
#ifndef OF_WINDOWS
if (fd >= (int)FD_SETSIZE)
@throw [OFOutOfRangeException exception];
#endif
if (fd > _maxFD)
_maxFD = fd;
FD_SET((OFSocketHandle)fd, &_readFDs);
[super addObjectForReading: object];
}
- (void)addObjectForWriting: (id <OFReadyForWritingObserving>)object
{
int fd = object.fileDescriptorForWriting;
if (fd < 0)
@throw [OFObserveKernelEventsFailedException
exceptionWithObserver: self
errNo: EBADF];
if (fd > INT_MAX - 1)
@throw [OFOutOfRangeException exception];
#ifndef OF_WINDOWS
if (fd >= (int)FD_SETSIZE)
@throw [OFOutOfRangeException exception];
#endif
if (fd > _maxFD)
_maxFD = fd;
FD_SET((OFSocketHandle)fd, &_writeFDs);
[super addObjectForWriting: object];
}
- (void)removeObjectForReading: (id <OFReadyForReadingObserving>)object
{
/* TODO: Adjust _maxFD */
int fd = object.fileDescriptorForReading;
if (fd < 0)
@throw [OFObserveKernelEventsFailedException
exceptionWithObserver: self
errNo: EBADF];
#ifndef OF_WINDOWS
if (fd >= (int)FD_SETSIZE)
@throw [OFOutOfRangeException exception];
#endif
FD_CLR((OFSocketHandle)fd, &_readFDs);
[super removeObjectForReading: object];
}
- (void)removeObjectForWriting: (id <OFReadyForWritingObserving>)object
{
/* TODO: Adjust _maxFD */
int fd = object.fileDescriptorForWriting;
if (fd < 0)
@throw [OFObserveKernelEventsFailedException
exceptionWithObserver: self
errNo: EBADF];
#ifndef OF_WINDOWS
if (fd >= (int)FD_SETSIZE)
@throw [OFOutOfRangeException exception];
#endif
FD_CLR((OFSocketHandle)fd, &_writeFDs);
[super removeObjectForWriting: object];
}
- (void)observeForTimeInterval: (OFTimeInterval)timeInterval
{
fd_set readFDs;
fd_set writeFDs;
struct timeval timeout;
int events;
#ifdef OF_AMIGAOS
BYTE cancelSignal;
ULONG execSignalMask;
#endif
void *pool;
if ([self of_processReadBuffers])
return;
#ifdef FD_COPY
FD_COPY(&_readFDs, &readFDs);
FD_COPY(&_writeFDs, &writeFDs);
#else
readFDs = _readFDs;
writeFDs = _writeFDs;
#endif
/*
* We cast to int before assigning to tv_usec in order to avoid a
* warning with Apple GCC on PowerPC. POSIX defines this as suseconds_t,
* however, this is not available on Win32. As an int should always
* satisfy the required range, we just cast to int.
*/
#ifndef OF_WINDOWS
timeout.tv_sec = (time_t)timeInterval;
#else
timeout.tv_sec = (long)timeInterval;
#endif
timeout.tv_usec = (int)((timeInterval - timeout.tv_sec) * 1000000);
#ifdef OF_AMIGAOS
if ((cancelSignal = AllocSignal(-1)) == (BYTE)-1)
@throw [OFObserveKernelEventsFailedException
exceptionWithObserver: self
errNo: EAGAIN];
execSignalMask = _execSignalMask | (1ul << cancelSignal);
Forbid();
_waitingTask = FindTask(NULL);
_cancelSignal = cancelSignal;
events = WaitSelect(_maxFD + 1, &readFDs, &writeFDs, NULL,
(void *)(timeInterval != -1 ? &timeout : NULL), &execSignalMask);
execSignalMask &= ~(1ul << cancelSignal);
_waitingTask = NULL;
FreeSignal(_cancelSignal);
Permit();
#else
events = select(_maxFD + 1, &readFDs, &writeFDs, NULL,
(timeInterval != -1 ? &timeout : NULL));
#endif
if (events < 0)
@throw [OFObserveKernelEventsFailedException
exceptionWithObserver: self
errNo: _OFSocketErrNo()];
#ifdef OF_AMIGAOS
if (execSignalMask != 0 &&
[_delegate respondsToSelector: @selector(execSignalWasReceived:)])
[_delegate execSignalWasReceived: execSignalMask];
#else
if (FD_ISSET(_cancelFD[0], &readFDs)) {
char buffer;
# ifdef OF_HAVE_PIPE
OFEnsure(read(_cancelFD[0], &buffer, 1) == 1);
# else
OFEnsure(recvfrom(_cancelFD[0], (void *)&buffer, 1, 0, NULL,
NULL) == 1);
# endif
}
#endif
pool = objc_autoreleasePoolPush();
for (id <OFReadyForReadingObserving> object in
[[_readObjects copy] autorelease]) {
void *pool2 = objc_autoreleasePoolPush();
int fd = object.fileDescriptorForReading;
if (FD_ISSET((OFSocketHandle)fd, &readFDs) &&
[_delegate respondsToSelector:
@selector(objectIsReadyForReading:)])
[_delegate objectIsReadyForReading: object];
objc_autoreleasePoolPop(pool2);
}
for (id <OFReadyForWritingObserving> object in
[[_writeObjects copy] autorelease]) {
void *pool2 = objc_autoreleasePoolPush();
int fd = object.fileDescriptorForWriting;
if (FD_ISSET((OFSocketHandle)fd, &writeFDs) &&
[_delegate respondsToSelector:
@selector(objectIsReadyForWriting:)])
[_delegate objectIsReadyForWriting: object];
objc_autoreleasePoolPop(pool2);
}
objc_autoreleasePoolPop(pool);
}
@end