c++實現文件復制並修改相應屬性


問題描述

完成一個目錄復制命令mycp,包括目錄下的文件和子目錄, 運行結果如下:

beta@bugs.com [~/]# ls –la 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 –la 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/

思路

這道題目主要涉及文件讀寫操作和屬性修改。需要支持文件夾復制、文件復制,在Linux下還要支持軟鏈接的復制。

思路如下:

  • 獲取待復制目錄的絕對路徑
  • 根據絕對路徑進行dfs或者bfs搜索所有子目錄項
  • 判斷子目錄是屬於什么類型:文件夾、普通文件、軟鏈接
  • 分別對三種(Windows下只有文件夾和文件)進行復制操作
  • 修改目標文件屬性與源文件保持一致

使用到的函數主要有:

Linux

判斷文件類型

  1. int lstat(const char *pathname, struct stat *statbuf);

    • const char *pathname 需要判斷的文件的路徑
    • struct stat *statbuf 用於保存文件屬性
    • return int 0成功,-1失敗
  2. 判斷

    文件類型 說明 判斷函數 例子
    普通文件 一般意義上的文件 S_ISREG() hello.c
    目錄文件 可包含其他文件或者目錄 S_ISDIR() /etc/
    字符設備文件 以無緩沖方式,提供對於設備的可變長度訪問 S_ISCHR() /dev/tty
    塊設備文件 以帶緩沖方式,提供對於設備的固定長度訪問 S_ISBLK() /dev/sda
    符號鏈接文件 指向另一個文件 S_ISLNK() /dev/cdrom
    命名管道文件 用於進程間通信 S_ISFIFO() /dev/inictl
    網絡套接字文件 用於進程間的網絡通信 S_ISSOCK() /dev/log

    值得注意的是,需要先判斷是不是符號鏈接文件。對符號鏈接文件進行S_ISREG()判斷時會出現將其判斷為普通文件的情況。可能是由於他判斷的是鏈接文件所指向的文件的類型。因此需要先判斷是不是鏈接文件。

