2010-11-13 71 views
28

在Unix中,可以通过创建和使用creat()打开并随后使用unlink()移除目录链接来创建匿名文件的句柄 - 让您离开与一个文件与一个inode和存储,但没有可能的方式来重新打开它。这样的文件经常用作临时文件(通常这是tmpfile()返回给你的东西)。重新链接匿名(未链接但打开)的文件

我的问题:有什么方法可以将这样的文件重新附加到目录结构中吗?如果你可以这样做,这意味着你可以例如实现文件写入,以便文件以原子形式出现并完全形成。这吸引了我强迫性的整洁。 ;)

当通过相关的系统调用函数戳时,我希望找到一个名为flink()(与chmod()/ fchmod()比较)的link()版本,但至少在Linux上不存在。

用于告诉我如何创建匿名文件而不简要地在磁盘的目录结构中公开文件名的奖励点。

回答

30

A patch for a proposed Linux flink() system call是几年前提交的,但当Linus声称"there is no way in HELL we can do this securely without major other incursions"时,这几乎结束了是否增加这个问题的争论。

更新:随着Linux 3.11的,现在可以创建使用open()O_TMPFILE标志没有目录条目的文件,并将其链接到文件系统一旦被使用linkat()/proc/self/fd/FD完全形成AT_SYMLINK_FOLLOW标志。

下面的例子提供了open()手册页:

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); 

注意linkat()不会允许的最后一个环节与unlink()删除后重新连接打开的文件。

+0

Ta。他提出了一个也应该起作用的解决方案,请介意你。虽然对于完整的强制整洁,您可能还需要一种方法来调用目录中的creat(),以便它创建文件和inode但不是目录条目,以便它从不首先链接。 – ijw 2010-11-23 18:35:23

+0

更新充满了胜利。我不能+2你,但我会如果我能。 – ijw 2014-05-31 06:40:45

+0

令人困惑的是,'linkat()'在试图重新连接一个正常的打开但未链接的文件时给出了'ENOENT'。 (带有'AT_SYMLINK_FOLLOW'或'AT_EMPTY_PATH') – 2015-02-21 22:07:14

1

我的问题:有什么办法可以将这样的文件重新附加到目录结构中吗?如果你可以这样做,这意味着你可以例如实现文件写入,以便文件以原子形式出现并完全形成。这吸引了我强迫的整洁。 ;)

如果这是您唯一的目标,您可以用更简单和更广泛的方式实现此目标。如果输出到a.dat

  1. 打开a.dat.part进行写入。
  2. 写下你的数据。
  3. a.dat.part更名为a.dat

我可以理解想要整洁,但取消链接文件并重新链接它只是为了“整洁”是一种愚蠢的。

This question on serverfault似乎表明这种重新链接不安全并且不受支持。

+0

cdhowie是正确的,只是写入临时文件好多了。请注意,你链接到的问题基本上说它不能完成:你不能从'/ proc'硬链接到另一个文件系统。 – poolie 2010-11-13 09:12:33

+0

@poolie不知何故,我错过了。切换链接到serverfault更合适的问题。 – cdhowie 2010-11-13 09:17:10

+2

不同的是,在serverfault问题的程序是不透明的事情(是一个系统管理员及所有 - 我在这里谈论的实际上有大约从进程中具有编程播放的文件句柄如果你可以断然排除我们也有答案;) – ijw 2010-11-13 10:38:37

-1

很明显,这是可能的 - 例如,fsck。但是,fsck会将其与主要的本地化文件系统mojo配合使用,并且显然不可移植,也不可作为非特权用户执行。这与上面的debugfs评论类似。

flink(2)调用将是一个有趣的练习。正如ijw指出的那样,它会提供一些优于临时文件重命名(重命名,注意,保证原子)的当前实践的一些优点。

-2

种迟到游戏但我刚刚发现http://computer-forensics.sans.org/blog/2009/01/27/recovering-open-but-unlinked-file-data其中可能回答这个问题。尽管如此,我还没有测试过,所以YMMV。它看起来很健康。

+1

正如我所料,这只是'cat/proc//fd/N> newfile'。整洁,如果你不知道/ proc/fd,但不是这个问题的答案。在用'cp'或'cat'获得的快照之后,对已删除文件的进一步更改将不会反映出来。 ('tail -c + 1 -f/proc//fd/N> newfile'应该为你提供内容的副本,但是如果只写入它的进程会被追加)。 – 2015-02-21 06:55:46

1

感谢@ mark4o张贴有关linkat(2),看到他对细节的答案。

我想试一试,看看究竟发生了什么努力实际上链接的匿名文件放回其存储在文件系统时。 (通常/tmp,例如,firefox正在播放的视频数据)。


从Linux 3.16开始,似乎仍无法取消删除仍然保持打开状态的已删除文件。无论AT_SYMLINK_FOLLOW也不AT_EMPTY_PATHlinkat(2)做的伎俩对于曾经有一个名字被删除的文件,甚至是root。

唯一的选择是tail -c +1 -f /proc/19044/fd/1 > data.recov,使一个单独的副本,你当它完成手动杀死它。


这是我制作测试的perl包装。使用strace -eopen,linkat linkat.pl - </proc/.../fd/123 newname来验证您的系统仍然无法取消删除打开的文件。 (即使使用sudo也是如此)。很明显,您应该阅读运行之前在Internet上找到的代码,或者使用沙盒帐户。

#!/usr/bin/perl -w 
# 2015 Peter Cordes <[email protected]> 
# 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