Date:        Mon, 12 Jul 2021 09:48:33 +0100
    From:        "Geoff Clare via austin-group-l at The Open Group" 
<[email protected]>
    Message-ID:  <20210712084833.GA31700@localhost>

  | That's not the only way. The usual traversal via .., ../.., etc. could
  | be done, but just storing dev/ino pairs on the way up and then reading
  | the directories and looking for those stored values on the way down.

One could do almost anything.   But why would anyone bother?

  | Or, a modern implementation could open a file descriptor for each
  | directory instead of storing dev/ino, and use openat() and fdopendir()
  | on the way down.

A modern implementation would use getcwd() - only an implementation from
before that existed would implement manual scanning in pwd itself, and one
from that long ago would not contemplate that method (even if it had been
possible back then) as the path depth would be limited by the number of
available file descriptors (now, not a problem, but back then, with just
16 or 20, a real issue - even now if RLIMIT_NOFILE is set low enough.)

The argument (assuming a non getcwd() implementation) that you should be
making is that the implementation (working up the tree) might store each
file name (path name component) in a separate buffer - perhaps even still
in the buffer as read from the directory, and then construct the final result
with a
        next = root_dir_entry;
        while (next != NULL) {
                printf("/%s", next->name);              /* note next->name can 
be "" */
                next = next->nxt;
        }
        printf("\n");

type loop, so it never needs to malloc() a buffer for the string, or strlen()
it, or realloc() it (possibly many times) as it grows.

That would be a plausible (prohibited) implementation (though if one ensured
sufficient buffer size for standard output buffering, and always forced it
on, perhaps not prohibited .. do the same thing using write() instead of
printf for an obviously prohibited, but plausible, version).

There's no real reason this needs to be prohibited though, you're certainly
right that the reason for the prohibition is almost certainly to prevent
writing before all the components have been determined, not to attempt to
control the number of write() system calls issued (a version using old stdio,
from before line buffering, writing to a terminal, would probably do one
write sys call for each character in the output).

That's why it is a bit weird, as it is hard to imagine any implementation,
from any era, ever behaving the way that is intended to prohibit - the whole
thing looks like someone's imagined "but they might..." musing, ignoring the
fact that no-one ever actually would.

  | I have already pointed out (not sure if I actually quoted them) the
  | parts of the standard that make it crystal clear that it requires what
  | I say it requires.

Sorry, that was never clear to me.   Still isn't.

  | As a reminder, these are the relevant parts of the standard:
  |
  | 1. The pwd DESCRIPTION and STDOUT say it writes an absolute pathname
  | of the current working directory to standard output. (And it is clear
  | that this is the sole purpose of pwd.)

Sure, though the "sole purpose" thing seems to be an invention thrown in
here because of discussions during this debate - that's not mentioned (as
an attribute, nor as a goal for any particular requirement, in the standard
either).

  | 2. The meaning of "standard output"

Let's forget that "issue", which isn't an issue - no-one is really
debating that (despite some of the earlier messages).   Further, it
doesn't really matter what it means to this discussion.  It is where
we send the output, and if someone ever got that wrong, that would be
a completely different discussion.

  | 3. The pwd EXIT STATUS section says that status 0 means "Successful
  | completion".

Sure.

  | Together these things constitute a requirement that pwd only exits
  | with status 0 if it has successfully written an absolute pathname of
  | the current working directory to file descriptor 1.

No they don't, that's a conclusion you're jumping to.  It might seem to
make sense, but nothing anywhere actually says that.

It says we have to write to standard output, pwd (the version I use) does
that.   It says to exit(0) when that has happened and pwd is satisfied,
my version does that.   It nowhere explicitly says that anything is required
to check the status of the write.   Even now you haven't attempted to show
anywhere that it does.   You're just assuming that "write to standard output"
means that the output must have successfully been transferred somewhere.

You believe that your view is the only thing that makes sense.   But I know
it cannot be correct, because POSIX was documenting the standard behaviour,
of the implementations, the same as it still does (or should) and this was
not a case where there was any real deviation.  No-one checked that kind of
thing, so to pretend (or require) that they did would not have been 
appropriate.   Hence any attempt to read the words as if it did cannot be
the correct reading.

  | It would be irresponsible of the standard developers to change the
  | standard to allow buggy behaviour just because it is common in
  | implementations, particularly with a bug that is as serious as this
  | one - utilities failing to diagnose write errors can cause silent data
  | loss.

Since there is nothing that is there now that needs changing to allow this,
the standard does not prohibit this "bug", that is not an issue.  No-one is
requesting a change to the standard for this, just pointing out the the
standard does not require what you think it does.

Let's abandon pwd for a while, and look at its close cousin, cd

82490 STDOUT
82491 If a non-empty directory name from CDPATH is used, or if the operand '-' 
is used, an absolute
82492 pathname of the new working directory shall be written to the standard 
output as follows:

