• a problem on pipes: making the child run less and parent writes to it

    From Meredith Montgomery@21:1/5 to All on Thu Nov 4 12:58:35 2021
    (*) Introduction

    I first wrote a program to fork and make parent read from the child.
    The child would invoke cat to write a file to stdout and the parent
    would invoke less to let me read the file on the screen. Everything
    worked as expected and I had no questions about it.

    (*) New experiment

    So I decided to reverse their roles --- make the child run less and the
    parent cat the file. But before getting there, let's try something
    easier --- instead of cat'ting the file, the parent just invokes write()
    on the write-end of the pipe. That's a little simpler because we don't
    call execl() and don't need to dup2-file-descriptors, say.

    (*) Unexpected result

    This first attempt at reversing these roles produces no effect.

    --8<---------------cut here---------------start------------->8--- %./parent-is-proc-and-child-is-less.exe
    %
    --8<---------------cut here---------------end--------------->8---

    What is my misunderstanding here? The full program is below with a lot
    of commentary so you can see what's on my mind. Thank you!

    (*) The program

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

    //--8<---------------cut here---------------start------------->8---
    // helpers
    void syserr_die(char* msg) { perror(msg); exit(1); }

    void pipe_die(int *fd) {
    int r; r = pipe(fd); if (r < 0) { syserr_die("pipe"); }
    }

    pid_t fork_die() {
    int p; p = fork(); if (p < 0) { syserr_die("fork"); }
    return p;
    }

    void dup2_die(int from, int to, char *msg) {
    int r;
    r = dup2(from, to); if (r < 0) syserr_die(msg);
    }
    //--8<---------------cut here---------------end--------------->8---

    int main() {
    int fd[2]; pid_t pid;

    pipe_die(&fd[0]);

    /*
    At this point I have a reading file descriptor at fd[0] and a
    writing one at fd[1].
    */

    pid = fork();
    if (pid < 0) {
    syserr_die("fork");
    }

    if (pid == 0) {
    /*
    I run /less/, so I must read from the pipe and not write to it.
    So I close fd[1] right away because I don't need it.
    */
    close(fd[1]);

    /*
    It is /less/ that will do the reading and it does it from STDIN,
    so I must ask system to make this replacement for me. Let's
    create the following vocabulary: move fd[0] to STDIN, that is,
    to 0.
    */

    dup2_die(fd[0], 0, "child dup2"); /* Let stdin be fd[0]. */

    /*
    When /less/ starts reading, it will be reading bytes written to
    the pipe. But it's important to notice that the pipe is an
    object in the kernel, not in our program. So fd[0] is not the
    pipe; it's just that number that refers to that pipe. The
    number 0 (which refers to STDIN) is already referring to the
    pipe, so we don't need a second reference --- so we destroy
    fd[0].
    */
    close(fd[0]);

    /*
    We are ready to run less now. The binary /less/ will be loaded in
    memory and what it finds as its kernel-context is everything we
    prepared above. We are done with the parent.
    */

    execl("/usr/bin/less", "less", (char*) 0);
    syserr_die("execl of less");

    /*
    I keep running until /less/ is dead.
    */

    }

    /* I'm the writer. My job is to just write data to fd[1] ---
    which is the writing end of the pipe. I don't need to read
    anything from the STDIN, so I begin by closing it. I will
    write to fd[1], not to STDOUT or STDERR. So close them too.

    */
    close(0); close(1); close(2);

    /*
    I'm going to write to fd[1]. I have no business with fd[0].
    */
    close(fd[0]);

    for (int i = 1; i <= 200; ++i) {
    char s[1000];
    sprintf(s, "i = %d\n", i);
    write(fd[1], s, strlen(s));
    }

    /*
    My job is done. I'm dead by now.
    */
    }

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Siri Cruise@21:1/5 to Meredith Montgomery on Thu Nov 4 11:02:24 2021
    In article <86a6ijvro4.fsf@levado.to>,
    Meredith Montgomery <mmontgomery@levado.to> wrote:

    execl("/usr/bin/less", "less", (char*) 0);

    less has two inputs, stdin and the controlling terminal. stdin is
    copied to stdout, but it interrupts the copy from time to time
    until you give a go ahead from the terminal.

    --
    :-<> Siri Seal of Disavowal #000-001. Disavowed. Denied. Deleted. @
    'I desire mercy, not sacrifice.' /|\ Discordia: not just a religion but also a parody. This post / \
    I am an Andrea Doria sockpuppet. insults Islam. Mohammed

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Lew Pitcher@21:1/5 to All on Thu Nov 4 18:07:13 2021
    On Thu, 04 Nov 2021 12:58:35 -0300, Meredith Montgomery wrote:

    Your program mostly works, but you need one small change. See below

    (*) Introduction

    I first wrote a program to fork and make parent read from the child.
    The child would invoke cat to write a file to stdout and the parent
    would invoke less to let me read the file on the screen. Everything
    worked as expected and I had no questions about it.

    (*) New experiment

    So I decided to reverse their roles --- make the child run less and the parent cat the file. But before getting there, let's try something
    easier --- instead of cat'ting the file, the parent just invokes write()
    on the write-end of the pipe. That's a little simpler because we don't
    call execl() and don't need to dup2-file-descriptors, say.

    (*) Unexpected result

    This first attempt at reversing these roles produces no effect.

    --8<---------------cut here---------------start------------->8--- %./parent-is-proc-and-child-is-less.exe
    %
    --8<---------------cut here---------------end--------------->8---

    What is my misunderstanding here? The full program is below with a lot
    of commentary so you can see what's on my mind. Thank you!

    (*) The program

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

    //--8<---------------cut here---------------start------------->8---
    // helpers

    IMHO, these "helper" functions are less than helpfull, in this program.
    When debugging a program that you are using to learn a new technique,
    it helps to have all the relevant logic in plain sight; helper functions
    hide logic from sight, making it harder to determine if a flaw is in
    your primary logic, or in the "helper" functions. Helper functions,
    if inexpertly used, also obfuscate the logical flow of the program,
    hiding more logic errors. Just a thought from a guy that spent 30 years designing, writing, debugging, and modifying complex programs for a
    living.

    void syserr_die(char* msg) { perror(msg); exit(1); }

    void pipe_die(int *fd) {
    int r; r = pipe(fd); if (r < 0) { syserr_die("pipe"); }
    }

    pid_t fork_die() {
    int p; p = fork(); if (p < 0) { syserr_die("fork"); }
    return p;
    }

    void dup2_die(int from, int to, char *msg) {
    int r;
    r = dup2(from, to); if (r < 0) syserr_die(msg);
    }
    //--8<---------------cut here---------------end--------------->8---

    int main() {
    int fd[2]; pid_t pid;

    pipe_die(&fd[0]);

    /*
    At this point I have a reading file descriptor at fd[0] and a
    writing one at fd[1].
    */

    pid = fork();
    if (pid < 0) {
    syserr_die("fork");
    }

    if (pid == 0) {
    /*
    I run /less/, so I must read from the pipe and not write to it.
    So I close fd[1] right away because I don't need it.
    */
    close(fd[1]);

    /*
    It is /less/ that will do the reading and it does it from STDIN,
    so I must ask system to make this replacement for me. Let's
    create the following vocabulary: move fd[0] to STDIN, that is,
    to 0.
    */

    dup2_die(fd[0], 0, "child dup2"); /* Let stdin be fd[0]. */

    /*
    When /less/ starts reading, it will be reading bytes written to
    the pipe. But it's important to notice that the pipe is an
    object in the kernel, not in our program. So fd[0] is not the
    pipe; it's just that number that refers to that pipe. The
    number 0 (which refers to STDIN) is already referring to the
    pipe, so we don't need a second reference --- so we destroy
    fd[0].
    */
    close(fd[0]);

    /*
    We are ready to run less now. The binary /less/ will be loaded in
    memory and what it finds as its kernel-context is everything we
    prepared above. We are done with the parent.
    */

    execl("/usr/bin/less", "less", (char*) 0);
    syserr_die("execl of less");

    /*
    I keep running until /less/ is dead.
    */

    }

    /* I'm the writer. My job is to just write data to fd[1] ---
    which is the writing end of the pipe. I don't need to read
    anything from the STDIN, so I begin by closing it. I will
    write to fd[1], not to STDOUT or STDERR. So close them too.

    */
    close(0); close(1); close(2);

    There's no need to close() stderr, and if you don't, then you gain the (dubious, to some) ability to insert debugging fprintf(stderr,...)
    statements into the code to trace execution or track down bugs.

    In fact, given that your logic write()'s to the write end of the
    pipe, you don't really have to close() stdin or stdout either.

    It's sometimes helpful to have these around.


    /*
    I'm going to write to fd[1]. I have no business with fd[0].
    */
    close(fd[0]);

    for (int i = 1; i <= 200; ++i) {
    char s[1000];
    sprintf(s, "i = %d\n", i);
    write(fd[1], s, strlen(s));

    A (bit) better approach here might have been to use the length returned
    by sprintf(3) as the write() count parameter.

    However, if, instead of closing stdout, you had
    dup2(STDOUT_FILENO,fd[1])
    then you could have
    printf("i = %d\n",i);
    directly to your pipe output.

    }

    Now, if your parent process terminates now, it closes the write end of the
    pipe before the child has read it to the end. From the pipe(7) manual page:
    "If all file descriptors referring to the write end of a pipe have been
    closed, then an attempt to read(2) from the pipe will see end-of-file
    (read(2) will return 0)"

    You need to wait(2) here for the child to complete it's processing.


    /*
    My job is done. I'm dead by now.
    */
    }

    --
    Lew Pitcher
    "In Skills, We Trust"

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Rainer Weikusat@21:1/5 to Meredith Montgomery on Thu Nov 4 19:06:31 2021
    Meredith Montgomery <mmontgomery@levado.to> writes:

    (*) Introduction

    I first wrote a program to fork and make parent read from the child.
    The child would invoke cat to write a file to stdout and the parent
    would invoke less to let me read the file on the screen. Everything
    worked as expected and I had no questions about it.

    (*) New experiment

    So I decided to reverse their roles --- make the child run less and the parent cat the file. But before getting there, let's try something
    easier --- instead of cat'ting the file, the parent just invokes write()
    on the write-end of the pipe. That's a little simpler because we don't
    call execl() and don't need to dup2-file-descriptors, say.

    (*) Unexpected result

    This first attempt at reversing these roles produces no effect.

    Working program (with the noise comments removed):

    ----
    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/wait.h>

    //--8<---------------cut here---------------start------------->8---
    // helpers
    void syserr_die(char* msg) { perror(msg); exit(1); }

    void pipe_die(int *fd) {
    int r; r = pipe(fd); if (r < 0) { syserr_die("pipe"); }
    }

    pid_t fork_die() {
    int p; p = fork(); if (p < 0) { syserr_die("fork"); }
    return p;
    }

    void dup2_die(int from, int to, char *msg) {
    int r;
    r = dup2(from, to); if (r < 0) syserr_die(msg);
    }
    //--8<---------------cut here---------------end--------------->8---

    int main() {
    int fd[2]; pid_t pid;

    pipe_die(&fd[0]);

    pid = fork();
    if (pid < 0) {
    syserr_die("fork");
    }

    if (pid == 0) {
    close(fd[1]);
    dup2_die(fd[0], 0, "child dup2"); /* Let stdin be fd[0]. */
    close(fd[0]);
    execl("/usr/bin/less", "less", (char*) 0);
    syserr_die("execl of less");
    }

    close(fd[0]);

    for (int i = 1; i <= 200; ++i) {
    char s[1000];
    sprintf(s, "i = %d\n", i);
    write(fd[1], s, strlen(s));
    }

    close(fd[1]);
    wait(NULL);
    }
    ----------

    fd[1] needs to be closed here because otherwise, less never sees an EOF
    and thus, blocks forever expecting input which won't ever
    arrive. Furher, the parent needs to wait because otherwise, the
    process running less becomes orphaned and will be killed (via SIGTTOU)
    when it tries to access the terminal.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Rainer Weikusat@21:1/5 to Rainer Weikusat on Thu Nov 4 20:15:06 2021
    Rainer Weikusat <rweikusat@talktalk.net> writes:

    [...]

    Furher, the parent needs to wait because otherwise, the
    process running less becomes orphaned and will be killed (via SIGTTOU)
    when it tries to access the terminal.

    A bit more precise: The shell runs each command in its own process
    group. The parent process of the less is the process group leader of
    this process group and the one the controlling terminal is associated
    with. As soon as it exits, this controlling terminal becomes free for
    reuse. The less will then be a background process running in an orphaned process group (as the process group leader has exited) and thus, the
    kernel will prevent it from using the terminal by making the system call
    fail with EIO (Linux, different on other systems).

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Geoff Clare@21:1/5 to Rainer Weikusat on Fri Nov 5 14:43:42 2021
    Rainer Weikusat wrote:

    The shell runs each command in its own process
    group. The parent process of the less is the process group leader of
    this process group and the one the controlling terminal is associated
    with. As soon as it exits, this controlling terminal becomes free for
    reuse.

    No, the controlling terminal doesn't become free until the shell (as
    session leader) exits.

    The less will then be a background process running in an orphaned
    process group (as the process group leader has exited) and thus, the
    kernel will prevent it from using the terminal by making the system call
    fail with EIO

    Correct.

    (Linux, different on other systems).

    How do other systems differ? The behaviour you described is what POSIX requires. XBD 11.1.4 Terminal Access Control:

    If TOSTOP is set, the process group of the writing process is
    orphaned, the writing process is not ignoring the SIGTTOU signal,
    and the writing thread is not blocking the SIGTTOU signal, the
    write() shall return -1, with errno set to [EIO] and no signal
    shall be sent.

    --
    Geoff Clare <netnews@gclare.org.uk>

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Rainer Weikusat@21:1/5 to Geoff Clare on Fri Nov 5 22:15:56 2021
    Geoff Clare <geoff@clare.See-My-Signature.invalid> writes:
    Rainer Weikusat wrote:
    The shell runs each command in its own process
    group. The parent process of the less is the process group leader of
    this process group and the one the controlling terminal is associated
    with. As soon as it exits, this controlling terminal becomes free for
    reuse.

    No, the controlling terminal doesn't become free until the shell (as
    session leader) exits.

    It becomes sort-of free because it's now the controlling terminal of a
    process unrelated to the still running background process. This could be
    the shell. Or the next command executed by it.

    Nevertheless, that was both sloppy use of language and lack of knowledge
    on my part.

    The less will then be a background process running in an orphaned
    process group (as the process group leader has exited) and thus, the
    kernel will prevent it from using the terminal by making the system call
    fail with EIO

    Correct.

    (Linux, different on other systems).

    How do other systems differ? The behaviour you described is what POSIX requires. XBD 11.1.4 Terminal Access Control:

    If TOSTOP is set, the process group of the writing process is
    orphaned, the writing process is not ignoring the SIGTTOU signal,
    and the writing thread is not blocking the SIGTTOU signal, the
    write() shall return -1, with errno set to [EIO] and no signal
    shall be sent.

    Glibc documentation claims they do:

    When a process in an orphaned process group (*note Orphaned
    Process Groups::) receives a 'SIGTSTP', 'SIGTTIN', or 'SIGTTOU'
    signal and does not handle it, the process does not stop.
    Stopping the process would probably not be very useful, since
    there is no shell program that will notice it stop and allow the
    user to continue it. What happens instead depends on the
    operating system you are using. Some systems may do nothing;
    others may deliver another signal instead, such as 'SIGKILL' or
    'SIGHUP'.

    [at the and of 24.2.5 Job Control Signals]

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Wayne Harris@21:1/5 to Lew Pitcher on Sat Nov 6 22:53:24 2021
    Lew Pitcher <lew.pitcher@digitalfreehold.ca> writes:

    On Thu, 04 Nov 2021 12:58:35 -0300, Meredith Montgomery wrote:

    Your program mostly works, but you need one small change. See below

    (*) Introduction

    I first wrote a program to fork and make parent read from the child.
    The child would invoke cat to write a file to stdout and the parent
    would invoke less to let me read the file on the screen. Everything
    worked as expected and I had no questions about it.

    (*) New experiment

    So I decided to reverse their roles --- make the child run less and the
    parent cat the file. But before getting there, let's try something
    easier --- instead of cat'ting the file, the parent just invokes write()
    on the write-end of the pipe. That's a little simpler because we don't
    call execl() and don't need to dup2-file-descriptors, say.

    (*) Unexpected result

    This first attempt at reversing these roles produces no effect.

    --8<---------------cut here---------------start------------->8---
    %./parent-is-proc-and-child-is-less.exe
    %
    --8<---------------cut here---------------end--------------->8---

    What is my misunderstanding here? The full program is below with a lot
    of commentary so you can see what's on my mind. Thank you!

    (*) The program

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

    //--8<---------------cut here---------------start------------->8---
    // helpers

    IMHO, these "helper" functions are less than helpfull, in this program.
    When debugging a program that you are using to learn a new technique,
    it helps to have all the relevant logic in plain sight; helper functions
    hide logic from sight, making it harder to determine if a flaw is in
    your primary logic, or in the "helper" functions. Helper functions,
    if inexpertly used, also obfuscate the logical flow of the program,
    hiding more logic errors. Just a thought from a guy that spent 30 years designing, writing, debugging, and modifying complex programs for a
    living.

    Point taken!

    void syserr_die(char* msg) { perror(msg); exit(1); }

    void pipe_die(int *fd) {
    int r; r = pipe(fd); if (r < 0) { syserr_die("pipe"); }
    }

    pid_t fork_die() {
    int p; p = fork(); if (p < 0) { syserr_die("fork"); }
    return p;
    }

    void dup2_die(int from, int to, char *msg) {
    int r;
    r = dup2(from, to); if (r < 0) syserr_die(msg);
    }
    //--8<---------------cut here---------------end--------------->8---

    int main() {
    int fd[2]; pid_t pid;

    pipe_die(&fd[0]);

    /*
    At this point I have a reading file descriptor at fd[0] and a
    writing one at fd[1].
    */

    pid = fork();
    if (pid < 0) {
    syserr_die("fork");
    }

    if (pid == 0) {
    /*
    I run /less/, so I must read from the pipe and not write to it.
    So I close fd[1] right away because I don't need it.
    */
    close(fd[1]);

    /*
    It is /less/ that will do the reading and it does it from STDIN,
    so I must ask system to make this replacement for me. Let's
    create the following vocabulary: move fd[0] to STDIN, that is,
    to 0.
    */

    dup2_die(fd[0], 0, "child dup2"); /* Let stdin be fd[0]. */

    /*
    When /less/ starts reading, it will be reading bytes written to
    the pipe. But it's important to notice that the pipe is an
    object in the kernel, not in our program. So fd[0] is not the
    pipe; it's just that number that refers to that pipe. The
    number 0 (which refers to STDIN) is already referring to the
    pipe, so we don't need a second reference --- so we destroy
    fd[0].
    */
    close(fd[0]);

    /*
    We are ready to run less now. The binary /less/ will be loaded in
    memory and what it finds as its kernel-context is everything we
    prepared above. We are done with the parent.
    */

    execl("/usr/bin/less", "less", (char*) 0);
    syserr_die("execl of less");

    /*
    I keep running until /less/ is dead.
    */

    }

    /* I'm the writer. My job is to just write data to fd[1] ---
    which is the writing end of the pipe. I don't need to read
    anything from the STDIN, so I begin by closing it. I will
    write to fd[1], not to STDOUT or STDERR. So close them too.

    */
    close(0); close(1); close(2);

    There's no need to close() stderr, and if you don't, then you gain the (dubious, to some) ability to insert debugging fprintf(stderr,...)
    statements into the code to trace execution or track down bugs.

    In fact, given that your logic write()'s to the write end of the
    pipe, you don't really have to close() stdin or stdout either.

    It's sometimes helpful to have these around.

    That makes sense. Thanks.


    /*
    I'm going to write to fd[1]. I have no business with fd[0].
    */
    close(fd[0]);

    for (int i = 1; i <= 200; ++i) {
    char s[1000];
    sprintf(s, "i = %d\n", i);
    write(fd[1], s, strlen(s));

    A (bit) better approach here might have been to use the length returned
    by sprintf(3) as the write() count parameter.

    However, if, instead of closing stdout, you had
    dup2(STDOUT_FILENO,fd[1])
    then you could have
    printf("i = %d\n",i);
    directly to your pipe output.

    Oh, true. Nice.

    }

    Now, if your parent process terminates now, it closes the write end of the pipe before the child has read it to the end. From the pipe(7) manual page:
    "If all file descriptors referring to the write end of a pipe have been
    closed, then an attempt to read(2) from the pipe will see end-of-file
    (read(2) will return 0)"

    You need to wait(2) here for the child to complete it's processing.

    But I think I must also close fd[1] like Rainer Weikusat said.
    (Otherwise /less/ blocks on a read(2) call.)


    /*
    My job is done. I'm dead by now.
    */
    }

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Wayne Harris@21:1/5 to Rainer Weikusat on Sat Nov 6 22:50:33 2021
    Rainer Weikusat <rweikusat@talktalk.net> writes:

    Meredith Montgomery <mmontgomery@levado.to> writes:

    (*) Introduction

    I first wrote a program to fork and make parent read from the child.
    The child would invoke cat to write a file to stdout and the parent
    would invoke less to let me read the file on the screen. Everything
    worked as expected and I had no questions about it.

    (*) New experiment

    So I decided to reverse their roles --- make the child run less and the
    parent cat the file. But before getting there, let's try something
    easier --- instead of cat'ting the file, the parent just invokes write()
    on the write-end of the pipe. That's a little simpler because we don't
    call execl() and don't need to dup2-file-descriptors, say.

    (*) Unexpected result

    This first attempt at reversing these roles produces no effect.

    Working program (with the noise comments removed):

    ----
    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/wait.h>

    //--8<---------------cut here---------------start------------->8---
    // helpers
    void syserr_die(char* msg) { perror(msg); exit(1); }

    void pipe_die(int *fd) {
    int r; r = pipe(fd); if (r < 0) { syserr_die("pipe"); }
    }

    pid_t fork_die() {
    int p; p = fork(); if (p < 0) { syserr_die("fork"); }
    return p;
    }

    void dup2_die(int from, int to, char *msg) {
    int r;
    r = dup2(from, to); if (r < 0) syserr_die(msg);
    }
    //--8<---------------cut here---------------end--------------->8---

    int main() {
    int fd[2]; pid_t pid;

    pipe_die(&fd[0]);

    pid = fork();
    if (pid < 0) {
    syserr_die("fork");
    }

    if (pid == 0) {
    close(fd[1]);
    dup2_die(fd[0], 0, "child dup2"); /* Let stdin be fd[0]. */
    close(fd[0]);
    execl("/usr/bin/less", "less", (char*) 0);
    syserr_die("execl of less");
    }

    close(fd[0]);

    for (int i = 1; i <= 200; ++i) {
    char s[1000];
    sprintf(s, "i = %d\n", i);
    write(fd[1], s, strlen(s));
    }

    close(fd[1]);
    wait(NULL);
    }
    ----------

    fd[1] needs to be closed here because otherwise, less never sees an EOF
    and thus, blocks forever expecting input which won't ever
    arrive. Furher, the parent needs to wait because otherwise, the
    process running less becomes orphaned and will be killed (via SIGTTOU)
    when it tries to access the terminal.

    That explains everything I was having trouble with. It's not easy to
    infer this from the documentation --- that the orphaned process would be
    killed via SIGTTOU. The default action --- from signal(3) --- is that
    it would stop the process, not kill it. I read the rest of thread ---
    thanks very much for your analysis here (very helpful) --- and saw your
    comment about the Glibc documentation and that's probably what's
    happening here.

    I looked up the section ``Orphaned Process Groups'' and, for the record,
    here's what it says.

    --8<---------------cut here---------------start------------->8---
    (*) Orphaned Process Groups

    When a controlling process terminates, its terminal becomes free and a
    new session can be established on it. (In fact, another user could log
    in on the terminal.) This could cause a problem if any processes from
    the old session are still trying to use that terminal.

    To prevent problems, process groups that continue running even after the session leader has terminated are marked as orphaned process groups.

    When a process group becomes an orphan, its processes are sent a SIGHUP
    signal. Ordinarily, this causes the processes to terminate. However, if
    a program ignores this signal or establishes a handler for it (see
    section Signal Handling), it can continue running as in the orphan
    process group even after its controlling process terminates; but it
    still cannot access the terminal any more.
    --8<---------------cut here---------------end--------------->8---

    So I infer that if the parent dies before /less/ reaches for the
    terminal, /less/ will then be a process in an orphaned process group and
    will, therefore, get a SIGHUP. That's probably what's killing /less/ in
    my case here.

    Trying to see it being killed, I added a sleep(1) before the death of
    the parent just to slow it down and that always makes /less/ leave the
    terminal in a bad state, which suggests it is being killed indeed.

    But I couldn't find a way to see which signals it was getting. I tried
    running my own program in place of /less/, but I believe I did not know
    how to imitate /less/ enough to simulate the behavior. My program never
    seem to get any signal and didn't seem to be killed either. I wonder if
    you have any suggestions here. Thank you!

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Lew Pitcher@21:1/5 to All on Sun Nov 7 19:53:47 2021
    I spent a bit of time to write a one-off that both performs the
    requisite task correctly, /and/ embodies the stylistic points that
    I made in the prior post. This is what I came up with...

    /******************* Code begins **********************************/

    /*
    ** This program demonstrates a solution to a problem posted by
    ** Meredith Montgomery in the comp.unix.programmer usenet group.
    **
    ** The programmer wants a program that creates a pipe between
    ** itself and the stdin of a child process. The child process
    ** runs less(1) on it's stdin (the read end of the pipe) while
    ** the parent process generates print data to the write end of
    ** the pipe.
    **
    ** The development of this program followed "Occam's razor",
    ** in that it subscribed to the principle that "entities
    ** should not be multiplied beyond necessity". In this case,
    ** I endeavoured to avoid needless encapsulation and abstraction
    ** of logic, in order to create a simple working version of the
    ** solution. Embellishments, such as logic encapsulation and
    ** isolation, are left to others to implement.
    **
    ** == Evolution ==
    ** 0: basic framework
    ** 1: create pipe
    ** 2: fork
    ** 3: child process reads input from pipe
    ** 4: parent process output to pipe
    ** 5: document finer points in comments
    */
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <errno.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/wait.h>

    int main(int argc, char *argv[])
    {
    int status = EXIT_FAILURE;
    int pfd[2];

    /*
    ** Step 1, make our pipe
    */
    if (pipe(pfd) == 0) /* On error, pipe(2) will return -1 and set errno */
    {
    /*
    ** Step 2: we got a valid pipe pair, now launch child and perform all processing
    */

    switch (fork())
    {
    /*
    ** On error, fork(2) will return -1, and set errno appropriately
    */
    case -1: /* fork failed */
    fprintf(stderr,"%s: fork() failed - %s\n",argv[0],strerror(errno));
    break;

    /*
    ** In the child process, fork(2) returns 0
    */
    case 0: /* CHILD process */
    /*
    ** Step 3: prepare the child process to read the pipe through stdin
    ** and run "less" on the input
    */
    close(pfd[1]); /* don't hold pipe write end open */
    if (dup2(pfd[0],STDIN_FILENO) != -1)
    {
    close(pfd[0]); /* close the now useless fd */
    execl("/usr/bin/less","less",NULL); /* use "less" to process stdin */
    /* we only get here if execl() failed */
    fprintf(stderr,"%s: execl() failed - %s\n",argv[0],strerror(errno));
    }
    else fprintf(stderr,"%s: child dup2() failed - %s\n",argv[0],strerror(errno));
    break;

    /*
    ** In the parent process, fork(2) returns the non-zero, non -1 PID
    ** of the child process
    */
    default: /* PARENT process */
    /*
    ** Step 4: prepare the parent process to write the pipe through stdout
    ** write some data so that the child can process it, wait for
    ** the child to process the data and then terminate the parent.
    */
    close(pfd[0]); /* dont hold pipe read end open */
    if (dup2(pfd[1],STDOUT_FILENO) != -1) /* use the write end of pipe as stdout */
    {
    close(pfd[1]); /* close the now useless fd */

    /* generate some output to be piped to the child */
    for (int count = 0; count < 100; ++count)
    printf("This is generated line number %d\n",count);
    fclose(stdout); /* signals EOF to child process stdin */

    /*
    ** The parent needs to wait because otherwise, the process running
    ** less(1) becomes orphaned and will be killed (via SIGTTOU) when
    ** it tries to access the terminal.
    ** (Rainer Weikusat <rweikusat@talktalk.net>)
    */
    wait(NULL); /* wait for the child to finish */

    status = EXIT_SUCCESS; /* Mission Accomplished! */
    }
    else fprintf(stderr,"%s: parent dup2() failed - %s\n",argv[0],strerror(errno));
    break;
    }
    }
    else fprintf(stderr,"%s: pipe() failed - %s\n",argv[0],strerror(errno));

    return status;
    }
    /******************* Code ends **********************************/




    --
    Lew Pitcher
    "In Skills, We Trust"

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Rainer Weikusat@21:1/5 to Wayne Harris on Sun Nov 7 19:54:49 2021
    Wayne Harris <wharris1@protonmail.com> writes:
    Rainer Weikusat <rweikusat@talktalk.net> writes:

    [...]


    I looked up the section ``Orphaned Process Groups'' and, for the record, here's what it says.


    (*) Orphaned Process Groups

    When a controlling process terminates, its terminal becomes free and a
    new session can be established on it. (In fact, another user could log
    in on the terminal.) This could cause a problem if any processes from
    the old session are still trying to use that terminal.

    To prevent problems, process groups that continue running even after the session leader has terminated are marked as orphaned process groups.

    When a process group becomes an orphan, its processes are sent a SIGHUP signal.

    That's not applicable because the session leader hasn't terminated. For
    an interactive terminal session, this will usually be the login shell.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Lew Pitcher@21:1/5 to Wayne Harris on Sun Nov 7 19:48:55 2021
    On Sat, 06 Nov 2021 22:53:24 -0300, Wayne Harris wrote:

    Lew Pitcher <lew.pitcher@digitalfreehold.ca> writes:
    [snip]
    You need to wait(2) here for the child to complete it's processing.

    But I think I must also close fd[1] like Rainer Weikusat said.
    (Otherwise /less/ blocks on a read(2) call.)

    Yah. I was wrong there. You need to close() first. Sorry; I didn't
    think it all the way through.


    /*
    My job is done. I'm dead by now.
    */
    }




    --
    Lew Pitcher
    "In Skills, We Trust"

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Geoff Clare@21:1/5 to Rainer Weikusat on Mon Nov 8 13:35:57 2021
    Rainer Weikusat wrote:

    Geoff Clare <geoff@clare.See-My-Signature.invalid> writes:

    How do other systems differ? The behaviour you described is what POSIX
    requires. XBD 11.1.4 Terminal Access Control:

    If TOSTOP is set, the process group of the writing process is
    orphaned, the writing process is not ignoring the SIGTTOU signal,
    and the writing thread is not blocking the SIGTTOU signal, the
    write() shall return -1, with errno set to [EIO] and no signal
    shall be sent.

    Glibc documentation claims they do:

    When a process in an orphaned process group (*note Orphaned
    Process Groups::) receives a 'SIGTSTP', 'SIGTTIN', or 'SIGTTOU'
    signal and does not handle it, the process does not stop.
    Stopping the process would probably not be very useful, since
    there is no shell program that will notice it stop and allow the
    user to continue it. What happens instead depends on the
    operating system you are using. Some systems may do nothing;
    others may deliver another signal instead, such as 'SIGKILL' or
    'SIGHUP'.

    [at the and of 24.2.5 Job Control Signals]

    My guess is that this text is very old and is referring to the
    behaviour of various systems pre-POSIX.

    --
    Geoff Clare <netnews@gclare.org.uk>

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Lew Pitcher@21:1/5 to Rainer Weikusat on Tue Nov 9 17:33:57 2021
    On Tue, 09 Nov 2021 17:25:22 +0000, Rainer Weikusat wrote:

    Lew Pitcher <lew.pitcher@digitalfreehold.ca> writes:

    [...]

    /*
    ** The parent needs to wait because otherwise, the process running
    ** less(1) becomes orphaned and will be killed (via SIGTTOU) when
    ** it tries to access the terminal.
    ** (Rainer Weikusat <rweikusat@talktalk.net>)
    */

    This is unfortunately almost completely wrong: The process becomes
    orphaned but this only means that it becomes a child of init (whatever
    that's called in the FatRat Lunix version du jour). The important bit
    here is that the process group becomes orphaned. When processes
    belonging to it which don't block or ignore SIGTTOU try to write to
    terminal, the system call will fail with EIO. That's presumably what terminates the less here.

    Noted.
    Corrected.



    --
    Lew Pitcher
    "In Skills, We Trust"

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Rainer Weikusat@21:1/5 to Lew Pitcher on Tue Nov 9 17:25:22 2021
    Lew Pitcher <lew.pitcher@digitalfreehold.ca> writes:

    [...]

    /*
    ** The parent needs to wait because otherwise, the process running
    ** less(1) becomes orphaned and will be killed (via SIGTTOU) when
    ** it tries to access the terminal.
    ** (Rainer Weikusat <rweikusat@talktalk.net>)
    */

    This is unfortunately almost completely wrong: The process becomes
    orphaned but this only means that it becomes a child of init (whatever
    that's called in the FatRat Lunix version du jour). The important bit
    here is that the process group becomes orphaned. When processes
    belonging to it which don't block or ignore SIGTTOU try to write to
    terminal, the system call will fail with EIO. That's presumably what
    terminates the less here.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Meredith Montgomery@21:1/5 to Lew Pitcher on Wed Mar 2 13:33:33 2022
    I have a question about Lew Pitcher's program. I still don't know what
    else must be done if we replace the printf() calls with an execution of
    cat. I seem to understand the original program completely, but my understanding must be wrong somewhere because I can't make a simple modification and predict the output. Please see the details below.
    (Notice I paste the full program at the end of the message just in case
    it helps anyone to copy it and compile it.)

    Lew Pitcher <lew.pitcher@digitalfreehold.ca> writes:

    I spent a bit of time to write a one-off that both performs the
    requisite task correctly, /and/ embodies the stylistic points that
    I made in the prior post. This is what I came up with...

    /******************* Code begins **********************************/

    /*
    ** This program demonstrates a solution to a problem posted by
    ** Meredith Montgomery in the comp.unix.programmer usenet group.
    **
    ** The programmer wants a program that creates a pipe between
    ** itself and the stdin of a child process. The child process
    ** runs less(1) on it's stdin (the read end of the pipe) while
    ** the parent process generates print data to the write end of
    ** the pipe.
    **
    ** The development of this program followed "Occam's razor",
    ** in that it subscribed to the principle that "entities
    ** should not be multiplied beyond necessity". In this case,
    ** I endeavoured to avoid needless encapsulation and abstraction
    ** of logic, in order to create a simple working version of the
    ** solution. Embellishments, such as logic encapsulation and
    ** isolation, are left to others to implement.
    **
    ** == Evolution ==
    ** 0: basic framework
    ** 1: create pipe
    ** 2: fork
    ** 3: child process reads input from pipe
    ** 4: parent process output to pipe
    ** 5: document finer points in comments
    */
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <errno.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/wait.h>

    int main(int argc, char *argv[])
    {
    int status = EXIT_FAILURE;
    int pfd[2];

    /*
    ** Step 1, make our pipe
    */
    if (pipe(pfd) == 0) /* On error, pipe(2) will return -1 and set errno */
    {
    /*
    ** Step 2: we got a valid pipe pair, now launch child and perform all processing
    */

    switch (fork())
    {
    /*
    ** On error, fork(2) will return -1, and set errno appropriately
    */
    case -1: /* fork failed */
    fprintf(stderr,"%s: fork() failed - %s\n",argv[0],strerror(errno));
    break;

    /*
    ** In the child process, fork(2) returns 0
    */
    case 0: /* CHILD process */
    /*
    ** Step 3: prepare the child process to read the pipe through stdin
    ** and run "less" on the input
    */
    close(pfd[1]); /* don't hold pipe write end open */
    if (dup2(pfd[0],STDIN_FILENO) != -1)
    {
    close(pfd[0]); /* close the now useless fd */
    execl("/usr/bin/less","less",NULL); /* use "less" to process stdin */
    /* we only get here if execl() failed */
    fprintf(stderr,"%s: execl() failed - %s\n",argv[0],strerror(errno));
    }
    else fprintf(stderr,"%s: child dup2() failed - %s\n",argv[0],strerror(errno));
    break;

    /*
    ** In the parent process, fork(2) returns the non-zero, non -1 PID
    ** of the child process
    */
    default: /* PARENT process */
    /*
    ** Step 4: prepare the parent process to write the pipe through stdout
    ** write some data so that the child can process it, wait for
    ** the child to process the data and then terminate the parent.
    */
    close(pfd[0]); /* dont hold pipe read end open */
    if (dup2(pfd[1],STDOUT_FILENO) != -1) /* use the write end of pipe as stdout */
    {
    close(pfd[1]); /* close the now useless fd */

    /* generate some output to be piped to the child */
    for (int count = 0; count < 100; ++count)
    printf("This is generated line number %d\n",count);
    fclose(stdout); /* signals EOF to child process stdin */

    What happens if I replace this for-loop with an execution of cat? For
    example, what happens if we place this entire for-loop with

    execl("/usr/bin/cat", "cat", "lew.c", NULL)?

    Assume we remove the fclose and the wait below, although I think that
    has no effect if execl succeeds.

    I expected it to work, but it doesn't. The less process gets killed and
    leaves the terminal in a inconsistent state.

    /*
    ** The parent needs to wait because otherwise, the process running
    ** less(1) becomes orphaned and will be killed (via SIGTTOU) when
    ** it tries to access the terminal.
    ** (Rainer Weikusat <rweikusat@talktalk.net>)
    */
    wait(NULL); /* wait for the child to finish */

    Wouldn't cat wait for the reader to close its reading end? Notice that
    Rainer Weikusat and Lew Pitcher discussed this comment above. I'll show
    it again here just in case it helps to answer my question.

    --8<---------------cut here---------------start------------->8---
    Path: aioe.org!news.swapon.de!fu-berlin.de!uni-berlin.de!individual.net!not-for-mail
    From: Rainer Weikusat <rweikusat@talktalk.net>
    Newsgroups: comp.unix.programmer
    Subject: Re: a problem on pipes: making the child run less and parent writes to it
    Date: Tue, 09 Nov 2021 17:25:22 +0000
    Lines: 19
    Message-ID: <87ilx1s0l9.fsf@doppelsaurus.mobileactivedefense.com>
    References: <86a6ijvro4.fsf@levado.to> <sm17gg$tp4$1@dont-email.me>
    <sm9asb$g1o$2@dont-email.me>
    Mime-Version: 1.0
    Content-Type: text/plain
    X-Trace: individual.net sxs5sVhUULAg0ZwigtwFiABaDVfBheWEJi/dzPUCrDgnmybJc= Cancel-Lock: sha1:J8Xuih6gHa9fFrX1f3ROY2bpM6U= sha1:C4vMs1rkbtNeEEqaS2XCfuavJgo=
    User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/24.5 (gnu/linux)
    Xref: aioe.org comp.unix.programmer:23115

    Lew Pitcher <lew.pitcher@digitalfreehold.ca> writes:

    [...]

    /*
    ** The parent needs to wait because otherwise, the process running
    ** less(1) becomes orphaned and will be killed (via SIGTTOU) when
    ** it tries to access the terminal.
    ** (Rainer Weikusat <rweikusat@talktalk.net>)
    */

    This is unfortunately almost completely wrong: The process becomes
    orphaned but this only means that it becomes a child of init (whatever
    that's called in the FatRat Lunix version du jour). The important bit
    here is that the process group becomes orphaned. When processes
    belonging to it which don't block or ignore SIGTTOU try to write to
    terminal, the system call will fail with EIO. That's presumably what
    terminates the less here.
    --8<---------------cut here---------------end--------------->8---

    So it does seem that somehow cat is not waiting for less? How is this
    properly done? I still haven't understood this. Thanks for your patience.


    status = EXIT_SUCCESS; /* Mission Accomplished! */
    }
    else fprintf(stderr,"%s: parent dup2() failed - %s\n",argv[0],strerror(errno));
    break;
    }
    }
    else fprintf(stderr,"%s: pipe() failed - %s\n",argv[0],strerror(errno));

    return status;
    }
    /******************* Code ends **********************************/

    (*) Full program

    /******************* Code begins **********************************/

    /*
    ** This program demonstrates a solution to a problem posted by
    ** Meredith Montgomery in the comp.unix.programmer usenet group.
    **
    ** The programmer wants a program that creates a pipe between
    ** itself and the stdin of a child process. The child process
    ** runs less(1) on it's stdin (the read end of the pipe) while
    ** the parent process generates print data to the write end of
    ** the pipe.
    **
    ** The development of this program followed "Occam's razor",
    ** in that it subscribed to the principle that "entities
    ** should not be multiplied beyond necessity". In this case,
    ** I endeavoured to avoid needless encapsulation and abstraction
    ** of logic, in order to create a simple working version of the
    ** solution. Embellishments, such as logic encapsulation and
    ** isolation, are left to others to implement.
    **
    ** == Evolution ==
    ** 0: basic framework
    ** 1: create pipe
    ** 2: fork
    ** 3: child process reads input from pipe
    ** 4: parent process output to pipe
    ** 5: document finer points in comments
    */
    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <errno.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/wait.h>

    int main(int argc, char *argv[])
    {
    int status = EXIT_FAILURE;
    int pfd[2];

    /*
    ** Step 1, make our pipe
    */
    if (pipe(pfd) == 0) /* On error, pipe(2) will return -1 and set errno */
    {
    /*
    ** Step 2: we got a valid pipe pair, now launch child and perform all processing
    */

    switch (fork())
    {
    /*
    ** On error, fork(2) will return -1, and set errno appropriately
    */
    case -1: /* fork failed */
    fprintf(stderr,"%s: fork() failed - %s\n",argv[0],strerror(errno));
    break;

    /*
    ** In the child process, fork(2) returns 0
    */
    case 0: /* CHILD process */
    /*
    ** Step 3: prepare the child process to read the pipe through stdin
    ** and run "less" on the input
    */
    close(pfd[1]); /* don't hold pipe write end open */
    if (dup2(pfd[0],STDIN_FILENO) != -1)
    {
    close(pfd[0]); /* close the now useless fd */
    execl("/usr/bin/less","less",NULL); /* use "less" to process stdin */
    /* we only get here if execl() failed */
    fprintf(stderr,"%s: execl() failed - %s\n",argv[0],strerror(errno));
    }
    else fprintf(stderr,"%s: child dup2() failed - %s\n",argv[0],strerror(errno));
    break;

    /*
    ** In the parent process, fork(2) returns the non-zero, non -1 PID
    ** of the child process
    */
    default: /* PARENT process */
    /*
    ** Step 4: prepare the parent process to write the pipe through stdout
    ** write some data so that the child can process it, wait for
    ** the child to process the data and then terminate the parent.
    */
    close(pfd[0]); /* dont hold pipe read end open */
    if (dup2(pfd[1],STDOUT_FILENO) != -1) /* use the write end of pipe as stdout */
    {
    close(pfd[1]); /* close the now useless fd */

    /* generate some output to be piped to the child */
    for (int count = 0; count < 100; ++count)
    printf("This is generated line number %d\n",count);
    fclose(stdout); /* signals EOF to child process stdin */

    /*
    ** The parent needs to wait because otherwise, the process running
    ** less(1) becomes orphaned and will be killed (via SIGTTOU) when
    ** it tries to access the terminal.
    ** (Rainer Weikusat <rweikusat@talktalk.net>)
    */
    wait(NULL); /* wait for the child to finish */

    status = EXIT_SUCCESS; /* Mission Accomplished! */
    }
    else fprintf(stderr,"%s: parent dup2() failed - %s\n",argv[0],strerror(errno));
    break;
    }
    }
    else fprintf(stderr,"%s: pipe() failed - %s\n",argv[0],strerror(errno));

    return status;
    }
    /******************* Code ends **********************************/

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Scott Lurndal@21:1/5 to Meredith Montgomery on Wed Mar 2 18:26:58 2022
    Meredith Montgomery <mmontgomery@levado.to> writes:
    I have a question about Lew Pitcher's program. I still don't know what
    else must be done if we replace the printf() calls with an execution of
    cat. I seem to understand the original program completely, but my >understanding must be wrong somewhere because I can't make a simple >modification and predict the output. Please see the details below.
    (Notice I paste the full program at the end of the message just in case
    it helps anyone to copy it and compile it.)

    /* generate some output to be piped to the child */
    for (int count = 0; count < 100; ++count)
    printf("This is generated line number %d\n",count);
    fclose(stdout); /* signals EOF to child process stdin */

    What happens if I replace this for-loop with an execution of cat? For >example, what happens if we place this entire for-loop with

    execl("/usr/bin/cat", "cat", "lew.c", NULL)?

    Assume we remove the fclose and the wait below, although I think that
    has no effect if execl succeeds.

    That execl(2) system call is executed in the context of the parent.

    What does exec(2) do? It completely replaces the current process
    with a new process. So, you're replacing the parent process with
    a shell executing the cat(1) command. The wait for the child, being part of the
    former parent process, can never be executed as that process no longer
    exists.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Lew Pitcher@21:1/5 to Meredith Montgomery on Wed Mar 2 21:20:22 2022
    On Wed, 02 Mar 2022 13:33:33 -0300, Meredith Montgomery wrote:

    Scott Lurndal's reply is spot on.

    I have a question about Lew Pitcher's program. I still don't know what
    else must be done if we replace the printf() calls with an execution of
    cat. I seem to understand the original program completely, but my understanding must be wrong somewhere because I can't make a simple modification and predict the output. Please see the details below.
    (Notice I paste the full program at the end of the message just in case
    it helps anyone to copy it and compile it.)

    In my local copy of the original code, I have this:
    default: /* PARENT process */
    /*
    ** Step 4: prepare the parent process to write the pipe through stdout
    ** write some data so that the child can process it, wait for
    ** the child to process the data and then terminate the parent.
    */
    close(pfd[0]); /* dont hold pipe read end open [Note 1] */
    if (dup2(pfd[1],STDOUT_FILENO) != -1) /* use the write end of pipe as our stdout */
    {
    close(pfd[1]); /* close the now useless write fd [Note 1] */

    /* generate some output to be piped to the child */
    for (int count = 0; count < 100; ++count)
    printf("This is generated line number %d\n",count);
    fclose(stdout); /* signals EOF to child process stdin [Note 1] */

    wait(NULL); /* wait for the child to finish [Note 2] */

    status = EXIT_SUCCESS; /* Mission Accomplished! */
    }
    else fprintf(stderr,"%s: parent dup2() failed - %s\n",argv[0],strerror(errno));
    break;

    and "Note 2" (comment beside the wait() call) later...
    == Note 2 ==
    Conceptually, the parent process, once it has written all its lines and
    closed the write end of the pipe, /should/ be able to just exit(3) and
    terminate. In this case, the only side-effect is that the child process
    becomes an orphan process until it terminates.

    However, when the child process runs less(2), other things happen. /If/
    we had forked off /usr/bin/cat instead of /usr/bin/less, we could have
    forgone the wait(2). But, /usr/bin/less expects to have control of the
    controlling terminal (where /usr/bin/cat does not), and once our parent
    process terminates, control of the controlling terminal is given back
    to the shell.

    Additionally, less(1) maintains an internal buffer of it's input data
    to permit backwards scrolling, etc.

    The combination of no terminal control, and an internal buffer causes
    less(1) to abort all output.

    So, instead of terminating the parent process immediately (and relinquishing
    the terminal), we wait(2) for less(1) to terminate before terminating
    the parent process. This guarantees that less(1) can access the terminal
    as it needs, and display its buffered data.

    While this comment talks about the difference in behaviour between less(1)
    and cat(1) with respect to the parent process, the observation also applies
    to how the parent process works. Specifically, if you substitute
    execl("/usr/bin/cat","cat","some.text",NULL);
    for the printf() loop in the parent process, you again do not wait() for the child process to terminate. And, as we saw with the original code, you need
    to wait() for this specific child process ("/usr/bin/less") to complete
    in order to retain a coherent terminal control state.

    I don't believe that I posted the original code with these comments.
    I apologize; I should have.


    [snip]

    HTH
    --
    Lew Pitcher
    "In Skills, We Trust"

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Ralf Fassel@21:1/5 to All on Thu Mar 3 10:15:27 2022
    * Lew Pitcher <lew.pitcher@digitalfreehold.ca>
    | However, when the child process runs less(2), other things happen. /If/
    | we had forked off /usr/bin/cat instead of /usr/bin/less, we could have
    | forgone the wait(2). But, /usr/bin/less expects to have control of the
    | controlling terminal (where /usr/bin/cat does not), and once our parent
    | process terminates, control of the controlling terminal is given back
    | to the shell.

    I always thought that fork() duplicates the current process with
    'everything', so I would have expected the child (which eventually execs "less") to also have "control of the controlling terminal".
    But obviously there is more to that...

    R'

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Lew Pitcher@21:1/5 to Ralf Fassel on Thu Mar 3 15:03:56 2022
    On Thu, 03 Mar 2022 10:15:27 +0100, Ralf Fassel wrote:

    * Lew Pitcher <lew.pitcher@digitalfreehold.ca>
    | However, when the child process runs less(2), other things happen. /If/ | we had forked off /usr/bin/cat instead of /usr/bin/less, we could have | forgone the wait(2). But, /usr/bin/less expects to have control of the | controlling terminal (where /usr/bin/cat does not), and once our parent | process terminates, control of the controlling terminal is given back
    | to the shell.

    I always thought that fork() duplicates the current process with 'everything', so I would have expected the child (which eventually execs "less") to also have "control of the controlling terminal".
    But obviously there is more to that...

    Well, /every/ process that has not divorced itself from it's controlling terminal has control of that terminal, in that every process can read from it, write to it, and change it's characteristics (line echo, etc.).

    I still don't understand the intricacies of it myself, but in it's simplest form, imagine if two processes both are trying to read from the controlling terminal: which process gets the input? How about one process that turns off echo (say, to prevent it's input from distorting the output on the terminal, like less(1) would do), and one process that assumes that echo is turned on (like, say, the shell). Or, if each process emits a termcap/termios control string at the same time; how does the terminal interpret two simultaneous, conflicting command strings?

    So, usually, only one process at a time uses the controlling terminal, while all other processes wait. I don't know the mechanism of that co-ordination;
    I'm just learning this stuff myself.

    HTH
    --
    Lew Pitcher
    "In Skills, We Trust"

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Scott Lurndal@21:1/5 to Ralf Fassel on Thu Mar 3 14:41:06 2022
    Ralf Fassel <ralfixx@gmx.de> writes:
    * Lew Pitcher <lew.pitcher@digitalfreehold.ca>
    | However, when the child process runs less(2), other things happen. /If/ >| we had forked off /usr/bin/cat instead of /usr/bin/less, we could have >| forgone the wait(2). But, /usr/bin/less expects to have control of the >| controlling terminal (where /usr/bin/cat does not), and once our parent >| process terminates, control of the controlling terminal is given back
    | to the shell.

    I always thought that fork() duplicates the current process with >'everything', so I would have expected the child (which eventually execs >"less") to also have "control of the controlling terminal".
    But obviously there is more to that...

    Indeed. You need to look at the controlling terminal in the context
    of a process "tree", for example:

    $ ps -ejH
    PID PGID SID TTY TIME CMD
    ...
    1807 1401 1401 ? 00:00:16 xterm
    1811 1811 1811 pts/3 00:00:02 ksh
    17690 17690 1811 pts/3 00:00:12 xpdf
    3518 3518 1811 pts/3 00:00:09 xpdf
    1101 1101 1811 pts/3 00:00:18 xpdf
    3533 3533 1811 pts/3 00:00:01 xpdf
    23125 23125 1811 pts/3 07:08:21 firefox
    12781 12781 1811 pts/3 00:51:03 firefox-bin
    12834 12781 1811 pts/3 00:00:00 Socket Process
    12922 12781 1811 pts/3 00:37:44 Web Content
    12998 12781 1811 pts/3 00:03:21 WebExtensions
    13058 12781 1811 pts/3 00:10:30 Web Content
    13112 12781 1811 pts/3 00:00:11 Privileged Cont
    13275 12781 1811 pts/3 00:08:39 Web Content
    13362 12781 1811 pts/3 00:02:07 RDD Process
    13517 12781 1811 pts/3 00:07:27 Web Content
    14273 12781 1811 pts/3 00:10:50 Web Content
    14356 12781 1811 pts/3 00:11:43 Web Content
    14393 12781 1811 pts/3 00:09:23 Web Content
    14671 12781 1811 pts/3 00:05:45 Web Content
    12281 12281 1811 pts/3 00:00:01 xrn
    12386 12386 1811 pts/3 00:00:00 ps
    ...

    Here, the controlling terminal is 'pts/3', which was
    opened by xterm and passed to the korn shell (ksh).
    ksh issued a 'setsid(2)' system call to become the session
    leader, and thus "owns" the controlling terminal (by
    virtual of being the session leader). Within a session
    are one or more process groups (usually created with
    the shell job-control facilities, e.g. '&' or control-z).

    Only one process group can be the 'foreground' group, and
    that group receives input from and sends output to
    the terminal until it is suspended
    (e.g. SIGTSTP/SIGSTOP) or the process group leader terminates.

    https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap11.html

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Lew Pitcher@21:1/5 to Scott Lurndal on Thu Mar 3 16:49:30 2022
    On Thu, 03 Mar 2022 16:23:53 +0000, Scott Lurndal wrote:

    Lew Pitcher <lew.pitcher@digitalfreehold.ca> writes:
    On Thu, 03 Mar 2022 10:15:27 +0100, Ralf Fassel wrote:

    * Lew Pitcher <lew.pitcher@digitalfreehold.ca>
    | However, when the child process runs less(2), other things happen. /If/
    | we had forked off /usr/bin/cat instead of /usr/bin/less, we could have
    | forgone the wait(2). But, /usr/bin/less expects to have control of the
    | controlling terminal (where /usr/bin/cat does not), and once our parent
    | process terminates, control of the controlling terminal is given back >>> | to the shell.

    I always thought that fork() duplicates the current process with
    'everything', so I would have expected the child (which eventually execs >>> "less") to also have "control of the controlling terminal".
    But obviously there is more to that...

    Well, /every/ process that has not divorced itself from it's controlling >>terminal has control of that terminal, in that every process can read from it,
    write to it, and change it's characteristics (line echo, etc.).

    I still don't understand the intricacies of it myself, but in it's simplest >>form, imagine if two processes both are trying to read from the controlling >>terminal: which process gets the input?

    "If a process is in the foreground process group of its controlling
    terminal, read operations shall be allowed, as described in Input
    Processing and Reading Data. Any attempts by a process in a background
    process group to read from its controlling terminal cause its process
    group to be sent a SIGTTIN signal unless one of the following special
    cases applies: if the reading process is ignoring the SIGTTIN signal
    or the reading thread is blocking the SIGTTIN signal, or if the
    process group of the reading process is orphaned, the read() shall
    return -1, with errno set to [EIO] and no signal shall be sent. The
    default action of the SIGTTIN signal shall be to stop the process
    to which it is sent."

    "If a process is in the foreground process group of its controlling
    terminal, write operations shall be allowed as described in Writing
    Data and Output Processing. Attempts by a process in a background
    process group to write to its controlling terminal shall cause the
    process group to be sent a SIGTTOU signal unless one of the following
    special cases applies: if TOSTOP is not set, or if TOSTOP is set and
    the process is ignoring the SIGTTOU signal or the writing thread is
    blocking the SIGTTOU signal, the process is allowed to write to the
    terminal and the SIGTTOU signal is not sent. If TOSTOP is set, the
    process group of the writing process is orphaned, the writing process
    is not ignoring the SIGTTOU signal, and the writing thread is not blocking
    the SIGTTOU signal, the write() shall return -1, with errno set to [EIO]
    and no signal shall be sent."


    This implies that multiple processes _within_ a process group need to coordinate both read and write operations to avoid conflicting with
    each other

    And, if they don't co-ordinate read and write operations, then....
    the unpredictable happens.

    Just like Meredith is seeing with the various iterations of her program,
    where both less(1) and the shell compete for input (and output) from
    the controlling terminal.


    (regardless of the type of file - block special, character
    special, fifo, pipe or regular file).

    --
    Lew Pitcher
    "In Skills, We Trust"

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Scott Lurndal@21:1/5 to Lew Pitcher on Thu Mar 3 16:23:53 2022
    Lew Pitcher <lew.pitcher@digitalfreehold.ca> writes:
    On Thu, 03 Mar 2022 10:15:27 +0100, Ralf Fassel wrote:

    * Lew Pitcher <lew.pitcher@digitalfreehold.ca>
    | However, when the child process runs less(2), other things happen. /If/
    | we had forked off /usr/bin/cat instead of /usr/bin/less, we could have >> | forgone the wait(2). But, /usr/bin/less expects to have control of the >> | controlling terminal (where /usr/bin/cat does not), and once our parent
    | process terminates, control of the controlling terminal is given back >> | to the shell.

    I always thought that fork() duplicates the current process with
    'everything', so I would have expected the child (which eventually execs
    "less") to also have "control of the controlling terminal".
    But obviously there is more to that...

    Well, /every/ process that has not divorced itself from it's controlling >terminal has control of that terminal, in that every process can read from it, >write to it, and change it's characteristics (line echo, etc.).

    I still don't understand the intricacies of it myself, but in it's simplest >form, imagine if two processes both are trying to read from the controlling >terminal: which process gets the input?

    "If a process is in the foreground process group of its controlling
    terminal, read operations shall be allowed, as described in Input
    Processing and Reading Data. Any attempts by a process in a background
    process group to read from its controlling terminal cause its process
    group to be sent a SIGTTIN signal unless one of the following special
    cases applies: if the reading process is ignoring the SIGTTIN signal
    or the reading thread is blocking the SIGTTIN signal, or if the
    process group of the reading process is orphaned, the read() shall
    return -1, with errno set to [EIO] and no signal shall be sent. The
    default action of the SIGTTIN signal shall be to stop the process
    to which it is sent."

    "If a process is in the foreground process group of its controlling
    terminal, write operations shall be allowed as described in Writing
    Data and Output Processing. Attempts by a process in a background
    process group to write to its controlling terminal shall cause the
    process group to be sent a SIGTTOU signal unless one of the following
    special cases applies: if TOSTOP is not set, or if TOSTOP is set and
    the process is ignoring the SIGTTOU signal or the writing thread is
    blocking the SIGTTOU signal, the process is allowed to write to the
    terminal and the SIGTTOU signal is not sent. If TOSTOP is set, the
    process group of the writing process is orphaned, the writing process
    is not ignoring the SIGTTOU signal, and the writing thread is not blocking
    the SIGTTOU signal, the write() shall return -1, with errno set to [EIO]
    and no signal shall be sent."


    This implies that multiple processes _within_ a process group need to coordinate both read and write operations to avoid conflicting with
    each other (regardless of the type of file - block special, character
    special, fifo, pipe or regular file).

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Scott Lurndal@21:1/5 to Lew Pitcher on Thu Mar 3 17:16:49 2022
    Lew Pitcher <lew.pitcher@digitalfreehold.ca> writes:
    On Thu, 03 Mar 2022 16:23:53 +0000, Scott Lurndal wrote:

    Lew Pitcher <lew.pitcher@digitalfreehold.ca> writes:
    On Thu, 03 Mar 2022 10:15:27 +0100, Ralf Fassel wrote:

    * Lew Pitcher <lew.pitcher@digitalfreehold.ca>
    | However, when the child process runs less(2), other things happen. /If/
    | we had forked off /usr/bin/cat instead of /usr/bin/less, we could have
    | forgone the wait(2). But, /usr/bin/less expects to have control of the
    | controlling terminal (where /usr/bin/cat does not), and once our parent
    | process terminates, control of the controlling terminal is given back
    | to the shell.

    I always thought that fork() duplicates the current process with
    'everything', so I would have expected the child (which eventually execs >>>> "less") to also have "control of the controlling terminal".
    But obviously there is more to that...

    Well, /every/ process that has not divorced itself from it's controlling >>>terminal has control of that terminal, in that every process can read from it,
    write to it, and change it's characteristics (line echo, etc.).

    I still don't understand the intricacies of it myself, but in it's simplest >>>form, imagine if two processes both are trying to read from the controlling >>>terminal: which process gets the input?

    "If a process is in the foreground process group of its controlling
    terminal, read operations shall be allowed, as described in Input
    Processing and Reading Data. Any attempts by a process in a background
    process group to read from its controlling terminal cause its process
    group to be sent a SIGTTIN signal unless one of the following special
    cases applies: if the reading process is ignoring the SIGTTIN signal
    or the reading thread is blocking the SIGTTIN signal, or if the
    process group of the reading process is orphaned, the read() shall
    return -1, with errno set to [EIO] and no signal shall be sent. The
    default action of the SIGTTIN signal shall be to stop the process
    to which it is sent."

    "If a process is in the foreground process group of its controlling
    terminal, write operations shall be allowed as described in Writing
    Data and Output Processing. Attempts by a process in a background
    process group to write to its controlling terminal shall cause the
    process group to be sent a SIGTTOU signal unless one of the following
    special cases applies: if TOSTOP is not set, or if TOSTOP is set and
    the process is ignoring the SIGTTOU signal or the writing thread is
    blocking the SIGTTOU signal, the process is allowed to write to the
    terminal and the SIGTTOU signal is not sent. If TOSTOP is set, the
    process group of the writing process is orphaned, the writing process
    is not ignoring the SIGTTOU signal, and the writing thread is not blocking
    the SIGTTOU signal, the write() shall return -1, with errno set to [EIO] >> and no signal shall be sent."


    This implies that multiple processes _within_ a process group need to
    coordinate both read and write operations to avoid conflicting with
    each other

    And, if they don't co-ordinate read and write operations, then....
    the unpredictable happens.

    Just like Meredith is seeing with the various iterations of her program, >where both less(1) and the shell compete for input (and output) from
    the controlling terminal.

    It gets even worse if both processes are sharing a file descriptor
    but are using different FILE objects to access that file descriptor
    due to the stdio buffering (avoided by disabling buffering with setvbuf(3)).

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Meredith Montgomery@21:1/5 to Scott Lurndal on Sat Mar 12 11:39:05 2022
    scott@slp53.sl.home (Scott Lurndal) writes:

    Lew Pitcher <lew.pitcher@digitalfreehold.ca> writes:
    On Thu, 03 Mar 2022 16:23:53 +0000, Scott Lurndal wrote:

    Lew Pitcher <lew.pitcher@digitalfreehold.ca> writes:
    On Thu, 03 Mar 2022 10:15:27 +0100, Ralf Fassel wrote:

    * Lew Pitcher <lew.pitcher@digitalfreehold.ca>
    | However, when the child process runs less(2), other things
    | happen. /If/
    | we had forked off /usr/bin/cat instead of /usr/bin/less, we
    | could have
    | forgone the wait(2). But, /usr/bin/less expects to have
    | control of the
    | controlling terminal (where /usr/bin/cat does not), and
    | once our parent
    | process terminates, control of the controlling terminal is given back
    | to the shell.

    I always thought that fork() duplicates the current process with
    'everything', so I would have expected the child (which eventually execs >>>>> "less") to also have "control of the controlling terminal".
    But obviously there is more to that...

    Well, /every/ process that has not divorced itself from it's controlling >>>>terminal has control of that terminal, in that every process can
    read from it,
    write to it, and change it's characteristics (line echo, etc.).

    I still don't understand the intricacies of it myself, but in it's simplest >>>>form, imagine if two processes both are trying to read from the controlling >>>>terminal: which process gets the input?

    "If a process is in the foreground process group of its controlling
    terminal, read operations shall be allowed, as described in Input
    Processing and Reading Data. Any attempts by a process in a background >>> process group to read from its controlling terminal cause its process >>> group to be sent a SIGTTIN signal unless one of the following special >>> cases applies: if the reading process is ignoring the SIGTTIN signal
    or the reading thread is blocking the SIGTTIN signal, or if the
    process group of the reading process is orphaned, the read() shall
    return -1, with errno set to [EIO] and no signal shall be sent. The
    default action of the SIGTTIN signal shall be to stop the process
    to which it is sent."

    "If a process is in the foreground process group of its controlling
    terminal, write operations shall be allowed as described in Writing
    Data and Output Processing. Attempts by a process in a background
    process group to write to its controlling terminal shall cause the
    process group to be sent a SIGTTOU signal unless one of the following >>> special cases applies: if TOSTOP is not set, or if TOSTOP is set and
    the process is ignoring the SIGTTOU signal or the writing thread is
    blocking the SIGTTOU signal, the process is allowed to write to the
    terminal and the SIGTTOU signal is not sent. If TOSTOP is set, the
    process group of the writing process is orphaned, the writing process >>> is not ignoring the SIGTTOU signal, and the writing thread is not blocking
    the SIGTTOU signal, the write() shall return -1, with errno set to [EIO] >>> and no signal shall be sent."


    This implies that multiple processes _within_ a process group need to
    coordinate both read and write operations to avoid conflicting with
    each other

    And, if they don't co-ordinate read and write operations, then....
    the unpredictable happens.

    Just like Meredith is seeing with the various iterations of her program, >>where both less(1) and the shell compete for input (and output) from
    the controlling terminal.

    It gets even worse if both processes are sharing a file descriptor
    but are using different FILE objects to access that file descriptor
    due to the stdio buffering (avoided by disabling buffering with setvbuf(3)).

    Thank you so much for this thread. It's very clear now the sort of
    stuff in the system that I must study.

    My exercise, therefore, needs another fork if I intend for the parent
    process to exec cat because cat won't wait for the child and so the
    child might lose its parent and, therefore, losing rights to talk to the terminal. Forking again before running cat allows the parent to wait
    for both cat and less, keeping both programs with the right to talk to
    the terminal. So it seems I may conclude that for a situation such as

    cat --> less

    we really need three processes, one for cat, one for less and one parent
    for waiting for both to avoid any of them from losing the right to talk
    to the terminal (assuming any of them might try to). In general, a
    pipeline of n processes requires at least n+1 processes. That's what I conclude right now. I suppose there could be exceptions there and I
    might be in general wrong, but I don't see any possible exceptions right
    now.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Rainer Weikusat@21:1/5 to Meredith Montgomery on Sun Mar 13 20:47:36 2022
    Meredith Montgomery <mmontgomery@levado.to> writes:

    [...]

    My exercise, therefore, needs another fork if I intend for the parent
    process to exec cat because cat won't wait for the child and so the
    child might lose its parent and, therefore, losing rights to talk to the terminal. Forking again before running cat allows the parent to wait
    for both cat and less, keeping both programs with the right to talk to
    the terminal. So it seems I may conclude that for a situation such as

    cat --> less

    we really need three processes, one for cat, one for less and one parent
    for waiting for both to avoid any of them from losing the right to talk
    to the terminal (assuming any of them might try to). In general, a
    pipeline of n processes requires at least n+1 processes.

    This happens to be true when creating pipelines from the shell as
    there's always a shell process involved. But in general, it's not
    true. You're just creating the pipeline in the wrong way. There are 3
    kinds of processes in a pipeline

    1. The first process if there's more than one. Stdin of the first
    process refers to the terminal and stdout refers to a pipe.

    2. n intermediate processes, n >= 0. Stdin of an intermediate process is
    the most recently create pipe. Stdout is a newly created pipe.

    3. The last process reads from the most recently created pipe if there's
    more than one process, otherwise from the terminal. It writes to the
    terminal.

    Assuming there are only well-behaved commands in the pipeline, ie
    commands which actually process all of their stdin input, the initial
    process needs to run the last command to ensure that everythings works
    as it should:

    Example:

    ----
    #include <unistd.h>

    static void exec_cmd(int in_fd, char *cmd)
    {
    if (in_fd != -1) {
    dup2(in_fd, 0);
    close(in_fd);
    }

    execlp("sh", "/bin/sh", "-c", cmd, (void *)0);
    }

    static int start_cmd(int in_fd, char *cmd)
    {
    int fds[2];

    pipe(fds);

    if (fork()) {
    close(in_fd);
    close(fds[1]);

    return *fds;
    }

    dup2(fds[1], 1);
    close(*fds);
    close(fds[1]);

    exec_cmd(in_fd, cmd);
    }

    int main(int argc, char **argv)
    {
    char *cmd, *next;
    int fd;

    fd = -1;
    ++argv;

    cmd = *argv;
    while (next = *++argv) {
    fd = start_cmd(fd, cmd);
    cmd = next;
    }

    exec_cmd(fd, cmd);
    }
    ----

    This shell commands as arguments an runs them in a pipeline, eg

    ./a.out 'cat /var/log/syslog' 'sed "s/Mar/Ram/"' less

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)