Search code examples
linuxunixgnufile-descriptor

Usage of dup2()


dup2(fd, 0);
dup2(fd, 1);
dup2(fd, 2);
if (fd > 2)
  close(fd);

According to the book Advanced Programming in the UNIX environment, the if statement above is necessary. The book suggests to work through it by considering what would happen if fd = 1 in one case, and if fd = 3 in another.

If fd = 1, 0 (stdin) would be closed and then would point at stdout, 1 would still point at stdout (since dup2() doesn't close a file descriptor if it's equal to the first argument, and will return 1) and 2 would point at stdout.

If fd = 3, every file descriptor would be first closed and then point at whatever file 3 points at.

Why is it necessary to close the descriptor if it's greater than 2?


Solution

  • You don't want to leave any unused file descriptors open. After you redirect stdin, stdout, and stderr to fd, you're not planning on using fd by itself -- it was just opened temporarily so that you could then duplicate all the standard descriptors to it. So you need to close it.

    But you don't want to close it if it's actually the same as stdin, stdout, or stderr (this should be unlikely, but it's possible if one of them was already closed before you opened fd). So you need the if to prevent that.

    As to why it's important to close the original descriptor, some types of streams do something special (and often important) only when all of the descriptors referring to it are closed. For instance, a TCP connection will not be closed until all the descriptors are closed, a deleted file will only have its data removed from disk when all links are removed and all descriptors are closed, and the read end of a pipe only receives EOF when all the descriptors referring to the write end are closed. So if you leave fd open, and later close the standard descriptors, it will prevent this final action from occurring.

    A very common situation where you need to close a descriptor is when you create a pipe to communicate with a child process. The code generally looks something like:

    pipe(fds);
    switch (fork()) {
        case 0: // child
            dup2(fds[0], STDIN_FILENO);
            close(fds[0]);
            close(fds[1]);
            execlp("program", "program", "arg1", (char*)NULL);
            break;
        case -1: // error
            perror("fork");
            exit(-1);
            break;
        default: // parent
            close(fds[0]);
            // Code that writes to fds[1]
    }
    

    The child needs to close the write end of the pipe (fds[1]), otherwise the child won't receive EOF when the parent closes it.