2010-11-13 72 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