link(2)

okay so bestie, gather round, because today we're talking about link and linkat from the link(2) manual page โ€” and honestly? iconic behavior from the kernel. no cap. โœจ

๐Ÿ“š LIBRARY

Standard C library (libc, -lc). yeah she lives in libc, where all the cool kids hang out ๐Ÿซถ


โœจ SYNOPSIS โœจ

okay first, the vibe. here's how you summon her:

#include <unistd.h>

int link(const char *oldpath, const char *newpath);

#include <fcntl.h>           /* Definition of AT_* constants */
#include <unistd.h>

int linkat(int olddirfd, const char *oldpath,
           int newdirfd, const char *newpath, int flags);

๐ŸŒธ Feature Test Macro Requirements for glibc ๐ŸŒธ

(and yes bestie, that's the feature_test_macros(7) situation, go look her up)

for linkat():

Since glibc 2.10:
    _POSIX_C_SOURCE >= 200809L
Before glibc 2.10:
    _ATFILE_SOURCE

like, define the right macro or she's not gonna show up. it's giving "you have to text first" ๐Ÿ˜ญ


๐Ÿ’… DESCRIPTION ๐Ÿ’…

so link() creates a new link โ€” also known as a hard link โ€” to an existing file. that's it. that's the whole girlboss move. ๐Ÿ’–

and if newpath already exists? she will NOT be overwritten. periodt. no takebacks, no oopsie-daisy. ๐Ÿ™ˆ

the tea is: this new name can be used exactly as the old one for any operation. both names refer to the same file (so they share the same permissions and the same ownership), and honestly? it is impossible to tell which name was the "original". she's just two names for one iconic file. main character energy from both. ๐Ÿ‘‘

๐Ÿฆ‹ linkat() ๐Ÿฆ‹

linkat() is the same girl, different outfit โ€” she operates in exactly the same way as link(), except for the little differences we're about to spill. ๐ŸŒˆ

  • if the pathname in oldpath is relative, then it gets interpreted relative to the directory that the file descriptor olddirfd refers to โ€” not relative to the current working directory of the calling process, which is what link() does for a relative pathname. she's fancy like that. ๐Ÿ’…
  • if oldpath is relative and olddirfd is the special value AT_FDCWD, then oldpath is interpreted relative to the current working directory of the calling process โ€” just like link(). so chill of her. ๐Ÿ˜Œ
  • if oldpath is absolute, then olddirfd is ignored. sorry babes, she's not looking at it. ๐Ÿ’€

and newpath? interpreted as for oldpath, except a relative pathname is interpreted relative to the directory referred to by the file descriptor newdirfd. consistent queen. ๐ŸŽ€

the flags you can bitwise OR in ๐Ÿ’ซ

  • AT_EMPTY_PATH (since Linux 2.6.39) โ€” if oldpath is an empty string, this creates a link to the file referenced by olddirfd (which may have been obtained using the open(2) O_PATH flag). in this case, olddirfd can refer to any type of file except a directory. > heads up though: this will generally not work if the file has a link count of zero โ€” files created with O_TMPFILE and without O_EXCL are the exception. also, the caller must have the CAP_DAC_READ_SEARCH capability to use this flag. and this flag is Linux-specific, so define _GNU_SOURCE to obtain its definition. iykyk ๐ŸŒŸ
  • AT_SYMLINK_FOLLOW (since Linux 2.6.18) โ€” by default, linkat(), does not dereference oldpath if it's a symbolic link (like link()). the flag AT_SYMLINK_FOLLOW can be specified in flags to cause oldpath to be dereferenced if it is a symbolic link. and if procfs is mounted, this can be used as an alternative to AT_EMPTY_PATH, like this:
linkat(AT_FDCWD, "/proc/self/fd/<fd>", newdirfd,
       newname, AT_SYMLINK_FOLLOW);

okay but bestie, history lesson: before Linux 2.6.18, the flags argument was unused, and had to be specified as 0. no options, no vibes, just zero. ๐Ÿ˜ญ

and if you're wondering why linkat() even needs to exist, see openat(2) for an explanation of the need for linkat(). she explains herself. go read her. ๐Ÿ“–


๐ŸŒŸ RETURN VALUE ๐ŸŒŸ

  • on success โ†’ zero is returned โœจ
  • on error โ†’ -1 is returned, and errno is set to indicate the error ๐Ÿ˜ญ

๐Ÿ’€ ERRORS ๐Ÿ’€

buckle up bestie, this is the long part, but every single one matters:

  • EACCES โ€” write access to the directory containing newpath is denied, or search permission is denied for one of the directories in the path prefix of oldpath or newpath. (see also path_resolution(7).) ๐Ÿšซ
  • EDQUOT โ€” the user's quota of disk blocks on the filesystem has been exhausted. you ran out, babes. ๐Ÿ’ธ
  • EEXIST โ€” newpath already exists. she's taken. ๐Ÿ™…
  • EFAULT โ€” oldpath or newpath points outside your accessible address space. ๐Ÿ’ฅ
  • EIO โ€” an I/O error occurred. ๐Ÿ’”
  • ELOOP โ€” too many symbolic links were encountered in resolving oldpath or newpath. it's a loop, it's giving spiral ๐ŸŒ€
  • EMLINK โ€” the file referred to by oldpath already has the maximum number of links to it. for example, on an ext4(5) filesystem that does not employ the dir_index feature, the limit on the number of hard links to a file is 65,000; on btrfs(5), the limit is 65,535 links. no more friends allowed. ๐Ÿ˜ญ
  • ENAMETOOLONG โ€” oldpath or newpath was too long. girl, that's a whole paragraph, not a filename โœ‹
  • ENOENT โ€” a directory component in oldpath or newpath does not exist or is a dangling symbolic link. ghosted fr ๐Ÿซฅ
  • ENOMEM โ€” insufficient kernel memory was available. brain empty. ๐Ÿง 
  • ENOSPC โ€” the device containing the file has no room for the new directory entry. no room at the inn ๐Ÿฉ
  • ENOTDIR โ€” a component used as a directory in oldpath or newpath is not, in fact, a directory. the audacity ๐Ÿ’€
  • EPERM โ€” oldpath is a directory. nope.
  • EPERM โ€” the filesystem containing oldpath and newpath does not support the creation of hard links. nope nope.
  • EPERM (since Linux 3.6) โ€” the caller does not have permission to create a hard link to this file (see the description of /proc/sys/fs/protected_hardlinks in proc_sys_fs(5)). nope nope nope.
  • EPERM โ€” oldpath is marked immutable or append-only. (see FS_IOC_SETFLAGS(2const).) ๐Ÿšซ
  • EROFS โ€” the file is on a read-only filesystem. she's frozen, literally ๐ŸงŠ
  • EXDEV โ€” oldpath and newpath are not on the same mounted filesystem. (Linux permits a filesystem to be mounted at multiple points, but link() does not work across different mounts, even if the same filesystem is mounted on both.) > so close yet so far bestie ๐Ÿ˜ญ

๐ŸŽ€ the extra errors that can happen for linkat() ๐ŸŽ€

  • EBADF โ€” oldpath (newpath) is relative but olddirfd (newdirfd) is neither AT_FDCWD nor a valid file descriptor. ๐Ÿ™ˆ
  • EINVAL โ€” an invalid flag value was specified in flags. girl that's not a real flag ๐Ÿ’…
  • ENOENT โ€” AT_EMPTY_PATH was specified in flags, but the caller did not have the CAP_DAC_READ_SEARCH capability. โœจ
  • ENOENT โ€” an attempt was made to link to the /proc/self/fd/NN file corresponding to a file descriptor created with
open(path, O_TMPFILE | O_EXCL, mode);

see open(2). ๐Ÿ‘€

  • ENOENT โ€” an attempt was made to link to a /proc/self/fd/NN file corresponding to a file that has been deleted. gone girl ๐Ÿ’”
  • ENOENT โ€” oldpath is a relative pathname and olddirfd refers to a directory that has been deleted, or newpath is a relative pathname and newdirfd refers to a directory that has been deleted. ๐Ÿซฅ
  • ENOTDIR โ€” oldpath is relative and olddirfd is a file descriptor referring to a file other than a directory; or similar for newpath and newdirfd. ๐Ÿ’€
  • EPERM โ€” AT_EMPTY_PATH was specified in flags, oldpath is an empty string, and olddirfd refers to a directory. no ma'am ๐Ÿšซ

๐Ÿ•ฐ๏ธ VERSIONS ๐Ÿ•ฐ๏ธ

okay so lore time. POSIX.1-2001 says that link() should dereference oldpath if it is a symbolic link. however โ€” and this is the plot twist โ€” since Linux 2.0, Linux does not do so: if oldpath is a symbolic link, then newpath is created as a (hard) link to the same symbolic link file (i.e., newpath becomes a symbolic link to the same file that oldpath refers to). some other implementations behave in the same manner as Linux. ๐Ÿ‘ฏ then POSIX.1-2008 changes the specification of link(), making it implementation-dependent whether or not oldpath is dereferenced if it is a symbolic link. and for precise control over the treatment of symbolic links when creating a link, use linkat(). she's the one with the settings. ๐Ÿ’…

๐ŸŒธ glibc ๐ŸŒธ

on older kernels where linkat() is unavailable, the glibc wrapper function falls back to the use of link(), unless the AT_SYMLINK_FOLLOW is specified. and when oldpath and newpath are relative pathnames, glibc constructs pathnames based on the symbolic links in /proc/self/fd that correspond to the olddirfd and newdirfd arguments. resourceful queen fr fr ๐Ÿ‘‘


๐Ÿ“œ STANDARDS ๐Ÿ“œ

POSIX.1-2024. she's certified, she's official, she's on the standards board. ๐ŸŽ“


๐Ÿ›๏ธ HISTORY ๐Ÿ›๏ธ

  • link() โ€” 4.3BSD, SVr4, POSIX.1-1988 (but see VERSIONS). an ancestor. a legend. ๐Ÿงš
  • linkat() โ€” POSIX.1-2008, glibc 2.4, Linux 2.6.16. the younger, more capable sibling. ๐ŸŒŸ

๐Ÿ“ NOTES ๐Ÿ“

hard links, as created by link(), cannot span filesystems. use symlink(2) if this is required. that's the whole note. short, sweet, life-changing. ๐Ÿซถ


๐Ÿ› BUGS ๐Ÿ›

on NFS filesystems, the return code may be wrong in case the NFS server performs the link creation and dies before it can say so. ๐Ÿ˜ต so bestie, use stat(2) to find out if the link actually got created. don't trust the return code, trust the stat. periodt. โœจ


๐Ÿ‘€ SEE ALSO ๐Ÿ‘€

ln(1), open(2), rename(2), stat(2), symlink(2), unlink(2), path_resolution(7), symlink(7)

go read them, they're all friends of the family ๐Ÿ’œ


and that's the whole page bestie!! this little gem comes from Linux man-pages 6.20, dated 2026-02-11 โ€” shoutout to the man-pages girlies for keeping us fed with documentation ๐Ÿซถ๐Ÿ’– no cap, go make some hard links, you absolute icon. slay!! โœจ๐ŸŽ€๐Ÿฆ‹๐Ÿ’…๐Ÿ‘‘๐ŸŒˆโญ

๐Ÿ“ฆ 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