Why does write() to stdin work?

T

Tim Rentsch

That's the undefined part. OP went outside the spec and got
lucky. That won't always happen.

I'd like to offer some counterpoint to the various "what
happened is undefined behavior" remarks.

1. There may indeed have been a declaration in scope,
resulting from flags or compiler option settings. We
cannot say for sure just by looking at the source.

2. Presumably the code compiled and ran, so either
there was an earlier declaration, or one was not
necessary for a successful compile.

3. A declaration is not necessary for a successful
compile in C90. Alternatively, there may have been a
compiler option to treat an undeclared function the
same way as C90 does (after issuing a diagnostic) in a
C99 compilation, which is a conforming choice.

4. Under C90 rules, there is technically undefined
behavior because of type incompatibility, but of a kind
that is benign in implementations where (as is likely
here) size_t == unsigned int (and ssize_t == int).
That's because of guarantees about representations of
the types involved, and also because of what happened
later with C99 (see next).

5. Under C99/C11 rules, the situation mentioned in (4)
was incorporated into the language definition and is
defined behavior.

6. The ISO C standard and POSIX both use (AFAIK) the
term "undefined behavior", and it may even be defined
the same way in both cases. In spite of that there is
a big difference between the two kinds of undefined
behavior, and glossing over that distinction does more
harm than good.

7. AFAICS there is no undefined behavior as far as the
write() call itself is concerned. Looking at the man
page, what write() does looks well-defined for all inputs
(with some non-portable aspects for special files, but
still not undefined behavior). What this write() call
does may be surprising and unexplained by its defining
document, but that's not because of undefined behavior
in the write() call.

8. The question being asked orginally is about why a call
to write() on stdin works. The answer to that question
has to do with the environment where the program was run,
even in the case where size_t != unsigned int. Focusing
on undefined behavior, especially to the exclusion of
other considerations, is a distraction from the question
and a disservice to the questioner.

9. Some people may feel that the posted code displays
some poor coding practices (or fails to follow some good
ones), and consider it important that these be pointed
out. And maybe that's right. But just saying the code
has undefined behavior is not a good way to do that.
 
G

glen herrmannsfeldt

