[comp.sys.hp] xmh and HP-UX 7.0

jtb@cs.edinburgh.ac.uk (Jo Blishen) (02/22/91)

We're having a problem with xmh on our HP9000/375s.  We are running
the X11R4 distribution.  Instead of running the MH commands xmh generates 
lots of output of the form:

Warning: couldn't inquire bytes in pipe; errno = 25 Not a typewriter
Warning: couldn't inquire bytes in pipe; errno = 25 Not a typewriter
Warning: couldn't inquire bytes in pipe; errno = 25 Not a typewriter

which seems to come from a FIONREAD ioctl on a pipe.  Does anyone have a patch
for this?  Has anyone else seen this?  Or is it a configuration problem?  
Any input gratefully received.

-- 
Jo Blishen                         JANET: jtb@uk.ac.ed.lfcs
LFCS, Dept. of Computer Science    UUCP:  ..!mcvax!ukc!lfcs!jtb
University of Edinburgh            ARPA:  jtb%lfcs.ed.ac.uk@nsfnet-relay.ac.uk
Edinburgh EH9 3JZ, UK.             Tel:   031-650-5192

hardy@golem.ps.uci.edu (Meinhard E. Mayer (Hardy)) (02/25/91)

Same problem here on a 9000/370. Something may be missing 
in the app-defaults file, but I haven't figured it out --
just stopped using Xmh except for moving messages between folders.
 

Hardy 
			  -------****-------
Meinhard E. Mayer (Prof.) Department of Physics, University of California
Irvine CA 92717;(714) 856 5543; hardy@golem.ps.uci.edu or MMAYER@UCI.BITNET

andreas@hpcvlx.cv.hp.com (Jim Andreas) (02/26/91)

The problem is that the FIONREAD ioctl call is broken when
applied to pipes when running HP-UX 7.0 on S300 systems.  
I have added a workaround to the xmh code that *mostly* fixes
the problem.  You will get xmh dialog boxes popping up now and
then with obscure messages from the underlying mh utilities;
but I have been sucessful at ignoring these :-)   I have not had
the cycles to go back and ferret out the reason the underlying
utilities have things to say.

If you prefer a compiled version, I have placed a compressed 
executable file on hpcvaaz (15.255.72.15) that is accessable to 
anyone on the Internet.  You can get the file via anonymous
ftp in:

./pub/MitX11R4/xmh.3.7.0.Z

-----------------------------------------------------------------------
Jim Andreas             | andreas@cv.hp.com                | INTERNET
Hewlett-Packard Company | {backbone}!hplabs!hp-pcd!andreas | UUCP
1000 N.E. Circle        | (USA) (503) 750-2860             | VOICE
Corvallis, OR 97330     | (USA) (503) 750-3788             | FAX  
-----------------------------------------------------------------------
This response does not represent the official position of, or statement by,
the Hewlett-Packard Company.  This response is provided for informational
purposes only.  It is supplied without warranty of any kind.
-----------------------------------------------------------------------




Here is the fix:  

=============
xmh/command.c
=============
To save (my) time I will post the fixed module in its entirety,
it is only a 15K module (based upon MIT R4 ./mit/clients/xmh
sources).

#if !defined(lint) && !defined(SABER)
static char rcs_id[] =
    "$XConsortium: command.c,v 2.32 89/12/16 21:54:01 rws Exp $";
