操作系统课程设计 文件复制实验报告

一、实验目的

1、熟悉Linux文件系统提供的有关文件操作的系统调用。文件系统是使用计算机信息系统的重要接口,通过使用文件系统的系统调用命令操作文件,以达到对文件系统实现功能的理解和掌握。
2、了解Windows的文件系统时如何管理保存在磁盘、光盘等存储介质上的信息。并通过文件系统提供的各种API,对文件进行操作,深入理解Windows文件系统的功能和作用,理解文件系统的系统调用的功能。

二、实验内容

完成一个目录复制命令mycp,包括目录下的文件和子目录。
在Windows下:调用CreateFile(), ReadFile(), WriteFile(), CloseHandle()等函数,完成文件的复制。
在Linux下:使用creat(),write(),read(),mkdir,readlink(),symlink()等系统调用,完成文件拷贝命令的实现,要求能够支持软连接文件的拷贝。
注意:不管时Windows下还是Linux下,要求每个文件不仅读写权限一致,而且时间属性一致。

三、实验环境

操作系统:Windows10
操作系统:Linux-5.9.10
虚拟机:Ubuntu 18.04
IDE:VS 2019、vim

四、程序设计与实现

4.1设计思路

对于给定的一个文件,首先看其是否为目录文件,如果为目录文件,首先复制此目录文件的权限等信息,再调用编写的MyCp()函数递归复制文件夹中的内容。MyCp()函数的基本思想是,如果当前查询的文件为普通文件,则调用编写的CopyFile()函数复制文件内容、权限和时间等信息到指定的文件路径;如果当前查询的文件是软连接文件,则调用编写的CopyLinkFile()函数,将软连接文件复制到指定文件路径;如果当前查询的文件是目录文件,则递归调用MyCp()函数处理目录文件,直到文件不在是目录文件为止。对于给定的文件,如果是非目录文件,则程序会直接调用MyCp()函数复制文件到指定的路径中。由于不管是目录文件还是其他文件,如果在创建时就设置时间信息,后续的访问会导致文件的时间信息被修改,所以对于每一个目录文件,只有当其中的所有子文件都被复制后,才修改其时间属性。最后,关闭打开的文件或打开的文件句柄。

4.2 Windows下的文件复制

4.2.1 结构体解析

WIN32_FIND_DATA结构体:

typedef struct _WIN32_FIND_DATA {
   DWORD dwFileAttributes; //文件属性
   FILETIME ftCreationTime; // 文件创建时间
   FILETIME ftLastAccessTime; // 文件最后一次访问时间
   FILETIME ftLastWriteTime; // 文件最后一次修改时间
   DWORD nFileSizeHigh; // 文件长度高32位
   DWORD nFileSizeLow; // 文件长度低32位
   DWORD dwReserved0; // 系统保留
   DWORD dwReserved1; // 系统保留
   TCHAR cFileName[ MAX_PATH ]; // 文件名
   TCHAR cAlternateFileName[ 14 ]; // 格式文件名
} WIN32_FIND_DATA, *PWIN32_FIND_DATA;

4.2.2 使用的函数功能解析

(1)FindFirstFile()
功能:查找指定路径的文件,成功则返回文件或目录句柄。
定义:

