link(2)
๐ link โ bestie, we're making a new name for a file ๐
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
oldpathis relative, then it gets interpreted relative to the directory that the file descriptorolddirfdrefers to โ not relative to the current working directory of the calling process, which is whatlink()does for a relative pathname. she's fancy like that. ๐ - if
oldpathis relative andolddirfdis the special valueAT_FDCWD, thenoldpathis interpreted relative to the current working directory of the calling process โ just likelink(). so chill of her. ๐ - if
oldpathis absolute, thenolddirfdis 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) โ ifoldpathis an empty string, this creates a link to the file referenced byolddirfd(which may have been obtained using theopen(2)O_PATHflag). in this case,olddirfdcan 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 withO_TMPFILEand withoutO_EXCLare the exception. also, the caller must have theCAP_DAC_READ_SEARCHcapability to use this flag. and this flag is Linux-specific, so define_GNU_SOURCEto obtain its definition. iykyk ๐AT_SYMLINK_FOLLOW(since Linux 2.6.18) โ by default,linkat(), does not dereferenceoldpathif it's a symbolic link (likelink()). the flagAT_SYMLINK_FOLLOWcan be specified inflagsto causeoldpathto be dereferenced if it is a symbolic link. and if procfs is mounted, this can be used as an alternative toAT_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 โ
-1is returned, anderrnois 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 containingnewpathis denied, or search permission is denied for one of the directories in the path prefix ofoldpathornewpath. (see alsopath_resolution(7).) ๐ซEDQUOTโ the user's quota of disk blocks on the filesystem has been exhausted. you ran out, babes. ๐ธEEXISTโnewpathalready exists. she's taken. ๐EFAULTโoldpathornewpathpoints outside your accessible address space. ๐ฅEIOโ an I/O error occurred. ๐ELOOPโ too many symbolic links were encountered in resolvingoldpathornewpath. it's a loop, it's giving spiral ๐EMLINKโ the file referred to byoldpathalready has the maximum number of links to it. for example, on anext4(5)filesystem that does not employ thedir_indexfeature, the limit on the number of hard links to a file is 65,000; onbtrfs(5), the limit is 65,535 links. no more friends allowed. ๐ญENAMETOOLONGโoldpathornewpathwas too long. girl, that's a whole paragraph, not a filename โENOENTโ a directory component inoldpathornewpathdoes 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 inoldpathornewpathis not, in fact, a directory. the audacity ๐EPERMโoldpathis a directory. nope.EPERMโ the filesystem containingoldpathandnewpathdoes 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_hardlinksinproc_sys_fs(5)). nope nope nope.EPERMโoldpathis marked immutable or append-only. (seeFS_IOC_SETFLAGS(2const).) ๐ซEROFSโ the file is on a read-only filesystem. she's frozen, literally ๐งEXDEVโoldpathandnewpathare not on the same mounted filesystem. (Linux permits a filesystem to be mounted at multiple points, butlink()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 butolddirfd(newdirfd) is neitherAT_FDCWDnor a valid file descriptor. ๐EINVALโ an invalid flag value was specified inflags. girl that's not a real flag ๐ENOENTโAT_EMPTY_PATHwas specified inflags, but the caller did not have theCAP_DAC_READ_SEARCHcapability. โจENOENTโ an attempt was made to link to the/proc/self/fd/NNfile 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/NNfile corresponding to a file that has been deleted. gone girl ๐ENOENTโoldpathis a relative pathname andolddirfdrefers to a directory that has been deleted, ornewpathis a relative pathname andnewdirfdrefers to a directory that has been deleted. ๐ซฅENOTDIRโoldpathis relative andolddirfdis a file descriptor referring to a file other than a directory; or similar fornewpathandnewdirfd. ๐EPERMโAT_EMPTY_PATHwas specified inflags,oldpathis an empty string, andolddirfdrefers 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
- Copyright 1992, Drew Eckhardt <drew@cs.colorado.edu>
- Copyright 1993, Ian Jackson <iwj10@cus.cam.ac.uk>
- Copyright 2006-2014, Michael Kerrisk <mtk.manpages@gmail.com>
- ๐ girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms