C++文件系统操作6 - 跨平台实现文件和文件夹的拷贝
- 1. 关键词
- 2. fileutil.h
- 3. fileutil.cpp
- 4. filesystem_win.h
- 5. filesystem_win.cpp
- 6. filesystem_unix.cpp
- 7. 源码地址
1. 关键词
C++ 文件系统操作 拷贝文件 拷贝文件夹 跨平台
2. fileutil.h
#pragma once
#include <string>
#include <cstdio>
#include <cstdint>
#include "filetype.h"
#include "filepath.h"
namespace cutl
{
/**
* @brief The file guard class to manage the FILE pointer automatically.
* file_guard object can close the FILE pointer automatically when his scope is exit.
*/
class file_guard
{
public:
/**
* @brief Construct a new file guard object
*
* @param file the pointer of the FILE object
*/
explicit file_guard(FILE *file);
/**
* @brief Destroy the file guard object
*
*/
~file_guard();
/**
* @brief Get the FILE pointer.
*
* @return FILE*
*/
FILE *getfd() const;
private:
FILE *file_;
};
/**
* @brief Copy a file or symbolic link(shortcut on windows).
*
* @param srcpath the filepath of the source file or symbolic link to be copied
* @param dstpath the filepath of the destination file or symbolic link to be copied to
* @param attributes whether to copy the file attributes, default is false.
* If true, means copy the file attributes, like the 'cp -p' command.
* @return true if the file or symbolic link is copied successfully, false otherwise.
* @note If the destination file or directory already exists, it will be overwritten.
* @return false
*/
bool copyfile(const filepath &srcpath, const filepath &dstpath, bool attributes = false);
// copy directory recursively
/**
* @brief Copy a directory recursively.
*
* @param srcdir the filepath of the source directory to be copied
* @param dstdir the filepath of the destination directory to be copied to
* @return true if the directory is copied successfully, false otherwise.
* @note If the destination directory already exists, it will be overwritten.
*/
bool copydir(const filepath &srcdir, const filepath &dstdir);
} // namespace cutl
3. fileutil.cpp
#include <cstdio>
#include <map>
#include <iostream>
#include <cstring>
#include <sys/stat.h>
#include "fileutil.h"
#include "inner/logger.h"
#include "inner/filesystem.h"
#include "strutil.h"
namespace cutl
{
file_guard::file_guard(FILE *file)
: file_(file)
{
}
file_guard::~file_guard()
{
if (file_)
{
// CUTL_DEBUG("close file");
int ret = fclose(file_);
if (ret != 0)
{
CUTL_ERROR("fail to close file, ret" + std::to_string(ret));
}
file_ = nullptr;
}
// ROBOLOG_DCHECK(file_ == nullptr);
}
FILE *file_guard::getfd() const
{
return file_;
}
bool copyfile(const filepath &srcpath, const filepath &dstpath, bool attributes)
{
// CUTL_INFO("file type: " + std::to_string(srcpath.type()) + ", " + filetype_flag(srcpath.type()) + ", " + srcpath.str() + ", dstpath:" + dstpath.str());
// copy file content
if (srcpath.isfile())
{
if (dstpath.exists())
{
// remove if exists
removefile(dstpath);
}
file_guard frd(fopen(srcpath.str().c_str(), "rb"));
if (frd.getfd() == nullptr)
{
CUTL_ERROR("open file failed, " + srcpath.str());
return false;
}
file_guard fwt(fopen(dstpath.str().c_str(), "wb"));
if (fwt.getfd() == nullptr)
{
CUTL_ERROR("open file failed, " + dstpath.str());
return false;
}
static constexpr size_t buf_size = 8 * 1024;
uint8_t buffer[buf_size] = {0};
size_t read_len = 0;
size_t write_len = 0;
while ((read_len = fread(buffer, 1, buf_size, frd.getfd())) > 0)
{
write_len = fwrite(buffer, 1, read_len, fwt.getfd());
if (write_len != read_len)
{
CUTL_ERROR("write file failed, only write " + std::to_string(write_len) + ", read_len:" + std::to_string(read_len));
return false;
}
}
// flush file to disk
int ret = fflush(fwt.getfd());
if (0 != ret)
{
CUTL_ERROR("fail to flush file:" + dstpath.str());
return false;
}
if (!file_sync(fwt.getfd()))
{
CUTL_ERROR("file_sync failed for " + dstpath.str());
return false;
}
}
else if (srcpath.issymlink())
{
if (dstpath.exists())
{
// remove if exists
file_removelink(dstpath.str());
}
auto link_path = file_readlink(srcpath.str());
if (link_path.empty())
{
CUTL_ERROR("readlink failed for " + srcpath.str());
return false;
}
if (!file_createlink(link_path, dstpath.str()))
{
CUTL_ERROR("createlink failed for " + dstpath.str());
return false;
}
}
else
{
CUTL_ERROR("not a file or symlink, cannot copy: [" + filetype_flag(srcpath.type()) + "]" + srcpath.str());
return false;
}
// copy file attributes
if (attributes && srcpath.isfile())
{
return copy_attributes(srcpath.str(), dstpath.str());
}
return true;
}
// https://www.cnblogs.com/harrypotterjackson/p/12113382.html
bool copydir(const filepath &srcdir, const filepath &dstdir)
{
if (!srcdir.isdir())
{
CUTL_ERROR("srcdir is not a directory: " + srcdir.str());
return false;
}
if (!dstdir.exists() && !createdir(dstdir, true))
{
CUTL_ERROR("createdir failed for " + dstdir.str());
return false;
}
auto filelist = list_files(srcdir, filetype::all, true);
for (size_t i = 0; i < filelist.size(); i++)
{
auto file = filelist[i];
auto src_file = file.filepath;
auto reletive_path = src_file.substr(srcdir.str().length() + 1);
auto dstpath = dstdir.join(reletive_path);
auto srcpath = cutl::path(src_file);
if (file.type == filetype::file || file.type == filetype::symlink)
{
if (!copyfile(srcpath, dstpath, true))
{
return false;
}
}
else if (file.type == filetype::directory)
{
if (!createdir(dstpath, true))
{
return false;
}
if (!copy_attributes(src_file, dstpath.str(), true))
{
return false;
}
}
else
{
CUTL_WARN("the file cannot be copy: [" + filetype_flag(srcpath.type()) + "]" + srcpath.str());
continue;
}
}
return true;
}
4. filesystem_win.h
#include <vector>
#include <string>
#pragma once
namespace cutl
{
// 拷贝文件/文件夹的属性,isdir参数只在windows下生效,Unix-like系统下不起作用
bool copy_attributes(const std::string &srcpath, const std::string &dstpath, bool isdir = false);
} // namespace cutl
5. filesystem_win.cpp
#if defined(_WIN32) || defined(__WIN32__)
#include <io.h>
#include <direct.h>
#include <Windows.h>
#include <stdlib.h>
#include "strutil.h"
#include "filesystem.h"
#include "logger.h"
namespace cutl
{
bool copy_attributes(const std::string &srcpath, const std::string &dstpath, bool isdir)
{
// 获取 修改访问、修改时间
FILETIME t_create, t_access, t_write;
HANDLE h_src = NULL;
if (isdir)
{
// 文件夹
h_src = CreateFile(
srcpath.c_str(),
GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS,
NULL);
}
else
{
h_src = CreateFileA(
srcpath.c_str(),
GENERIC_READ,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_BACKUP_SEMANTICS,
NULL);
}
if (h_src == INVALID_HANDLE_VALUE)
{
CUTL_ERROR("Failed to open file " + srcpath + ", error code: " + std::to_string(GetLastError()));
CloseHandle(h_src);
return false;
}
if (!GetFileTime(h_src, &t_create, &t_access, &t_write))
{
CUTL_ERROR("Failed to get file times for " + srcpath + ", error code: " + std::to_string(GetLastError()));
CloseHandle(h_src);
return false;
}
CloseHandle(h_src);
// 设置 修改访问、修改时间
HANDLE h_dst = NULL;
if (isdir)
{
h_dst = CreateFile(
dstpath.c_str(),
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS,
NULL);
}
else
{
h_dst = ::CreateFileA(
dstpath.c_str(),
// GENERIC_WRITE,
// GENERIC_READ | GENERIC_WRITE,
GENERIC_ALL,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
}
if (h_dst == INVALID_HANDLE_VALUE)
{
CUTL_ERROR("Failed to open file " + dstpath + ", error code: " + std::to_string(GetLastError()));
CloseHandle(h_dst);
return false;
}
if (!SetFileTime(h_dst, &t_create, &t_access, &t_write))
{
CUTL_ERROR("Failed to set file times for " + dstpath + ", error code: " + std::to_string(GetLastError()));
CloseHandle(h_dst);
return false;
}
CloseHandle(h_dst);
// 修改文件属性
// 注意:文件访问权限的属性设置要放在文件时间的修改后面,因为只读权限的文件不允许修改时间
DWORD attributes = GetFileAttributesA(srcpath.c_str());
if (attributes == INVALID_FILE_ATTRIBUTES)
{
CUTL_ERROR("Failed to get file attributes for " + srcpath + ", error code: " + std::to_string(GetLastError()));
return false;
}
if (!SetFileAttributesA(dstpath.c_str(), attributes))
{
CUTL_ERROR("Failed to set file attributes for " + dstpath + ", error code: " + std::to_string(GetLastError()));
return false;
}
return true;
}
} // namespace cutl
#endif // defined(_WIN32) || defined(__WIN32__)
6. filesystem_unix.cpp
#if defined(_WIN32) || defined(__WIN32__)
// do nothing
#else
#include <unistd.h>
#include <sys/stat.h>
#include <dirent.h>
#include <stack>
#include <cstring>
#include <utime.h>
#include <stdlib.h>
#include <sys/time.h>
#include "filesystem.h"
#include "inner/logger.h"
namespace cutl
{
bool copy_attributes(const std::string &srcpath, const std::string &dstpath, bool isdir)
{
struct stat attr_of_src;
int ret = lstat(srcpath.c_str(), &attr_of_src);
if (ret != 0)
{
CUTL_ERROR("lstat error. srcpath:" + srcpath + ", error:" + strerror(errno));
return false;
}
// 修改文件属性
ret = chmod(dstpath.c_str(), attr_of_src.st_mode);
if (ret != 0)
{
CUTL_ERROR("chmod error. dstpath:" + dstpath + ", error:" + strerror(errno));
return false;
}
// 修改文件用户组
ret = chown(dstpath.c_str(), attr_of_src.st_uid, attr_of_src.st_gid);
if (ret != 0)
{
CUTL_ERROR("chown error. dstpath:" + dstpath + ", error:" + strerror(errno));
return false;
}
// 修改文件访问、修改时间
if (S_ISLNK(attr_of_src.st_mode))
{
// TODO: 编译还有问题,需要确定编译宏
// 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;
// ret = lutimes(dstpath.c_str(), time_buf);
// if (ret != 0)
// {
// CUTL_ERROR("lutimes error. dstpath:" + dstpath + ", error:" + strerror(errno));
// return false;
// }
}
else
{
struct utimbuf tbuf;
tbuf.actime = attr_of_src.st_atime;
tbuf.modtime = attr_of_src.st_mtime;
ret = utime(dstpath.c_str(), &tbuf);
if (ret != 0)
{
CUTL_ERROR("utime error. dstpath:" + dstpath + ", error:" + strerror(errno));
return false;
}
}
return true;
}
} // namespace cutl
#endif // defined(_WIN32) || defined(__WIN32__)
7. 源码地址
更多详细代码,请查看本人写的C++ 通用工具库: common_util, 本项目已开源,代码简洁,且有详细的文档和Demo。
【SunLogging】
扫码二维码,关注微信公众号,阅读更多精彩内容