0

What code can my program call to behave similarly to pressing Ctrl+D in a terminal? That is, to cause a read function called on STDIN to return 0 in a child process, but without having this "STDIN" file descriptor closed in the parent process?

I'm trying to understand how the EOF condition is communicated to a process in Linux.

It appears read returning 0 is only advisory, and you can actually carry on reading after encountering such an EOF condition. Consider the following program:

#include <unistd.h>
#include <stdio.h>

void terminate_buf(char *buf, ssize_t len)
{
    if(len > 0) {
        buf[len-1] = '\0';
    } else {
        buf[0] = '\0';
    }
}

int main()
{
    int r;
    char buf[1024];

    while(1) {
        r = read(0, buf, 1024);
        terminate_buf(buf, r);
        printf("read %d bytes: %s\n", r, buf);
    }

    return 0;
}

If you compile (gcc -o reader reader.c, assuming you named it reader.c) and run this program in a terminal, then press Ctrl+D, then input foobarEnter, you will see this:

$ ./reader 
read 0 bytes: 
foobar
read 7 bytes: foobar


indicating it's totally possible to read meaningful data after an EOF event has occurred. You can press Ctrl+D multiple times on a line of its own, followed by some text, and reader will carry on reading your data as if nothing had happened (after printing "read 0 bytes: " for each press of Ctrl+D). The file descriptor remains open.

So what's going on here and how can I replicate this behaviour? How can I cause a child process to see some data after seeing EOF? Is there a way to do it using just regular file I/O (and perhaps ioctls), or do I need to open a pty? Calling write with a count of 0 doesn't seem to work, read doesn't even return on the other end. What is EOF, really?

  • 1
    How can I cause a child process to see some data after seeing EOF? You just did it: you ignore the EOF and keep on reading. Note that that only happens while reading terminal devices, and even in those, you can change the EOF character if you happen to need ^D. – Eduardo Trápani May 24 '20 at 22:34
  • @EduardoTrápani I mean how can I cause this to happen in the parent process. As in, what to do in the parent so that the child sees an EOF while still being able to read. I'm guessing creating a PTY could somehow simulate this, but I'm wondering if there are simpler ways (and if there aren't, how to do it via PTY). – uukgoblin May 24 '20 at 22:39
  • 1
    What do we know about the parent process? It kind of depends if it's an interactive shell script, a pipe, or another program you compiled. Could you provide an example of the programs, or the execution, maybe the situation you are trying to solve? – Eduardo Trápani May 24 '20 at 22:43
  • @EduardoTrápani it's another program I'm writing myself. I'm not sure how to provide an example of the parent program because I'm not sure how to write it. Assume a typical fork scenario with pipes, i.e. as in https://stackoverflow.com/a/4812963 . So I'm asking: what can parent do to mypipe[1] so that the child sees EOF on mypipe[0], and communication can keep on happening afterwards. As for the situation I'm trying to solve – there isn't any particular, but I am wondering if I can send multiple "logical" files to a process' stdin this way. – uukgoblin May 24 '20 at 22:51
  • Also, I'm curious as to why reading after encountering EOF is possible at all. Any background or explanation of why this is so would be greatly appreciated. It seems really weird. – uukgoblin May 24 '20 at 23:27
  • 1
    Related questions are https://unix.stackexchange.com/q/323750/5132 , https://unix.stackexchange.com/q/237334/5132 , https://unix.stackexchange.com/q/517064/5132 , https://unix.stackexchange.com/q/377254/5132 , https://unix.stackexchange.com/q/379347/5132 , and https://unix.stackexchange.com/q/339674/5132 . – JdeBP May 25 '20 at 07:34

2 Answers2

1

Ctrl-D (0x04) is mapped to EOF by a terminal device. If you are reading from a pipe or file, that will not happen.

echo -e "a\x04b" | cat

or

echo -e "a\x04b" | hd

If the first program in the pipe is reading from a terminal and you want to ignore the mapped EOF, you can, for example, change the terminal behaviour:

stty eof -

And Ctrl-D will no longer work. Or you can change the whole line discipline with:

stty raw

And read it as you would read a file, with no conversions.

You could use the EOF character to send multiple files, provided that none of those files contain that character. A pty would do it.

But you might be better off with established solutions though, like MIME multipart, things like zmodem, or archiving to the pipe (for example here the second tar lists the files the first one archived: tar cf - *txt | tar tf -).

  • "You could use the EOF character to send multiple files, provided that none of those files contain that character" – well yeah, but then my "child" program would be looking for \x04 bytes rather than real EOF. Thanks for the answer, but I'm afraid it's not precisely for the question I asked (or meant): I would like to be able to trigger EOF followed by sending readable data to a pipe. Even if it's purely theoretical and not common practice. And if PTY is the only way to do it, then how do I actually do it via a PTY? – uukgoblin May 24 '20 at 23:22
  • To clarify further: I'm not interested in sending the \x04 byte from the parent process. I'm also not interested in ignoring ^D. Pressing ^D in child process' terminal does NOT send the \x04 byte to that child: instead, it does something remarkably special. I'm extremely curious: what DOES it actually do? How can I do it myself? – uukgoblin May 24 '20 at 23:34
  • a pty is surely a way to do it (that's how terminal emulators work). How to use a pty is another question and it is been answered. What I tried to point out is that sending multiple files over a channel with in-band signaling can be done in other ways. Forcing a pipe through a pty to behave as terminal just so that you can use the pseudo EOF the line discipline creates is a bit puzzling. But you now have all the pieces. – Eduardo Trápani May 24 '20 at 23:35
1

The thing is that there really is no such thing as an "EOF condition". By convention, a read returning 0 is interpreted as the end of a file, but as you have seen, you can go on reading. Additional reads may return 0 again, or they can return more data, if some other process has written to the file in the mean time.

Not even a Ctrl-D from the terminal has a special "EOF" signalling property. What it really does is return the current line input to the process, just like pressing the Enter key does, but with the difference that no newline is appended. If Ctrl-D is pressed as the first input on a line, i.e. an empty line, then zero characters are returned, which is, again by convention, interpreted as an "EOF".

It is up to the process invoking the read system call how to interpret a return value of 0 from read.

Johan Myréen
  • 13,168
  • So how can this situation be created? What can I do to the "parent" end of a pipe so that the "child's" read returns 0 but the pipe remains open? Is there any other way than going via a kernel device such as /dev/pts/*? – uukgoblin May 25 '20 at 12:40