了解在Windows中,文件系统如何保存在磁盘、光盘等存储介质上的信息。并通过文件系统提供的各种API,对文件进行同步和异步读写,深入理解Windows文件系统的功能和作用,掌握同步I/O和异步I/O的特点
熟悉Linux文件系统提供的有关文件操作的系统调用。文件系统是使用计算机信息系统的重要接口。通过使用文件系统的系统调用命令操作文件,以达到对文件系统实现功能的理解和掌握
完成一个目录复制命令mycp
,包括目录下的文件和子目录,运行结果如下:
beta@bugs.com [~/]# ls –l sem
total 56
drwxr-xr-x 3 beta beta 4096 Dec 19 02:53 ./
drwxr-xr-x 8 beta beta 4096 Nov 27 08:49 ../
-rw-r--r-- 1 beta beta 128 Nov 27 09:31 Makefile
-rwxr-xr-x 1 beta beta 5705 Nov 27 08:50 consumer*
-rw-r--r-- 1 beta beta 349 Nov 27 09:30 consumer.c
drwxr-xr-x 2 beta beta 4096 Dec 19 02:53 subdir/
beta@bugs.com [~/]# mycp sem target
beta@bugs.com [~/]# ls –l target
total 56
drwxr-xr-x 3 beta beta 4096 Dec 19 02:53 ./
drwxr-xr-x 8 beta beta 4096 Nov 27 08:49 ../
-rw-r--r-- 1 beta beta 128 Nov 27 09:31 Makefile
-rwxr-xr-x 1 beta beta 5705 Nov 27 08:50 consumer*
-rw-r--r-- 1 beta beta 349 Nov 27 09:30 consumer.c
drwxr-xr-x 2 beta beta 4096 Dec 19 02:53 subdir/
说明:
create
、read
、write
等系统调用,要求支持软链接CreateFile()
、ReadFile()
、WriteFile()
、CloseHandle()
等函数本实验基于本机macOS系统和Windows虚拟机完成,具体实验环境如下:
macOS环境配置如下:
Windows虚拟机环境配置如下:
接受到的源目录后,打开源目录遍历目录下所有的目录和文件并完成复制。若遍历到目录,则递归调用复制目录,若遍历到文件,则调用复制文件
对接收到的目标目录路径,若不存在该目录,则新创建一个同名目录,并更新其权限和时间同源目录一致
复制目录时,若在目标路径下不存在该目录,则要创建一个同名目录,并更新其权限和时间同复制源目录一致
复制文件时,需要调用读文件和写文件函数,将源文件内容写入目标文件,并更新其权限和时间同复制源文件一致
Windows系统使用WIN32_FIND_DATA
数据结构保存FindFirstFile
或FindNextFile
找到的文件信息
定义如下:
typedef struct _WIN32_FIND_DATAA {
DWORD dwFileAttributes;
FILETIME ftCreationTime;
FILETIME ftLastAccessTime;
FILETIME ftLastWriteTime;
DWORD nFileSizeHigh;
DWORD nFileSizeLow;
DWORD dwReserved0;
DWORD dwReserved1;
CHAR cFileName[MAX_PATH];
CHAR cAlternateFileName[14];
DWORD dwFileType;
DWORD dwCreatorType;
WORD wFinderFlags;
} WIN32_FIND_DATAA, *PWIN32_FIND_DATAA, *LPWIN32_FIND_DATAA;
说明:(只说明部分用到的成员)
dwFileAttributes
:文件的文件属性nfileSizeHigh
:文件大小的高位DWORD值,以字节为单位nfileSizeLow
:文件大小的低位DWORD值,以字节为单位minwinbase.h
包含于Windows.h
中Windows系统中,用FindFirstFile
和FindNextFile
函数遍历目录下的文件或目录
语法如下:
HANDLE FindFirstFileA(
LPCSTR lpFileName,
LPWIN32_FIND_DATAA lpFindFileData
);
BOOL FindNextFileA(
HANDLE hFindFile,
LPWIN32_FIND_DATAA lpFindFileData
);
说明:
lpFileName
:目录或路径以及文件名,文件名可以包含通配符lpFindFileData
:指向WIN32_FIND_DATA
结构的指针,该结构接收相关找到的文件或目录的信息FindNextFile
和FindClose
使用的句柄,而lpFindFileData
包含找到文件或目录的信息fileapi.h
包含与Windows.h
中Windows系统使用CreateFile
创建或打开一个文件或I/O设备
语法如下:
HANDLE CreateFileA(
LPCSTR lpFileName,
DWORD dwDesiredAccess,
DWORD dwShareMode,
LPSECURITY_ATTRIBUTES lpSecurityAttributes,
DWORD dwCreationDisposition,
DWORD dwFlagsAndAttributes,
HANDLE hTemplateFile
);
说明:
lpFileName
:要创建或打开的文件或设备名称dwDesiredAccess
:请求访问文件或设备的权限
GEnERIC_READ
,GENERIC_WRITE
或两者的或dwShareMode
:请求的文件或设备的共享模式,可以是读取,写入,删除等lpSecurityAttributes
:指向SECURITY_ATTRIBUTES
结构的指针,描述安全性dwCreationDisposition
:对存在或不存在的文件或设备指向的操作dwFlagAndAttributes
:文件或设备属性和标志,文件中最常见默认值为FILE_ATTRIBUTE_NORMAL
hTemplateFile
:具有GENERIC_READ
访问权限的模版文件的有效句柄,可以为NULL
fileapi.h
包含于Windows.h
Windows系统使用ReadFile
从指定文件或I/O设备读取数据
语法如下:
BOOL ReadFile(
HANDLE hFile,
LPVOID lpBuffer,
DWORD nNumberOfBytesToRead,
LPDWORD lpNumberOfBytesRead,
LPOVERLAPPED lpOverlapped
);
说明:
hFile
:文件或设备的句柄lpBuffer
:指向缓冲区的指针,该缓冲区接收从文件或设备读取的数据nNumberOfBytesToRead
:要读取的最大字节数lpNumberOfBytesRead
:指向变量的指针,该变量接收使用同步hFile
参数时读取的字节lpOverlapped
:若使用FILE_FLAG_OVERLAPPED
打开hFile
参数,则需要指向一个OVERLAPPED
结构,否则可以为NULL
fileapi.h
包含于Windows.h
中Windows操作系统使用WriteFile
函数将数据写入指定的文件或I/O设备中
语法如下:
BOOL WriteFile(
HANDLE hFile,
LPCVOID lpBuffer,
DWORD nNumberOfBytesToWrite,
LPDWORD lpNumberOfBytesWritten,
LPOVERLAPPED lpOverlapped
);
说明:
hFile
:文件或设备的句柄lpBuffer
:指向要写入文件或设备的数据的缓冲区指针nNumberOfBytesToRead
:要写入文件或设备的最大字节数lpNumberOfBytesRead
:指向变量的指针,该变量接收使用同步hFile
参数时读取的字节lpOverlapped
:若使用FILE_FLAG_OVERLAPPED
打开hFile
参数,则需要指向一个OVERLAPPED
结构,否则可以为NULL
fileapi.h
包含于Windows.h
中修改文件时间
Windows系统使用SetFileTime函数进行文件时间属性修改
语法如下:
BOOL SetFileTime(
HANDLE hFile,
const FILETIME *lpCreationTime,
const FILETIME *lpLastAccessTime,
const FILETIME *lpLastWriteTime
);
说明:
hFile
:要修改文件的句柄lpCreationTime
:文件创建时间lpLastAccessTime
:文件最后访问时间lpLastWriteTime
:文件最后写入时间程序代码以附件方式给出,共一个文件mycp.cpp
,如下:
#include
#include
#include
#include
#include
#define BUFF_SIZE 4096
void copyFile(const char *sourceFile,const char *destinationFile){
WIN32_FIND_DATA lpfilefinddata;
HANDLE hfirstfile=FindFirstFile(sourceFile,&lpfilefinddata);
HANDLE hsourcefile=CreateFile(sourceFile,GENERIC_READ | GENERIC_WRITE,FILE_SHARE_READ,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
HANDLE hdestinationfile=CreateFile(destinationFile,GENERIC_READ | GENERIC_WRITE,FILE_SHARE_READ,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
LONG filesize=lpfilefinddata.nFileSizeLow-lpfilefinddata.nFileSizeHigh;
DWORD word;
int *buff=new int[filesize];
ReadFile(hsourcefile,buff,filesize,&word,NULL);
WriteFile(hdestinationfile,buff,filesize,&word,NULL);
SetFileTime(hdestinationfile,&lpfilefinddata.ftCreationTime,&lpfilefinddata.ftLastAccessTime,&lpfilefinddata.ftLastWriteTime);
CloseHandle(hfirstfile);
CloseHandle(hsourcefile);
CloseHandle(hdestinationfile);
}
void copyDir(const char *sourceDir,const char *destinationDir){
WIN32_FIND_DATA lpfilefinddata;
char tempsourceDir[BUFF_SIZE],tempdestinationDir[BUFF_SIZE];
memset(tempsourceDir,0,sizeof(tempsourceDir));
lstrcpy(tempsourceDir,sourceDir);
lstrcat(tempsourceDir,"\\*.*");
HANDLE hfirstfile=FindFirstFile(tempsourceDir,&lpfilefinddata);
while(FindNextFile(hfirstfile,&lpfilefinddata)!=0){
if(lpfilefinddata.dwFileAttributes==16){ //文件属性为16表示是一个目录
if (strcmp(lpfilefinddata.cFileName,".")!=0&&strcmp(lpfilefinddata.cFileName,"..")!=0) {
memset(tempsourceDir,0,sizeof(tempsourceDir));
memset(tempdestinationDir,0,sizeof(tempdestinationDir));
lstrcpy(tempsourceDir,sourceDir);
lstrcpy(tempdestinationDir,destinationDir);
lstrcat(tempsourceDir,"\\");
lstrcat(tempdestinationDir,"\\");
lstrcat(tempsourceDir,lpfilefinddata.cFileName);
lstrcat(tempdestinationDir,lpfilefinddata.cFileName);
CreateDirectory(tempdestinationDir,NULL);
HANDLE hdestinationfile=CreateFile(tempdestinationDir,GENERIC_READ | GENERIC_WRITE,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_FLAG_BACKUP_SEMANTICS,NULL);
HANDLE hsourcefile=CreateFile(tempsourceDir,GENERIC_READ | GENERIC_WRITE,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_FLAG_BACKUP_SEMANTICS,NULL);
FILETIME create,access,write;
GetFileTime(hsourcefile,&create,&access,&write);
SetFileTime(hdestinationfile,&create,&access,&write);
printf("sourcefile:%s\ndestinationfile:%s\n",tempsourceDir,tempdestinationDir);
copyDir(tempsourceDir,tempdestinationDir);
}
}
else{
memset(tempsourceDir,0,sizeof(tempsourceDir));
memset(tempdestinationDir,0,sizeof(tempdestinationDir));
lstrcpy(tempsourceDir,sourceDir);
lstrcpy(tempdestinationDir,destinationDir);
lstrcat(tempsourceDir,"\\");
lstrcat(tempdestinationDir,"\\");
lstrcat(tempsourceDir,lpfilefinddata.cFileName);
lstrcat(tempdestinationDir,lpfilefinddata.cFileName);
printf("sourcefile:%s\ndestinationfile:%s\n",tempsourceDir,tempdestinationDir);
copyFile(tempsourceDir,tempdestinationDir);
}
}
CloseHandle(hfirstfile);
}
int main(int argc, char const *argv[])
{
if(argc!=3){
printf("Please enter VALID parameters\n");
printf("For example: mycp.exe sourceDir destinationDir\n");
exit(0);
}
WIN32_FIND_DATA lpfindfiledata;
if(FindFirstFile(argv[1],&lpfindfiledata)==INVALID_HANDLE_VALUE){
printf("Can not find the source directory\n");
exit(0);
}
if(FindFirstFile(argv[2],&lpfindfiledata)==INVALID_HANDLE_VALUE){
printf("Create new directory %s\n",argv[2]);
CreateDirectory(argv[2],NULL);
}
copyDir(argv[1],argv[2]);
printf("Copy is done\n");
return 0;
}
运行方式如下:
g++ mycp.cpp -o mycp.exe
.\mycp.exe sourcedir destinationdir
创建源目录,如下:
使用mycp.exe
复制,如下:
复制结果如下:
接受到的源目录后,打开源目录(注意要支持软连接)遍历目录下所有的目录和文件并完成复制。若遍历到目录,则递归调用复制目录,若遍历到文件,则调用复制文件
对接收到的目标目录路径,若不存在该目录,则新创建一个同名目录,并更新其权限和时间同源目录一致
复制目录时,若在目标路径下不存在该目录,则要创建一个同名目录,并更新其权限和时间同复制源目录一致
复制文件时,需要调用读文件和写文件函数,将源文件内容写入目标文件,并更新其权限和时间同复制源文件一致
类似于FILE结构体,是一个内部结构体,用于保存当前被读取的目录相关信息
定义如下:
struct __dirstream{
void* __fd;
char* __data;
int __entry_data;
char* __ptr;
int __entry_ptr;
size_t __allocation;
size_t __size;
__libc_lock_define (,__lock)
};
typedef struct __dirstream DIR;
本结构体为内部结构体,实验中不详细说明其功能
Linux系统使用struct stat
结构体描述一个文件系统中的文件属性的结构
定义如下:
struct stat{
mode_t st_mode;
ino_t st_ino;
dev_t st_dev;
dev_t st_rdev;
nlink_t st_nlink;
uid_t st_uid;
git_t st_gid;
off_t st_size;
time_t st_atime;
time_t st_mtime;
time_t st_ctime;
blksize_t st_blksize;
blkcnt_t st_blocks;
};
说明:
st_mode
:文件对应的模式,包含权限信息st_ino
:inode节点号st_dev
:设备号st_rdev
:特殊设备号st_nlink
:文件链接数st_uid
:文件所有者st_gid
:文件所有者对应的组st_size
:文件对应的字节数st_atime
:文件最后被访问的时间st_mtime
:文件内容最后被修改的时间st_ctime
:文件状态改变的时间st_blksize
:文件内容对应的块大小st_blocks
:文件对应的块数量sys/stat.h
Linux系统使用utimbuf
作为文件时间属性结构体
定义如下:
struct utimbuf{
time_t actime;
time_t modtime;
}
说明:
actime
:文件的访问时间modtime
:文件的修改时间utime
使用该结构体为文件设置时间属性sys/utime.h
Linux系统使用dirent
结构体存储读取目录的内容
定义如下:
struct dirent{
long d_ino;
off_t d_off;
unsigned short d_reclen;
unsigned char d_type;
char d_name[NAME_MAX+1];
};
说明:
d_ino
:索引节点号d_off
:在目录文件中的偏移d_reclen
:文件名长度d_type
:文件类型d_name
:文件名Linux系统使用opendir
打开一个目录
语法如下:
DIR* opendir(const char* path);
说明:
path
:目录的路径DIR
结构体指针,打开失败时返回空指针sys/stat.h
,dirent.h
Linux系统使用mkdir
创建一个目录
语法如下:
int mkdir(const char *path, mode_t mode);
说明:
path
:要创建的目录的路径mode
:目录权限,实验中由stat.st_mode
指示sys/stat.h
,dirent.h
Linux系统使用readdir
函数读取目录内容
语法如下:
struct dirent* readdir(DIR *dir_handle)
说明:
dir_handle
:一个DIR
结构体指针dirent
结构体指针dirent.h
Linux系统使用utime
函数为一个文件修改时间属性
语法如下:
int utime(const char *pathname, const struct utimbuf *times);
int lutimes(const char *pathname,const struct timeval *times);
说明:
lutes
:用于修改软连接文件信息pathname
:要修改的文件的路径times
:一个utimbuf
结构体,存储文件时间属性utime.h
Linux系统使用stat
和lstat
函数获取文件相关信息,但是stat
不支持软连接文件,所以在实验中使用lstat
函数获取文件信息
语法如下:
int lstat(const char *path, struct stat *buf);
说明:
path
:文件路径名buf
:指向stat
结构体的指针lstat
函数,则将文件对应的属性装载进stat
结构体中sys/stat.h
Linux系统使用open
函数打开一个文件,获得该文件的访问句柄(一个整数)
定义如下:
int fd=open(const char *pathname,int flag);
int fd=open(const char *pathname,int flag,mode_t mode);
说明:
pathname
:要打开文件的路径名flag
:避免系统安全问题使用的旗标,指定文件打开方式。实验中只需要读取源文件内容,所以使用的是O_RDONLY
mode
:只有在创建文件时才需要用到,指明文件的权限属性sys/types.h
,sys/stat.h
,fcntl.h
Linux系统使用creat
函数创建一个文件或重写一个已经存在的文件
定义如下:
int creat(const char *pathname,mode_t mode);
说明
pathname
:要创建的文件的路径mode
:要创建文件的权限属性,本实验中该属性需要和复制源文件相同fcntl.h
Linux系统在不需要再使用打开的文件时,需要关闭该文件
定义如下:
int close(int fd);
说明:
fd
:打开文件时返回的文件访问句柄unistd.h
Linux系统使用write
函数向已经打开的文件中写入数据
定义如下:
ssize_t write(int fd,const void *buf,size_t nbyte);
说明:
fd
:打开文件时返回的文件访问句柄buf
:缓冲区数据块指针nbyte
:数据的大小buf
所指的内存写入nbyte
个字节到fd
所指的文件中,文件读写位置也会随之移动nbyte
unistd.h
读文件
Linux系统使用read
函数从已经打开的文件读取文件
定义如下:
ssize_t read(int fd,void *buf,size_t nbyte);
说明:
fd
:打开文件时返回的文件访问句柄buf
:缓冲区数据块指针nbyte
:文件读取的大小fd
所指向的文件读出nbyte
个字节填入buf
所指的数据块中nbyte
比较,若少了可能读到了文件末尾unistd.h
程序代码以附件形式给出,共一个文件mycp.c
,如下:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define BUFF_SIZE 4096
void copyLink(char *source, char *destination){
char oldpath[BUFF_SIZE];
int rest=readlink(source,oldpath,BUFF_SIZE);
int sy=symlink(oldpath,destination);
struct stat statbuff;
lstat(source,&statbuff);
struct timeval times[2];
times[0].tv_sec=statbuff.st_atime;
times[0].tv_usec=0;
times[1].tv_sec=statbuff.st_mtime;
times[1].tv_usec=0;
lutimes(destination,times);
}
void copyFile(char *source, char *destination)
{
int sourcefile = open(source, O_RDONLY);
if (sourcefile == -1)
{
printf("Can not open file %s\n", source);
exit(0);
}
struct stat statbuff;
lstat(source, &statbuff);
int destinationfile = creat(destination, statbuff.st_mode);
if (destinationfile == -1)
{
printf("Can not create file %s\n", destination);
exit(0);
}
int word;
char buff[BUFF_SIZE];
while ((word = read(sourcefile, buff, BUFF_SIZE)) > 0)
{
if (write(destinationfile, buff, word) != word)
{
printf("Write error");
exit(0);
}
}
struct utimbuf timebuff;
timebuff.actime = statbuff.st_atime;
timebuff.modtime = statbuff.st_mtime;
utime(destination, &timebuff);
close(sourcefile);
close(destinationfile);
}
void copyDir(const char *source, const char *destination)
{
DIR *dir = opendir(source);
struct dirent *entry;
char tempsource[2048], tempdestination[2048];
while ((entry = readdir(dir)) != NULL)
{
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
{
continue;
}
if (entry->d_type == 4) //d_type为4表示是一个目录
{
strcpy(tempsource, source);
strcpy(tempdestination, destination);
strcat(tempsource, "/");
strcat(tempdestination, "/");
strcat(tempsource, entry->d_name);
strcat(tempdestination, entry->d_name);
//将源路径和目标路径都复制到temp中
struct stat statbuff;
lstat(tempsource, &statbuff);
mkdir(tempdestination, statbuff.st_mode);
struct utimbuf timebuff;
timebuff.actime = statbuff.st_atime;
timebuff.modtime = statbuff.st_mtime;
utime(tempdestination, &timebuff);
copyDir(tempsource, tempdestination); //递归操作
}
else if (entry->d_type==10) //d_type为4表示是一个符号连接
{
strcpy(tempsource, source);
strcpy(tempdestination, destination);
strcat(tempsource, "/");
strcat(tempsource, entry->d_name);
strcat(tempdestination, "/");
strcat(tempdestination, entry->d_name);
//将源路径和目标路径都复制到temp中
copyLink(tempsource,tempdestination);
}
else
{
strcpy(tempsource, source);
strcpy(tempdestination, destination);
strcat(tempsource, "/");
strcat(tempsource, entry->d_name);
strcat(tempdestination, "/");
strcat(tempdestination, entry->d_name);
//将源路径和目标路径都复制到temp中
copyFile(tempsource, tempdestination);
}
}
}
int main(int argc, char const *argv[])
{
if (argc != 3)
{
printf("Please enter VALID parameters\n");
printf("Like: mycp sourceDIR destinationDIR\n");
}
DIR *dir;
if ((dir = opendir(argv[1])) == NULL)
{
printf("Can not find the Source Direct");
exit(0);
}
closedir(dir);
if ((dir = opendir(argv[2])) == NULL)
{
struct stat statbuff;
struct utimbuf timebuff;
lstat(argv[1], &statbuff);
//使用lstat可以支持软连接
mkdir(argv[2], statbuff.st_mode);
timebuff.actime = statbuff.st_atime;
timebuff.modtime = statbuff.st_mtime;
utime(argv[2], &timebuff);
}
copyDir(argv[1],argv[2]);
printf("Copy is ok");
return 0;
}
运行方式如下:
$ gcc mycp.c -o mycp
$ ./mycp source destination
先创建测试目录和对应的链接文件,运行如下代码
$ mkdir sourcedir
$ touch sourcedir/testfile
$ ln -s sourcedir/testfile sourcedir/testlinkfile
$ mkdir sourcedir/testdir
$ ln -s sourcedir/testdir sourcedir/testlinkdir
$ ls -l sourcedir
结果如下:
运行mycp
程序,如下:
$ ./mycp sourcedir destinationdir
$ ls -l destinationdir
结果如下: