Duplicating (dup() And Friends)#

#include <unistd.h>

int dup(int oldfd);
int dup2(int oldfd, int newfd);
int dup3(int oldfd, int newfd, int flags);

What’s A File Descriptor Again?#

  • A reference to in-kernel “open file description”

  • Open file description contains all runtime information

../../../../../../../_images/file-descriptor.svg

Duplicating: Creating Another Reference#

  • Sometimes a second reference - a file descriptor - to an open file description is needed

  • Example: Shell output redirection

  • Operations on either file descriptor share the same underlying structure

  • ⟶ manipulate a shared offset

Here dup() is used to create a new reference to an existing open file. dup() implicitly chooses the next free file descriptor number - 42 in this case. (See jjj How Descriptor Numbers Are Chosen)

../../../../../../../_images/dup.svg

dup(): Program#

  • Create second reference using dup()

  • Append bytes to file via original file descriptor

  • Inquire offset via dupped file descriptor

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

int main()
{
    int origfd = open("/tmp/somefile", O_WRONLY|O_CREAT|O_EXCL, 0666);
    if (origfd == -1) {
        perror("open");
        return 1;
    }

    int dupfd = dup(origfd);
    if (dupfd == -1) {
        perror("dup");
        return 1;
    }

    char content[] = "0123456789";
    ssize_t nwritten = 
        write(origfd, content, sizeof(content));       // <-- write via origfd
    if (nwritten == -1) {
        perror("write");
        return -1;
    }

    off64_t pos = lseek64(dupfd, 0, SEEK_CUR);         // <-- offset via dupfd
    if (pos == -1) {
        perror("lseek");
        return 1;
    }

    std::println("offset: {}", pos);

    return 0;                                          // <-- fds automatically closed at exit
}
$ touch /tmp/somefile
$ ./sysprog-dup-dup
offset: 11

Standardout Redirection With dup()#

  • println() (and many others) write to stdout (descriptor number 1)

  • Want them to write to /tmp/somefile instead

Solution:

  • Close stdout

  • Use dup() to select 1 as new file descriptor (see here for why)

../../../../../../../_images/first-free.svg
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <print>

int main()
{
    int origfd = open("/tmp/somefile", O_WRONLY|O_CREAT|O_EXCL, 0666);
    if (origfd == -1) {
        perror("open");
        return 1;
    }

    close(1);                                          // <-- or use STDOUT_FILENO for clarity
    int dupfd = dup(origfd);                           // <-- dupfd == STDOUT_FILENO
    if (dupfd == -1) {
        perror("dup");
        return 1;
    }

    std::println("hah, this won't be seen on the terminal");

    return 0;
}
$ ./sysprog-dup-stdout-redir
$ cat /tmp/somefile
hah, this won't be seen on the terminal

Standardout Redirection With dup2()#

  • dup() chooses first free file descriptor

  • Fragile trickery needed when I insist in STDOUT_FILENO

  • dup2() lets me specify target number

  • If target file descriptor is already occupied, dup2() releases (close()) it first

../../../../../../../_images/dup2.svg
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <print>

int main()
{
    int origfd = open("/tmp/somefile", O_WRONLY|O_CREAT|O_EXCL, 0666);
    if (origfd == -1) {
        perror("open");
        return 1;
    }

    int dupfd = dup2(origfd, 1);                       // <-- close 1 (terminal), reallocate/return 1
    if (dupfd == -1) {
        perror("dup2");
        return 1;
    }

    std::println("hah, this won't be seen on the terminal");

    return 0;
}
$ ./sysprog-dup-stdout-redir-dup2
$ cat /tmp/somefile
hah, this won't be seen on the terminal

Shell IO Redirection And Pipes#