#endif
/*
 *			  COPYRIGHT 1987, 1989
 *		   DIGITAL EQUIPMENT CORPORATION
 *		       MAYNARD, MASSACHUSETTS
 *			ALL RIGHTS RESERVED.
 *
 * THE INFORMATION IN THIS SOFTWARE IS SUBJECT TO CHANGE WITHOUT NOTICE AND
 * SHOULD NOT BE CONSTRUED AS A COMMITMENT BY DIGITAL EQUIPMENT CORPORATION.
 * DIGITAL MAKES NO REPRESENTATIONS ABOUT THE SUITABILITY OF THIS SOFTWARE FOR
 * ANY PURPOSE.  IT IS SUPPLIED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY.
 *
 * IF THE SOFTWARE IS MODIFIED IN A MANNER CREATING DERIVATIVE COPYRIGHT
 * RIGHTS, APPROPRIATE LEGENDS MAY BE PLACED ON THE DERIVATIVE WORK IN
 * ADDITION TO THAT SET FORTH ABOVE.
 *
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted, provided
 * that the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the name of Digital Equipment Corporation not be
 * used in advertising or publicity pertaining to distribution of the software
 * without specific, written prior permission.
 */

/* command.c -- interface to exec mh commands. */

#include "xmh.h"
#include <sys/ioctl.h>
#include <sys/signal.h>
#ifndef SYSV
#include <sys/wait.h>
#endif	/* SYSV */

/* number of user input events to queue before malloc */
#define TYPEAHEADSIZE 20

#ifdef macII
#define vfork() fork()
#endif /* macII */

#if defined(SYSV) && !defined(hpux)
#define vfork() fork()
#endif /* SYSV and not hpux */


#ifndef FD_SET
#define NFDBITS         (8*sizeof(fd_set))
#define FD_SETSIZE      NFDBITS
#define FD_SET(n, p)    ((p)->fds_bits[(n)/NFDBITS] |= (1 << ((n) % NFDBITS)))
#define FD_CLR(n, p)    ((p)->fds_bits[(n)/NFDBITS] &= ~(1 << ((n) % NFDBITS)))
#define FD_ISSET(n, p)  ((p)->fds_bits[(n)/NFDBITS] & (1 << ((n) % NFDBITS)))
#define FD_ZERO(p)      bzero((char *)(p), sizeof(*(p)))
#endif /* FD_SET */


