0

Using the advise found here: Restoring stdout after using dup I tried to restore the stdin and stdout. However, when using printf to check if stdout was restored I could not read an output. The code is as follows. After restoring stdout as 1, I tried printing "done".

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

void main(int argv, char *argc) {
    int stdin_copy = dup(STDIN_FILENO);
    int stdout_copy = dup(STDOUT_FILENO);
    int testpipe[2];
    pipe(testpipe); 
    int PID = fork();
    if (PID == 0) { 
        dup2(testpipe[0], 0);
        close(testpipe[1]);
        execl("./multby", "multby", "3", NULL);// Just multiplies argument 3 with the number in testpipe.
    } else {
        dup2(testpipe[1], 1);
        close(testpipe[0]);
        printf("5");
        fclose(stdout);
        close(testpipe[1]);
        char initialval[100];
        read(testpipe[0], initialval, 100);
        fprintf(stderr, "initial value: %s\n", initialval);
        wait(NULL);
        dup2(stdin_copy, 0);
        dup2(stdout_copy, 1);
        printf("done");//was not displayed when I run code.
    }
}

However, I did not see a "done" when I ran the code. (There should be a done after 15). This is my output: initial value: exec successful a: 3 b: 5 multby successful 15

What did I do wrong when restoring stdout?

2 Answers2

1

You're calling fclose(stdout); which closes the stdout FILE object as well as STDOUT_FILELO -- the stdout file descriptor (they're two different things, linked together). So when you later call dup2(stdout_copy, 1);, that restores the file descriptor, but the FILE object remains closed, so you can't use it to output anything. There's no way to reopen the FILE object to refer to a file descriptor1, so your best bet is just to remove the fclose line. The dup2 will close the file descriptor you're replacing (so you don't really need a separate close) and you should be able to see the output


1You could possibly use freopen with /dev/fd on some systems, but /dev/fd is non-portable