From "/dev/tty", of course. :)
(BTW, I wouldn't use stderr for asking the question, either.)

Some years ago (SunOS 3.x if I remember right) I had a program that
wrote to /dev/tty. When I rlogin to another machine to run it, the
output would come out on the screen of that machine, not on mine.
(Confusing to the person using that machine.)

Not quite as long ago, we had some HP software to run under HP-UX.
We installed a new version, which wrote messages to /dev/console.
The getty that was supposed to allow console login then failed, as
the device was in use. I went into the executable file and change it
to /dev/null (The messages weren't that useful) and then called HP.
I told them I had changed it to /dev/null, and the phone person thought
that was a fine solution and didn't even want to take a bug report.

-- glen
 
J

James Kuyper

On 04/25/2013 10:36 AM, Ken Brody wrote:
....
However, this whole topic is OT for clc, as write() is not standard C but
POSIX. And even then, you may be dealing with UB, as I doubt that there is
any guarantee that stdin is writable.

I couldn't find any specification that stdin is (or is not) writable.
However, either way, the behavior should be well-defined (by POSIX, not
by the C standard): if stdin is writable, write() should perform
normally. If not, it should set errno to EBADF and return 0.
 
K

Keith Thompson

James Kuyper said:
On 04/25/2013 10:36 AM, Ken Brody wrote:
...

I couldn't find any specification that stdin is (or is not) writable.
However, either way, the behavior should be well-defined (by POSIX, not
by the C standard): if stdin is writable, write() should perform
normally. If not, it should set errno to EBADF and return 0.

A quibble: the name "stdin" refers to the FILE* object declared in
<stdio.h>. The program we're discussing refers to file descriptor 0,
which refers to standard input, but not to "stdin". (File descriptors
are POSIX-specific.)

Veering back on-topic, I don't see anything in the C standard that says
writing to stdin will necessarily fail. N1570 7.21.3p7 says:

At program startup, three text streams are predefined and need not
be opened explicitly -- *standard input* (for reading conventional
input), *standard output* (for writing conventional output), and
*standard error* (for writing diagnostic output).

And stdin, stdout, and stderr, respectively, are associated with those
streams.

As far as I can tell, a conforming implementation *could*, but need not,
open the standard input stream with the equivalent of

stdin = fopen("some_name", "r+");

which would enable both input and output.

(On my system, `write(0, ...)` succeeds, while `fputs("...", stdin)`
fails.)
 
J

James Kuyper

On 04/25/2013 03:14 PM, Keith Thompson wrote:
....
A quibble: the name "stdin" refers to the FILE* object declared in
<stdio.h>. The program we're discussing refers to file descriptor 0,
which refers to standard input, but not to "stdin". (File descriptors
are POSIX-specific.)

Yes, and POSIX specifies that file descriptor 0 corresponds to stdin.
 
K

Keith Thompson

James Kuyper said:
On 04/25/2013 03:14 PM, Keith Thompson wrote:
...

Yes, and POSIX specifies that file descriptor 0 corresponds to stdin.

Quite: but the writability of stdin and the writability of file
descriptor 0 are two distinct questions (and, at least on some systems,
have different answers).
 
N

Nobody

I should have clarified that the above was discussing the situation where
fd 0 is not, in fact, writeable.
There is no rule against writing to file descriptor zero.

The convention for inetd programs is that they read and write of fd 0.

inetd accepts a connection and dup2()s the socket to fds 0, 1, and 2 in
the child process. The intention is that "simple" programs which read from
stdin and write to stdout and/or stderr are automatically usable as inetd
services, while code which expects a single descriptor which refers to a
socket can also be used.
If fd 0 is a terminal, writing should be fine.

More accurately: if the standard descriptors inherited by the program
remain as they were initally set up by getty, writing to fd 0 will be fine.

Conventionally, getty open()s the terminal read-write then dup()s the
terminal fd to fds 0, 1 and 2. While this doesn't appear to be formally
specified (neither getty nor login are specified by POSIX), opening the
terminal once for each descriptor could break programs which assume the
conventional behaviour (e.g. each descriptor would have a separate
O_NONBLOCK flag).
 
N

Nobody

Somewhat off-topic:

As far as I can tell from a quick reading of the POSIX specification, a
call to write() can either succeed or fail.

Technically true, but a more useful "taxonomy" of results is that it can
write all of the requested data, write only some of it, or fail. Not
allowing for a partial write (or not handling it correctly, e.g.
treating it as a persistent failure instead of a transient failure) is a
common bug.
(I certainly think that writing to stdin is a silly thing to do.)

It depends whether you mean "FILE* stdin" or "int STDIN_FILENO".
Writing to the latter isn't universally as bad an idea as to the former.
 
G

glen herrmannsfeldt

Keith Thompson said:
A quibble: the name "stdin" refers to the FILE* object declared in
<stdio.h>. The program we're discussing refers to file descriptor 0,
which refers to standard input, but not to "stdin". (File descriptors
are POSIX-specific.)

Looking at the man pages for unix shells, it seems that csh uses the
term stdout once, and never stdin or stderr. That one is probably a
mistake, as more generally they use "standard input", "standard output"
and "diagnostic output".

bash recognizes redirect to files named /dev/stdin, /dev/stdout, and
/dev/stderr and redirects as appropriate.

Some systems also include in the actual /dev tree entries for
/dev/stdin, /dev/stdout, and /dev/stderr. This usage seems to be copied
from the C standard usage, but not connected to the C standard.

Otherwise, many unix programmers might use the term stdin where the more
correct 'standard input' should be used.

-- glen
 
K

Keith Thompson

glen herrmannsfeldt said:
Looking at the man pages for unix shells, it seems that csh uses the
term stdout once, and never stdin or stderr. That one is probably a
mistake, as more generally they use "standard input", "standard output"
and "diagnostic output".

bash recognizes redirect to files named /dev/stdin, /dev/stdout, and
/dev/stderr and redirects as appropriate.

I seem to recall that not actually working as specified, but I don't
remember the details (which are off-topic anyway).
Some systems also include in the actual /dev tree entries for
/dev/stdin, /dev/stdout, and /dev/stderr. This usage seems to be copied
from the C standard usage, but not connected to the C standard.

Otherwise, many unix programmers might use the term stdin where the more
correct 'standard input' should be used.

There's an unfortunate but understandable tendency, when a short
technical term is derived from a longer English word or phrase but
doesn't mean exactly the same thing, to use the shorter term in place of
the longer one.

Examples: "stdout" for "standard output", "int" for "integer", "const"
for "constant", "EOF" for "end of file".
 
A

army1987

How do you know that it worked? As far as I can tell, your program makes
no attempt to determine whether or not the call to write() was
successful.

He/she noticed that the terminal did display four lines each containing
"Hello world!"?
 
J

James Kuyper

He/she noticed that the terminal did display four lines each containing
"Hello world!"?

That's not what happened when I ran his program. I only got three lines,
and his program provided no mechanism for determining which call to
write() failed. On my system, it was the fourth one, but I had to modify
the program to determine that.
 
J

James Dow Allen

Yes, and POSIX specifies that file descriptor 0 corresponds to stdin.

(My post does not consider what POSIX systems may or must
do, but on what most (or all?) UNIX systems do in fact do.
If that's off-topic here, please disregard this post.
BTW, Cygwin and Linux both behave like Unix on the two
points made below.)

1. Consider a program with the following executable code:
fclose(stdin); /* close any input script
*/
freopen("Torture.log", "a", stderr); /* redirect diagnostics
*/
freopen("/dev/tty", "r", stdin); /* use terminal for input
*/
printf("stdin=%d stderr=%d\n", fileno(stdin), fileno(stderr));
I think this code is mostly posix. Yet, as shown by the
output of printf, it reverses the file descriptors for
stdin and stderr! Posix errors: Is this a spec bug?

2. On another matter, writing to stdin is very ancient in
Unix. I learned this when browsing source code for
a standard utility -- sorry, don't remember which --
and noted that it had a "write(0, ..." for some error
message as an emergency when neither 1 nor 2 was connected
to a terminal.

James
 
J

James Dow Allen

Posix errors:  Is this a spec bug?

I meant to write:
Posix experts:  Is this a bug in the specification?

Is substituting homonyms when typing a sign of
old age? I must be getting very old as
"experts" and "errors" aren't very homonymous.

James
 
J

James Kuyper

(My post does not consider what POSIX systems may or must
do, but on what most (or all?) UNIX systems do in fact do.
If that's off-topic here, please disregard this post.
BTW, Cygwin and Linux both behave like Unix on the two
points made below.)

1. Consider a program with the following executable code:
fclose(stdin); /* close any input script
*/
freopen("Torture.log", "a", stderr); /* redirect diagnostics
*/
freopen("/dev/tty", "r", stdin); /* use terminal for input
*/
printf("stdin=%d stderr=%d\n", fileno(stdin), fileno(stderr));
I think this code is mostly posix. Yet, as shown by the
output of printf, it reverses the file descriptors for
stdin and stderr! Posix errors: Is this a spec bug?

Later, he corrected that last sentence:
I meant to write:
Posix experts: �Is this a bug in the specification?

"The following symbolic values in <unistd.h> define the file descriptors
that shall be associated with the C-language stdin, stdout, and stderr
when the application is started:

STDIN_FILENO
Standard input value, stdin. Its value is 0.
STDOUT_FILENO
Standard output value, stdout. Its value is 1.
STDERR_FILENO
Standard error value, stderr. Its value is 2."

Key words: "when the application is started".
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
474,076
Messages
2,570,565
Members
47,201
Latest member
IvyTeeter

Latest Threads

Top