dup(2)

โœจ dup(2) โœจ

okay so bestie, this is the System Calls Manual page for dup(2) and honestly? it's giving "how to clone a file descriptor" and it ate ๐Ÿ’… no cap.


๐ŸŒธ NAME ๐ŸŒธ

dup, dup2, dup3 โ€” duplicate a file descriptor

๐ŸŽ€ LIBRARY ๐ŸŽ€

Standard C library (libc, -lc) ๐Ÿซถ

โœจ SYNOPSIS โœจ

#include <unistd.h>

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

#define _GNU_SOURCE             /* See feature_test_macros(7) */
#include <fcntl.h>              /* Definition of O_* constants */
#include <unistd.h>

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

the _GNU_SOURCE thing is literally the vibe check you gotta pass before dup3 will even look at you, iykyk ๐Ÿ’€


๐Ÿ’– DESCRIPTION ๐Ÿ’–

so the dup() system call is the main character here: it allocates a brand-new file descriptor that refers to the same open file description as the descriptor oldfd. (and if you're sitting there like "bestie what even is an open file description", go read open(2), it explains it, trust ๐ŸŒˆ.)

the new file descriptor number is guaranteed to be the lowest-numbered file descriptor that was unused in the calling process. like, it literally goes looking for the first free spot. so efficient. so iconic. ๐Ÿ‘‘

after a successful return, the old and new file descriptors may be used interchangeably. and because the two file descriptors refer to the same open file description, they share the file offset and the file status flags. for example: if the file offset is modified by using lseek(2) on one of the file descriptors, the offset is also changed for the other one. they're linked. soulmates, basically ๐Ÿซถ๐Ÿ’ž

BUT โ€” and this is the plot twist โ€” the two file descriptors do not share file descriptor flags. that's the close-on-exec flag. the close-on-exec flag (FD_CLOEXEC; see fcntl(2)) for the duplicate descriptor is off. so it starts fresh, no baggage ๐Ÿ’…

๐Ÿฆ‹ dup2() ๐Ÿฆ‹

the dup2() system call does the same task as dup(), but instead of grabbing the lowest-numbered unused file descriptor, it uses the file descriptor number you specify in newfd. in other words: newfd gets adjusted so that it now refers to the same open file description as oldfd. you're not getting a hand-me-down slot, you're claiming a specific one โœจ

if the file descriptor newfd was previously open, it gets closed before being reused โ€” and that close is performed silently, which means any errors during the close are not reported by dup2(). it just... doesn't tell you. ๐Ÿ™ˆ

the steps of closing and reusing the file descriptor newfd are performed atomically. this is important, bestie, because if you tried to build this yourself with close(2) and dup() you'd be living in race condition city, where newfd might get reused between those two steps. that reuse could happen because the main program got interrupted by a signal handler that allocates a file descriptor, or because a parallel thread allocates a file descriptor. chaos. not the cute kind either ๐Ÿ’€

note the following points:

  • if oldfd is not a valid file descriptor, then the call fails, and newfd is not closed. (protective king energy, honestly.)
  • if oldfd is a valid file descriptor, and newfd has the same value as oldfd, then dup2() does nothing, and returns newfd. it simply refuses to do the thing. periodt.

๐ŸŒˆ dup3() ๐ŸŒˆ

dup3() is the same as dup2(), except that:

  • the caller can force the close-on-exec flag to be set for the new file descriptor by specifying O_CLOEXEC in flags. (see the description of the same flag in open(2) for reasons why this may be useful ๐Ÿ‘€)
  • if oldfd equals newfd, then dup3() fails with the error EINVAL. no silent no-op for you, sorry babe.

๐Ÿ‘‘ RETURN VALUE ๐Ÿ‘‘

on success, these system calls return the new file descriptor. on error, -1 is returned, and errno is set to indicate the error. classic. ๐Ÿคทโ€โ™€๏ธ

๐Ÿ’€ ERRORS ๐Ÿ’€

  • EBADF โ€” oldfd isn't an open file descriptor.
  • EBADF โ€” newfd is out of the allowed range for file descriptors (see the discussion of RLIMIT_NOFILE in getrlimit(2)).
  • EBUSY โ€” (Linux only) this may be returned by dup2() or dup3() during a race condition with open(2) and dup().
  • EINTR โ€” the dup2() or dup3() call was interrupted by a signal; see signal(7) ๐Ÿ˜ญ
  • EINVAL โ€” (dup3()) flags contain an invalid value.
  • EINVAL โ€” (dup3()) oldfd was equal to newfd.
  • EMFILE โ€” the per-process limit on the number of open file descriptors has been reached (see the discussion of RLIMIT_NOFILE in getrlimit(2)). you've officially collected too many. hoarder behavior. respect. ๐Ÿ“
  • ENOMEM โ€” insufficient kernel memory was available.

โญ STANDARDS โญ

POSIX.1-2024. she standards-certified, your honor โœจ

๐Ÿงš HISTORY ๐Ÿงš

dup() , dup2() โ€” 4.3BSD, SVr4, POSIX.1-1988.

dup3() โ€” POSIX.1-2024. Linux 2.6.27, glibc 2.9.

look at the lore. the ancestry. we love a documented glow-up ๐ŸŒธ


๐Ÿ’œ NOTES ๐Ÿ’œ

the error returned by dup2() is different from the error returned by fcntl(..., F_DUPFD, ...) when newfd is out of range. on some systems, dup2() also sometimes returns EINVAL like F_DUPFD. it's messy, it's inconsistent, we simply accept it ๐Ÿซ 

if newfd was open, any errors that would have been reported at close(2) time are lost. and if this is of concern to you โ€” then, unless the program is single-threaded and does not allocate file descriptors in signal handlers โ€” the correct approach is not to close newfd before calling dup2(), because of the race condition described above. instead, code something like the following could be used:

/* Obtain a duplicate of 'newfd' that can subsequently
   be used to check for close() errors; an EBADF error
   means that 'newfd' was not open.  */

tmpfd = dup(newfd);
if (tmpfd == -1 && errno != EBADF) {
    /* Handle unexpected dup() error.  */
}

/* Atomically duplicate 'oldfd' on 'newfd'.  */

if (dup2(oldfd, newfd) == -1) {
    /* Handle dup2() error.  */
}

/* Now check for close() errors on the file originally
   referred to by 'newfd'.  */

if (tmpfd != -1) {
    if (close(tmpfd) == -1) {
        /* Handle errors from close.  */
    }
}
bestie this is the correct pattern, do not fight it, do not "optimize" it, just copy it ๐Ÿ’…

๐Ÿ”ฅ SEE ALSO ๐Ÿ”ฅ

close(2), fcntl(2), open(2), pidfd_getfd(2)


and that's the whole page, no crumbs left ๐ŸŽ€ this certified banger of a man page comes to you from Linux man-pages 6.20, dated 2026-02-08 โ€” go forth and duplicate responsibly, my love ๐Ÿ’–โœจ

๐Ÿ“ฆ where this page comes from
  • arch rolling: man-pages 6.20-1 ยท BSD-2-Clause AND BSD-3-Clause AND BSD-4.3TAHOE AND BSD-4-Clause-UC AND GPL-1.0-or-later AND GPL-2.0-only AND GPL-2.0-or-later AND GPL-3.0-or-later AND LGPL-3.0-linking-exception AND LGPL-3.0-or-later AND LicenseRef-Public-Domain AND LicenseRef-UltraPermissive AND Linux-man-pages-1-para AND Linux-man-pages-copyleft AND Linux-man-pages-copyleft-2-para AND Linux-man-pages-copyleft-var AND MIT AND Spencer-94
  • nixpkgs unstable: man-pages 6.20
  • ๐Ÿ“œ the page's license: Linux-man-pages-copyleft
  • ๐Ÿ’– girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms