[ACCEPTED]-Relinking an anonymous (unlinked but open) file-unlink

Accepted answer
Score: 42

A patch for a proposed Linux flink() system call was submitted several years ago, but when 10 Linus stated "there is no way in HELL we can do this securely without major other incursions", that pretty much ended the 9 debate on whether to add this.

Update: As of Linux 8 3.11, it is now possible to create a file 7 with no directory entry using open() with the 6 new O_TMPFILE flag, and link it into the filesystem 5 once it is fully formed using linkat() on /proc/self/fd/fd with 4 the AT_SYMLINK_FOLLOW flag.

The following example is provided 3 on the open() manual page:

    char path[PATH_MAX];
    fd = open("/path/to/dir", O_TMPFILE | O_RDWR, S_IRUSR | S_IWUSR);

    /* File I/O on 'fd'... */

    snprintf(path, PATH_MAX,  "/proc/self/fd/%d", fd);
    linkat(AT_FDCWD, path, AT_FDCWD, "/path/for/file", AT_SYMLINK_FOLLOW);

Note that linkat() will not 2 allow open files to be re-attached after 1 the last link is removed with unlink().

Score: 2

My question: is there any way to re-attach 13 a file like this back into the directory 12 structure? If you could do this it means 11 that you could e.g. implement file writes 10 so that the file appears atomically and 9 fully formed. This appeals to the my compulsive 8 neatness. ;)

If this is your only goal, you 7 can achieve this in a much simpler and more 6 widely used manner. If you are outputting 5 to a.dat:

  1. Open a.dat.part for write.
  2. Write your data.
  3. Rename a.dat.part to a.dat.

I can understand wanting to be neat, but 4 unlinking a file and relinking it just to 3 be "neat" is kind of silly.

This question on serverfault seems 2 to indicate that this kind of re-linking 1 is unsafe and not supported.

Score: 2

Thanks to @mark4o posting about linkat(2), see his 18 answer for details.

I wanted to give it a 17 try to see what actually happened when trying 16 to actually link an anonymous file back 15 into the filesystem it is stored on. (often 14 /tmp, e.g. for video data that firefox is playing).


As 13 of Linux 3.16, there still appears to be 12 no way to undelete a deleted file that's 11 still held open. Neither AT_SYMLINK_FOLLOW nor AT_EMPTY_PATH for linkat(2) do 10 the trick for deleted files that used to 9 have a name, even as root.

The only alternative 8 is tail -c +1 -f /proc/19044/fd/1 > data.recov, which makes a separate copy, and you 7 have to kill it manually when it's done.


Here's 6 the perl wrapper I cooked up for testing. Use 5 strace -eopen,linkat linkat.pl - </proc/.../fd/123 newname to verify that your system still can't 4 undelete open files. (Same applies even 3 with sudo). Obviously you should read code 2 you find on the Internet before running 1 it, or use a sandboxed account.

#!/usr/bin/perl -w
# 2015 Peter Cordes <peter@cordes.ca>
# public domain.  If it breaks, you get to keep both pieces.  Share and enjoy

# Linux-only linkat(2) wrapper (opens "." to get a directory FD for relative paths)
if ($#ARGV != 1) {
    print "wrong number of args.  Usage:\n";
    print "linkat old new    \t# will use AT_SYMLINK_FOLLOW\n";
    print "linkat - <old  new\t# to use the AT_EMPTY_PATH flag (requires root, and still doesn't re-link arbitrary files)\n";
    exit(1);
}

# use POSIX qw(linkat AT_EMPTY_PATH AT_SYMLINK_FOLLOW);  #nope, not even POSIX linkat is there

require 'syscall.ph';
use Errno;
# /usr/include/linux/fcntl.h
# #define AT_SYMLINK_NOFOLLOW   0x100   /* Do not follow symbolic links.  */
# #define AT_SYMLINK_FOLLOW 0x400   /* Follow symbolic links.  */
# #define AT_EMPTY_PATH     0x1000  /* Allow empty relative pathname */
unless (defined &AT_SYMLINK_NOFOLLOW) { sub AT_SYMLINK_NOFOLLOW() { 0x0100 } }
unless (defined &AT_SYMLINK_FOLLOW  ) { sub AT_SYMLINK_FOLLOW  () { 0x0400 } }
unless (defined &AT_EMPTY_PATH      ) { sub AT_EMPTY_PATH      () { 0x1000 } }


sub my_linkat ($$$$$) {
    # tmp copies: perl doesn't know that the string args won't be modified.
    my ($oldp, $newp, $flags) = ($_[1], $_[3], $_[4]);
    return !syscall(&SYS_linkat, fileno($_[0]), $oldp, fileno($_[2]), $newp, $flags);
}

sub linkat_dotpaths ($$$) {
    open(DOTFD, ".") or die "open . $!";
    my $ret = my_linkat(DOTFD, $_[0], DOTFD, $_[1], $_[2]);
    close DOTFD;
    return $ret;
}

sub link_stdin ($) {
    my ($newp, ) = @_;
    open(DOTFD, ".") or die "open . $!";
    my $ret = my_linkat(0, "", DOTFD, $newp, &AT_EMPTY_PATH);
    close DOTFD;
    return $ret;
}

sub linkat_follow_dotpaths ($$) {
    return linkat_dotpaths($_[0], $_[1], &AT_SYMLINK_FOLLOW);
}


## main
my $oldp = $ARGV[0];
my $newp = $ARGV[1];

# link($oldp, $newp) or die "$!";
# my_linkat(fileno(DIRFD), $oldp, fileno(DIRFD), $newp, AT_SYMLINK_FOLLOW) or die "$!";

if ($oldp eq '-') {
    print "linking stdin to '$newp'.  You will get ENOENT without root (or CAP_DAC_READ_SEARCH).  Even then doesn't work when links=0\n";
    $ret = link_stdin( $newp );
} else {
    $ret = linkat_follow_dotpaths($oldp, $newp);
}
# either way, you still can't re-link deleted files (tested Linux 3.16 and 4.2).

# print STDERR 
die "error: linkat: $!.\n" . ($!{ENOENT} ? "ENOENT is the error you get when trying to re-link a deleted file\n" : '') unless $ret;

# if you want to see exactly what happened, run
# strace -eopen,linkat  linkat.pl
Score: 0

Clearly, this is possible -- fsck does it, for 8 example. However, fsck does it with major localized 7 file system mojo and will clearly not be 6 portable, nor executable as an unprivileged 5 user. It's similar to the debugfs comment above.

Writing 4 that flink(2) call would be an interesting exercise. As 3 ijw points out, it would offer some advantages 2 over current practice of temporary file 1 renaming (rename, note, is guaranteed atomic).

More Related questions