You're not supposed to be able to do that (but read below for an interesting exception).
If the kernel was to let it happen, then a call like:
fd = open(filename, O_CREAT|O_RDWR, 0666);
unlink(filename);
linkat(fd, "", 0, "/new/path", AT_EMPTY_PATH);
would succeed even when the inode referenced by fd
has a link count of 0, when done by a process with CAP_DAC_READ_SEARCH
caps.
But the kernel is actively preventing it from happening, without regard to the capabilities or privileges of the process doing it.
int vfs_link(struct dentry *old_dentry, ...
{
...
/* Make sure we don't allow creating hardlink to an unlinked file */
if (inode->i_nlink == 0 && !(inode->i_state & I_LINKABLE))
error = -ENOENT;
This is also documented in the manpage:
AT_EMPTY_PATH
(since Linux 2.6.39)
If oldpath
is an empty string, create 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. This will generally not work if the file has a link count of zero (files created with O_TMPFILE
and without O_EXCL
are an exception). The caller must have the CAP_DAC_READ_SEARCH
capability in order to use this flag. This flag is Linux-specific; define _GNU_SOURCE
to obtain its definition.
based on the kernel source, there seems to be no other exception besides O_TMPFILE
. O_TMPFILE
is documented in the open(2)
manpage; below is a small example based on that:
#define _GNU_SOURCE 1
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <err.h>
int main(int ac, char **av){
char path[64]; int fd;
if(ac < 3) errx(1, "usage: %s dir newpath", av[0]);
if((fd = open(av[1], O_TMPFILE|O_RDWR, 0666)) == -1) err(1, "open");
/*
* ...
* write stuff to fd and only "realize" the file at the end if
* everything has succeeded
*/
/* the following line only works with CAP_DAC_READ_SEARCH */
/* if(linkat(fd, "", 0, av[2], AT_EMPTY_PATH)) err(1, "linkat"); */
snprintf(path, sizeof path, "/proc/self/fd/%d", fd);
if(linkat(AT_FDCWD, path, AT_FDCWD, av[2], AT_SYMLINK_FOLLOW))
err(1, "linkat");
return 0;
}