1.题目
DESCRIPTION
The tcp utility copies the contents of the source to target.
If target is a directory, tcp will copy source into this directory.
EXAMPLES
tcp file1 file2
tcp file1 dir
tcp file1 dir/file2
tcp file1 dir/subdir/subsubdir/file2
Unlike tcp(1), tcpm uses mmap(2) and memcpy(2) instead of read(2) and write(2),
2.思路
敲代码之前没有考虑完全所有的情况,导致测试脚本25个例子有许多都不能通过,debug花费了较长时间,因此在敲代码之前,还是拿张纸把思路和所有可能出错的情况给列举出来,编程时才能思路更清晰。话不多说上思路:
(1)参数
参数个数不为3,exit
第二个参数和第三个参数为空,exit
(2)源文件
普通文件,直接获得文件名
目录文件,从最后一个‘/’后截取文件名。
目录,exit
(3)目标文件
普通文件。
目录文件。
目录,和‘/’,源文件文件名拼接,得到目录文件。
(包含“.”,代表当前路径,拼接后在当前目录创建文件)
3.用到的函数和系统调用
open、close、lstat、read、write、mmap、ftruncate、msync
memcpy、strrchr、strcat、strlen
perror、exit、strerror(errno)
/*基本上就是文件的系统调用和字符串操作的库函数和错误信息相关的函数*/
4.Bug
(1)segmentation fault (core dumped)
内存不当操作造成。空指针、野指针的读写操作,数组越界访问,破坏常量等。
原因1:使用mmap函数返回目标文件的内存映射地址作为参数传递给memcpy函数,创建的目标文件未设置读写权限,导致mmap返回空指针,memcpy对dest指针进行操作导致内存操作不当。
原因2:strcpy(src_file_name,argv[1]);//char*src_file_name;在进行字符串复制的时候,没有为src_file_name申请内存,导致字符串复制时溢出。改为src_file_name=strcpy((char*)malloc(sizeof(char)*50),argv[1]);
(2)括号问题
while ((read_size = read(fd_src_file, n, BUFFSIZE)) > 0){
if (write(fd_dest_file, n, read_size) != read_size)
printf("write error");
exit(1);
}
这就导致了程序读取文件时进入while循环,就会直接退出,导致许多测试不能通过。这个我是真的没看出来。不得不吐槽unix文本编辑器,此处感谢坤哥救人于水火之中,一眼帮我看穿了括号问题,大恩不言谢,下次bug还是要仰仗坤哥。
把括号改到if后面即可。打死我我也找不到这儿来啊!!
(3)small file,zero file,big file
从一个size小的文件复制到size大的文件,size大的文件后面部分会被阶段保留。总之创建一个新的文件直接覆盖就爽了。
(4)使用mmap函数之前一定要判断文件的大小是不是zero,是的话mmap函数就会报错。so,注意函数参数的取值范围是很重要的!!!!
5. 代码
tcp
#include <unistd.h>
#include<stdio.h>
#include<string.h>
#include <fcntl.h>
#include<sys/stat.h>
#include<stdlib.h>
#define BUFFSIZE 4096
int main(int argc, char**argv) {
char n[BUFFSIZE];
int read_size;
int fd_src_file;
int fd_dest_file;
char *src_file_name;
char dest_file_name[50];
struct stat src_buf;
struct stat dest_buf;
/*if the number of the argument is not 3 or any arguments is empty,
it doesn't satisfy the "tcp" command's format*/
if (argc != 3) {
printf("error");
exit(1);
}
if(argv[1]==NULL||argv[2]==NULL){
printf("Please enter two valid filenames or directories!\n");
exit(1);
}
/*get the information of the argv[1] and argv[2]
the argv[2] can be a file that doesn't exist
but the argv[1] must be a file*/
if(lstat(argv[1], &src_buf) < 0) {
printf("lstat error when manage the source file\n");
exit(1);
}
lstat(argv[2], &dest_buf);
/*if the argv[1] is a directory then exit*/
if(S_ISDIR(src_buf.st_mode)){
printf("The first argument is a directory, but not a file");
exit(1);
}
/*if the argv[1] and argv[2] point to the same file then exit*/
if(src_buf.st_ino==dest_buf.st_ino){
printf("The src-file and the dest-file are same\n");
exit(1);
}
/*Get the source file name from argv[1]*/
if(strrchr(argv[1],'/')!=NULL){
src_file_name=strrchr(argv[1],'/');
src_file_name++;
}
else{
src_file_name=strcpy((char*)malloc(sizeof(char)*50),argv[1]);
}
/*open the source file*/
if ((fd_src_file = open(argv[1],O_RDONLY) )< 0) {//open the source file
perror("open source file fail");
exit(1);
}
if (S_ISDIR(dest_buf.st_mode)) {
//judge whether the argv[2] is a directory
strcpy(dest_file_name,argv[2]);
if(argv[2][strlen(argv[2])-1]!='/')
strcat(dest_file_name,"/");
strcat(dest_file_name,src_file_name);
printf("%s",dest_file_name);
//change the directory to dir file name
if ((fd_dest_file = open(dest_file_name, O_WRONLY | O_CREAT | O_TRUNC, 0700)) < 0) {//S_IRWXU
//create a new file under the directory
perror("create dest_file fail1");
exit(1);
}
}
else{
if ((fd_dest_file = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0700)) < 0) {//S_IRWXU
//create a new file under the directory
perror("create dest_file fail");
exit(1);
}
}
/*read the source and write the destination*/
while ((read_size = read(fd_src_file, n, BUFFSIZE)) > 0)
if (write(fd_dest_file, n, read_size) != read_size){
printf("write error");
exit(1);
}
if (read_size < 0){
perror("read error");
exit(1);
}
/*close the file*/
close(fd_src_file);
close(fd_dest_file);
return 0;
}
tcpm
#include <unistd.h>
#include<stdio.h>
#include<string.h>
#include <fcntl.h>
#include<sys/mman.h>
#include<sys/stat.h>
#include<stdlib.h>
#include <errno.h>
#define BUFFSIZE 4096
int tcpmfile(char* src_file,char*dest_file){
struct stat src_info;
struct stat dest_info;
char* src_file_name;
char* dest_file_name;
int fd_src_file;
int fd_dest_file;
void*dest;
void*src;
/*get the information of the argv[1] and argv[2]
the argv[2] can be a file that doesn't exist
but the argv[1] must be a file*/
if(lstat(src_file,&src_info)<0){
printf("lstat the source file error\n");
return 1;
}
lstat(dest_file,&dest_info);
/*if(S_ISDIR(src_info.st_mode)){
printf("The first argument is a directory, but not a file");
return 1;
}*/
/*if the argv[1] and argv[2] point to the same file then exit*/
if(src_info.st_ino==dest_info.st_ino){
printf("The src-file and the dest-file are same\n");
return 1;
}
/*open the source file*/
if ((fd_src_file = open(src_file,O_RDONLY) )< 0) {
printf("open source file fail\n");
return 1;
}
/*Get the source file name from src_file*/
if(strrchr(src_file,'/')!=NULL){
src_file_name=strrchr(src_file,'/');
src_file_name++;
}
else{
src_file_name=strcpy((char*)malloc(sizeof(char)*50),src_file);
}
dest_file_name=dest_file;
if(S_ISDIR(dest_info.st_mode)){
char*slash="/";
if(dest_file[strlen(dest_file)-1]!='/')
strcat(dest_file_name,slash);
strcat(dest_file_name,src_file_name);
}
if((fd_dest_file = open(dest_file_name, O_RDWR|O_CREAT|O_TRUNC,S_IRWXU)) < 0) {
printf("create dest_file fail\n");
return 1;
}
ftruncate(fd_dest_file,src_info.st_size);
/*judge whether the source file's size is zero*/
if(src_info.st_size!=0){
/*map the content of the file form disk to memory*/
src=mmap(NULL,src_info.st_size,PROT_READ,MAP_SHARED,fd_src_file,0);
dest=mmap(NULL,src_info.st_size,PROT_READ|PROT_WRITE,MAP_SHARED,fd_dest_file,0);
if(src==MAP_FAILED||dest==MAP_FAILED){
printf("Mmap error: %s!\n",strerror(errno));
exit(1);
}
/*memory copy*/
memcpy(dest,src,src_info.st_size);
/*update the file content*/
if(msync(dest,src_info.st_size,MS_SYNC)==-1){
printf("msync error");
munmap(src,src_info.st_size);
munmap(dest,src_info.st_size);
return 1;
}
}
munmap(src,src_info.st_size);
munmap(dest,src_info.st_size);
close(fd_src_file);
close(fd_dest_file);
return 0;
}
int main(int argc,char**argv){
/*if the number of the argument is not 3 or any arguments is empty,
it doesn't satisfy the "tcp" command's format*/
if (argc != 3) {
printf("please input three arguments");
exit(1);
}
char*src_file;
char*dest_file;
src_file=argv[1];
dest_file=argv[2];
/*file copy*/
if(tcpmfile(src_file,dest_file)!=0){
printf("copy fail\n");
exit(1);
}
printf("copy successfully\n");
return 0;
}
6.总结
(1)把大量的操作封装成函数,在main函数里面只进行函数调用。
(2)看到一个C语言库函数或者系统调用的原型时,每次在程序中调用,都要考虑函数的返回值是否正常,判断不正常时,程序退出。
(3)对于unix的文本编辑器,可以在每段逻辑性的代码后面进行printf来进行类似断点的调试。道路千万条,debug第一条,断点打不对,码农两行泪。
(4)动手编程之前先写思路,不仅可以帮助编程,还能再debug的时候发现少考虑的情况。
(5)码代码之前先看看这个功能库函数里面有没有,有了就直接用。毕竟人生苦短。
(6)对于文件的操作,记得关闭。ps:close,munmap。
//好啦,今天就写到这里,感觉C语言和C++的字符串还是博大精深的,随后还会总结一下。
//关于文件操作系统调用的函数,等unix这部分学完了也会总结一下,顺便带上unix的文件系统。
//Unix着实硬核的一门课,这次老师又布置了需要一个半月完成的任务...溜啦...