Text File Busy 问题讨论

由头: 在更新Linux服务器上正在运行的程序时, 直接通过scp更新会报Text file busy的错误, 而利用ansible -m copy, 则能够正常覆盖目标文件. 这是什么原因呢?

其实不光scp, 直接cp一个正在打开的文件也会同样报错. 改用cp -f则能够正常覆盖, 用mv也能达到同样目的. 这里面机制有什么不同呢?

跟踪一下cp -f的系统调用:

# strace cp -f src dst
...
open("dst", O_WRONLY|O_TRUNC)             = -1 ETXTBSY (Text file busy)
unlink("dst")                             = 0
open("dst", O_WRONLY|O_CREAT|O_EXCL, 0775) = 4
...

可以看到, 在不能正常打开目标文件后, 首先调用unlink“删除”之, 然后创建一个新的文件. 这里“删除”之所以打引号, 是因为该删除操作对于目前使用该文件的进程是透明的, 仍然可以正常读写, 参考unlink(2)的文档:

   If the name was the last link to a file but any processes still have the file open, the file will remain in existence until  the  last  file
   descriptor referring to it is closed.

另外提一点, rm命令也是通过调用unlink来“删除”文件.

看下mv命令的系统调用

# strace mv src dst
...
access("dst", W_OK)
rename("src", "dst")

可以发现, mv先检查下是否有写目标文件权限, 然后执行的是rename系统调用(当然这个也不严格正确, 和文件是否处于同一挂在点下有关, 不同挂载点下文件mv, 先执行拷贝操作, 然后再调用rename), 不涉及到对现有目标文件的写操作, 因此也就不会遇到Text file busy的问题.

通过ls -i, 可以看到, cp前后, 目标文件文件inode值不变; cp -f后, inode值发生了变化; mv src dst后, dst的inode值和原来src的相同.

回到我们的由头: scp直接打开目标文件进行写入, 因此会遇到问题; ansible copy是先将文件拷贝到目标机器的一个临时目录, 然后mv到目标文件, 故可以正常覆盖.

总结一下:

对于可执行文件来说, 在执行伊始, 整个文件已经加载到内存中了. 修改执行文件内容对于在运行进程没有影响. 可以通过unlink或者rename系统调用安全覆盖掉原有文件.

另一方面, 如果cp -f/mv其他程序正在写入的文件, 那么就会发生数据丢失的问题. 这种场景下, 就需要通过额外的机制, 如文件权限等, 来保证写入的安全.

HOME