我的问题是:有没有办法将这样的文件重新附加到目录结构中?如果可以这样做,则意味着您可以实现文件写入,使得文件显示为原子性和完全形成。这很符合我的强迫整洁症;)
当浏览相关的系统调用函数时,我希望找到一个名为flink()的link()版本(类似于chmod()/fchmod()),但至少在Linux上没有这个功能。
额外奖励积分的方法是告诉我如何创建匿名文件,而不会在磁盘目录结构中暴露文件名。
几年前提交了一个针对Linux flink()
系统调用的补丁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开始,现在可以使用带有新标志O_TMPFILE
的open()
创建无目录项文件,并使用具有AT_SYMLINK_FOLLOW
标志的linkat()
将其链接到文件系统中的/proc/self/fd/
fd。
以下示例提供在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()
删除最后一个链接后重新连接已打开的文件。感谢@mark4o发布关于linkat(2)
的帖子,有关详细信息请参阅他的答案。
我想试一试,看看将匿名文件链接回存储它的文件系统时实际发生了什么。(通常是/tmp
,例如Firefox正在播放的视频数据)。
截至Linux 3.16,似乎仍然没有办法恢复已删除但仍被打开的文件。即使作为root用户,对于曾经有名称的已删除文件,linkat(2)
的AT_SYMLINK_FOLLOW
和AT_EMPTY_PATH
也无法做到这一点。
唯一的替代方法是tail -c +1 -f /proc/19044/fd/1 > data.recov
,它会创建一个单独的副本,并且完成后必须手动杀死它。
这是我为测试而编写的perl包装器。使用strace -eopen,linkat linkat.pl - </proc/.../fd/123 newname
来验证您的系统仍然无法恢复打开的文件。(即使使用sudo
也是如此)。显然,在运行它之前,您应该阅读在互联网上找到的代码,或使用沙箱帐户。
#!/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
显然,这是可能的--例如fsck
就可以。 但是,fsck
使用了主要的本地化文件系统技巧,并且显然不可移植,也不能作为非特权用户执行。 这类似于上面的debugfs
注释。
编写flink(2)
调用将是一项有趣的练习。 正如ijw指出的那样,它会比当前的临时文件重命名做法(重命名注意,是保证原子性的)提供一些优势。
cat /proc/<pid>/fd/N > newfile
。如果你不知道 /proc/fd 的话,那很好,但这并不是这个问题的答案。在使用 cp
或 cat
拍摄快照后,对已删除文件的进一步更改将不会反映出来。(如果写入它的进程只是添加,则使用 tail -c +1 -f /proc/<pid>/fd/N > newfile
应该会让你得到内容的副本。) - Peter Cordes
linkat()
在尝试重新连接一个普通的打开但未链接的文件时会返回ENOENT
。(使用AT_SYMLINK_FOLLOW
或AT_EMPTY_PATH
) - Peter Cordeslinkat
在没有/proc
的系统上(例如 macOS)也可以使用吗?如果可以,第一个路径参数是什么? - splicerln
,你需要指定-L
来硬链接此链接的目标(给出 ENOENT Peter Cordes 提到的)。 - user185953