移动附加的文件是否安全?

Flu*_*ffy 29 open-files synchronization files mv

我有一个 node.js 进程,用于fs.appendFilefile.log. 仅附加每行大约 40 个字符的完整行,例如调用 like fs.appendFile("start-end"),而不是 2 个调用 likefs.appendFile("start-")fs.appendFile("end")。如果我将此文件移动到file2.log我可以确保没有丢失或部分复制行吗?

And*_*ese 36

只要您不跨文件系统边界移动文件,操作就应该是安全的。这是由于机制,“移动”实际上是如何完成的。

如果您mv的文件位于同一文件系统上,则实际上不会触及该文件,而只会更改文件系统条目。

$ mv foo bar
Run Code Online (Sandbox Code Playgroud)

实际上做了类似的事情

$ ln foo bar
$ rm foo
Run Code Online (Sandbox Code Playgroud)

这将为命名的文件(实际上是文件系统条目指向的 inode)创建一个链接(第二个目录条目)并删除该条目。由于现在删除时,有第二个文件系统条目指向的 inode,删除旧条目实际上并没有删除属于 inode 的任何块。foobarfoofoofoofoo

无论如何,您的程序会很高兴地附加到文件中,因为它的打开文件句柄指向文件的 inode,而不是文件系统条目。

注意:如果您的程序在写入之间关闭并重新打开文件,您最终将使用旧文件系统条目创建一个文件!

跨文件系统移动:

如果您跨文件系统边界移动文件,事情就会变得很糟糕。在这种情况下,您不能保证您的文件保持一致,因为mv实际上

  • 在目标文件系统上创建一个新文件
  • 将旧文件的内容复制到新文件中
  • 删除旧文件

或者

$ cp /path/to/foo /path/to/bar
$ rm /path/to/foo
Run Code Online (Sandbox Code Playgroud)

分别

$ touch /path/to/bar
$ cat < /path/to/foo > /path/to/bar
$ rm /path/to/foo
Run Code Online (Sandbox Code Playgroud)

根据在写入应用程序期间复制是否到达文件末尾,可能会发生新文件中只有一半行的情况。

此外,如果您的应用程序没有关闭并重新打开旧文件,它会继续写入旧文件,即使它似乎已被删除:内核知道哪些文件是打开的,尽管它会删除文件系统条目,但它在您的应用程序关闭其打开的文件句柄之前,不会删除旧文件的 inode 和相关块。

  • 仅供参考,早期版本的 Unix 没有 `rename()` 系统调用。所以`mv`的原始版本实际上确实调用了`link()`来创建硬链接,然后是`unlink()`来删除原始名称。`rename()` 是在 FreeBSD 中添加的,以在内核中原子地实现。 (3认同)

Ilm*_*nen 10

既然您说您使用的是 node.js,我假设您将使用fs.rename()(或fs.renameSync()) 来重命名文件。这个 node.js 方法被记录为使用rename(2)系统调用,它不会以任何方式接触文件本身,而只是更改它在文件系统中列出的名称:

rename () 重命名文件,如果需要,在目录之间移动它。任何其他到文件的硬链接(如使用link (2)创建的)不受影响。oldpath 的打开文件描述符也不受影响。”

特别要注意上面引用的最后一句话,它说任何打开的文件描述符(例如您的程序将用于写入文件)将继续指向它,即使它已被重命名。因此,即使文件在同时写入时被重命名,也不会有数据丢失或损坏。


正如 Andreas Weise 在他的回答中指出的那样,rename(2) 系统调用(因此fs.rename()在 node.js 中)将无法跨文件系统边界工作。因此,尝试以这种方式将文件移动到不同的文件系统只会失败。

Unixmv命令试图通过检测错误来隐藏此限制,而是通过将文件内容复制到新文件并删除原始文件来移动文件。不幸的是,如果在写入文件时移动文件,像这样移动文件确实存在数据丢失的风险。因此,如果你想安全地重新命名可以同时写入,您应将文件不能使用mv(或者,至少,你应该绝对确保新老路径是在同一文件系统)。