HANDLE FindFirstFile(
            LPCTSTR lpFileName,// 目录名
            LPWIN32_FIND_DATA lpFindFileData// 文件信息结构体);

(2)FindNextFile()
功能:查找FindFirstFile函数搜索后的下一个文件,成功则返回非0,否则返回0。
定义:

FindNextFile(
	HANDLE hFindFile,//调用FindFirstFile返回的句柄
	LPWIN32_FIND_DATA lpFindFileData//文件信息结构体
);

(3)CreateDirectory()
功能:创建文件夹()目录文件
定义:

BOOL CreateDirectory(
	LPCTSTR lpPathName,//文件路径
	LPSECURITY_ATTRIBUTES lpSecurityAttributes//常为NULL
  			);

(4)CreateFile()
功能:打开或创建文件,返回该文件的句柄
定义:

HANDLE CreateFile(
	LPCTSTR lpFileName,    // 指向文件名的指针 
	DWORD dwDesiredAccess,    // 访问模式(写 / 读) 
	DWORD dwShareMode,    // 共享模式 
 	LPSECURITY_ATTRIBUTES lpSecurityAttributes,//安全属性 
	DWORD dwCreationDisposition,//创建方式 
	DWORD dwFlagsAndAttributes,//文件属性 
	HANDLE hTemplateFile//用于复制文件句柄
	 );

(5)SetFileTime()
功能:设置文件的时间属性,创建时间、最后一次修改和访问时间
定义:

Long SetFileTime(
	Long hFile,//文件句柄
	FILETIME lpCreationTime,//文件创建时间
	FILETIME lpLastAccessTime,//文件最后一次访问时间
	FILETIME lpLastWriteTime//文件最后一次修改时间
);

(6)SetFileAttributes()
功能:设置文件属性
定义:

BOOL SetFileAttributes(
	LPCTSTR lpFileName, //文件路径
	DWORD dwFileAttributes  // 文件属性);

(7)ReadFile()
功能:从指定路径的文件中读取指定字节长度的数据
定义:

BOOL ReadFile(
	HANDLE hFile, //文件的句柄
  LPVOID lpBuffer, //用于保存读入数据的一个缓冲区
  DWORD nNumberOfBytesToRead, //要读入的字节数
  LPDWORD lpNumberOfBytesRead, //实际读取字节数的指针
  LPOVERLAPPED lpOverlapped //一般置为NULL
  );

(8)WriteFile()
功能:从文件指针指向的位置开始将数据写入到一个文件中
定义:

BOOL WriteFile(
  HANDLE hFile, // 文件句柄
 	LPCVOID lpBuffer, // 数据缓存区指针
  DWORD nNumberOfBytesToWrite, // 写入字节数
  LPDWORD lpNumberOfBytesWritten, // 指向实际写入字节数
  LPOVERLAPPED lpOverlapped // 结构体指针,一般为NULL);

4.2.3 程序的设计和实现

1、 主函数的设计与实现

在主函数中,将从命令行中接收三个参数,第一个(argv[0])是将要执行的可执行程序路径,第二个(argv[1])是等待被复制的文件路径(此后统一称为旧路径),第三个(argv[2])是将把旧文件复制到的目标路径(此后同一称为新路径)。
主函数(main函数)设计流程如下:

(1)在主函数中,首先判断输入的参数是否为3个,如果是则转(2),否则退出执行。
(2)调用FindFIrstFile(argv[1], &lpFindData)打开argv[1]路径下的旧文件,返回句柄hFindFile,并将其信息保存到lpFindNewData结构体中。如果成功打开则转(3),否则退出执行。
(3)调用FindFIrstFile(argv[2], &lpFindNewData)检查是否存在路径为argv[2]的文件,如果存在则退出执行,否则调用CreateDirectory(argv[2],NULL)在argv[2]路径上创建新文件。
(4)调用MyCp()递归函数复制就文件夹中的子文件。
(5)递归函数返回后,调用CreateFile()函数打开argv[2]路径上的新文件,返回句柄hDirFile,并调用SetFileTime()函数修改其时间属性,调用SetFileAttributes()函数设置文件属性。
(6)关闭句柄hFindFile以及hDirFile,将其值设置为INVALID_HANDEL_VALUE。

2、MyCp(char* OldPath, char* NewPath)函数设计与实现

在此函数中,传入的参数OldPath为旧文件路径(源文件),NewPath为新文件路径(目标文件)。此外,设置char* 型的参数new_path和old_path,用于拼接目录中的子文件路径。WIN32_FIND_DATA型结构体lpFindData,句柄hFindData,BOOL型参数hFound。MyCp()函数流程如下:
(1)定义参数old_path,new_path,结构体lpFindData,句柄hFindData,hFound。
(2)通过lstrcpy(str1,str2)接口对old_path,new_path赋值,其值分别为OldPath,NewPath。
(3)调用FinFirstFile(old_path,&lpFindData)查找old_path路径下的文件并返回句柄hFindData,将文件信息存于lpFindData之中。
(4)如果hFindData值为INVALID_HANDLE_VALUE,则表示查找文件失败,程序退出执行;否则转(5)。
(5)调用hFIndData(hFinData,&lpFindData)查找OldPath路径上的文件夹中的下一个文件,并返回hFound(表示是否成功查找),若成功,则将文件信息存入lpFindData且转(6),否则说明已遍历当前文件夹中的最后一个文件。
(6)lpFindData.cFilename中存放当前将复制文件的相对路径,调用lstrcpy()拼接且结果为:old_path+’/’+lpFindData.cFilename,new_path+’/’+lpFindData.cFilename,拼接后的路径仍然赋值给old_path和new_path。
(7)调用IsChildDir(lpFindData)判断当前文件是否为子目录文件,如果为子目录文件,首先在new_path路径下创建一个新的目录文件,后递归调用MyCp()函数,对old_path下的目录文件(此时old_path对应的目录文件即当前的子目录文件)复制到new_path,复制完成后,再修改new_path下的目录文件的时间属性。在修改时间属性时,需要通过CreateFile()函数打开new_path下的文件并返回句柄hDirFile,后调用SetFileTime()函数设置时间属性(如何复制时间属性将在后续阐述),调用SetFileAttributes()函数设置文件属性。值得注意的是,在打开文件夹时,应设置dwCreationDisposition字段为OPEN_EXISTING,设置dwFlagsAndAttributes字段为FILE_FLAG_BACKUP_SEMANTICS。
(8)如果不为目录文件,则为其他文件,调用CopyFile()函数完成文件的复制。
(9)关闭句柄hFindData,设置其值为INVALID_HANDLE_VALUE,执行结束并返回。
在MyCp()函数中,涉及到判断是否为子目录的函数IsChildDir()定义如下:

BOOL IsChildDir(WIN32_FIND_DATA& lpFindData) {
	return (
		((lpFindData.dwFileAttributes &
			FILE_ATTRIBUTE_DIRECTORY) != 0) &&
		(lstrcmp(lpFindData.cFileName, __TEXT(".")) != 0) &&
		(lstrcmp(lpFindData.cFileName, __TEXT("..")) != 0));
}

3、CopyFile(char* OldPaht, char* NewPath)的设计与实现

(1)定义句柄hOldFile(指向旧文件的句柄)、hNewFile(指向新文件的句柄),WIN32_FIND_DATA型参数lpFindData(存放文件信息)。
(2)调用CreateFile()打开OldPath路径下的旧文件,返回句柄hOldFile。
(3)调用CreateFile()在NewPath路径下创建新文件,返回句柄hNewFile。
(4)如果hOldFile和hNewFile不是无效值,转(5),否则退出执行。
(5)循环调用ReadFile()从旧文件中读并且调用WriteFile()函数往新文件中写,设置每次读写的大小BUFSIZE为1024,记录每次实际读写的大小dwxfer,当BUFSIZE不等于dwxfer时,说明复制结束,退出循环。
(6)调用SetFileTime()函数设置文件时间属性,调用SetFileAttributes()函数设置文件属性。
(7)关闭文件句柄hNewFile、hOldFile,设置其值为INVALID_HANDLE_VALUE。
在lpFindData结构体中,保存有旧文件的创建时间lpFindData.ftCreationTime、最近访问时间lpFindData.ftLastAccessTime和最近修改时间lpFindData.ftLastWriteTime,可以借此用于修改新文件的时间属性。此外,还保存有旧文件属性信息lpFindData.dwFileAttributes,可用于修改新文件属性。

4.3 Linux下的文件复制

4.3.1 结构体解析

(1)stat结构体

struct stat  {   
    dev_t	st_dev;  /* ID of device containing file -文件所在设备的ID*/  
    ino_t	st_ino;  /* inode number -inode节点号*/    
    mode_t 	  st_mode;  /* protection -保护模式?*/    
    nlink_t 	  st_nlink;  /* number of hard links -链向此文件的连接数(硬连接)*/    
    uid_t  st_uid;  /* user ID of owner -user id*/    
    gid_t  st_gid;  /* group ID of owner - group id*/    
    dev_t  st_rdev;  /* device ID (if special file) -设备号,针对设备文件*/    
    off_t  st_size;    /* total size, in bytes -文件大小,字节为单位*/    
    blksize_t  st_blksize; /* blocksize for filesystem I/O -系统块的大小*/    
    blkcnt_t  st_blocks;  /* number of blocks allocated -文件所占块数*/    
    time_t  st_atime;  /* time of last access -最近存取时间*/    
    time_t  st_mtime;  /* time of last modification -最近修改时间*/    
	time_t  st_ctime;  /* time of last status change - */
	 };  

(2)utimbuf结构体

struct utimbuf{
	time_t  atime;  /*access time :访问时间*/
	time_t  modtime;  /*modification time : 更改时间*/
	};

(3)dirent结构体

struct dirent{
  long d_ino; /* inode number 索引节点号 */
  off_t d_off; /* offset to this dirent 在目录文件中的偏移 */
  unsigned short d_reclen; /* length of this d_name 文件名长 */
  unsigned char d_type; /* the type of d_name 文件类型 */
  char d_name [NAME_MAX+1]; /* file name (null-terminated) 文件名,最长255字符 */
  };

;

4.3.2 函数功能解析

(1) DIR * opendir(const char * name);
功能:打开指定的目录,并返回DIR*型的目录流。
(2)int mkdir (const char *filename, mode_t mode)
功能:创建名为filename的目录,创建成功返回0,失败返回-1。
(3)int stat(const char *path, struct stat buf)
参数:文件路径名,stat类型结构体。
功能:获取path路径下的文件属性信息,成功返回0,失败返回-1。
(4)Int utime(const char filename, const struct utimbuf times)
参数:文件路径名,utimbuf类型结构体。
功能:修改文件的时间属性,包括最后一词访问和修改时间,成功则返回0,失败则返回-1。
(5)int closedir(DIR dir)

功能:关闭目录文件指针。
(6)struct dirent readdir(DIR
dir_handle);

功能:读取下一个目录,返回DIR
类型的指针。
(7)S_ISDIR()
功能:判断一个文件是否为目录文件。
(8)S_ISLNK()
功能:判断一个文件是否为符号链接文件。
(9)int lstat(const char *path, struct stat *buf);
功能:与stat()相似,只是path下的文件是符号链接文件时,lstat()函数获取该符号链接文件的信息。
*(10)int readlink(const char path, char buf, size_t bufsize)
功能:读取符号链接文件本身的信息,组合了打开、读和关闭的所有操作。成功则返回读到缓冲区buf的字节数,否则返回-1。
(11)int syslink(const char *oldpath, const char *sympath)
参数:已存在的文件路径名,将要创建的符号链接文路径名。
功能:创建一个符号链接文件。成功时返回0,否则返回-1。
*(12)int chmod(const char filename, int pmode)
参数:文件路径名,文件读取许可权限。
功能:改变文件的读写许可权限,成功改变则返回0,否则返回-1。
(13) lutimes(const char *filename, struct timeval *tv)
参数:符号链接文件路径名,timeval结构体
功能:改变符号链接文件的时间属性,成功返回0,否则返回-1。

4.3.3 程序设计与实现

1、主函数设计与实现

与Windows相同,主函数从命令行接收三个参数,第一个(argv[0])是将要执行的可执行程序路径,第二个(argv[1])是等待被复制的文件路径(此后统一称为旧路径),第三个(argv[2])是将把旧文件复制到的目标路径(此后同一称为新路径)。其设计流程如下:
(1)判断从命令行接受的参数是否为3个,若是,转(2),否则退出之执行。
(2)定义stat结构体statbuf,用于修改文件属性;utimbuf结构体uTime,用于修改文件的时间属性,DIR指针dirptr、dirptr2指向打开的文件夹。
(3)调用opendir(argv[1])打开旧文件夹并返回指向该文件夹目录流的指针dirptr,如果dirptr为NULL则退出执行,否则说明打开成功,继续下一步。
(4)调用opendir(argv[2])打开新文件夹,返回指向该文件夹目录流的指针dirptr2,如果dirptr2为NULL,则调用mkdir(argv[2],statbuf.st_mode)函数在argv[2]路径下创建文件夹,st_mode指定新创建文件夹权限;否则退出执行。
(5)调用自己编写的MyCopy()函数复制文件夹中的内容。
(6)调用stat(argv[1],&statbuf)函数将argv[1]路径下的目录文件信息存入statbuf中,并用statbuf中的时间属性更新uTime,调用utime(argv[2],uTime)接口修改argv[2]路径下文件夹的时间属性。
(7)关闭closedir()接口关闭指向文件夹的指针dirptr和dirptr2。

2、MyCopy(char* OldPath, char* NewPath)函数设计与实现

(1)定义stat结构参数statbuf,用于存储文件属性;utimbuf结构体参数uTime,用于记录文件的时间属性;dirent结构体指针entry,指向存储文件夹中的文件目录信息,DIR*形式的目录流dirptr;old_path存放旧文件路径,new_path存放新文件路径,并调用strcpy()函数将OldPath、NewPath分别复制给old_path和new_path。
(2)调用opendir()打开old_path路径下的文件夹,返回指针dirptr。
(3)调用readdir(dirptr)读取文件中下一条目录信息,并返回给entry,若entry为空,则说明已到目录结尾,转();否则entry为一个指向dirent结构的指针,此结构包含该目录项指向的文件信息,转(4)。
(4)如果当前的目录项既非父目录也非OldPath路径下的文件夹,则说明是OldPath中的文件的目录项,转(5),否则转(3)。
(5)entry->name为目录中的文件名,将此文件名与路径连接,得到该文件的路径以及目标路径。其中:
old_path = old_path + ‘/’ + entry->name
new_path = new_path + ‘/’ +entry->name
(6)调用lstat(old_path,&statbuf)接口,将old_path路径下的文件信息保存到statbuf中,根据statbuf.st_mode字段来判断文件类型。
(7)如果是子目录文件(调用S_ISDIR(statbuf.st_mode)返回真),调用mkdir(new_path)接口在new_path路径下创建一个新的目录文件,再递归调用MyCopy()函数完成子目录文件的复制,复制完成后,更新uTime,并调用utime()接口修改文件时间属性;若不是子目录文件,则转(8)。
(8)如果是符号链接文件(调用S_ILINK(statbuf.st_mode)返回为真),调用自己编写的CopyLinkFile()接口进行复制;否则为其他文件,转(9)。
(9)对于其他文件,调用CopyFile()接口进行文件复制。
(10)已完成对OldPath路径下的附件的复制,关闭打开的指针dirptr。

3、CopyFile(char* OldPath, char* NewPath)函数的设计与实现

传入参数OldPath为旧文件路径,NewPath为新文件的路径。
(1)struct stat Statbuf;//用于存放旧文件的各项属性信息
struct utimbuf uTime;//用于记录旧文件的最后修改和最后访问时间int NewFd,OldFd;//分别为新文件描述符、旧文件描述符
int size;//为实际从文件中读取的字节数
char buffer[READSIZE];//存放数据的缓冲区,READSIZE为1024
(2)调用stat(OldPath,&statbuf)接口将OldPath路径下的文件属性信息存入statbuf中;调用creat(NewPath,statbuf.st_mode)在NewPath路径下创建新文件,新文件的权限应与旧文件(statbuf.st_mode)一致,并获取新文件描述符NewFd。
(3)调用open()接口以只读方式打开OldPath路径下的文件,如果打开失败,则结束执行,如果打开成功,这返回旧文件描述符OldFd。
(4)调用read(OldFd,buffer,READSIZE)函数从旧文件中读取READSIZE字节大小的内容,并将读取的内容暂存于buffer缓冲区中,OldFd为文件描述符,并返回实际读取的字节数size。如果size大于0,转(5),否则转(6)
(5)调用write(NewFd,&buffer,size)将buffer缓冲区的内容写入文件描述符为NewFd的新文件中,数据大小为size。转(4)。
(6)更新uTime,并调用utime(NewPath,&uTime)修改新文件的时间属性。
(7)关闭新旧文件描述符。
实际上,在修改文件时间属性时,应先更新uTime的内容:
uTime.actime = statbuf.st_atime;
uTime.modtime = statbuf.st_mtime;

4、CopyLinkFile(char* LinkPath, char* NewPath)函数的设计与实现

传入参数中,LinkPath为软连接文件的路径,NewPath为新文件路径。
(1)定义参数如下:
struct stat statbuf;//存放软连接文件的各项属性信息的结构体
struct timeval tv[2];//记录软连接文件的最后访问和修改
char path_buf[LinkPathLen];//存放软连接文件中的文件路径
(2)对于软连接文件,lstat()函数得到的是当前文件的属性信息,并非连接文件的属性信息,所以使用lstat(LinkPath,&statbuf)接口得到当前文件的属性信息,并保存于statbuf中。
(3)调用readlink(LinkPath,path_buf,LinkPathLen)读取连接文件的内容(实际上是文件中保存的路径),读取的最大长度为LinkPathLen,将内容保存在path_buf缓冲区中。
(4)symlink(path_buf,NewPath)建立新的软连接文件,新文件路径为NewPath,链接文件的路径为path_buf。成功创建,则转(5),否则退出执行。
(5)chmod(NewPaht,statbuf。st_mode)设置新文件的权限信息,且权限与旧的符号链接文件(statbuf.st_mode)一致。
(6)在tv结构体数组中记录旧文件的时间属性:
tv[0].tv_sec = statbuf.st_atime;
tv[0].tv_usec = 0;
tv[1].tv_sec = statbuf.st_mtime;
tv[1].tv_usec = 0;
并调用lutimes(NewPath,tv)修改新文件的时间属性与旧文件一致。

4.4 实验结果

4.4.1 Windows下的实验结果

将F:/Dev-cpp/Project1文件夹复制到F:/VS 2019中,过程如下:
在这里插入图片描述

查看F:/Dev-cpp/Project1文件信息
在这里插入图片描述

查看F:/VS 2019/Project1文件信息
在这里插入图片描述

4.4.2 Linux下的实验结果

将/home/Test2/Os_3文件夹复制到/home/Test2/OS3
在这里插入图片描述

拷贝完成后,查看旧文件夹与新文件夹的文件信息
在这里插入图片描述

五、实验收获与体会

在Windows上实现时,应该调用SetFileAttributes()完成对文件存去权限的设置,很多时候,容易忘记修改新文件的权限。
在使用readlink()函数时,不需要再打开文件和关闭文件,因为readlink()系统调用将完成打开文件、读取和关闭文件的一系列操作,在读取链接文件本身信息的时候才使用readlink()系统调用。
stat()调用与lstat()调用很相似,不同之处在于,对于符号链接文件,stat()调用将统计符号链接文件所指向的普通文件的信息,而lstat()文件只统计符号链接文件本身的信息。所以,为了取得符号链接文件中的路径,应该使用lstat()而不是stat()。
不管是Windows还是Linux,一个目录文件的目录项总是包含本文件目录(.)和父目录(…),只是一般情况下,这两个目录项都是隐藏的,在Linux命令行中调用ls命令,或在Windows命令行中调用dir命令,都能够显示这两个目录项。
在复制文件时,应该递归复制,因为目录下可能会有子目录。
通过实验,掌握了Linux、Windows中对文件和目录操作的系统调用的使用,理解了其原理。理解和掌握了文件系统的实现功能。

六、实验源码

Linux下的源码

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/time.h>
#include<fcntl.h>
#include<string.h>
#include<dirent.h>
#include<utime.h>
#define READSIZE 1024
#define LinkPathLen 1024
/// <summary>
/// OldPath为待复制文件路径
/// NewPath为新文件路径
/// </summary>
/// <param name="OldPath"></param>
/// <param name="NewPath"></param>

void CopyFile(char* OldPath, char* NewPath) {
	struct stat statbuf;///stat用于存储文件各项属性信息
	struct utimbuf uTime;///uTime记录文件的最后修改时间和最后存取时间
	int NewFd;//新文件描述符
	int OldFd;//旧文件描述符
	int size = 0;//每次从文件中读取的数据量
	char buffer[READSIZE];//缓冲区,用于存放文件里的数据
	memset(buffer, 0, sizeof(buffer));

	stat(OldPath, &statbuf);
	NewFd = creat(NewPath, statbuf.st_mode);//创建新文件,返回文件描述符
	
	//打开文件
	if ((OldFd = open(OldPath, O_RDONLY)) < 0) {
		printf("open file:%s error!\n", OldPath);
		exit(-1);
	}

	//复制文件内容
	while ((size = read(OldFd, buffer, READSIZE)) > 0) {
		write(NewFd, &buffer, size);
	}

	//修改新文件时间属性
	uTime.actime = statbuf.st_atime;
	uTime.modtime = statbuf.st_mtime;
	utime(NewPath, &uTime);
	
	///关闭文件
	close(OldFd);
	close(NewFd);
}

void CopyLinkFile(char* LinkPath, char* NewPath) {
	struct stat statbuf;
	struct timeval tv[2];
	char path_buf[LinkPathLen];
	memset(path_buf, 0, sizeof(path_buf));
	lstat(LinkPath, &statbuf);
	readlink(LinkPath, path_buf, LinkPathLen);
	if (symlink(path_buf, NewPath) == -1) {
		printf("create link error!\n");
		_exit(-1);
	}
	printf("软连接文件复制成功\n");
	chmod(NewPath, statbuf.st_mode);
	tv[0].tv_sec = statbuf.st_atime;
	tv[0].tv_usec = 0;
	tv[1].tv_sec = statbuf.st_mtime;
	tv[1].tv_usec = 0;
	lutimes(NewPath, tv);
}

void MyCopy(char *OldPath, char *NewPath) {
	struct stat statbuf;///stat用于存储文件各项属性信息
	struct stat copybuf;
	struct utimbuf uTime;///uTime记录文件的最后修改时间和最后存取时间
	struct dirent* entry = NULL;///DR存储目录中的文件信息
	DIR* dirptr = NULL;///指向打开的目录
	char old_path[128], new_path[128];//存放路径
	
	strcpy(old_path, OldPath);
	strcpy(new_path, NewPath);
	dirptr = opendir(old_path);
	while ((entry = readdir(dirptr)) != NULL) {
		if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue;
		//路径名拼接
		strcpy(new_path, NewPath);
		strcpy(old_path, OldPath);
		strcat(old_path, "/");
		strcat(new_path, "/");
		strcat(old_path, entry->d_name);
		strcat(new_path, entry->d_name);
		lstat(old_path, &statbuf);
		
		//是子目录
		if(S_ISDIR(statbuf.st_mode)) {
			printf("Director: %s,正在拷贝...\n",old_path);
			stat(old_path, &copybuf);
			mkdir(new_path, copybuf.st_mode);
			MyCopy(old_path, new_path);
			uTime.actime = copybuf.st_atime;
			uTime.modtime = copybuf.st_mtime;
			utime(new_path, &uTime);
			printf("Directory: %s,拷贝完成\n",old_path);
		}

		//是符号链接文件
		else if (S_ISLNK(statbuf.st_mode)) {
     		printf("LinkFileName: %s,正在拷贝...\n",old_path);
			CopyLinkFile(old_path, new_path);
		}
		
		//是其他文件
		else {
     		printf("FileName: %s,正在拷贝...\n",old_path);
			CopyFile(old_path, new_path);
			printf("FileName: %s,拷贝完成\n",old_path);
		}
	}
	closedir(dirptr);
}

int main(int arcg, char* argv[]) {
	///如果命令行参数超过三个
	if (arcg != 3) {
		printf("arcg error!\n");
		exit(-1);
	}

	///定义参数
	struct stat statbuf;///stat用于存储文件各项属性信息
	struct utimbuf uTime;///uTime记录文件的最后修改时间和最后存取时间
	struct dirent *DR;///DR存储目录中的文件信息
	DIR *dirptr = NULL;///指向打开的目录
	DIR *dirptr2 = NULL;///指向打开的目录

	//打开目录
	if ((dirptr = opendir(argv[1])) == NULL) {
		printf("open dir error!\n");
		exit(-1);
	}

	//创建目录,前提是为该名字的目录不存在
	if ((dirptr2 = opendir(argv[2])) == NULL) {
		//得到argv[1]指向的文件信息,存储时间信息
		stat(argv[1], &statbuf);
		uTime.actime = statbuf.st_atime;
		uTime.modtime = statbuf.st_mtime;
		//创建目录
		if (mkdir(argv[2], statbuf.st_mode) < 0) {
			printf("create dir error!\n");
			exit(-1);
		}
	}
	else {
		printf("Filename duplication!\n");
		exit(-1);
	}

	//复制目录中的内容
	MyCopy(argv[1], argv[2]);
	utime(argv[2], &uTime);//修改目录时间属性
	printf("copy finished!\n");
	closedir(dirptr);
	closedir(dirptr2);
	return 0;
}

windows下的源码

#include<windows.h>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<windowsx.h>
#include <winnt.rh>
using namespace std;
#define PATH_SIZE 512
#define BUFSIZE 1024

/// <summary>
/// OldPath为源文件路径,该文件不是目录
/// NewPath为新创建的文件路径
/// </summary>
/// <param name="OldPath"></param>
/// <param name="NewPath"></param>
void CopyFile(char* OldPath, char* NewPath) {
	WIN32_FIND_DATA lpFindData;
	HANDLE hFind = FindFirstFile(OldPath, &lpFindData);
	//打开源文件
	HANDLE hOldFile = CreateFile(
		OldPath,
		GENERIC_READ | GENERIC_WRITE,
		FILE_SHARE_READ,
		NULL,
		OPEN_ALWAYS,
		FILE_ATTRIBUTE_NORMAL,
		NULL);
	//创建新文件
	HANDLE hNewFile = CreateFile(
		NewPath,
		GENERIC_READ | GENERIC_WRITE,
		FILE_SHARE_READ,
		NULL,
		OPEN_ALWAYS,
		FILE_ATTRIBUTE_NORMAL,
		NULL);
	if (hNewFile != INVALID_HANDLE_VALUE && hOldFile != INVALID_HANDLE_VALUE) {
		char buffer[BUFSIZE];
		DWORD dwxfer(0);
		//读源文件内容
		while (true) {
			ReadFile(
				hOldFile,
				buffer,
				BUFSIZE,
				&dwxfer,
				NULL);
			//向新文件中写
			WriteFile(
				hNewFile,
				buffer,
				dwxfer,
				&dwxfer,
				NULL);
			if (dwxfer != BUFSIZE) break;
		}
		//修改文件时间属性
		SetFileTime(
			hNewFile,
			&lpFindData.ftCreationTime,
			&lpFindData.ftLastAccessTime,
			&lpFindData.ftLastWriteTime);
		SetFileAttributes(NewPath, lpFindData.dwFileAttributes);
	}
	CloseHandle(hNewFile);
	CloseHandle(hOldFile);
	hOldFile = INVALID_HANDLE_VALUE;
	hNewFile = INVALID_HANDLE_VALUE;
}

/// <summary>
/// 判断是否为子目录文件
/// </summary>
/// <param name="lpFindData"></param>
/// <returns></returns>
BOOL IsChildDir(WIN32_FIND_DATA& lpFindData) {
	return (
		((lpFindData.dwFileAttributes &
			FILE_ATTRIBUTE_DIRECTORY) != 0) &&
		(lstrcmp(lpFindData.cFileName, __TEXT(".")) != 0) &&
		(lstrcmp(lpFindData.cFileName, __TEXT("..")) != 0));
}

/// <summary>
/// OldPath为待复制的源文件目录路径
/// NewPath为新创建的目录路径
/// </summary>
/// <param name="OldPath"></param>
/// <param name="NewPath"></param>
void MyCp(char* OldPath, char* NewPath) {
	char old_path[PATH_SIZE], new_path[PATH_SIZE];
	WIN32_FIND_DATA lpFindData;
	lstrcpy(old_path, OldPath);
	lstrcpy(new_path, NewPath);
	strcat(old_path, "/*.*");
	HANDLE hFindData;
	BOOL hFound = 0;
	hFindData = FindFirstFile(old_path, &lpFindData);
	if (hFindData != INVALID_HANDLE_VALUE) {
		//查找下一个文件
		while ((hFound = FindNextFile(hFindData, &lpFindData)) != 0) {
			//路径拼接
			lstrcpy(old_path, OldPath);
			lstrcpy(new_path, NewPath);
			lstrcat(old_path, "/");;
			lstrcat(new_path, "/");
			lstrcat(old_path, lpFindData.cFileName);
			lstrcat(new_path, lpFindData.cFileName);
			//判断文件是否为目录文件
			if (IsChildDir(lpFindData)) {
				printf("child directory:%s begain copy\n", lpFindData.cFileName);
				CreateDirectory(new_path, NULL);
				MyCp(old_path, new_path);
				//修改子目录时间属性
				HANDLE hDirFile = CreateFile(
					new_path,
					GENERIC_WRITE | GENERIC_READ,
					FILE_SHARE_READ,
					NULL,
					OPEN_EXISTING,
					FILE_FLAG_BACKUP_SEMANTICS,
					NULL);
				SetFileTime(
					hDirFile,
					&lpFindData.ftCreationTime,
					&lpFindData.ftLastAccessTime,
					&lpFindData.ftLastWriteTime);
				SetFileAttributes(new_path, lpFindData.dwFileAttributes);
				CloseHandle(hDirFile);
				hDirFile = INVALID_HANDLE_VALUE;
				printf("child directory:%s copy finished\n", lpFindData.cFileName);
			}
			else {
				CopyFile(old_path, new_path);
				cout << "Filename:" << lpFindData.cFileName << "copy finished" << endl;
			}
		}
	}
	else {
		cout << "find file error!" << endl;
		exit(-1);
	}
	CloseHandle(hFindData);
	hFindData = INVALID_HANDLE_VALUE;
}

/// <summary>
/// argv接收从命令行传来的三个参数,执行程序 旧文件路径 新文件路径
/// </summary>
/// <param name="argc"></param>
/// <param name="argv"></param>
/// <returns></returns>
int main(int argc, char* argv[]) {
	WIN32_FIND_DATA lpFindData, lpFindNewData;
	HANDLE hFindFile;
	//命令行输入的参数只能是3,
	if (argc != 3) {
		cout << "input parameters are error!" << endl;
		exit(-1);
	}
	//查找源文件
	if ((hFindFile = FindFirstFile(argv[1], &lpFindData)) == INVALID_HANDLE_VALUE) {
		cout << "Finding source file error!" << endl;
		exit(-1);
	}
	//创建新目录
	if (FindFirstFile(argv[2], &lpFindNewData) == INVALID_HANDLE_VALUE) {
		CreateDirectory(argv[2], NULL);
		cout << "Creating directory successfully" << endl;
	}
	MyCp(argv[1], argv[2]);
	HANDLE hDirFile = CreateFile(
		argv[2],
		GENERIC_WRITE | GENERIC_READ,
		FILE_SHARE_READ,
		NULL,
		OPEN_EXISTING,
		FILE_FLAG_BACKUP_SEMANTICS,
		NULL);
	SetFileTime(
		hDirFile,
		&lpFindData.ftCreationTime,
		&lpFindData.ftLastAccessTime,
		&lpFindData.ftLastWriteTime);
	SetFileAttributes(argv[2], lpFindData.dwFileAttributes);
	CloseHandle(hDirFile);
	CloseHandle(hFindFile);
	hFindFile = INVALID_HANDLE_VALUE;
	hDirFile = INVALID_HANDLE_VALUE;
	cout << "copy finished!" << endl;
	return 0;
}
Logo

鸿蒙生态一站式服务平台。

更多推荐