[comp.windows.x] redirecting stderr; passing arg lists

argv@SUN.COM (Dan Heller) (11/09/89)

> On Nov  8, 10:01am, Bill Wohler wrote:
>   i want to redirect all of the output going to stderr and have it go
>   to a X toolkit widget instead of the controlling window.  i figured
>   a 
> 	XtAddInput(fileno(stderr), XtInputReadMask|XtInputWriteMask, 
> 		   output_procedure, output_widget);
If you've been reading comp.windows.x, you'll see a very short-lived
discussion about how XtAddInput is broken --I recommend *strongly*
that you avoid using it till it's fixed.  This includes R4; I checked
the source.  The problem is that your routine gets called continuously
rather than, as the spec says, "when there is data to be read."
(details below.)

You can redirect stderr of a process to a widget quite simply :-)

    int stderr_pipe[2];
    ...
    pipe(stderr_pipe); /* set up a pipe */
    dup2(stderr_pipe[1], fileno(stderr)); /* redirect stderr to pipe */

Now your stderr has been closed and any writing to stderr will now go
thru the pipe.  You have to read the data from the other end of the pipe.
If you want to continue to use FILE *'s to read stderr, then you can do
the following:
    FILE *fp = fdopen(stderr_pipe[0], "r");  /* read the other end of pipe */
Now you can do things like'
    fgets(buf, sizeof buf, fp);
Note, this is buffered IO now, so you might want to setbuf(fp, NULL).

Now that you have stderr redirected to a place you can read from, you
need to determine when there is data to be read.  As you noted, the
best way to do it would be using XtAddInput() almost as you had it above.
Just remove the "WriteMask" becuase you're not writing to the filedesc.
	XtAddInput(stderr_pipe[0], XtInputReadMask, 
		   widget_output_procedure, output_widget);
The problem here is that widget_output_procedure() is going to be called
continuously.  So, instead, I recommend that you set up a timer to
time out evewry 5 seconds or so using XtAddTimeOut(...).  (I'm not going
to go into the details about how to use this function.)  When your callback
function from the timer is called, simply check if there is stuff to read:
    ioctl(stderr_pipe[0], FIONREAD, &stuff_to_read);
If (stuff_to_read == 0), then return.  NOTE: you should do that in the
callback routine specified in XtAddInput anyway!  This is what Xt should be
doing before it decides to call your callback routine (which is why it's
"broken").

-----------
>   as a workaround, i then replaced all my fprintf(stderrs) with a
>   function call to write directly to the widget.
> 	output_procedure("%s: cannot open %s.", "mycommand", filename);

void
output_procedure(va_alist)
va_dcl
{
    char *fmt, buf[BUFSIZ];
    va_list args;

    va_start(args);
    fmt = va_arg(args, char *);
    (void) vsprintf(buf, fmt, args);
    va_end(args);
    WidgetSet(output_widget, XtNstring, buf, NULL);
}

>   a six-pack of weitzen to the person who solves problem 2 and a case
>   each of weitzen and pils to the person who solves problem 1.  thanks!!!
I'll be in your neighborhood around xmas time -- I'm looking
forward to those Pils!


					--dan

mayer@hplabsz.HPL.HP.COM (Niels Mayer) (11/10/89)

In article <8911081827.AA12003@turnpike.sun.com> argv@SUN.COM (Dan Heller) writes:
>If you've been reading comp.windows.x, you'll see a very short-lived
>discussion about how XtAddInput is broken --I recommend *strongly*
>that you avoid using it till it's fixed.  This includes R4; I checked
>the source.  The problem is that your routine gets called continuously
>rather than, as the spec says, "when there is data to be read."
>(details below.)

Thanks for the info, Dan. However, I need some clarification:

I've been using XtAddInput() with no problems in programs like xwebster
(ftp from expo.lcs.mit.edu:contib/xwebster.tar.Z) and winterp (hopefully on
x11r4 contrib tape). I have not noticed the problems you describe in either
program.

Does the problem arise only when trying to use XtAddInput() with a
FILE*/Pipe? I think I might have seen such problems in an early version of
xwebster in which I communicated to the webster dictionary server via a
telnet process that had been popen'd. I then decided to make things more
efficient by mucking with sockets directly. Winterp also uses sockets
selected via XtAddInput() in order to allow other programs to send lisp
forms to the "widget interpreter" (a lisp server). Both of these use
XtAddInput() with no problems that I can tell.

I seem to have missed the "short lived iscussion about how XtAddInput is
broken". Would anyone happen to have it archived anywhere? Can you
mail it to me, or repost it?

-------------------------------------------------------------------------------
	    Niels Mayer -- hplabs!mayer -- mayer@hplabs.hp.com
		  Human-Computer Interaction Department
		       Hewlett-Packard Laboratories
			      Palo Alto, CA.
				   *

argv%turnpike@Sun.COM (Dan Heller) (11/10/89)

In article <4338@hplabsz.HPL.HP.COM> mayer@hplabs.hp.com (Niels Mayer) writes:
> In article <8911081827.AA12003@turnpike.sun.com> argv@SUN.COM (Dan Heller) writes:
> >If you've been reading comp.windows.x, you'll see a very short-lived
> >discussion about how XtAddInput is broken --I recommend *strongly*
> >that you avoid using it till it's fixed.

I feel awful every time I read that :-|  Sorry, XtAddInput is fine..
My apologies to those I may have confused/offended :-)  My previous note
on this issue attempts to discuss the problem of what to do when EOF
is reached.  I can't add to much to that part of the discussion tho,
since I haven't had the time to play with it much more than I already
have ...

However, the whole issue about redirecting stderr is still viable.
You can use the code I explained in the original message to redirect
stderr of forked programs or even your own program to a pipe in which
you can read it and send the data to a text widget/object of some kind.
This is quite useful..

> I seem to have missed the "short lived iscussion about how XtAddInput is
> broken". Would anyone happen to have it archived anywhere? Can you
> mail it to me, or repost it?

It's about 100 messages back from this one (depending on your new server),
and having reread it, you're not missing a thing :-)

> 		  Human-Computer Interaction Department
That sounds scary ...or kinky.   ;-)

dan

wohler@ITSTD.SRI.COM (Bill Wohler) (11/28/89)

dan,

  thanks to you and ted nolan for helping me with my problem.  the
  reason i didn't reply sooner is that i had to do a bit of hacking to
  your solution.

  to snarf stderr, i piped and dupped as you said.  i also found that
  things worked better to make the pipes unbuffered since a newline
  was not always in the error messages.

  to check if there was any input to read in stderr, you suggested:

         ioctl(stderr_pipe[0], FIONREAD, &stuff_to_read);

     If (stuff_to_read == 0), then return.  NOTE: you should do that
     in the callback routine specified in XtAddInput anyway!  This is
     what Xt should be doing before it decides to call your callback
     routine (which is why it's "broken").

  this works great on my sun.  however, the x folks would not want to
  put this in their toolkit since the FIONREAD is documented not to
  support pipes in hpux and it is also documented not to be
  implemented at all on the 300 series (woe is me!)

  alternatively, i tried select, except that select still said that
  there was stuff in the input buffer after my read!  so that was of
  no use (unless someone can explain why this was so).

  my final solution was to use a non-blocking read.  appears to work
  great.  how inefficient or ugly is this?  here are the code
  fragments that work on both my sun (os 3.4) and hp (hp9000s300, os
  6.2). 

  dan, guess i still owe you a couple of bottles of weizen or so. ;-)

static
dbat_errinit()
{
	int stderr_pipe[2];		/* maybe needs to be static? */
	extern	int dbat_errdisp();

	(void)pipe(stderr_pipe);
	(void)dup2(stderr_pipe[1], fileno(stderr));
	(void)fcntl(stderr_pipe[0], F_SETFL, O_NDELAY);
	setbuf(stderr, NULL);
	XtAddTimeOut(ERRDISP_UPDATE*MSECPERSEC, dbat_errdisp, stderr_pipe[0]);
}

static
dbat_errdisp(client_data, id)
	caddr_t	client_data;
	XtIntervalId *id;
{
	int	fd = (int)client_data;
	register int chrs;
	char	buf[BUFSIZ];
	extern	int dbat_errdisp();

	buf[0] = '\0';
	chrs = read(fd, buf, sizeof(buf));

	if (buf[0]) {
		buf[chrs] = '\0';
		XwTextInsert(Errors, buf);
	}

	XtAddTimeOut(ERRDISP_UPDATE*MSECPERSEC, dbat_errdisp, fd);
}