82493 "%s\n", <new directory>

(line numbers again from 202x-D2).

That's very similar wording to that in pwd.

82512 CONSEQUENCES OF ERRORS
82513 The working directory shall remain unchanged.

If your view is correct, then if there is a write error writing the
pathname of the new working directory (and because of that wording, it
must be after a successful chdir() operation, or we would not know that
it is the new working directory) then the working directory remains
unchanged (ie: cd would be required to return to the previous working
directory ... but absolutely nothing does that, so that is not the standard.)

And (some of EXIT STATUS)

82501 EXIT STATUS
82502 The following exit values shall be returned:

82503 0 The current working directory was successfully changed and the value of 
the PWD
82504 environment variable was set correctly.

82508 >0 Either the -e option or the -P option is not in effect, and an error 
occurred.

Then let's do a test, using bash (it is a little messy, as doing a cd
to the left of a pipe almost certainly changes only the sub-shell, so we
need to put all of the reporting in there, none of that is important).

bash5 $ trap '' PIPE
bash5 $ echo $PWD $OLDPWD
/tmp/b /tmp/a
bash5 $ { sleep 1; cd - ; echo status:$? >&2; pwd >&2 ; } | :
bash: cd: write error: Broken pipe
status:1
/tmp/a

The test used the '-' operand, so the "if" in the STDOUT section is
satisfied.   So bash wrote the new PWD to stdout, as required, or
attempted to, and (as has been reported before) checks for a write
error, and when that happens, does exit 1, and writes a diagnostic
message to stderr (we know that as stdout goes nowhere).

All seems good, except for the Consequences of Errors.   "The working
directory shall remain unchanged" - but it didn't.    And in case you're
unsure about that, maybe it was just PWD that changed, and pwd reported $PWD
instead of the actual working directory name, here's a more brutal version:

bash5 $ trap '' PIPE
bash5 $ echo $PWD $OLDPWD
/tmp/b /tmp/a
bash5 $ { sleep 1; cd - ; echo status:$?>&2; unset PWD; /bin/pwd >&2 ; } | :
bash: cd: write error: Broken pipe
status:1
/tmp/a

Broken, but all because of checking for the write error.   Do the
same thing using other shells:

FreeBSD:  same as bash except exit status is 2
dash:  nothing on stderr, exit status 1, directory changed
zsh:  error message on stderr, status 0, directory changed

Everything else (NetBSD, mksh (@(#)MIRBSD KSH R59 2020/05/16), bosh,
ksh93, yash) does: nothing on stderr, status 0, directory changed.

The bosh test is not useful though, since it doesn't bother to do
the required output at all ...

bosh $ echo $PWD $OLDPWD
/tmp/b /tmp/a
bosh $ cd - 
bosh $ echo $PWD $OLDPWD
/tmp/a /tmp/b

Bosh does however do:

bosh $ { sleep 1;  pwd;  echo status:$? >&2; } | :
status:0

however, so I would guess that if it did write the directory pathname
it would ignore the write error there, just as it does for pwd's output.


(All the others, not just the "everything else" set, all, write the
new directory to stdout as required on a simple "cd -" when no write
errors are forced).

Which of these do you believe best fits the needs of the users, and
for that matter, the standard?

bash and the FreeBSD sh are simply broken - dash is doubly broken as
it changed the directory, exited status 1, and did not write an error msg.

The only time the standard allows exit 1 after successfully changing
directory is when using both -P and -e (I used neither explicitly, though
-P is the default (and only) in the NetBSD sh) and PWD after the chdir()
could not be determined (which is not the case here, no problem discovering
the new directory name - it was its output that caused the issue).
(-e and everything related to it is new, bash, the FreeBSD sh, and mksh
were the only shells I tested that support it at all).

IMO - the answer is the "everything else" the directory successfully changed,
so the exit status is 0, a non-zero exit status (other then -Pe) should
always mean that the current working directory of the process (script) was
not altered.  zsh comes close, but the message on stderr indicates there
was an error, and when there's an error, the status is not supposed to be 0,
right?

The way we get to something sane is by not reporting stdout write errors,
the way it has always been, and which the standard still allows.

If you now spout some "not the sole purpose of cd" argument, you know that
I am going to ask you to cite the text in the standard which says that, or
anything like it.  So just go ahead and include the relevant text in your
reply if you make that argument, otherwise we will just treat it as specious.

You can't even really file yet another bug report here, and try to add a new
case for a non-zero exit status when the directory changed, but the write
of its name failed, as that is clearly *not* the standard behaviour of shells,
it would be (close enough to) pure invention, and completely inappropriate.

kre

  • Re: utilities and wri... Robert Elz via austin-group-l at The Open Group

Reply via email to