typedef struct _CommandStatus {
    Widget	popup;		 /* must be first; see PopupStatus */
    struct _LastInput lastInput; /* must be second; ditto */
    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();
static CheckReadFromPipe();

static void SystemError(text)
    char* text;
{
    extern int sys_nerr;
    extern char* sys_errlist[];
    char msg[BUFSIZ];
    sprintf( msg, "%s; errno = %d %s", text, errno, 
	     (errno < sys_nerr) ? sys_errlist[errno] : NULL );
    XtWarning( msg );
}


/* Return the full path name of the given mh command. */

static char *FullPathOfCommand(str)
  char *str;
{
    static char result[100];
    (void) sprintf(result, "%s/%s", app_resources.mh_path, str);
    return result;
}


/*ARGSUSED*/
static void ReadStdout(closure, fd, id)
    XtPointer closure;
    int *fd;
    XtInputId *id;	/* unused */
{
    register CommandStatus status = (CommandStatus)closure;
    CheckReadFromPipe(*fd, &status->output_buffer, &status->output_buf_size);
}


/*ARGSUSED*/
static void ReadStderr(closure, fd, id)
    XtPointer closure;
    int *fd;
    XtInputId *id;	/* unused */
{
    register CommandStatus status = (CommandStatus)closure;
    CheckReadFromPipe(*fd, &status->error_buffer, &status->error_buf_size);
}


static int childdone;		/* Gets nonzero when the child process
				   finishes. */
ChildDone()
{
    childdone++;
}

/* Execute the given command, and wait until it has finished.  While the
   command is executing, watch the X socket and cause Xlib to read in any
   incoming data.  This will prevent the socket from overflowing during
   long commands.  Returns 0 if stderr empty, -1 otherwise. */

static int _DoCommandToFileOrPipe(argv, inputfd, outputfd, bufP, lenP)
  char **argv;			/* The command to execute, and its args. */
  int inputfd;			/* Input stream for command. */
  int outputfd;			/* Output stream; /dev/null if == -1 */
  char **bufP;			/* output buffer ptr if outputfd == -2 */
  int *lenP;			/* output length ptr if outputfd == -2 */
{
    int return_status;
    int old_stdin, old_stdout, old_stderr;
    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));
    }

    if (outputfd == -1) {
	if (!app_resources.debug) { /* Throw away stdout. */
	    outputfd = open( "/dev/null", O_WRONLY, 0 );
	    old_stdout = dup(fileno(stdout));
	    (void) dup2(outputfd, fileno(stdout));
	    close(outputfd);
	}
    }
    else if (outputfd == -2) {	/* make pipe */
	if (pipe(status->output_pipe) /*failed*/) {
	    SystemError( "couldn't re-direct standard output" );
	    status->output_pipe[0]=0;
	}
	else {
	    outputfd = status->output_pipe[1];
	    old_stdout = dup(fileno(stdout));
	    (void) dup2(status->output_pipe[1], fileno(stdout));
	    FD_SET(status->output_pipe[0], &fds);
	    status->output_inputId =
		XtAddInput( status->output_pipe[0], (XtPointer)XtInputReadMask,
			    ReadStdout, (XtPointer)status
			   );
	    status->output_buffer = NULL;
	    status->output_buf_size = 0;
	    output_to_pipe = True;
	}
    }
    else {
	old_stdout = dup(fileno(stdout));
	(void) dup2(outputfd, fileno(stdout));
    }

    if (pipe(status->error_pipe) /*failed*/) {
	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));
	FD_SET(status->error_pipe[0], &fds);
	status->error_inputId =
	    XtAddInput( status->error_pipe[0], (XtPointer)XtInputReadMask,
		        ReadStderr, (XtPointer)status
		       );
    }

    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) {			/* We're the parent process. */
	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)) {
		/* this is gross, but the only other way is by
		 * polling on timers or an extra pipe, since we're not
		 * guaranteed to be able to malloc in a signal handler.
		 */
		readfds = fds;
                if (childdone) break;
DEBUG("blocking.\n")
		(void) select(num_fds, (int *) &readfds,
			  (int *) NULL, (int *) NULL, (struct timeval *) NULL);
DEBUG1("unblocked; child%s done.\n", childdone ? "" : " not")
		if (childdone) break;
		if (!FD_ISSET(ConnectionNumber(theDisplay), &readfds))
{DEBUG("reading alternate input...")
		    XtProcessEvent((unsigned) XtIMAlternateInput);
DEBUG("read.\n")}
	    }
	    if (childdone) break;
	    XtAppNextEvent(app, eventP);
	    switch(eventP->type) {
	      case LeaveNotify:
		if (type_ahead_count) {
		    /* do compress_enterleave here to save memory */
		    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;
		    }
		}
		/* fall through */
	      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);
	    }
	}
#ifdef SYSV
	(void) wait((int *) NULL);
#else /* !SYSV */
	(void) wait((union wait *) NULL);
#endif /* !SYSV */

	DEBUG("done\n")
	subProcessRunning = False;
	if (output_to_pipe) {
	    CheckReadFromPipe( status->output_pipe[0],
			       &status->output_buffer,
			       &status->output_buf_size
			      );
	    *bufP = status->output_buffer;
	    *lenP = status->output_buf_size;
	    close( status->output_pipe[0] );
	    close( status->output_pipe[1] );
	    XtRemoveInput( status->output_inputId );
	}
	if (status->error_pipe[0]) {
	    CheckReadFromPipe( status->error_pipe[0],
			       &status->error_buffer,
			       &status->error_buf_size
			      );
	    close( status->error_pipe[0] );
	    close( status->error_pipe[1] );
	    XtRemoveInput( status->error_inputId );
	}
	if (status->error_buffer != 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 {			/* We're the child process. */
	(void) execv(FullPathOfCommand(argv[0]), argv);
	(void) execvp(argv[0], argv);
	Punt("Execvp failed!");
	return_status = -1;
    }
    return return_status;
}


