psyclpc/src/util/xerq/erq.c

830 lines
22 KiB
C

/*---------------------------------------------------------------------------
* XErq - Main module.
* (C) Copyright 1995 by Brian Gerst.
* (C) Copyright 2001 by Brian Gerst, Frank Kirschner, Lars Duening.
*---------------------------------------------------------------------------
* This module implements the main() function with the central loop, plus
* a bunch of utility functions.
*
* Dispatch of the various ERQ requests is implemented with a lookup table
* holding the addresses of the request functions.
*---------------------------------------------------------------------------
*/
#include "defs.h"
/*-------------------------------------------------------------------------*/
void (*erq_table[])(char *, int)
= { erq_rlookup
, erq_execute
, erq_fork
, erq_auth
, erq_spawn
, erq_send
, erq_kill
, erq_open_udp
, erq_open_tcp
, erq_listen
, erq_accept
, erq_lookup
#ifdef USE_IPV6
, erq_rlookupv6
#endif
};
/* Dispatchtable for the ERQ request functions.
* Arguments are (message, msg_len).
*/
#ifndef USE_IPV6
# define ERQ_REQUEST_MAX ERQ_LOOKUP
#else
# define ERQ_REQUEST_MAX ERQ_RLOOKUPV6
#endif
/*-------------------------------------------------------------------------*/
const char * erq_dir = ERQ_DIR;
/* The filename of the directory with the ERQ executables. */
child_t *childs;
/* List of active children. The main loop will remove _EXITED children.
*/
socket_t *sockets;
/* List of opened sockets, including those to communicate with spawned
* commands.
*/
retry_t *retries;
/* List of function calls to retry at a later point of time.
*/
equeue_t *stdout_queue;
/* List of messages pending to write to stdout.
*/
int in_select;
/* TRUE while erq is in select() - during this time sig_child()
* can write its replies directly.
*/
int seq_number;
/* The last sequence number assigned to ticket, incremented
* in <seq_interval>s, initialized with a random number.
*/
int seq_interval;
/* The interval used to increment seq_number, initialized with
* an odd random number.
*/
pid_t master_pid;
/* The pid of the 'master' erq process.
*/
/* When spawning a short-running child process, it used to happen that the
* process finishes before the parent even manages to put the pid into
* the child control structure; causing sig_child() not to find the child
* structure when it gets the signal.
* To avoid this, the spawning code employs a synchronisation scheme to
* make sure that the child doesn't execute before the parent completed
* initialisation.
*
* For ERQ_FORKed children (which are not synchronized), and as a fallback
* solution in case the synchronisation fails and the child terminates
* prematurely, sig_child() stores the relevant data in these globals for
* the main process to evaluate. The calling pattern (one ERQ
* command per select() round) guarantees that there can be only one such
* 'unfinished' child control structure at a time.
*/
volatile int pending_sig;
/* Set to true if these variables hold data of for an unaccounted SIG_CLD.
*/
volatile wait_status_t pending_status;
volatile pid_t pending_pid;
/* The status and pid for the pending SIG_CLD, valid only while pending_sig
* is TRUE.
*/
/*-------------------------------------------------------------------------*/
char *
time_stamp (void)
/* Return a textual representation of the current time
* in the form "YYYY.MM.DD HH:MM:SS [xerq]".
* Result is a pointer to a static buffer.
*
* Putting this function in strfuns is not a good idea, because
* it is need by almost every module anyway.
*/
{
time_t t;
static char result[27+20];
struct tm *tm;
int pid;
t = time(NULL);
tm = localtime(&t);
pid = getpid();
if (pid == master_pid)
strftime(result, sizeof(result), "%Y.%m.%d %H:%M:%S [xerq]", tm);
else
{
strftime(result, sizeof(result), "%Y.%m.%d %H:%M:%S [xerq:", tm);
sprintf(result+26, "%d]", pid);
}
return result;
} /* time_stamp() */
/*-------------------------------------------------------------------------*/
int
main(int argc, char *argv[])
/* The main program and -loop of the ERQ.
*/
{
int num;
master_pid = getpid();
/* Print information about this daemon to help debugging */
{
fprintf(stderr, "%s XERQ %s: Path '%s', debuglevel %d\n"
, time_stamp(), __DATE__, argv[0], ERQ_DEBUG
);
}
/* Quick and dirty commandline parser */
{
int is_forked = 0;
int i;
for (i = 1; i < argc; i++)
{
if (!strcmp(argv[i], "--forked"))
is_forked = 1;
else if (!strcmp(argv[i], "--execdir"))
{
if (i+1 >= argc)
{
fprintf(stderr, "%s Missing value for --execdir.\n"
, time_stamp());
die();
}
erq_dir = argv[i+1];
i++;
}
else
{
fprintf(stderr, "%s Unknown argument '%s'.\n"
, time_stamp(), argv[i]);
die();
}
}
/* Check if we have been forked off the driver */
if (is_forked)
{
write(1, "1", 1); /* indicate sucessful fork/execl */
fprintf(stderr, "%s Demon started\n", time_stamp() );
}
else
{
fprintf(stderr, "%s Dynamic attachement unimplemented\n"
, time_stamp());
die();
}
}
/* Initialize */
in_select = 0;
pending_sig = 0;
signal(SIGCLD, sig_child);
signal(SIGPIPE, SIG_IGN);
sockets = NULL;
childs = NULL;
retries = NULL;
stdout_queue = NULL;
randomize(time(0));
seq_number = get_ticket();
seq_interval = get_ticket() | 1; /* make sure it is odd */
#ifdef DETACH
/* Detach from console */
num = open("/dev/tty", O_RDWR);
if (num >= 0) {
ioctl(num, TIOCNOTTY, 0);
close(num);
}
#endif
/* The main loop */
while(1)
{
fd_set read_fds, write_fds;
int num_fds;
child_t *chp;
retry_t *rtp, **rtpp;
socket_t *sp;
struct timeval timeout;
/* Clean up the list of children (may close some sockets) */
for (chp = childs; chp;)
{
child_t *this = chp;
chp = chp->next;
/* If there is a pending SIG_CLD for this child, handle it.
* This is to be expected for CHILD_FORK children.
*/
if (pending_sig && this->pid == pending_pid)
{
if (this->type != CHILD_FORK)
fprintf(stderr, "%s Pending SIG_CLD for pid %d delivered.\n"
, time_stamp(), pending_pid);
this->status = pending_status;
this->pid = pending_pid;
pending_sig = 0;
}
if (this->status == CHILD_EXITED)
{
XPRINTF((stderr, "%s Child %p exited.\n", time_stamp(), this));
remove_child(this); /* will also unlink it from the list */
}
}
/* look for sockets to select on */
FD_ZERO(&read_fds);
FD_ZERO(&write_fds);
FD_SET(0, &read_fds);
if (stdout_queue)
FD_SET(1, &write_fds);
num_fds = 2;
for (sp = sockets; sp; sp = sp->next)
{
switch(sp->type)
{
case SOCKET_WAIT_CONNECT:
case SOCKET_WAIT_AUTH:
FD_SET(sp->fd, &write_fds);
FD_SET(sp->fd, &read_fds);
if (sp->fd >= num_fds)
num_fds=sp->fd+1;
break;
default:
FD_SET(sp->fd, &read_fds);
if (sp->fd >= num_fds)
num_fds=sp->fd+1;
break;
case SOCKET_WAIT_ACCEPT:
/* do nothing */;
/* Without the ; above, Metrowerks Codewarrior reports
* an error :-( */
}
if (sp->queue)
FD_SET(sp->fd, &write_fds);
} /* for (sockets) */
/* Scan the list of pending retries for the soonest one.
* Put the time till then into timeout.
* (If the list is empty, select() will receive NULL for timeout).
*/
if (retries)
{
time_t t;
t = retries->time;
for (rtp = retries; rtp; rtp = rtp->next)
{
if (rtp->time < t)
t = rtp->time;
}
timeout.tv_sec = t - time(NULL);
timeout.tv_usec = 0;
XPRINTF((stderr, "%s Soonest retry_t: in %ld seconds.\n"
, time_stamp(), (long)timeout.tv_sec));
if (timeout.tv_sec < 0)
timeout.tv_sec = 0;
}
#if ERQ_DEBUG > 1
fprintf(stderr, "%s select()\n", time_stamp());
#endif
in_select = 1; /* so sig_child() can write reply directly */
num = select(num_fds, &read_fds, &write_fds, 0, retries ? &timeout : 0);
in_select = 0; /* don't want sig_child() writing now */
#if ERQ_DEBUG > 1
{
int myerrno = errno;
fprintf(stderr, "%s select() returns %d, time() %ld\n"
, time_stamp(), num, (long)time(NULL));
errno = myerrno;
}
#endif
#if ERQ_DEBUG > 0
if (num < 0)
/* Give an error now, but don't abort this loop,
* because the retries have to be handled first.
*/
{
int myerrno = errno;
fprintf(stderr, "%s select() errno = %d", time_stamp(), errno);
errno = myerrno;
perror(" ");
}
#endif
/* Is stdout ready to write? Then flush the queue. */
if (num >= 0 && FD_ISSET(1, &write_fds))
{
XPRINTF((stderr, "%s stdout_queue ready for flush.\n", time_stamp()));
flush_queue(&stdout_queue, 1, 0);
}
/* Check for retries */
for (rtpp = &retries; *rtpp; )
{
rtp = *rtpp;
if (rtp->time <= time(NULL))
{
XPRINTF((stderr, "%s Call retry %p (time %ld)\n"
, time_stamp(), rtp, (long)rtp->time));
(*(rtp->func))(rtp->mesg, read_32(rtp->mesg));
*rtpp = rtp->next;
free(rtp);
}
else
{
rtpp = &rtp->next;
}
}
/* Error in select */
if (num < 0)
continue;
/* check for input from driver */
if (FD_ISSET(0, &read_fds))
{
XPRINTF((stderr, "%s New command from driver.\n", time_stamp()));
erq_cmd();
}
/* Handle the ready sockets.
* Remember that read_socket() may close the socket.
*/
for (sp = sockets; sp; )
{
socket_t *this = sp;
int rc;
sp = sp->next;
rc = 0;
if (FD_ISSET(this->fd, &read_fds))
{
XPRINTF((stderr, "%s Socket %p (%d) ready for reading.\n"
, time_stamp(), this, this->fd));
rc = read_socket(this, 0);
}
if (!rc && FD_ISSET(this->fd, &write_fds))
{
XPRINTF((stderr, "%s Socket %p (%d) ready for writing.\n"
, time_stamp(), this, this->fd));
(void)read_socket(this, 1);
}
}
} /* while(1) */
/* NOTREACHED */
return 0;
} /* main() */
/*-------------------------------------------------------------------------*/
void
erq_cmd (void)
/* There is data ready from the driver - read and execute it when complete.
* The function maintains a static buffer for the data read - incomplete
* messages are buffered until they are complete.
*/
{
static char buf[ERQ_MAX_SEND];
static int pos = 0;
/* Position in buf[]. If it extends beyond the end of buf,
* it is because the message is too long and the function
* is in the process of skipping the extraneous characters.
*/
int len, mesg_len;
char request;
/* Clear the buffer so that errors can be detected more easily */
memset(buf, 0, sizeof(buf));
/* Read the message header */
if (pos < 9)
{
len = read(0, buf+pos, 9-pos);
if (len <= 0)
{
perror("[xerq] read");
die();
}
XPRINTF((stderr, "%s Read %d of the missing %d header bytes.\n"
, time_stamp(), len, 9-pos));
pos += len;
if (pos < 9)
return;
}
mesg_len = read_32(buf);
if (mesg_len > sizeof(buf))
{
/* This doesn't happen in a functioning system */
fprintf(stderr
, "%s Received too long packet: %d bytes.\n"
, time_stamp(), mesg_len);
die();
}
/* Get the rest of the message */
if (pos < mesg_len)
{
len = read(0, buf+pos, mesg_len-pos);
if (len <= 0)
{
perror("read");
die();
}
XPRINTF((stderr, "%s Read %d of the missing %d message bytes.\n"
, time_stamp(), len, mesg_len-pos));
pos += len;
if (pos < mesg_len)
return;
}
XPRINTF((stderr, "%s Message complete.\n", time_stamp()));
pos = 0; /* Message complete */
/* Branch on the request */
request = buf[8];
if (request <= ERQ_REQUEST_MAX)
{
#if ERQ_DEBUG > 0
char *mesg, *mesgs[]={
"rlookup","execute","fork","auth","spawn","send","kill",
"open_udp","open_tcp","listen","accept","lookup", "rlookupv6"};
mesg=mesgs[(int)request];
fprintf(stderr, "%s command: %s\n", time_stamp(), mesg);
#endif
(*erq_table[(int)request])(buf, mesg_len);
}
else
bad_request(buf);
} /* erq_cmd() */
/*-------------------------------------------------------------------------*/
void
die(void)
/* Terminate the ERQ with status 1.
*/
{
fprintf(stderr, "%s Demon exiting.\n", time_stamp());
exit(1);
} /* die() */
/*-------------------------------------------------------------------------*/
#ifndef _AIX
void
sig_child()
#else
void
sig_child(int sig)
#endif
/* A child process exited - update its child structure.
*/
{
wait_status_t status;
pid_t pid;
struct child_s *chp;
pid = wait(&status);
#if ERQ_DEBUG > 0
fprintf(stderr, "%s [sigchild] pid=%d status=%d\n"
, time_stamp(), pid, status);
#endif
/* Look for the child and mark it as exited */
for (chp = childs; chp; chp = chp->next)
{
if (chp->pid != pid)
continue;
chp->status = CHILD_EXITED;
chp->return_code = status;
#if ERQ_DEBUG > 0
fprintf(stderr, "%s [sigchild] Caught SIGCLD for pid %d, child %p.\n"
, time_stamp(), pid, chp);
#endif
if (in_select)
remove_child(chp); /* safe to do it from here */
/* if we're in select, we know we're not going to be messing up
the main loop with stuff we're doing here */
break;
}
if (!chp)
{
/* There is no valid child. Maybe we caught the signal before
* the child structure was complete (this can happen especially
* with short-lived CHILD_FORK sub processes).
*/
if (pending_sig)
{
fprintf(stderr, "%s [sigchild] SIGCLD for pid %d not delivered.\n"
, time_stamp(), pending_pid);
}
#if ERQ_DEBUG > 0
fprintf(stderr, "%s [sigchild] SIGCLD for unknown pid %d received.\n"
, time_stamp(), pid);
#endif
pending_status = status;
pending_pid = pid;
pending_sig = 1;
}
/* Restore the signal handler */
signal(SIGCLD, sig_child);
} /* sig_child() */
/*-------------------------------------------------------------------------*/
void
add_retry (void (*func)(char *, int), char *mesg, int len, int t)
/* Add a new retry: function <func> is to be executed in <t> seconds
* with (<mesg>, <len>) as arguments.
*/
{
struct retry_s *retry;
retry = malloc(sizeof(struct retry_s)+len);
XPRINTF((stderr, "%s New retry %p: %d seconds, func %p, data %p:%d\n"
, time_stamp(), retry, t, func, mesg, len));
retry->time = time(NULL)+t;
retry->func = func;
memcpy(&retry->mesg, mesg, len);
retry->next = retries;
retries = retry;
} /* add_retry() */
/*-------------------------------------------------------------------------*/
void
bad_request (char *mesg)
/* ERQ received a bad message in <mesg> - print some diagnostics.
*/
{
fprintf(stderr, "%s Bad request %d\n", time_stamp(), mesg[8]);
fprintf(stderr, "%s %x %x %x %x %x %x %x %x %x\n", time_stamp(),
mesg[0], mesg[1], mesg[2], mesg[3], mesg[4],
mesg[5], mesg[6], mesg[7], mesg[8]);
fprintf(stderr, "%s %c %c %c %c %c %c %c %c %c\n", time_stamp(),
mesg[0], mesg[1], mesg[2], mesg[3], mesg[4],
mesg[5], mesg[6], mesg[7], mesg[8]);
reply1(get_handle(mesg), "", 0);
} /* bad_request() */
/*-------------------------------------------------------------------------*/
void
reply1 (int32 handle, const void *data, int32 len)
/* Compose a reply message from <handle> and the <len> bytes of <data>
* and send it back to the driver.
*/
{
char reply[ERQ_MAX_REPLY];
write_32(reply, len+8);
write_32(reply+4, handle);
memcpy(reply+8, data, len);
write1(reply, len+8);
} /* reply1() */
/*-------------------------------------------------------------------------*/
void
reply1keep (int32 handle, const void *data, int32 len)
/* Compose a reply message from <handle> and the <len> bytes of <data>
* and send it back to the driver. The message will be an _KEEP_HANDLE
* message.
*/
{
char reply[ERQ_MAX_REPLY];
write_32(reply, len+12);
write_32(reply+4, ERQ_HANDLE_KEEP_HANDLE);
write_32(reply+8, handle);
memcpy(reply+12, data, len);
write1(reply, len+12);
} /* reply1keep() */
/*-------------------------------------------------------------------------*/
void
replyn (int32 handle, int keep, int num, ...)
/* Compose and send to the driver a replymessage for <handle> with
* the <num> data arguments concatenated as body. If <keep> is true,
* a _KEEP_HANDLE message is composed.
*
* Each data argument is a tuple (char *data, int len).
*/
{
char reply[ERQ_MAX_REPLY];
char *p;
int total;
va_list va;
/* Determine the size of the header */
total = (keep ? 12 : 8);
p = reply+total;
/* Catenate the data arguments */
va_start(va, num);
while (num-- > 0 && total < ERQ_MAX_REPLY)
{
char *data;
int len;
data = va_arg(va, char *);
len = va_arg(va, int);
if (total + len > ERQ_MAX_REPLY)
{
fprintf(stderr, "%s Too much data in replyn(): %d bytes omitted.\n"
, time_stamp(), total + len - ERQ_MAX_REPLY);
len = ERQ_MAX_REPLY - total;
}
memcpy(p, data, len);
p += len;
total += len;
}
va_end(va);
if (num > 0)
{
fprintf(stderr, "%s Too much data in replyn(): Remaining %d "
"data blocks omitted.\n"
, time_stamp(), num);
}
/* Create the header */
write_32(reply, total);
if (keep)
{
write_32(reply+4, ERQ_HANDLE_KEEP_HANDLE);
write_32(reply+8, handle);
}
else
{
write_32(reply+4, handle);
}
/* Send the reply */
write1(reply, total);
} /* replyn() */
/*-------------------------------------------------------------------------*/
void
reply_errno (int32 handle)
/* Send a (errcode, errno) message to the driver for <handle>.
*/
{
char mesg[2];
switch(errno)
{
case EWOULDBLOCK:
#if EAGAIN != EWOULDBLOCK
case EAGAIN:
#endif
mesg[0] = ERQ_E_WOULDBLOCK;
break;
case EPIPE:
mesg[0] = ERQ_E_PIPE;
break;
default:
mesg[0] = ERQ_E_UNKNOWN;
break;
}
mesg[1] = errno;
reply1(handle, mesg, 2);
} /* reply_errno() */
/*-------------------------------------------------------------------------*/
int
writen (int fd, char *mesg, int len, struct equeue_s **qpp)
/* Send or queue the message <mesg> (length <len> bytes) to <fd>.
* If *<qpp> is non-NULL, the message is queued immediately.
* Otherwise, the function tries to send as much of the message
* as possible, and the queues whatever is left.
*/
{
int l = 0;
XPRINTF((stderr, "%s writen(%d, %p:%d, %p (-> %p) ))\n"
, time_stamp(), fd, mesg, len, qpp, *qpp));
if (!(*qpp))
{
/* Send as much of the message as possible */
do
l = write(fd, mesg, len);
while (l == -1 && errno == EINTR);
XPRINTF((stderr, "%s Wrote %d bytes.\n", time_stamp(), l));
if (l < 0 || l == len)
return l;
mesg += l;
len -= l;
}
if (!len)
return 0;
XPRINTF((stderr, "%s Queuing data %p:%d\n", time_stamp(), mesg, len));
add_to_queue(qpp, mesg, len, 0);
return l;
} /* writen() */
/*-------------------------------------------------------------------------*/
void
write1 (void *mesg, int len)
/* Write the <len> bytes of <mesg> to stdout, ie to the driver.
*/
{
int l;
l = writen(1, mesg, len, &stdout_queue);
if (l < 0)
{
int myerrno = errno;
fprintf(stderr, "%s Error occurred on driver socket, errno=%d",
time_stamp(), errno);
errno = myerrno;
perror(" ");
die();
}
#if ERQ_DEBUG > 0
if (l != len)
fprintf( stderr
, "%s Driver-erq socket blocked, queueing %d bytes\n"
, time_stamp(), len);
#endif
} /* write1() */
/***************************************************************************/