Skip site navigation (1)Skip section navigation (2)
Date:      Mon, 6 Mar 2017 20:21:12 +1100 (EST)
From:      Bruce Evans <brde@optusnet.com.au>
To:        Ed Schouten <ed@nuxi.nl>
Cc:        "Conrad E. Meyer" <cem@freebsd.org>,  src-committers <src-committers@freebsd.org>, svn-src-all@freebsd.org,  svn-src-head@freebsd.org
Subject:   Re: svn commit: r314685 - head/bin/ps
Message-ID:  <20170306183546.E1414@besplex.bde.org>
In-Reply-To: <CABh_MKkjZdOWm05Hi%2BkcNh_UNRoK=JF_MZdkzOhC912%2B=1aONA@mail.gmail.com>
References:  <201703042238.v24McAD8008837@repo.freebsd.org> <CABh_MKnux-yfKhFeuApsZSNgyO1sktA_%2BDphhHQXaZezx92Cpw@mail.gmail.com> <CAG6CVpX_Q9JiW35Po7c4=TDSKy94%2BorFRSk_6u093T%2BbY2NL1Q@mail.gmail.com> <CABh_MKkjZdOWm05Hi%2BkcNh_UNRoK=JF_MZdkzOhC912%2B=1aONA@mail.gmail.com>

next in thread | previous in thread | raw e-mail | index | archive | help
On Sun, 5 Mar 2017, Ed Schouten wrote:

> 2017-03-05 0:08 GMT+01:00 Conrad Meyer <cem@freebsd.org>:
>>> - If something is a TTY, then our implementation of the TTY layer
>>> guarantees that TIOCGWINSZ always works.
>>
>> Do you know if it did in 1990 too?  It's hard to tell why Marc@ made
>> this change way back then.  I wasn't sure so I left it alone.
>
> I can't tell. :-)

Actually, it is easy to tell.  This has nothing to do with kernel bugs
that prevent TIOCGWINSZ working.  It is to find the user's terminal
so that the correct width can be determined.  The correct default width
is always that of the user's terminal.  The problem is that it isn't
clear what the user's terminal is in redirected cases.

As described in the log message, the default width was originally hard-
coded as 80 (not even 79 to allow for autowrap).

   (BTW, althought the commit was remarkably diligent in describing the
   history, it is inconvenient to look up the details on the web, and
   the web pages are almost unreadable even after downloading them and
   demangling the formatting a bit.)

   (The commit was not diligent enough to track down the history of
   the man pages and whether they were consistent with the code.  The
   original man page should have described the hard-coded default as
   being 80, and said how wrong this is in the BUGS section, including
   its off-by-1 error.)

   (The commit and perhaps the man page were also not diligent enough
   to describe the details of the overrides for the default.  In the
   original version, one -w gave width 132 (another off-by-1-error),
   and more than 1 -w gave width BUFSIZ (the worst abuse of stdio's
   BUFSIZ mistake that I remember noticing).)

Kirk changed the default to the terminal width given by TIOCWINSZ on
stdin (spelled 0 instead of the current STDIN_FILENO).  That is good
for most uses.  Users normally have all 3 std fd's connected to the
same terminal, so TIOCWINSZ on any of them works.  Plain ps goes
to /dev/tty so any choice works.  Normal redirection is ps | less and
ps >foo; this doesn't redirect stdin, so TIOCWINSZ on stdin works.
Hopefully he changed the man page to describe the new behaviour.

The following bugs are visible in Kirk's patch:
- the magic numbers 80 and 132 and the full terminal width still have
   the off-by-1 error
- the magic numbers are more broken than before.  The magic width of
   80 is still abused as a flag to determine if -w has already been
   applied.  This no longer works.  If the terminal width is anything
   except 80, then the first -w gives width BUFSIZ with no intermediate
   step to 132, although such a step is more needed than before if the
   terminal width is < 80.  This is fixed in -current by counting -w's.

Next, marc@ improved the detection of user terminals.  TIOCGWINSZ is
tried on all 3 std fd's.  This fixes the case where stdin is redirected
but one of the other std fd's is not redirected (and none of the redirected
fd's is to a terminal with unsuitable size).  It would be unusual to
redirect stdin, and not necessary for ps since ps doesn't read stdin.
It might be done for invoking a script that uses ps for output but
piped input for other utilities in the script.  Not redirecting stderr
together with stdin has even more uses.

The order of the TIOCGWINSZ calls is subtle.  marc@ kept stdin first,
perhaps for compatibility.  stdout is obviously more relevant than
stderr so is checked next, then stderr.  But since the output should
normally be formatted for stdout, especially if stdout is a terminal,
it is better to check stdout first.  This was done soon atfer marc@'s
change.  The diligence in the commit wasn't remarkable enough to uncover
the details.  The order was improved to stdout first and stdin last
by some change beteen marc@ and FreeBSD-1.  FreeBSD also used the fancy
spellings STD*_FILENO.  I thought it was from Net/2 slightly earlier
than 1990.  ps.c has a 1990 Regents copyright but a 5.43 (Berkelely)
7/1/91 sccsid.  marc@'s diff has a 1989 Regents copyright and 1.8 and
5.20 sccsids (obscured by the diff format, but clearly early).