遍歷文件目錄

  1. DIR *opendir(const char *name);

    • const char *name 待打開的目錄路徑
    • return DIR 返回目錄數據結構
  2. struct dirent *readdir(DIR *dirp);

    • ``````DIR *dirp``` 待讀取的目錄

    • return struct dirent* 返回順序讀取的該目錄下的目錄項

      注意,第一個目錄項是., 第二個目錄項是..

復制文件

  1. int open(const char *pathname, int flags, mode_t mode);
    • const char* pathname 待打開的文件路徑
    • int flags O_RDONLY, O_WRONLY, or O_RDWR
    • mode_t mode
    • return int 返回值是打開的文件描述符
  2. int creat(const char *pathname, mode_t mode);
    • const char* pathname 待創建的文件名(可以包含路徑)
    • mode_t mode 創建屬性
    • return int 待創建文件描述符
  3. ssize_t read(int fd, void *buf, size_t count);
    • int fd 待讀取的文件的描述符
    • void* buf 讀取的內容存放緩沖區。盡管這里是void*buf在創建的時候應該是```char*``````
    • ​ ``````size_t count 要讀取的內容的大小。如果大於fd```中的數目,則讀取相應大小內容
    • return ssize_t 返回實際讀取的內容的數目。失敗返回-1
  4. ssize_t write(int fd, const void *buf, size_t count);
    • int fd 要寫入的文件的描述符
    • const void *buf 要寫入的內容。
    • size_t count 要寫入的內容大小
    • return ssize_t 成功返回實際寫入的數目。失敗返回-1

復制文件夾

復制文件夾就是創建同名文件夾

  1. int mkdir(const char *pathname, mode_t mode);
    • const char* pathname 待創建的目錄項的地址
    • mode_t mode 創建目錄項的屬性
    • return int 如果成功返回為0,否則返回-1

復制軟鏈接

  1. ssize_t readlink(const char *pathname, char *buf, size_t bufsiz);
    • const cha* pathname 待讀取軟鏈接的路徑
    • char* buf 軟鏈接中的內容保存到buf
    • size_t bufsiz buf緩沖區的大小
    • return ssize_t 返回值是軟鏈接指向路徑的長度
  2. int symlink(const char *target, const char *linkpath);
    • const char* target 待創建的軟鏈接的路徑
    • const char* linkpath 待創建的軟鏈接所指向的路徑
    • return int 成功返回0,否則返回-1

獲取屬性

  1. int lstat(const char *pathname, struct stat *statbuf);

    • const char *pathname 需要提取屬性的文件或者文件夾的路徑
    • struct stat *statbuf 獲取到的屬性存儲緩沖區
    • return int 成功返回0,否則-1
  2. int chmod(const char *pathname, mode_t mode);

    • const char *pathname 待修改屬性的文件的路徑
    • mode_t mode 將待修改文件修改改為該屬性
    • return int 若成功返回0,否則返回-1
  3. int chown(const char *pathname, uid_t owner, gid_t group);

    • const char* pathname 待更改的目錄路徑
    • uid_t owner 如果是-1則不改變屬性
    • gid_t group 如果是-1則不改變屬性
  4. int lutimes(const char *filename, const struct timeval tv[2]);

    這個命令用於修改目標文件的access_time modify_timelutimes可以修改軟鏈接的屬性。

    • const char *filename 待修改的文件路徑
    • const struct timeval tv[2] tv[0]access_timetv[1]modify_time
    • return int 如果成功返回0,否則返回-1

Windows

這里主要列出所用到的函數,有需要的話可以詳細去MSDN查相關的函數說明:

FindFirstFile

FindNextFile

CreateFile

GetFileSize

ReadFile

WriteFile

_wmkdir

GetFileAttributes

SetFileAttributes

GetFileTime

SetFileTime

源代碼實現

Linux

#include <iostream>
#include <string>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <utime.h>
#include <time.h>
#include <sys/time.h>

//文件夾復制采用dfs或者bfs進行搜索是等價的
void searchDfs(char *src_path, char *dest_path);

//復制文件
void copyFile(const char *src_file, const char *dst_file);

//復制文件夾
void copyDir(const char *src_dir, const char *dst_dir);

//復制軟鏈接
void copySln(const char *src_file, const char *dst_file);

//修改文件屬性與源文件保持一致
void changeAttr(const char *src, const char *dst);

//在src路徑后面加上cat路徑
void changePathname(char *src, char *cat)
{
    //在src路徑后面加上cat路徑
    strcat(src, (char *)"/");
    strcat(src, cat);
}

int main(int argc, char const *argv[])
{
    //異常處理
    if (argc < 3)
    {
        printf("No file or directory specified\n");
        exit(-1);
    }
    if (argc > 3)
    {
        printf("Too many arguments\n");
        exit(-1);
    }

    char src_path[1024], dest_path[1024];
    char *current_dir = getcwd(NULL, 0); //獲得當前路徑

    //判斷需要復制的是文件還是文件夾
    struct stat state_of_entry;
    lstat(argv[1], &state_of_entry);
    if (S_ISDIR(state_of_entry.st_mode)) //如果要復制的是文件夾
    {
        if (chdir(argv[1])) //目錄錯誤
        {
            perror("chdir");
            exit(-1);
        }
        strcpy(src_path, getcwd(NULL, 0)); //獲取源文件夾絕對路徑
        chdir(current_dir);

        //判斷第二個參數是不是目標路徑
        lstat(argv[2], &state_of_entry);
        if (S_ISDIR(state_of_entry.st_mode)) //目錄
        {
            if (chdir(argv[2])) //目錄錯誤
            {
                perror("chdir");
                exit(-1);
            }
            strcpy(dest_path, getcwd(NULL, 0)); //獲取目標文件夾絕對路徑
            chdir(current_dir);

            chdir(dest_path);

            chdir(src_path); //切換到要復制的文件夾
            searchDfs(src_path, dest_path);
        }
        //如果目標路徑錯誤
        else
        {
            printf("error. No destination directory.\n");
            exit(-1);
        }
    }

    else //文件直接復制
    {
        char dest_path[1024];
        lstat(argv[2], &state_of_entry);
        if (S_ISDIR(state_of_entry.st_mode)) //目錄
        {
            strcpy(dest_path, getcwd(NULL, 0)); //獲取目標文件夾絕對路徑
        }
        else
        {
            strcpy(dest_path, "./");
            strcat(dest_path, argv[2]);
        }
        copyFile(argv[1], argv[2]);
    }

    return 0;
}

void searchDfs(char *src_path, char *dest_path)
{
    DIR *src_dir = opendir(src_path);
    DIR *dest_dir = opendir(dest_path);
    struct dirent *entry = NULL;
    struct stat state_of_entry;
    while ((entry = readdir(src_dir)) != NULL)
    {
        if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
            continue;

        lstat(entry->d_name, &state_of_entry);
        if (S_ISLNK(state_of_entry.st_mode)) //符號鏈接
        {
            char src_file[1024];
            char dest_file[1024];
            strcpy(src_file, src_path);
            //獲取源文件絕對路徑
            changePathname(src_file, entry->d_name);
            strcpy(dest_file, dest_path);
            //獲取目標文件絕對路徑
            changePathname(dest_file, entry->d_name);
            //復制
            copySln(src_file, dest_file);
        }
        else if (S_ISREG(state_of_entry.st_mode)) //普通文件
        {
            char src_file[1024];
            char dest_file[1024];
            strcpy(src_file, src_path);
            changePathname(src_file, entry->d_name);
            strcpy(dest_file, dest_path);
            changePathname(dest_file, entry->d_name);
            copyFile(src_file, dest_file);
        }
        else if (S_ISDIR(state_of_entry.st_mode)) //目錄
        {
            char src[1024];
            char dest[1024];
            strcpy(src, src_path);
            changePathname(src, entry->d_name);
            strcpy(dest, dest_path);
            changePathname(dest, entry->d_name);
            copyDir(src, dest);
            searchDfs(src, dest);
        }
    }
    //保持目標文件夾與源文件夾屬性相同
    changeAttr(src_path, dest_path);
}

void copyFile(const char *src_file, const char *dest_file)
{
    int src_fd = open(src_file, O_RDONLY);
    int dest_fd = creat(dest_file, O_WRONLY);

    unsigned char buf[1024];
    while (read(src_fd, buf, sizeof(buf)) > 0)
    {
        write(dest_fd, buf, sizeof(buf));
    }

    changeAttr(src_file, dest_file);

    close(src_fd);
    close(dest_fd);
}

void copyDir(const char *src_dir, const char *dst_dir)
{
    mkdir(dst_dir, 0777);
    changeAttr(src_dir, dst_dir);
}

void copySln(const char *src_file, const char *dst_file)
{
    //軟鏈接所指向的文件路徑
    char slink_path[1024];
    memset(slink_path, 0, sizeof(slink_path));
    int len = 0;
    if ((len = readlink(src_file, slink_path, sizeof(slink_path))) > 0)
    {
        char src_dir[1024];
        strcpy(src_dir, src_file);
        //獲取源文件的絕對路徑,不含文件名
        for (int i = strlen(src_dir); i > -1; i--)
        {
            if (src_dir[i] == '/')
            {
                src_dir[i + 1] = 0;
                break;
            }
        }

        if (slink_path[0] != '/')        //一定是相對路徑,如果首字符是'/'則是從根目錄出發的絕對路徑
            strcat(src_dir, slink_path); //將相對軟鏈接的路徑添加到軟鏈接文件路徑的后面
        else
            strcpy(src_dir, slink_path); //絕對路徑拷貝過去即可

        //保存軟鏈接所指向的文件的名字
        char file_name[1024];
        //提取所指向的文件的目錄,由於可能是相對路徑,顧切換到對應的文件夾下獲取絕對路徑
        for (int i = strlen(src_dir); i > -1; i--)
        {
            if (src_dir[i] == '/')
            {
                strcpy(file_name, src_dir + i);//將軟鏈接所指向的文件名保存在file_name中
                src_dir[i + 1] = 0;
                break;
            }
        }

        chdir(src_dir);//切換到軟鏈接所指向的文件的路徑
        strcpy(src_dir, getcwd(NULL, 0)); //獲取軟鏈接所指向文件的絕對路徑

        strcat(src_dir, file_name);//添加軟鏈接所指向的文件名即組成了絕對路徑

        //創建軟鏈接
        if (symlink(src_dir, dst_file) == -1)
        {
            perror("symlink");
        }

        //修改目標軟鏈接屬性使其與源軟鏈接屬性一致
        changeAttr(src_file, dst_file);
    }
    else
    {
        printf("%s: 軟鏈接錯誤\n",src_file);
    }
    
}

void changeAttr(const char *src, const char *dst)
{
    struct stat attr_of_src;
    lstat(src, &attr_of_src);

    //修改文件屬性
    chmod(dst, attr_of_src.st_mode);
    //修改文件用戶組
    chown(dst, attr_of_src.st_uid, attr_of_src.st_gid);

    //修改文件訪問、修改時間
    if (S_ISLNK(attr_of_src.st_mode))
    {
        struct timeval time_buf[2];
        time_buf[0].tv_sec = attr_of_src.st_atim.tv_sec;
        time_buf[0].tv_usec = attr_of_src.st_atim.tv_nsec / 1000;
        time_buf[1].tv_sec = attr_of_src.st_mtim.tv_sec;
        time_buf[1].tv_usec = attr_of_src.st_mtim.tv_nsec / 1000;
        if (lutimes(dst, time_buf) == -1)
        {
            printf("%s\n", dst);
            perror("lutimes");
        }
    }
    else
    {
        struct utimbuf tbuf;
        tbuf.actime = attr_of_src.st_atime;
        tbuf.modtime = attr_of_src.st_mtime;
        utime(dst, &tbuf);
    }
}

Windows

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <Windows.h>
#include <cstring>
#define BUF_SIZE 1024
using namespace std;

//文件夾復制采用dfs或者bfs進行搜索是等價的
void searchDfs(wchar_t* src_path, wchar_t* dest_path);
//復制文件
void copyFile(const wchar_t* src_file, const wchar_t* dst_file);
//復制文件夾
void copyDir(const wchar_t* src_dir, const wchar_t* dst_dir);
//修改文件屬性與源文件保持一致
void changeAttr(const wchar_t* src_name, const wchar_t* dst_name, HANDLE h_src, HANDLE h_dst);

int wmain(int argc, wchar_t* argv[])
{
	setlocale(LC_ALL, "");//設置wchar支持中文
	wchar_t* src_path = new wchar_t[BUF_SIZE];
	wchar_t* dst_path = new wchar_t[BUF_SIZE];
	ZeroMemory(src_path, BUF_SIZE);
	ZeroMemory(dst_path, BUF_SIZE);
	wcscpy(src_path, argv[1]);//獲取源文件夾路徑
	wcscat(src_path, L"\\*");//末尾添加"\\*"表明要對其進行遍歷
	wcscpy(dst_path, argv[2]);//獲取目標文件夾路徑
	searchDfs(src_path, dst_path);//對源文件夾進行搜索遍歷復制
	delete[] src_path;
	delete[] dst_path;
	return 0;
}

void searchDfs(wchar_t* src_path, wchar_t* dst_path)
{
	//保存文件夾下的目錄信息
	WIN32_FIND_DATA find_data;
	//第一個文件的handle
	HANDLE h_find = FindFirstFile(src_path, &find_data);
	if (h_find == INVALID_HANDLE_VALUE)
	{
		cout << "Fail to find first file" << endl;
		return;
	}

	copyDir(src_path, dst_path);//目標文件夾不存在時先創建

	//遍歷該文件夾下的所有文件和文件夾
	do
	{
		//wcout << find_data.cFileName << endl;
		if (wcscmp(find_data.cFileName, L".") == 0 || wcscmp(find_data.cFileName, L"..") == 0)
		{
			continue;
		}
		//該項是目錄
		if (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
		{
			wchar_t n_src_path[BUF_SIZE];
			wcscpy(n_src_path, src_path);
			n_src_path[wcslen(n_src_path) - 1] = '\0';//刪除最后一位的'*'
			wcscat(n_src_path, find_data.cFileName);//添加下層文件夾名稱以獲取下級目錄
			wcscat(n_src_path, L"\\*");

			wchar_t n_dst_path[BUF_SIZE];
			wcscpy(n_dst_path, dst_path);
			wcscat(n_dst_path, L"\\");
			wcscat(n_dst_path, find_data.cFileName);//要復制到的目標文件夾路徑

			copyDir(n_src_path, n_dst_path);//復制文件夾

			searchDfs(n_src_path, n_dst_path);//向下搜索
		}
		else//該項是文件
		{
			//獲得該項的絕對路徑
			wchar_t n_src_path[BUF_SIZE];
			wcscpy(n_src_path, src_path);
			n_src_path[wcslen(n_src_path) - 1] = '\0';//刪除最后一位的'*'
			wcscat(n_src_path, find_data.cFileName);//添加文件名稱以獲取絕對路徑
			
			//要復制到的文件的絕對路徑
			wchar_t n_dst_path[BUF_SIZE];
			wcscpy(n_dst_path, dst_path);
			wcscat(n_dst_path, L"\\");
			wcscat(n_dst_path, find_data.cFileName);

			copyFile(n_src_path, n_dst_path);
		}

	} while (FindNextFile(h_find, &find_data));

	//目標文件夾已存在,這里實際上只是修改目的權限與源權限一致
	copyDir(src_path, dst_path);

	return;
}


//復制文件
void copyFile(const wchar_t* src_file, const wchar_t* dst_file)
{
	//打開源文件
	HANDLE h_src = ::CreateFile(
			src_file, 
			GENERIC_READ, 
			0, 
			NULL, 
			OPEN_EXISTING,
		    FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS, 
			NULL);
	//創建目標文件
	HANDLE h_dst = ::CreateFile(
			dst_file,
			GENERIC_WRITE | GENERIC_READ,
			0,
			NULL,
			CREATE_ALWAYS,
			FILE_ATTRIBUTE_NORMAL,
			NULL);

	if (h_src == INVALID_HANDLE_VALUE || h_dst == INVALID_HANDLE_VALUE)
	{
		printf("Open file error!\n");
		CloseHandle(h_src);
		CloseHandle(h_dst);
		exit(-1);
	}

	DWORD all_bytes = GetFileSize(h_src, NULL);
	char* buffer = new char[all_bytes + 1];
	ZeroMemory(buffer, all_bytes + 1);

	//復制文件
	DWORD bytes = 0;
	ReadFile(h_src, buffer, all_bytes, &bytes, NULL);
	WriteFile(h_dst, buffer, all_bytes, &bytes, NULL);

	//修改文件屬性
	changeAttr(src_file, dst_file, h_src, h_dst);

	CloseHandle(h_src);
	CloseHandle(h_dst);
	delete[] buffer;
	return;
}

//復制文件夾
void copyDir(const wchar_t* src_dir_name, const wchar_t* dst_dir)
{
	//獲得源目錄路徑
	wchar_t* src_dir = new wchar_t[BUF_SIZE];
	wcscpy(src_dir, src_dir_name);
	src_dir[wcslen(src_dir) - 2] = '\0';

	//創建文件夾
	_wmkdir(dst_dir);

	//打開源文件夾
	HANDLE h_src = CreateFile(
		src_dir,
		GENERIC_READ,
		FILE_SHARE_READ | FILE_SHARE_DELETE,
		NULL,
		OPEN_EXISTING,
		FILE_FLAG_BACKUP_SEMANTICS,
		NULL
	);
	if (h_src == INVALID_HANDLE_VALUE)
	{
		printf("Open src directory error!\n");
		CloseHandle(h_src);
		exit(-1);
	}

	//打開目標文件夾
	HANDLE h_dst = CreateFile(
		dst_dir,
		GENERIC_READ | GENERIC_WRITE,
		FILE_SHARE_READ | FILE_SHARE_DELETE,
		NULL,
		OPEN_EXISTING,
		FILE_FLAG_BACKUP_SEMANTICS,
		NULL
	);
	if (h_dst == INVALID_HANDLE_VALUE)
	{
		printf("Open dst directory error!\n");
		CloseHandle(h_dst);
		exit(-1);
	}
	//修改目標文件夾屬性與源文件夾屬性一致
	changeAttr(src_dir, dst_dir, h_src, h_dst);

	CloseHandle(h_src);
	CloseHandle(h_dst);
	return;
}


//修改文件屬性與源文件保持一致
void changeAttr(const wchar_t* src_name, const wchar_t* dst_name, HANDLE h_src, HANDLE h_dst)
{
	//修改文件屬性
	DWORD attr = GetFileAttributes(src_name);
	SetFileAttributes(dst_name, attr);

	//修改訪問、修改時間
	FILETIME t_create, t_access, t_write;
	SYSTEMTIME syst_create, syst_access, syst_write;
	GetFileTime(h_src, &t_create, &t_access, &t_write);
	SetFileTime(h_dst, &t_create, &t_access, &t_write);
}



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM