Strange pager (less, more) behavior



Hi,

Presently, I'm developing a set of C programs that run via the command
line and exclusively use standard C library stream I/O: fopen, fputs,
fprintf, fclose, etc.. As a convenience to the user, I allow many of
the output files to be defaulted to stdout or stderr. For instance,
a message log file might default to stderr. My file open logic for
such files amounts to something like this:

FILE *f;
char *filename;

if (!filename || strcmp (filename, "-") == 0)
f = stdout;
else
f = fopen (filename, "w");

In such a manner, my programs frequently assign stdout and stderr
to program-defined FILE pointers, but I _never_ assign values to
stdout or stderr, and I never use freopen. As far as I've been able
to tell from C programming books and the Internet, my use of
stdout and stderr is legitimate.

However, I've noticed on my Linux 2.6.9 (and above) PCs that at
least some of my programs sometimes gets involved in a rather strange
bit of (mis)behavior when I pipe its output to a pager such as 'less'
or 'more'. When I do this, my command line looks something like
this:

$ ./myprogram 2>&1 | less

99.44% of the time, this works perfectly, but every once in a while,
the pager doesn't appear. Instead, 'bash' gives me these messages:

[1]+ Stopped ./myprogram 2>&1 | less

as though I'd pressed Ctrl-Z to stop the pipeline even though I've not
pressed any such key. 'ps auxw' reveals that my program has completed
(i.e., no process exists that has my program's name), but that 'less'
is in 'T' (traced or stopped) status. If I bring up 'gdb' and attach
it to the stopped 'less' process (foregrounding 'less' after attaching
it), I always see that 'less' is waiting in a call to tcsetattr()
with the TCSADRAIN flag (the 'less' function that calls tcsetattr() is
named raw_mode()). This flag indicates that the new terminal settings
are to take effect after pending I/O to the terminal has completed.
Since my program has finished running, the I/O that's expected by 'less'
is never going to happen, so it waits indefinitely until someone (the
Linux kernel) sends it a SIGSTOP (or SIGTSTP?) signal. When I issue 'fg',
'less' appears, complete with my program's output.

So, when this problem occurs, my program runs to completion, but 'less'
fails to appear, and instead is put into a "stopped" state. I've also
seen this problem with 'more' but not with 'most', so it's not just a
'less' problem. I've seen this behavior with Linux 2.6.9, 2.6.10,
and 2.6.16, but I've never seen it with Linux 2.6.5. It occurs with
bash 2.05b and 3.1, or with less 382 or 394. It also occurs with the
'more' that came with the util-linux-2.12 RPM that shipped with Fedora
Core 2. I've seen similar behavior involving a pager such as 'less'
with other programs such as 'grep'. The problem occurs in at least
two terminal emulators: konsole and xterm. Also, I've seen it happen
in a virtual console (i.e., outside of X11). In addition to 'bash',
I've also recreated the problem in AT&T ksh.

Although it may be significant that I've seen this problem only with
Linux 2.6.9 and above, I'm open to the possibility that my programs
might somehow be abusing stdout and stderr, thus helping to cause this
strange problem. The C programming resources I've seen don't provide
much of an example of how to direct file streams to stdout or stderr,
but if you could provide me with a pointer to a book or Web document
that provides details on how programs should handle stdout/stderr, that
would be great. Also, I've seen different opinions on if/when the stdio
streams ought to be fclose()d. I lean towards not explicitly closing
them, but I've seen this problem occur whether I fclose() them or not.
Finally, if this is a kernel bug or configuration error, I'd appreciate
some guidance on how I might work around it or fix it. Am I correct
that the kernel, bash, or some other process is issuing SIGSTOP against
'less' on account of the long wait in tcsetattr()? The PCs that suffer
from the problem started out as Fedora Core 2 and have been upgraded
with Linux 2.6.16, Bash 3.1, and less 394.

Thanks,
Dave
--
Dave Ulrick
Email: d-ulrick@xxxxxxx
Web: http://www.niu.edu/~ulrick/
.



Relevant Pages

  • Re: test whether stdout==stderr?
    ... > where the output is equal whether assigned in common or not. ... FreeBSD 4.10, and Linux 2.4.20: ... stdout and stderr are different ...
    (comp.os.linux.development.apps)
  • Re: Console redirection >> /var/log/... & /dev/ttyX
    ... And>> /var/log/domino means that stdout is redirected and appended to ... everything that goes to stdout or stderr is saved in the file. ... See man bash, under redirection. ... I'm not too hot with the shell (or Linux in general) and it's a ...
    (alt.os.linux)
  • FAQ 8.25 How can I capture STDERR from an external command?
    ... This message is one of several periodic postings to comp.lang.perl.misc ... both STDOUT and STDERR will go the same place as the ... script's STDOUT and STDERR, unless the systemcommand redirects them. ... You can also use file-descriptor redirection to make STDERR a duplicate ...
    (comp.lang.perl.misc)
  • FAQ 8.25 How can I capture STDERR from an external command?
    ... This message is one of several periodic postings to comp.lang.perl.misc ... both STDOUT and STDERR will go the same place as the ... script's STDOUT and STDERR, unless the systemcommand redirects them. ... You can also use file-descriptor redirection to make STDERR a duplicate ...
    (comp.lang.perl.misc)
  • FAQ 8.25 How can I capture STDERR from an external command?
    ... This message is one of several periodic postings to comp.lang.perl.misc ... both STDOUT and STDERR will go the same place as the ... script's STDOUT and STDERR, unless the systemcommand redirects them. ... You can also use file-descriptor redirection to make STDERR a duplicate ...
    (comp.lang.perl.misc)