Counting for -w was still broken in FreeBSD-1, but was fixed in 4.4Lite1.

The next fix in FreeBSD was to add COLUMNS support in 1997 (tjr r97804).
According to the commit log, this is for SUSv3.  This is now required
by POSIX.  POSIX also has considerable detail on allowable truncations
of fields, but at least in 2001 doesn't have anything about the default
total width.

After that, all related changes discussed here are regressions.  Apple
places the !isatty(STDOUT_FILENO) check later than in this commit, so
it isn't so clear what it does.  When stdout is not a tty, the Apple
change blows away the defaults set earlier,including the COLUMNS
override specified by POSIX.  In FreeBSD, isatty() is implemented using
tcgetattr(), and tcgetattr() is implemented using ioctl(...TIOCGETA...).
It would take a weird terminal to support TIOCGETA but not TIOCGWINSZ.
Weird terminals (including initial and lock state devices?) tend to
supply dummy values for both.  Perhaps they shouldn't supply either,
since they are not really terminals.

Assume for simplicity that isatty() is true if TIOCGWINSZ succeeds.  Then
in Apple's version, the carefully ordered TIOCGWINSZ calls on stderr and
stdout are dead code, since the result from them will be canceled if they
were reached.

The FreeBSD version is similar except it doesn't break COLUMNS or obfuscate
the initialization by canceling it much later.  After COLUMNS, it checks
isatty(STDOUT_FILENO).  Then under the simplifing assumption, if
isatty(STDOUT_FILENO) is true, TIOCGWINSZ on stdout succeeds and the other
2 carefully ordered TIOCGWINSZ calls are unreachable (Apple reaches them
but cancels them).

Aother bug in the FreeBSD version is that it doesn't update the
documentation.  Actually, the documentation is fuzzy enough to not be
affected by the change.  It mainly says:

X      -w      Use 132 columns to display information, instead of the default
X              which is your window size.  If the -w option is specified more
X              than once, ps will use as many columns as necessary without
X              regard for your window size.  Note that this option has no effect
X              if the ``command'' column is not the last column displayed.
X 
X      COLUMNS      If set, specifies the user's preferred output width in
X                   column positions.  By default, ps attempts to automatically
X                   determine the terminal width.

The first error is the off-by-1 error in 131 and no mention of auto-wrap.
Next, the the default width is fuzzily described, but not fuzzily enough
to have no errors, exept for COLUMNS it is described as the "best automatic
attempt".  That the default when there is no terminal is 79 is not mentioned.
In the description of -w, it is stated that the default is "your" window
size, but as this mail shows it is unclear where this window is.

I now remember noticing the caveat in the last sentence for -w.  -w works
reasonably for ps l, but not for fancier formats.  COLUMNS must be broken
for fancier formats too, since COLUMNS affects the width variable like -w.

>>> - If we're only interested in testing stdout whether it's a TTY, I
>>> think it makes little sense to check TIOCGWINSZ on stdin, stderr.

This removes the code that was essentially killed by the commit, but it
shouldn't have been killed.

>>> I think there would therefore be very little harm to use something like this:
>>>
>>> |         if ((cols = getenv("COLUMNS")) != NULL && *cols != '\0')
>>> |                 termwidth = atoi(cols);
>>> |         else if ((ioctl(STDOUT_FILENO, TIOCGWINSZ, (char *)&ws) ==
>>> -1 && ws.ws_row == 0)
>>
>> Shouldn't this remain || ws.ws_row == 0?
>
> Err, yes. ||, not &&.

When any of the 3 std fd's is a terminal, the fixes in 1990-1991 are clearly
right.  When none of them is a terminal, the default should have been
UNLIMITED, but it has always been 79 or 80.  ps users should know this,
although it is undocumented.  If you break this, then you also should break
-w and -ww and the 131-column in-beteen value that they give.  131 is
just a convenient value between 79 and UNLIMITED.  Changing this would
break all scripts that assume the undocumented 79 or use -w to get the
documented 131.  I use ps in scripts mainly for logging and don't really
care about the width.

I think your suggestion is to remove the defaulting to 79 too, and default
to UNLIMITED.  This completes breaking of the undocumented 79, but leaves
the documented 131 and -w untouched and almost useless.  When the default
is not 79, they are only reachable if TIOCWINSZ succeeds on stdout (and
COLUMNS is not set -- -w really shouldn't pply if COLUMNS is set, but it
does).  Then if the terminal width is < 132, ps's width can be increased
to 131 with one -w.  Not very useful.  Two -w's are useful for reaching
UNLIMITED.

Bruce



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?20170306183546.E1414>