linux cp 命令的实现

时间:2021-04-13 11:32:36


        使用Apc中,在频繁上线源码时会机率性地出现class ‘xxx’ not found in yyy的错误,上线通常是用cp 或rsync 进行的,分析Apc代码后,怀疑是源码文件在覆盖过程中,由于st_mtime改变了被Apc取得,缓存到一个不完整的class,导致出错。因为对cp的原理不清楚,网络相关的文章也少,所以就看了下cp代码,源码可以从这获得: http://ftp.gnu.org/gnu/coreutils/。


一、源码分析

主要文件有:copy.c  cp.c cp-hash.c。

咋们跳过参数解析,直接进入文件拷贝的代码。

 
    source_desc = open (src_path, O_RDONLY);
//...

/* These semantics are required for cp.
The if-block will be taken in move_mode. */
if (*new_dst)
{
dest_desc = open (dst_path, O_WRONLY | O_CREAT, dst_mode); //如果目标文件不存在,则创建打开
}
else
{
dest_desc = open (dst_path, O_WRONLY | O_TRUNC, dst_mode); //如果目标文件存在,则截短长度为0打开,也就是清空文件

if (dest_desc < 0 && x->unlink_dest_after_failed_open) //如果打开文件出错,就删除文件,然后重新创建
{
if (unlink (dst_path))
{
error (0, errno, _("cannot remove %s"), quote (dst_path));
return_val = -1;
goto close_src_desc;
}

/* Tell caller that the destination file was unlinked. */
*new_dst = 1;

/* Try the open again, but this time with different flags. */
dest_desc = open (dst_path, O_WRONLY | O_CREAT, dst_mode);
}
}

//...
for (;;)
{
ssize_t n_read = read (source_desc, buf, buf_size);
//文件拷贝,包括一些文件空洞的处理代码,预了解文件空洞是什么的同学请移步到 blog:http://mazheng.org/?p=289 。
}

//...

return return_val;
}


二、实战

下面通过两个实例来对代码进行回顾和验证,通过strace 观察系统调用情况。

sample:

1、cp ,目标文件/usr/local/nginx/html/echo.php存在;

2、cp ,目标文件/usr/local/nginx/html/echo.php不存在。

sudo strace   ./cp ~/echo.php /usr/local/nginx/html/echo.php 

输出1:

lstat("/usr/local/nginx/html/echo.php", {st_mode=S_IFREG|0664, st_size=1223, ...}) = 0

stat("/usr/local/nginx/html/echo.php", {st_mode=S_IFREG|0664, st_size=1223, ...}) = 0
stat("/home/chenfei-s/echo.php", {st_mode=S_IFREG|0664, st_size=1223, ...}) = 0
stat("/usr/local/nginx/html/echo.php", {st_mode=S_IFREG|0664, st_size=1223, ...}) = 0
open("/home/chenfei-s/echo.php", O_RDONLY) = 3
fstat(3, {st_mode=S_IFREG|0664, st_size=1223, ...}) = 0
open("/usr/local/nginx/html/echo.php", O_WRONLY| O_TRUNC) = 4  若文件存在,把文件长度截断为0
fstat(4, {st_mode=S_IFREG|0664, st_size=0, ...}) = 0
fstat(3, {st_mode=S_IFREG|0664, st_size=1223, ...}) = 0
read(3, "<?php\n\necho 'xxxxxx';\necho 'xxxx"..., 4096) = 1223
write(4, "<?php\n\necho 'xxxxxx';\necho 'xxxx"..., 1223) = 1223
read(3, "", 4096)                       = 0
close(4)                                = 0
close(3)                                = 0

exit_group(0)                           = ?


输出2:

lstat("/usr/local/nginx/html/echo.php", 0x7fffd3d69b20) = -1 ENOENT (No such file or directory)
stat("/home/chenfei-s/echo.php", {st_mode=S_IFREG|0664, st_size=1223, ...}) = 0
open("/home/chenfei-s/echo.php", O_RDONLY) = 3
fstat(3, {st_mode=S_IFREG|0664, st_size=1223, ...}) = 0
open("/usr/local/nginx/html/echo.php", O_WRONLY|O_CREAT, 0100644) = 4
fstat(4, {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
fstat(3, {st_mode=S_IFREG|0664, st_size=1223, ...}) = 0
read(3, "<?php\n\necho 'xxxxxx';\necho 'xxxx"..., 4096) = 1223
write(4, "<?php\n\necho 'xxxxxx';\necho 'xxxx"..., 1223) = 1223
read(3, "", 4096)                       = 0
close(4)                                = 0
close(3)                                = 0
exit_group(0)                           = ?


三、总结

    从上我们可以得出结论:cp 拷贝文件时,如果目标文件存在,则把文件截短为0,然后把新内容写入;如果目标文件不存在,则创建文件,然后把文件写入目标文件。


四、后记

      既然Apc是通过st_mtime来判断一个文件是不是最新的,我们引申出一个问题:open("/usr/local/nginx/html/echo.php", O_WRONLY|O_TRUNC)  这个系统调用会不会改变文件的st_mtime呢?

遂做了个测试,代码如下:

  #include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
#include <string.h>

int main(int argc, char **argv)
{
char *file = "/home/echo.php";
int fd;
char buf[] = "willas test!";

fd = open (file, O_WRONLY|O_TRUNC);

sleep(100);
write(fd,buf,strlen(buf));

return 0;
}

我们使用stat echo.php 来观察执行前,执行中,执行后st_mtime的变化:

执行前:

Access: 2013-08-29 17:44:18.000000000 +0800
Modify: 2013-08-29 18:50:27.000000000 +0800
Change: 2013-08-29 18:50:27.000000000 +0800

执行中:

Access: 2013-08-29 17:44:18.000000000 +0800
Modify: 2013-08-29 18:58:33.000000000 +0800
Change: 2013-08-29 18:58:33.000000000 +0800

执行后:

Access: 2013-08-29 17:44:18.000000000 +0800
Modify: 2013-08-29 19:00:13.000000000 +0800
Change: 2013-08-29 19:00:13.000000000 +0800

我们看到文件以O_TRUNC打开后st_mtime时间是会变化的,19:00:13 - 18:58:33 刚好是sleep 的100秒!这就给Apc问题了一个很好的回答,更新源码过程中可能会出现apc 取得空文件或不完整文件两种可能,最终导致出错。

但是执行完cp后st_mtime也有改变,如上,写入后st_mtime变为  19:00:13 ,Apc应该会再次取得源文件进行编译缓存,class ‘xxx’ not found in yyy 应该只会短暂地出现,那问题到底出在哪?有待进一步研究Apc源码。