Chris Dodd
  • 119,907
  • 13
  • 134
  • 226
  • Since [`freopen()`](https://port70.net/~nsz/c/c11/n1570.html#7.21.5.4) is part of standard C, it is pretty portable. Since [`fdopen()`](http://pubs.opengroup.org/onlinepubs/9699919799/functions/fdopen.html) is a part of standard POSIX, it too is reasonably portable — if anything POSIX is available on the target machine. – Jonathan Leffler Feb 18 '20 at 05:53
  • @JonathanLeffler but /dev/fd is not standard – Chris Dodd Feb 18 '20 at 05:59
  • Ah — I see. What you are claiming is non-portable is different from what I assumed you were saying. Maybe the "that" should be "`/dev/fd`"? – Jonathan Leffler Feb 18 '20 at 06:03
  • But isn't fclose() needed to flush the input (5) into the pipe? Otherwise I get no input from dup(testpipe[0], 0) – Marine Biologist Kujo Feb 18 '20 at 06:25
  • Depends on your buffering mode -- use fflush(stdout) to flush if it is buffered. – Chris Dodd Feb 18 '20 at 15:47
  • @JonathanLeffler Usually `fclose` calls `close` on the file descriptor stored inside the `FILE`. Also the `https` side of port70.net uses a legacy Symantec certificate so it gives a warning for Chrome (and possibly other browser) users. – S.S. Anne Feb 20 '20 at 01:54
  • @S.S.Anne — regarding Chrome, I tried both 8.0.3987.106 and 8.0.3987.116 (and have since updated the 106 version to 116), and they did indeed object. I think (but can no longer easily prove) that I tried 8.0.3987.87 and it didn't object. Anyway, duly noted; I'll aim to remember to use the http links instead. Thanks! – Jonathan Leffler Feb 20 '20 at 02:08
  • @JonathanLeffler I'm still on 79 (Chrome OS) and I think it's been doing it for a while (probably since 70 or even before that). – S.S. Anne Feb 20 '20 at 02:21
  • @S.S.Anne: Curious — I have a work and home machine (located about 3 metres apart), and I use Chrome mainly on the work machine (and Firefox mainly on the home machine), and have connected to the site without problems until now. Hmmm: the URL bookmark has no visible prefix on that machine. When I add `https://` it complains; when I add `http://`, it doesn't. I'm not sure of the significance of that. Both machines now have `http://` on that URL, so as I copy'n'paste I'll be giving the 'insecure' URL, though I think the risks are fairly limited. I'm not planning to retrofit older answers. – Jonathan Leffler Feb 20 '20 at 02:26
1

General remarks

Reiterating what I've said before on SO in other answers.

You aren't closing enough file descriptors in the child process.


Rule of thumb: If you dup2() one end of a pipe to standard input or standard output, close both of the original file descriptors returned by pipe() as soon as possible. In particular, you should close them before using any of the exec*() family of functions.

The rule also applies if you duplicate the descriptors with either dup() or fcntl() with F_DUPFD or F_DUPFD_CLOEXEC.


If the parent process will not communicate with any of its children via the pipe, it must ensure that it closes both ends of the pipe early enough (before waiting, for example) so that its children can receive EOF indications on read (or get SIGPIPE signals or write errors on write), rather than blocking indefinitely. Even if the parent uses the pipe without using dup2(), it should normally close at least one end of the pipe — it is extremely rare for a program to read and write on both ends of a single pipe.

Note that the O_CLOEXEC option to open(), and the FD_CLOEXEC and F_DUPFD_CLOEXEC options to fcntl() can also factor into this discussion.

If you use posix_spawn() and its extensive family of support functions (21 functions in total), you will need to review how to close file descriptors in the spawned process (posix_spawn_file_actions_addclose(), etc.).

Note that using dup2(a, b) is safer than using close(b); dup(a); for a variety of reasons. One is that if you want to force the file descriptor to a larger than usual number, dup2() is the only sensible way to do that. Another is that if a is the same as b (e.g. both 0), then dup2() handles it correctly (it doesn't close b before duplicating a) whereas the separate close() and dup() fails horribly. This is an unlikely, but not impossible, circumstance.


Analysis of code

The question contains the code:

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

void main(int argv, char *argc) {
    int stdin_copy = dup(STDIN_FILENO);
    int stdout_copy = dup(STDOUT_FILENO);
    int testpipe[2];
    pipe(testpipe); 
    int PID = fork();
    if (PID == 0) { 
        dup2(testpipe[0], 0);
        close(testpipe[1]);
        execl("./multby", "multby", "3", NULL);// Just multiplies argument 3 with the number in testpipe.
    } else {
        dup2(testpipe[1], 1);
        close(testpipe[0]);
        printf("5");
        fclose(stdout);
        close(testpipe[1]);
        char initialval[100];
        read(testpipe[0], initialval, 100);
        fprintf(stderr, "initial value: %s\n", initialval);
        wait(NULL);
        dup2(stdin_copy, 0);
        dup2(stdout_copy, 1);
        printf("done");//was not displayed when I run code.
    }
}

The line void main(int argv, char *argc) { should be int main(void) since you do not use the command line arguments. You also have the names argv and argc reversed from the normal convention — the first argument is normally called argc (argument count) and the second is normally called argv (argument vector). Additionally, the type for the second argument should be char **argv (or char **argc if you want to confuse all your casual readers). See also What should main() return in C and C++?

The next block of code that warrants discussion is:

    if (PID == 0) { 
        dup2(testpipe[0], 0);
        close(testpipe[1]);
        execl("./multby", "multby", "3", NULL);// Just multiplies argument 3 with the number in testpipe.
    }

This breaks the rule of thumb. You should also put error handling code after the execl().

if (PID == 0)
{
    dup2(testpipe[0], STDIN_FILENO);
    close(testpipe[0]);
    close(testpipe[1]);
    execl("./multby", "multby", "3", (char *)NULL);
    fprintf(stderr, "failed to execute ./multby\n");
    exit(EXIT_FAILURE);
}

The next block of code to analyze is:

dup2(testpipe[1], 1);
close(testpipe[0]);
printf("5");
fclose(stdout);
close(testpipe[1]);

In theory, you should use STDOUT_FILENO instead of 1, but I have considerable sympathy with the use of 1 (not least because when I first learned C, there was no such symbolic constant). You do actually close both ends of the pipe, but I'd prefer to see both closes immediately after the dup2() call, in line with the rule of thumb. The printf() without a newline does send anything down the pipe; it stashes the 5 in the I/O buffer.

As Chris Dodd identified in their answer, the fclose(stdout) call is a source of much trouble. You should probably simply replace it with fflush(stdout).

Moving on:

char initialval[100];
read(testpipe[0], initialval, 100);
fprintf(stderr, "initial value: %s\n", initialval);
wait(NULL);
dup2(stdin_copy, 0);
dup2(stdout_copy, 1);
printf("done");

You didn't check whether the read() worked. It didn't; it failed with EBADF, because just above this you use close(testpipe[0]);. What you print on stderr there is an uninitialized string — that's not good. In practice, if you want to read information from the child reliably, you need two pipes, one for parent-to-child communication and the other for child-to-parent communication. Otherwise, there's no guarantee that the parent won't read what it wrote. If you waited for the child to die before reading, you'd be in with a decent chance of it working, but you can't always rely on being able to do that.

The first of the two dup2() calls is pointless; you didn't change the redirection for standard input (so in fact stdin_copy is unnecessary). The second changes the assignment of the standard output file descriptor, but you had already closed stdout, so there is no easy way to reopen it so that the printf() would work. The message should end with a newline — most printf() format strings should end with a newline, unless it is deliberately being used to build up a single line of output piecemeal. However, if you paid attention to the return value, you'd find that it failed (-1) and the chances are good that you'd find errno == EBADF again.

Fixing the code — fragile solution

Given this code for multby.c:

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

int main(int argc, char **argv)
{
    if (argc != 2)
    {
        fprintf(stderr, "Usage; %s number", argv[0]);
        return 1;
    }
    int multiplier = atoi(argv[1]);
    int number;
    if (scanf("%d", &number) == 1)
        printf("%d\n", number * multiplier);
    else
        fprintf(stderr, "%s: failed to read a number from standard input\n", argv[0]);
    return 0;
}

and this code (as pipe23.c, compiled to pipe23):

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

int main(void)
{
    int stdout_copy = dup(STDOUT_FILENO);
    int testpipe[2];
    pipe(testpipe);
    int PID = fork();
    if (PID == 0)
    {
        dup2(testpipe[0], 0);
        dup2(testpipe[1], 1);
        close(testpipe[1]);
        close(testpipe[0]);
        execl("./multby", "multby", "3", NULL);// Just multiplies argument 3 with the number in testpipe.
        fprintf(stderr, "failed to execute ./multby (%d: %s)\n", errno, strerror(errno));
        exit(EXIT_FAILURE);
    }
    else
    {
        dup2(testpipe[1], 1);
        close(testpipe[1]);
        printf("5\n");
        fflush(stdout);
        close(1);
        wait(NULL);
        char initialval[100];
        read(testpipe[0], initialval, 100);
        close(testpipe[0]);
        fprintf(stderr, "initial value: [%s]\n", initialval);
        dup2(stdout_copy, 1);
        printf("done\n");
    }
}

the combination barely works — it is not a resilient solution. For example, I added the newline after the 5. The child waits for another character after the 5 to determine that it has finished reading the number. It doesn't get EOF because it has the write end of the pipe open for sending the response to the parent, even if it is hung reading from the pipe so it never will write to it. But because it only attempts to read one number, it is OK.

The output is:

initial value: [15
]
done

Fixing the code — robust solution

If you were dealing with arbitrary quantities of numbers, you'd need to use two pipes — it is the only reliable way of doing the task. This would also work for a single number passed to the child, of course.

Here's a modified multby.c which loops on reading:

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

int main(int argc, char **argv)
{
    if (argc != 2)
    {
        fprintf(stderr, "Usage; %s number", argv[0]);
        return 1;
    }
    int multiplier = atoi(argv[1]);
    int number;
    while (scanf("%d", &number) == 1)
        printf("%d\n", number * multiplier);
    return 0;
}

and here's a modified pipe23.c that uses two pipes and writes 3 numbers to the child, and gets back three results. Note that it doesn't need to put a newline after the third number with this organization (though there'd be no harm done if it did include a newline). Also, if you're devious, the second space in the list of numbers is unnecessary too; the - isn't part of the second number, so the scanning stops after the 0.

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

int main(void)
{
    int stdout_copy = dup(STDOUT_FILENO);
    int p_to_c[2];
    int c_to_p[2];
    if (pipe(p_to_c) != 0 || pipe(c_to_p) != 0)
    {
        fprintf(stderr, "failed to open a pipe (%d: %s)\n", errno, strerror(errno));
        exit(EXIT_FAILURE);
    }
    int PID = fork();
    if (PID == 0)
    {
        dup2(p_to_c[0], 0);
        dup2(c_to_p[1], 1);
        close(c_to_p[0]);
        close(c_to_p[1]);
        close(p_to_c[0]);
        close(p_to_c[1]);
        execl("./multby", "multby", "3", NULL);// Just multiplies argument 3 with the number in testpipe.
        fprintf(stderr, "failed to execute ./multby (%d: %s)\n", errno, strerror(errno));
        exit(EXIT_FAILURE);
    }
    else
    {
        dup2(p_to_c[1], 1);
        close(p_to_c[1]);
        close(p_to_c[0]);
        close(c_to_p[1]);
        printf("5 10 -15");
        fflush(stdout);
        close(1);
        char initialval[100];
        int n = read(c_to_p[0], initialval, 100);
        if (n < 0)
        {
            fprintf(stderr, "failed to read from the child (%d: %s)\n", errno, strerror(errno));
            exit(EXIT_FAILURE);
        }
        close(c_to_p[0]);
        wait(NULL);
        fprintf(stderr, "initial value: [%.*s]\n", n, initialval);
        dup2(stdout_copy, 1);
        printf("done\n");
    }
}

Note that there are lots of calls to close() in there — two for each of the 4 descriptors involved in handling two pipes. This is normal. Not taking care to close the file descriptors can easily lead to hung systems.

The output from running this pipe23 is this, which is what I wanted:

initial value: [15
30
-45
]
done
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278