I'm having a lot of trouble building a 'middleman' logger - the intention is to place it on the path above an item in /usr/bin and capture everything going to and from the application. (Black box 3rd-party app is failing FTP for some reason.) Once run, the middleman will fork, redirect stdout and stdin to/from pipes that the parent has control of, and then execute the program in /usr/bin. (Hardcoded; yes, I know, I'm bad.)
However, once I run poll(), things get weird. I lose the handle to my logfile, the poll on the output pipe from the child throws an error, cats and dogs start living together, et cetera.
Can anyone shed some light on this?
Here's what I currently have... The poll() in question is marked with non-indented comments for ease of location.
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <poll.h>
#include <time.h>
#include <sys/types.h>
#include <fcntl.h>
#define MAX_STR_LEN 1024
static int directionFlag; /* 0 = input, 1 = output */
static int eofFlag;
/* Splits the next char from the stream inFile, with extra
information logged if directionFlag swaps */
void logChar(int inFilDes, int outFilDes, FILE *logFile, int direction)
{
char inChar = 0;
if(read(inFilDes, &inChar, sizeof(char)) > 0)
{
if(direction != directionFlag)
{
directionFlag = direction;
if(direction)
{
fprintf(logFile, "\nOUTPUT: ");
} else {
fprintf(logFile, "\nINPUT: ");
}
}
write(outFilDes, &inChar, sizeof(char));
fputc(inChar, stderr);
fputc(inChar, logFile);
} else {
eofFlag = 1;
}
return;
}
int main(int argc, char* argv[])
{
pid_t pid;
int childInPipe[2];
int childOutPipe[2];
eofFlag = 0;
/* [0] is input, [1] is output*/
if(pipe(childInPipe) < 0 || pipe(childOutPipe) < 0) {
fprintf(stderr,"Pipe error; aborting\n");
exit(1);
}
if((pid = fork()) == -1){
fprintf(stderr,"Fork error; aborting\n");
exit(1);
}
if(pid)
{
/*Parent process*/
int i;
int errcode;
time_t rawtime;
struct tm * timeinfo;
time(&rawtime);
timeinfo=localtime(&rawtime);
struct pollfd pollArray[2] = {
{ .fd = 0, .events = POLLIN, .revents = 0 },
{ .fd = childOutPipe[0], .events = POLLIN, .revents = 0 }
};
/* Yet again, 0 = input, 1 = output */
nfds_t nfds = sizeof(struct pollfd[2]);
close(childInPipe[0]);
close(childOutPipe[1]);
/* We don't want to change around the streams for this one,
as we will be logging everything - and I do mean everything */
FILE *logFile;
if(!(logFile = fopen("/opt/middleman/logfile.txt", "a"))) {
fprintf(stderr, "fopen fail on /opt/middleman/logfile.txt\n");
exit(1);
}
fprintf(logFile, "Commandline: ");
for(i=0; i < argc; i++)
{
fprintf(logFile, "%s ", argv[i]);
}
fprintf(logFile, "\nTIMESTAMP: %s\n", asctime(timeinfo));
while(!eofFlag)
{
// RIGHT HERE is where things go to pot
errcode = poll(pollArray, nfds, 1);
// All following fprintf(logfile)s do nothing
if(errcode < 0) {
fprintf(stderr, "POLL returned with error %d!", errcode);
eofFlag = 1;
}
if((pollArray[0].revents && POLLERR) & errno != EAGAIN ) {
fprintf(stderr, "POLL on input has thrown an exception!\n");
fprintf(stderr, "ERRNO value: %d\n", errno);
fprintf(logFile, "POLL on input has thrown an exception!\n");
eofFlag = 1;
} else if(pollArray[0].revents && POLLIN) {
logChar(pollArray[0].fd, childInPipe[1], logFile, 0);
} else if((pollArray[1].revents && POLLERR) & errno != EAGAIN ) {
fprintf(stderr, "POLL on output has thrown an exception!\n");
fprintf(stderr, "ERRNO value: %d\n", errno);
fprintf(logFile, "POLL on output has thrown an exception!\n");
eofFlag = 1;
} else if(pollArray[1].revents && POLLIN) {
logChar(pollArray[1].fd, 1, logFile, 1);
}
}
fclose(logFile);
}
else
{
/*Child process; switch streams and execute application*/
int i;
int catcherr = 0;
char stmt[MAX_STR_LEN] = "/usr/bin/";
close(childInPipe[1]);
close(childOutPipe[0]);
strcat(stmt, argv[0]);
if(dup2(childInPipe[0],0) < 0) {
fprintf(stderr, "dup2 threw error %d on childInPipe[0] to stdin!\n", errno);
}
// close(childInPipe[0]);
if(dup2(childOutPipe[1],1) < 0)
{
fprintf(stderr, "dup2 threw error %d on childInPipe[1] to stdout!\n", errno);
}
/* Arguments need to be in a different format for execv */
char* args[argc+1];
for(i = 0; i < argc; i++)
{
args[i] = argv[i];
}
args[i] = (char *)0;
fprintf(stderr, "Child setup complete, executing %s\n", stmt);
fprintf(stdout, "Child setup complete, executing %s\n", stmt);
if(execv(stmt, args) == -1) {
fprintf(stderr, "execvP error!\n");
exit(1);
}
}
return 0;
}
EDIT 6/23/09 12:20PM
After the fixes, I have attempted to run 'banner' through this program, and here's the output I get...
Child setup complete, executing /usr/bin/banner
POLL on output has thrown an exception!
ERRNO value: 0
The logfile has the following:
Commandline: banner testing
TIMESTAMP: Tue Jun 23 11:21:00 2009
The reason ERRNO has a 0 in it is because poll() returns just fine; it's the pollArray[1].revents that came back with an error, which means childOutPipe[0] polled as having an error. logChar(), as far as I can tell, never gets called.
I'm going to try splitting out poll() into two different calls.
Okay, the moment I poll() - even on stdin, which doesn't return with an error message - it kills my ability to write to the logFile. Also, I discovered that the while() loop runs several times before the output poll comes back with an error on the pipe. I'm becoming increasingly convinced that poll() is simply a lost cause.
Every attempt to write to logFile fails after the poll(), even a successful poll(), with errno set to "Bad file number". This really should not be happening. I honestly cannot see how it would be affecting my file handle.
Okay, so apparently I'm a moron. Thanks for setting me straight; I was assuming nfds was a byte size, not an array size. That's fixed, and voila! It's not killing my logFile handle any more.