/*
* Copyright (C) 2003 Sam Horrocks
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
*/
/*
* Persistent Frontend Program
*/
#include "perperl.h"
/*
* FILE DESCRIPTOR CODE
*/
#define FD_UNKNOWN 0
#define FD_CLOSE 1
#define FD_BLOCK 2
#define FD_NOBLOCK 3
typedef struct _fdinfo {
int flags;
char state;
} fdinfo_t;
static int fdinfo_size;
static fdinfo_t *fdinfo;
static void fd_change(int fd, int state) {
if (state != fdinfo[fd].state && fdinfo[fd].state != FD_CLOSE) {
if (state == FD_CLOSE) {
/* SGI's /dev/tty goes crazy unless we turn on blocking I/O. */
if (fdinfo[fd].state == FD_NOBLOCK && fd <= 2)
fd_change(fd, FD_BLOCK);
close(fd);
fdinfo[fd].state = FD_CLOSE;
} else {
int flags;
fdinfo[fd].state = state;
if (state == FD_BLOCK) {
flags = fdinfo[fd].flags & ~O_NONBLOCK;
} else {
flags = fdinfo[fd].flags | O_NONBLOCK;
}
if (fcntl(fd, F_SETFL, flags) == -1 && errno == EBADF) {
fdinfo[fd].state = FD_CLOSE;
}
}
}
}
static void fd_init(int fd, int flags, int state) {
if (fd >= fdinfo_size) {
#ifdef PERPERL_EFENCE
fdinfo_size = fd + 1;
#else
fdinfo_size = fd + 10;
#endif
perperl_renew(fdinfo, fdinfo_size, fdinfo_t);
}
fdinfo[fd].flags = flags;
fdinfo[fd].state = FD_UNKNOWN;
fd_change(fd, state);
}
#define fd_closed(fd) (fdinfo[fd].state == FD_CLOSE)
#define fd_open(fd) (!fd_closed(fd))
/*
* END OF FILE DESCRIPTOR SECTION
*/
static void killme(int sig) {
sigset_t sigs;
signal(sig, SIG_DFL);
sigemptyset(&sigs);
sigaddset(&sigs, sig);
sigprocmask(SIG_UNBLOCK, &sigs, NULL);
kill(perperl_util_getpid(), sig);
perperl_util_exit(sig+128, 0);
}
static int sig_pipes[2];
static volatile int got_sig;
static void catch_sig(int sig) {
got_sig++;
write(sig_pipes[1], "", 1);
}
/*
* When profiling, only call perperl_opt_init once
*/
#ifdef PERPERL_PROFILING
static int did_opt_init, profile_runs;
#define DO_OPT_INIT if (!did_opt_init++) perperl_opt_init
#else
#define DO_OPT_INIT perperl_opt_init
#endif
#ifdef FILL_STDIN_BUF
#define STDIN_INITIAL_STATE FD_BLOCK
#else
#define STDIN_INITIAL_STATE FD_NOBLOCK
#endif
#define CB_IN (cb[0])
#define CB_OUT (cb[1])
#define CB_ERR (cb[2])
extern char **environ;
static CopyBuf cb[NUMFDS];
static int got_stdout, stop_sock_reads, read_stopped[NUMFDS];
/* See if done copying through this buf */
static int my_copydone(const CopyBuf *b) {
return perperl_cb_copydone(b) &&
(got_stdout || b != &CB_OUT || perperl_cb_eof(b));
}
/* See if we can read into this buf */
static int my_canread(const CopyBuf *b) {
return
!((b) != &CB_IN && stop_sock_reads && read_stopped[(b)-&CB_IN]) &&
(perperl_cb_canread(b) ||
(!got_stdout && b == &CB_OUT && !perperl_cb_eof(b)));
}
/* Try to close this copy buf */
static void try_close(const CopyBuf *b) {
if (my_copydone(b)) {
fd_change(b->rdfd, FD_CLOSE);
fd_change(b->wrfd, FD_CLOSE);
}
}
static void doit(const char * const *argv, int *exit_on_sig, int *exit_val)
{
PollInfo pi;
PersistentBuf ibuf;
int backend_exited = 0, am_child = 0, in_is_tty;
int socks[NUMFDS];
register int i;
slotnum_t fslotnum;
got_stdout = stop_sock_reads = 0;
/* Initialize file descriptors */
fd_init(0, O_RDONLY, STDIN_INITIAL_STATE);
for (i = 1; i <= 2; ++i)
fd_init(i, O_WRONLY, FD_NOBLOCK);
/* Is stdin a tty? */
in_is_tty = fd_open(0) ? isatty(0) : 0;
/* Initialize options */
DO_OPT_INIT(argv, (const char * const *)environ);
# ifdef IAMSUID
if (perperl_util_geteuid() == 0) {
int new_uid;
/* Set group-id */
if (perperl_script_getstat()->st_mode & S_ISGID) {
if (setegid(perperl_script_getstat()->st_gid) == -1)
perperl_util_die("setegid");
}
/* Must set euid to something - either the script owner
* or the real-uid
*/
if (perperl_script_getstat()->st_mode & S_ISUID) {
new_uid = perperl_script_getstat()->st_uid;
} else {
new_uid = perperl_util_getuid();
}
if (perperl_util_seteuid(new_uid) == -1)
perperl_util_die("seteuid");
}
# endif
/* Create buffer with env/argv data to send */
perperl_frontend_mkenv(
(const char * const *)environ, perperl_opt_script_argv(), 0, &ibuf, 0
);
/* Allocate buffers for copying below: */
/* fd0 -> cb[0] -> s */
perperl_cb_init(
&CB_IN,
max(OPTVAL_BUFSIZPOST, ibuf.alloced),
0,
-1,
&ibuf
);
/* Read as much as possible from stdin, then make it non-blocking */
#ifdef FILL_STDIN_BUF
if (fd_open(0)) {
perperl_cb_read(&CB_IN);
fd_change(0, FD_NOBLOCK);
}
#endif
/* Connect up with a backend */
if (!perperl_frontend_connect(socks, &fslotnum))
DIE_QUIET("Cannot spawn backend process");
/* Get ready to catch sig as soon as we send over environment to be */
pipe(sig_pipes);
signal(SIGUSR1, catch_sig);
/* Non-blocking I/O on sockets */
for (i = 0; i < NUMFDS; ++i)
fd_init(socks[i], O_RDWR, FD_NOBLOCK);
/* Allocate buffers for copying below: */
/* fd0 -> cb[0] -> s */
/* s -> cb[1] -> fd1 */
/* e -> cb[2] -> fd2 */
perperl_cb_setfd(&CB_IN, 0, socks[0]);
perperl_cb_init(
&CB_OUT,
OPTVAL_BUFSIZGET,
socks[1],
1,
NULL
);
perperl_cb_init(
&CB_ERR,
512,
socks[2],
2,
NULL
);
/* Disable i/o on any fd's that are not open */
if (fd_closed(0))
perperl_cb_seteof(&CB_IN);
for (i = 1; i < NUMFDS; ++i) {
if (fd_closed(i))
perperl_cb_set_write_err(cb+i, EBADF);
}
/* Poll/select may not wakeup on intial eof, so set for initial read here.
* (this is tested in initial_eof test #1)
*/
{
int m = socks[0];
for (i = 1; i < NUMFDS; ++i)
m = max(m, socks[i]);
perperl_poll_init(&pi, m);
}
perperl_poll_reset(&pi);
if (!in_is_tty && my_canread(&CB_IN))
perperl_poll_set(&pi, 0, PERPERL_POLLIN);
for (i = 1; i < NUMFDS; ++i) {
if (my_canread(cb+i))
perperl_poll_set(&pi, cb[i].rdfd, PERPERL_POLLIN);
}
/* Try to write our env/argv without dropping into select */
if (perperl_cb_canwrite(&CB_IN))
perperl_poll_set(&pi, CB_IN.wrfd, PERPERL_POLLOUT);
/* Turn off sigpipes so when we epipe on our sockets we don't die */
/* The backend should sigpipe anyways, and we'll get that status */
signal(SIGPIPE, SIG_IGN);
#ifdef SIGTTIN
/* We don't want to get a stop signal */
signal(SIGTTIN, SIG_IGN);
#endif
/* Try to close copy bufs if possible now */
for (i = 0; i < NUMFDS; ++i)
try_close(cb+i);
/* We seem to lose our non-blocking on 0 if frontend_connect
* forks a backend. Bug somewhere...
*/
if (fd_open(0))
fcntl(0, F_SETFL, O_RDONLY|O_NONBLOCK);
/* Copy streams */
while (1) {
/* Do reads/writes */
for (i = 0; i < NUMFDS; ++i) {
register CopyBuf *b = cb + i;
int do_read = my_canread(b) &&
perperl_poll_isset(&pi, b->rdfd, PERPERL_POLLIN);
int do_write = perperl_cb_canwrite(b) &&
perperl_poll_isset(&pi, b->wrfd, PERPERL_POLLOUT);
while (do_read || do_write) {
if (do_read) {
int data_read, sz = perperl_cb_data_len(b);
perperl_cb_read(b);
data_read = perperl_cb_data_len(b) > sz;
if (!data_read)
read_stopped[i] = 1;
if (!got_stdout && b == &CB_OUT && data_read) {
got_stdout = 1;
perperl_frontend_proto2(socks[2], perperl_cb_shift(b));
}
if (perperl_cb_canwrite(b) &&
(perperl_cb_eof(b) || data_read))
{
do_write = 1;
}
do_read = 0;
}
/* Attempt write now if we did a read. Slightly more efficient
* and on SGI if we are run with >/dev/null, select won't
* initially wakeup (this is tested in initial_eof test #2)
*/
if (do_write) {
int sz = perperl_cb_data_len(b);
perperl_cb_write(b);
if (my_canread(b) && perperl_cb_data_len(b) < sz &&
!(i == 0 && in_is_tty))
{
do_read = 1;
}
do_write = 0;
}
/* Try to close files now, so we can wake up the backend
* and do more I/O before dropping into select
*/
if (!do_read && !do_write)
try_close(b);
}
}
/* All done with reads/writes after backend exited */
if (backend_exited) {
if (am_child) {
if (my_copydone(&CB_OUT) && my_copydone(&CB_ERR))
break;
}
else if (!perperl_cb_canwrite(&CB_OUT) &&
!perperl_cb_canwrite(&CB_ERR))
{
/* If eof, all done */
if (my_copydone(&CB_OUT) && my_copydone(&CB_ERR))
break;
/* See if all reads on sockets have stopped */
for (i = 1; i < NUMFDS; ++i) {
if (!read_stopped[i])
break;
}
/* If all have stopped */
if (i == NUMFDS) {
/* Continue copying in the background until eof */
if (fork())
break;
am_child = 1;
stop_sock_reads = 0;
}
}
}
/* See if the backend exited */
else if (got_sig || perperl_poll_isset(&pi, sig_pipes[0], PERPERL_POLLIN))
{
char c;
/* See if backend exited and if so, get status */
perperl_file_set_state(FS_CORRUPT);
backend_exited =
perperl_frontend_collect_status(fslotnum, exit_on_sig, exit_val);
perperl_file_set_state(FS_OPEN);
/* Exit & EOF? If so, no need to continue */
if (backend_exited && my_copydone(&CB_OUT) && my_copydone(&CB_ERR))
break;
/* Ack signal */
got_sig = 0;
read(sig_pipes[0], &c, 1);
if (backend_exited) {
/* Don't need sig any more */
signal(SIGUSR1, SIG_IGN);
/* Continue until no more data from socket */
stop_sock_reads = 1;
for (i = 0; i < NUMFDS; ++i)
read_stopped[i] = 0;
perperl_poll_reset(&pi);
for (i = 1; i < NUMFDS; ++i) {
perperl_poll_set(&pi, cb[i].rdfd, PERPERL_POLLIN);
perperl_poll_set(&pi, cb[i].wrfd, PERPERL_POLLOUT);
}
continue;
}
}
/* Reset events */
perperl_poll_reset(&pi);
/* Do select on signal pipe */
if (!backend_exited)
perperl_poll_set(&pi, sig_pipes[0], PERPERL_POLLIN);
/* Set read/write events */
for (i = 0; i < NUMFDS; ++i) {
CopyBuf *b = cb + i;
if (my_canread(b))
perperl_poll_set(&pi, b->rdfd, PERPERL_POLLIN);
if (perperl_cb_canwrite(b))
perperl_poll_set(&pi, b->wrfd, PERPERL_POLLOUT);
}
/* Poll... */
i = perperl_poll_wait(&pi, 5000);
if (i < 1) {
if (i == -1 && errno != EINTR)
perperl_util_exit(1, 0);
/* Want to check whether backend is still alive */
if (!backend_exited)
catch_sig(0);
perperl_poll_reset(&pi);
}
}
/* SGI's /dev/tty goes crazy unless we turn on blocking I/O. */
for (i = 0; i < NUMFDS; ++i)
fd_change(i, FD_BLOCK);
if (am_child)
perperl_util_exit(0, 0);
#ifdef PERPERL_PROFILING
/* Slightly faster to skip these, since we're exiting anyways. */
if (profile_runs) {
for (i = 0; i < NUMFDS; ++i)
fd_change(socks[i], FD_CLOSE);
perperl_poll_free(&pi);
perperl_cb_free(&CB_IN);
perperl_cb_free(&CB_OUT);
perperl_cb_free(&CB_ERR);
signal(SIGUSR1, SIG_DFL);
signal(SIGPIPE, SIG_DFL);
close(pipes[0]);
close(pipes[1]);
}
#endif
}
int main(int argc, char **argv, char **_junk) {
int exit_on_sig, exit_val;
perperl_util_unlimit_core();
#ifdef PERPERL_PROFILING
char *runs = getenv("PERPERL_PROFILE_RUNS");
profile_runs = runs ? atoi(runs) : 1;
while (profile_runs--)
#endif
doit((const char * const *)argv, &exit_on_sig, &exit_val);
/* If signal, try to kill ourself with the same sig the backend died on */
if (exit_on_sig)
killme(exit_val);
return exit_val;
}
/*
* Glue Functions
*/
void perperl_abort(const char *s) {
write(2, s, strlen(s));
perperl_util_exit(1, 0);
}
#ifdef PERPERL_EFENCE
void *efence_malloc (size_t size);
void efence_free (void *ptr);
void *efence_realloc (void *ptr, size_t size);
void * malloc (size_t size) {
return efence_malloc(size);
}
void free (void *ptr) {
efence_free(ptr);
}
void * realloc (void *ptr, size_t size) {
return efence_realloc(ptr, size);
}
#endif