#include "xmh.h"
#include <X11/Xpoll.h>
#include <sys/ioctl.h>
#include <signal.h>
#ifndef SYSV
#include <sys/wait.h>
#endif
#if defined(SVR4) && !defined(SCO325) && !defined(DGUX)
#include <sys/filio.h>
#endif
#define TYPEAHEADSIZE 20
#ifndef HAS_VFORK
#define vfork() fork()
#else
#if defined(sun) && !defined(SVR4)
#include <vfork.h>
#endif
#endif
typedef struct _CommandStatus {
Widget popup;
struct _LastInput lastInput;
char* shell_command;
int child_pid;
XtInputId output_inputId;
XtInputId error_inputId;
int output_pipe[2];
int error_pipe[2];
char* output_buffer;
int output_buf_size;
char* error_buffer;
int error_buf_size;
} CommandStatusRec, *CommandStatus;
typedef char* Pointer;
static void FreeStatus(XMH_CB_ARGS);
static void CheckReadFromPipe(int, char **, int *, Bool);
static void SystemError(char* text)
{
char msg[BUFSIZ];
sprintf( msg, "%s; errno = %d %s", text, errno,
strerror(errno));
XtWarning( msg );
}
static char *FullPathOfCommand(char *str)
{
static char result[100];
(void) sprintf(result, "%s/%s", app_resources.mh_path, str);
return result;
}
static void ReadStdout(
XtPointer closure,
int *fd,
XtInputId *id)
{
register CommandStatus status = (CommandStatus)closure;
CheckReadFromPipe(*fd, &status->output_buffer, &status->output_buf_size,
False);
}
static void ReadStderr(
XtPointer closure,
int *fd,
XtInputId *id)
{
register CommandStatus status = (CommandStatus)closure;
CheckReadFromPipe(*fd, &status->error_buffer, &status->error_buf_size,
False);
}
static volatile int childdone;
static void
ChildDone(int n)
{
childdone++;
}
static int _DoCommandToFileOrPipe(
char **argv,
int inputfd,
int outputfd,
char **bufP,
int *lenP)
{
XtAppContext appCtx = XtWidgetToApplicationContext(toplevel);
int return_status;
int old_stdin = 0, old_stdout = 0, old_stderr = 0;
int pid;
fd_set readfds, fds;
Boolean output_to_pipe = False;
CommandStatus status = XtNew(CommandStatusRec);
FD_ZERO(&fds);
FD_SET(ConnectionNumber(theDisplay), &fds);
DEBUG1("Executing %s ...", argv[0])
if (inputfd != -1) {
old_stdin = dup(fileno(stdin));
(void) dup2(inputfd, fileno(stdin));
close(inputfd);
}
if (outputfd == -1) {
if (!app_resources.debug) {
outputfd = open( "/dev/null", O_WRONLY, 0 );
}
}
else if (outputfd == -2) {
if (pipe(status->output_pipe) ) {
SystemError( "couldn't re-direct standard output" );
status->output_pipe[0]=0;
}
else {
outputfd = status->output_pipe[1];
FD_SET(status->output_pipe[0], &fds);
status->output_inputId =
XtAppAddInput( appCtx,
status->output_pipe[0], (XtPointer)XtInputReadMask,
ReadStdout, (XtPointer)status
);
status->output_buffer = NULL;
status->output_buf_size = 0;
output_to_pipe = True;
}
}
if (pipe(status->error_pipe) ) {
SystemError( "couldn't re-direct standard error" );
status->error_pipe[0]=0;
}
else {
old_stderr = dup(fileno(stderr));
(void) dup2(status->error_pipe[1], fileno(stderr));
close(status->error_pipe[1]);
FD_SET(status->error_pipe[0], &fds);
status->error_inputId =
XtAppAddInput( appCtx,
status->error_pipe[0], (XtPointer)XtInputReadMask,
ReadStderr, (XtPointer)status
);
}
if (outputfd != -1) {
old_stdout = dup(fileno(stdout));
(void) dup2(outputfd, fileno(stdout));
close(outputfd);
}
childdone = FALSE;
status->popup = (Widget)NULL;
status->lastInput = lastInput;
status->error_buffer = NULL;
status->error_buf_size = 0;
(void) signal(SIGCHLD, ChildDone);
pid = vfork();
if (inputfd != -1) {
if (pid != 0) dup2(old_stdin, fileno(stdin));
close(old_stdin);
}
if (outputfd != -1) {
if (pid != 0) dup2(old_stdout, fileno(stdout));
close(old_stdout);
}
if (status->error_pipe[0]) {
if (pid != 0) dup2(old_stderr, fileno(stderr));
close(old_stderr);
}
if (pid == -1) Punt("Couldn't fork!");
if (pid) {
XEvent typeAheadQueue[TYPEAHEADSIZE], *eventP = typeAheadQueue;
XEvent *altQueue = NULL;
int type_ahead_count = 0, alt_queue_size = 0, alt_queue_count = 0;
XtAppContext app = XtWidgetToApplicationContext(toplevel);
int num_fds = ConnectionNumber(theDisplay)+1;
if (output_to_pipe && status->output_pipe[0] >= num_fds)
num_fds = status->output_pipe[0]+1;
if (status->error_pipe[0] >= num_fds)
num_fds = status->error_pipe[0]+1;
status->child_pid = pid;
DEBUG1( " pid=%d ", pid )
subProcessRunning = True;
while (!childdone) {
while (!(XtAppPending(app) & XtIMXEvent)) {
readfds = fds;
if (childdone) break;
DEBUG("blocking.\n")
(void) Select(num_fds, &readfds, NULL, NULL, NULL);
DEBUG1("unblocked; child%s done.\n", childdone ? "" : " not")
if (childdone) break;
if (!FD_ISSET(ConnectionNumber(theDisplay), &readfds)) {
DEBUG("reading alternate input...")
XtAppProcessEvent(appCtx, (unsigned)XtIMAlternateInput);
DEBUG("read.\n")
}
}
if (childdone) break;
XtAppNextEvent(app, eventP);
switch(eventP->type) {
case LeaveNotify:
if (type_ahead_count) {
XEvent *prevEvent;
if (alt_queue_size && (alt_queue_count == 0))
prevEvent = &typeAheadQueue[type_ahead_count-1];
else
prevEvent = eventP - 1;
if (prevEvent->type == EnterNotify
&& prevEvent->xany.display == eventP->xany.display
&& prevEvent->xany.window == eventP->xany.window) {
eventP = prevEvent;
if (alt_queue_count > 0)
alt_queue_count--;
else
type_ahead_count--;
break;
}
}
case KeyPress:
case KeyRelease:
case EnterNotify:
case ButtonPress:
case ButtonRelease:
case MotionNotify:
if (type_ahead_count < TYPEAHEADSIZE) {
if (++type_ahead_count == TYPEAHEADSIZE) {
altQueue = (XEvent*)XtMalloc(
(Cardinal)TYPEAHEADSIZE*sizeof(XEvent) );
alt_queue_size = TYPEAHEADSIZE;
eventP = altQueue;
}
else
eventP++;
}
else {
if (++alt_queue_count == alt_queue_size) {
alt_queue_size += TYPEAHEADSIZE;
altQueue = (XEvent*)XtRealloc(
(char*)altQueue,
(Cardinal)alt_queue_size*sizeof(XEvent) );
eventP = &altQueue[alt_queue_count];
}
else
eventP++;
}
break;
default:
XtDispatchEvent(eventP);
}
}
(void) wait(0);
DEBUG("done\n")
subProcessRunning = False;
if (output_to_pipe) {
CheckReadFromPipe( status->output_pipe[0],
&status->output_buffer,
&status->output_buf_size,
True
);
*bufP = status->output_buffer;
*lenP = status->output_buf_size;
close( status->output_pipe[0] );
XtRemoveInput( status->output_inputId );
}
if (status->error_pipe[0]) {
CheckReadFromPipe( status->error_pipe[0],
&status->error_buffer,
&status->error_buf_size,
True
);
close( status->error_pipe[0] );
XtRemoveInput( status->error_inputId );
}
if (status->error_buffer != NULL) {
if ((strcmp(argv[0], "/bin/sh") == 0) &&
(strcmp(argv[1], "-c") == 0)) {
status->shell_command = XtNewString(argv[2]);
} else status->shell_command = (char*) NULL;
while (status->error_buffer[status->error_buf_size-1] == '\0')
status->error_buf_size--;
while (status->error_buffer[status->error_buf_size-1] == '\n')
status->error_buffer[--status->error_buf_size] = '\0';
DEBUG1( "stderr = \"%s\"\n", status->error_buffer )
PopupNotice( status->error_buffer, FreeStatus, (Pointer)status );
return_status = -1;
}
else {
XtFree( (Pointer)status );
return_status = 0;
}
for (;alt_queue_count;alt_queue_count--) {
XPutBackEvent(theDisplay, --eventP);
}
if (type_ahead_count) {
if (alt_queue_size) eventP = &typeAheadQueue[type_ahead_count];
for (;type_ahead_count;type_ahead_count--) {
XPutBackEvent(theDisplay, --eventP);
}
}
} else {
(void) execvp(argv[0], argv);
(void) execv(FullPathOfCommand(argv[0]), argv);
progName = argv[0];
Punt("(cannot execvp it)");
return_status = -1;
}
return return_status;
}
static void
CheckReadFromPipe(
int fd,
char **bufP,
int *lenP,
Bool waitEOF)
{
int nread;
#ifdef FIONREAD
if (!ioctl( fd, FIONREAD, &nread )) {
if (nread) {
int old_end = *lenP;
*bufP = XtRealloc( *bufP, (Cardinal) ((*lenP += nread) + 1) );
read( fd, *bufP+old_end, nread );
(*bufP)[old_end+nread] = '\0';
}
return;
}
#endif
do {
char buf[512];
int old_end = *lenP;
nread = read( fd, buf, 512 );
if (nread <= 0)
break;
*bufP = XtRealloc( *bufP, (Cardinal) ((*lenP += nread) + 1) );
memmove( *bufP+old_end, buf, (int) nread );
(*bufP)[old_end+nread] = '\0';
} while (waitEOF);
}
static void FreeStatus(
Widget w,
XtPointer closure,
XtPointer call_data)
{
CommandStatus status = (CommandStatus)closure;
if (status->popup != (Widget)NULL) {
XtPopdown( status->popup );
XtDestroyWidget( status->popup );
}
if (status->error_buffer != NULL) XtFree(status->error_buffer);
XtFree( closure );
}
int DoCommand(
char **argv,
char *inputfile,
char *outputfile)
{
int fd_in, fd_out;
int status;
if (inputfile != NULL) {
FILEPTR file = FOpenAndCheck(inputfile, "r");
fd_in = dup(fileno(file));
myfclose(file);
}
else
fd_in = -1;
if (outputfile) {
FILEPTR file = FOpenAndCheck(outputfile, "w");
fd_out = dup(fileno(file));
myfclose(file);
}
else
fd_out = -1;
status = _DoCommandToFileOrPipe( argv, fd_in, fd_out, (char **) NULL,
(int *) NULL );
return status;
}
char *DoCommandToString(char ** argv)
{
char *result = NULL;
int len = 0;
_DoCommandToFileOrPipe( argv, -1, -2, &result, &len );
if (result == NULL) result = XtMalloc((Cardinal) 1);
result[len] = '\0';
DEBUG1("('%s')\n", result)
return result;
}
char *DoCommandToFile(char **argv)
{
char *name;
FILEPTR file;
int fd;
name = MakeNewTempFileName();
file = FOpenAndCheck(name, "w");
fd = dup(fileno(file));
myfclose(file);
_DoCommandToFileOrPipe(argv, -1, fd, (char **) NULL, (int *) NULL);
return name;
}