static /*void*/
CheckReadFromPipe( fd, bufP, lenP )
    int fd;
    char **bufP;
    int *lenP;
{
#ifdef   hpux
    int oldflags;
    int newflags;
    char c;
    /*
     * The FIONREAD ioctl is broken on HP-UX, so substitute
     * the following workaround.   This code sets the O_NDELAY
     * mode on the pipe, so that we can read from it without
     * blocking.  The code then reads bytes from the pipe
     * until the read returns with zero bytes.
     */
    if (( oldflags = fcntl( fd, F_GETFL, 0 )) < 0 )
	SystemError( "CheckReadFromPipe: error on fcntl F_GETFL" );
    newflags = oldflags | O_NDELAY;
    if ( fcntl( fd, F_SETFL, newflags ) < 0 )
	SystemError( "CheckReadFromPipe: error on fcntl F_SETFL" );

    if (read( fd, &c, 1 ) == 1 )
    {
	char * pChar;
	int    ByteCount = 1;
	int    old_len = *lenP;

	*bufP = XtRealloc( *bufP, (Cardinal) ((*lenP += BUFSIZ) + 1) );
	pChar = *bufP + old_len;
	*pChar++ = c;
	for ( ;; )
	{
	    int RetCode;

	    if (( RetCode = read( fd, &c, 1 )) == 0 )
	    {
		*pChar = '\0';
		break;
	    }
	    if ( RetCode < 0 )
	    {
		SystemError( "CheckReadFromPipe: error on read from pipe" );
		*pChar = '\0';
		break;
	    }
	    if ( ByteCount == BUFSIZ )
	    {
		old_len = *lenP;
		*bufP = XtRealloc( *bufP, (Cardinal) ((*lenP += BUFSIZ) + 1) );
		ByteCount = 0;
		pChar = *bufP + old_len;
	    }
	    *pChar++ = c;
	    ByteCount++;
	}
    }
    if ( fcntl( fd, F_SETFL, oldflags ) < 0 )
	SystemError( "CheckReadFromPipe: error on fcntl F_SETFL" );

#else /* hpux */

    long nread;
    if (ioctl( fd, FIONREAD, &nread ) /*failed*/) {
	SystemError( "couldn't inquire bytes in pipe" );
    }
    else if (nread) {
	char buf[BUFSIZ];
	int old_end = *lenP;
	*bufP = XtRealloc( *bufP, (Cardinal) ((*lenP += nread) + 1) );
	while (nread > BUFSIZ) {
	    read( fd, buf, BUFSIZ );
	    bcopy( buf, *bufP+old_end, BUFSIZ );
	    nread -= BUFSIZ;
	    old_end += BUFSIZ;
	}
	read( fd, buf, (int) nread );
	bcopy( buf, *bufP+old_end, (int) nread );
	(*bufP)[old_end+nread] = '\0';
    }
#endif /* hpux */
}


/* ARGSUSED */
static void FreeStatus( w, closure, call_data )
    Widget w;			/* unused */
    Pointer closure;
    Pointer call_data;		/* unused */
{
    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 );
}

/* Execute the given command, waiting until it's finished.  Put the output
   in the specified file path.  Returns 0 if stderr empty, -1 otherwise */

DoCommand(argv, inputfile, outputfile)
  char **argv;			/* The command to execute, and its args. */
  char *inputfile;		/* Input file for command. */
  char *outputfile;		/* Output file for command. */
{
    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 );
    if (fd_in != -1) close(fd_in);
    if (fd_out != -1) close(fd_out);
    return status;
}

/* Execute the given command, waiting until it's finished.  Put the output
   in a newly mallocced string, and return a pointer to that string. */

char *DoCommandToString(argv)
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;
}
    

/* Execute the command to a temporary file, and return the name of the file. */

char *DoCommandToFile(argv)
  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);
    close(fd);
    return name;
}


/* EOF */