I've had quite a few issues with poll
and kqueue
on Mac OS X/Darwin. When trying to poll for events using fileno(stdin)
or STDIN_FILENO
, it gives errors such as Operation not supported
(kqueue
) or POLLNVAL
(poll
).
On Darwin (at least up until Mac OS X 10.5.5), stdin
, stdout
and stderr
are actually devices! Not pipes like one might expect. Also, poll
is implemented on top of kqueue
, therefore problems with kqueue
also affect poll
.
Because of this, polling stdin
won't work. Here is an example which fails on Mac OS X 10.5.5:
#include <stdio.h>
#include <poll.h>
int main (int argc, char ** argv)
{
struct pollfd pfd;
pfd.fd = fileno(stdin);
pfd.events = POLLIN|POLLOUT;
pfd.revents = 0;
int result = poll(&pfd, 1, 0);
if (result < 0) {
printf("poll = %d\n", result);
perror("poll failed");
}
printf("pfd.revents = %d\n", pfd.revents);
if (pfd.revents & POLLNVAL) {
printf("invalid file descriptor\n");
}
return 0;
}
A hackity solution
This code uses boost threads. What it does is create three pipes, one for stdin
, one for stdout
and one for stderr
. The originals are moved to different fd
s and then we spawn three threads to process data between the original devices and the newly created pipes. The result of this is that STDIN_FILENO
, as an example, now refers to the write end of a pipe. The other end of this pipe is being read by the thread, and any data is written out to the original stdin
device.
class ConsolePipeRedirector {
boost::thread_group m_threads;
bool m_done;
public:
ConsolePipeRedirector () : m_done(false)
{
}
static void copyDataBetweenFileDescriptors (int input, int output)
{
char buf[512];
while(1) {
ssize_t count = read(input, buf, 512);
if (count < 0) {
perror("copyDataBetweenFileDescriptors");
} else if (count == 0) {
break;
}
write(output, buf, count);
}
}
void reopenStandardFileDescriptorsAsPipes ()
{
if (m_done) return;
m_done = true;
int result;
int stdinPipe[2], stdoutPipe[2], stderrPipe[2];
// Create a new pipe for stdin data
result = pipe(stdinPipe);
if (result) perror("pipe(stdinPipe)");
// Copy the stdin device to a new fd
int stdinDevice = dup(STDIN_FILENO);
// Same again for stdout
result = pipe(stdoutPipe);
if (result) perror("pipe(stdoutPipe)");
int stdoutDevice = dup(STDOUT_FILENO);
// Same again for stderr
result = pipe(stderrPipe);
if (result) perror("pipe(stderrPipe)");
int stderrDevice = dup(STDERR_FILENO);
// Copy the pipe endpoint into the standard place for in, out and err.
dup2(stdinPipe[0], STDIN_FILENO);
dup2(stdoutPipe[1], STDOUT_FILENO);
dup2(stderrPipe[1], STDERR_FILENO);
// Spawn threads to handle reading and writing in a blocking fashion.
m_threads.create_thread(bind(copyDataBetweenFileDescriptors, stdinDevice, stdinPipe[1]));
m_threads.create_thread(bind(copyDataBetweenFileDescriptors, stdoutPipe[0], stdoutDevice));
m_threads.create_thread(bind(copyDataBetweenFileDescriptors, stderrPipe[0], stderrDevice));
}
};
In this case, I've fixed all three standard fd
s. However, it is probably only useful in most cases to fix stdin
- this is likely to be the only device you would want to poll
. I've included support for all three as an example of how it would be done.
Poll specifically is a bit different across different platforms. Therefore, you need to be aware of the differences.