20

如果我有一个打开文件的句柄,是否可以在从文件系统中删除对该文件的所有引用后创建指向该文件的硬链接?

例如,像这样:

fd = fopen("/tmp/foo", "w");
unlink("/tmp/foo");
fwrite(fd, "Hello, world!\n");
create_link_from_fd(fd, "/tmp/hello");
fclose(fd);

具体来说,我想这样做,以便我可以安全地写入大型数据文件,然后将它们原子地移动到位,而不必担心如果我的程序在写入文件的过程中被杀死,我自己会进行清理。

4

3 回答 3

13

O_TMPFILE open(2)新发布的 linux 3.11 通过新标志为这个问题提供了解决方案。使用此标志,您可以在某个文件系统(由该文件系统中的目录指定)中创建一个“不可见”文件(即没有硬链接的 inode)。然后,在文件完全设置后,您可以使用linkat. 它是这样工作的:

fd = open("/tmp", O_TMPFILE | O_RDWR, 0600);
// write something to the file here
// fchown()/fchmod() it
linkat(fd, "", AT_FDCWD, "/tmp/test", AT_EMPTY_PATH);

请注意,除了 >=3.11 内核要求之外,这还需要底层文件系统的支持(我在 ext3 上尝试了上面的代码片段并且它有效,但它似乎不适用于 btrfs)。

于 2013-09-05T19:34:15.457 回答
4

一般不会,不会。[编辑:由于 Linux 3.11 现在有linkat;请参阅safsaf32 的回答。这通常不适用于 POSIX 系统,因为 POSIXlinkat仅限于目录。] 这里有安全考虑:有人可以将一个打开的文件描述符传递给您,而您通常无法open自行处理,例如:

mkdir lock; chmod 700 lock
echo secret contents > lock/in
sudoish cmd < lock/in

此处以对输入文件 ( ) 的名称cmd没有权限的用户身份运行,但仍可以从中读取。如果可以在同一个文件系统上创建一个新名称,它可以将文件内容传递给以后的进程。(显然它可以复制那些内容,所以这个问题更像是“错误传递内容”而不是“故意传递内容”。)openlock/incmd

也就是说,人们已经提出了通过 inode/vnode 在内部“重新链接”文件的方法(在大多数文件系统中很容易做到),因此您可以为它进行自己的私有系统调用。当然,描述符必须引用适当挂载点上的真实文件——没有办法将管道、套接字或设备“重新链接”为常规文件。

否则,您将陷入“捕捉信号并清理并希望最好的”或类似的技巧,“分叉子进程,运行它,如果它成功/失败,则采取适当的移动/清理行动”。


编辑添加历史注释:上面的lock例子不是特别好,但是在 V6 Unix 的日子里,MDQS使用了这个技巧的一个更高级的版本。今天,MDQS 的零碎以各种形式存在。

于 2013-06-15T20:31:22.010 回答
3

/proc/self/fd在 Linux 上,您可能会尝试通过尝试调用来使用不可移植的技巧

 char pbuf[64];
 snprintf (pbuf, sizeof(pbuf), "/proc/self/fd/%d", fd);
 link(pbuf, "/tmp/hello");

unlink("/tmp/foo")如果那个技巧在......我没有尝试过之后奏效,我会感到惊讶。

一种更便携(但不太健壮)的方法是生成一个“独特的临时路径”,也许像

 int p = (int) getpid();
 int t = (int) time(0);
 int r = (int) random();
 sprintf(pbuf, sizeof(pbuf), "/tmp/out-p%d-r%d-t%d.tmp", p, r, t);
 int fd = open  (pbuf, O_CREAT|O_WRONLY);

一旦文件被写入并关闭,你rename(2)就可以找到更合理的路径。您可以atexit在您的程序中使用来进行重命名(或删除)。

并且有一些工作每小时cron清理一次[旧] ....../tmp/out*.tmp

于 2013-06-15T20:29:23.153 回答