Files
c-utils/fileop.cpp
2025-07-27 10:23:33 +08:00

711 lines
19 KiB
C++

#if HAVE_UTILS_CONFIG_H
#include "utils_config.h"
#endif
#include "fileop.h"
#include <sys/types.h>
#include <sys/stat.h>
#ifdef _WIN32
#include <Windows.h>
#include <io.h>
#include <direct.h>
#include <fileapi.h>
#else
#include <dirent.h>
#include <unistd.h>
#include <utime.h>
#endif
#include <fcntl.h>
#include <ctype.h>
#include "err.h"
#include "str_util.h"
#include "wchar_util.h"
#include "time_util.h"
#include <regex>
#include <list>
#ifdef _WIN32
#if HAVE__ACCESS_S
#define access _access_s
#endif
#if HAVE__WACCESS_S
#define _waccess _waccess_s
#endif
#if HAVE_PRINTF_S
#define printf printf_s
#endif
#if HAVE_SSCANF_S
#define sscanf sscanf_s
#endif
#endif
#ifdef _WIN32
bool exists_internal(wchar_t* fn) {
return !_waccess(fn, 0);
}
FILE* fopen_internal(wchar_t* fn, const wchar_t* mode) {
return _wfopen(fn, mode);
}
bool remove_internal(wchar_t* fn, bool print_error) {
int ret = _wremove(fn);
if (ret && print_error && errno != ENOENT) {
std::string o;
std::string tfn;
if (err::get_errno_message(o, errno) && wchar_util::wstr_to_str(tfn, fn, CP_UTF8)) {
printf("Can not remove file \"%s\": %s.\n", tfn.c_str(), o.c_str());
}
}
return !ret;
}
int open_internal(wchar_t* fn, int* fd, int oflag, int shflag, int pmode) {
return _wsopen_s(fd, fn, oflag, shflag, pmode);
}
bool isdir_internal(wchar_t* fn, bool& result) {
#if __MINGW32__
struct _stat stats;
if (_wstat(fn, &stats)) {
#else
struct __stat64 stats;
if (_wstat64(fn, &stats)) {
#endif
return false;
}
result = stats.st_mode & S_IFDIR;
return true;
}
bool mkdir_internal(wchar_t* fn) {
return !_wmkdir(fn);
}
HANDLE set_file_time_internal(wchar_t* fn) {
return CreateFileW(fn, FILE_WRITE_ATTRIBUTES, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
}
HANDLE get_file_size_internal(wchar_t* fn) {
return CreateFileW(fn, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
}
bool listdir_internal(wchar_t* fn, std::list<std::string>& list, bool ignore_hidden_file) {
std::list<std::string> li;
WIN32_FIND_DATAW data;
HANDLE re = FindFirstFileW(fn, &data);
if (re == INVALID_HANDLE_VALUE) return false;
std::string tfn;
do {
if (!wchar_util::wstr_to_str(tfn, data.cFileName, CP_UTF8)) {
FindClose(re);
return false;
}
if ((ignore_hidden_file && tfn.find(".") != 0) || (!ignore_hidden_file && tfn != "." && tfn != "..")) li.push_back(tfn);
BOOL r = FindNextFileW(re, &data);
if (!r) {
FindClose(re);
if (GetLastError() == ERROR_NO_MORE_FILES) break;
return false;
}
} while (1);
list = li;
return true;
}
template <typename T, typename ... Args>
T fileop_internal(const char* fname, UINT codePage, T(*callback)(wchar_t* fn, Args... args), T failed, Args... args) {
int wlen;
wchar_t* fn;
DWORD opt = wchar_util::getMultiByteToWideCharOptions(MB_ERR_INVALID_CHARS, codePage);
wlen = MultiByteToWideChar(codePage, opt, fname, -1, nullptr, 0);
if (!wlen) return failed;
fn = (wchar_t*)malloc(sizeof(wchar_t) * wlen);
if (!MultiByteToWideChar(codePage, opt, fname, -1, fn, wlen)) {
free(fn);
return failed;
}
T re = callback(fn, args...);
free(fn);
return re;
}
#endif
bool fileop::exists(std::string fn) {
#if _WIN32
UINT cp[] = { CP_UTF8, CP_OEMCP, CP_ACP };
int i;
for (i = 0; i < 3; i++) {
if (fileop_internal(fn.c_str(), cp[i], &exists_internal, false)) return true;
}
return !access(fn.c_str(), 0);
#else
return !access(fn.c_str(), 0);
#endif
}
bool fileop::remove(std::string fn, bool print_error) {
#if _WIN32
UINT cp[] = { CP_UTF8, CP_OEMCP, CP_ACP };
int i;
for (i = 0; i < 3; i++) {
if (fileop_internal(fn.c_str(), cp[i], &remove_internal, false, print_error)) return true;
}
#endif
int ret = ::remove(fn.c_str());
if (ret && print_error) {
std::string o;
if (err::get_errno_message(o, errno)) {
printf("Can not remove file \"%s\": %s.\n", fn.c_str(), o.c_str());
}
}
return !ret;
}
std::string fileop::dirname(std::string fn) {
auto i = fn.find_last_of('/');
auto i2 = fn.find_last_of('\\');
i = (i == std::string::npos || (i2 != std::string::npos && i2 > i)) ? i2 : i;
return i == std::string::npos ? "" : fn.substr(0, i);
}
bool fileop::is_url(std::string fn) {
return fn.find("://") != std::string::npos;
}
std::string fileop::basename(std::string fn) {
if (!is_url(fn)) {
auto i = fn.find_last_of('/');
auto i2 = fn.find_last_of('\\');
i = (i == std::string::npos || (i2 != std::string::npos && i2 > i)) ? i2 : i;
return i == std::string::npos ? fn : fn.substr(i + 1, fn.length() - i - 1);
} else {
auto iq = fn.find_first_of('?');
auto iq2 = fn.find_first_of('#');
iq = (iq == std::string::npos || (iq2 != std::string::npos && iq2 < iq)) ? iq2 : iq;
auto i = fn.find_last_of('/', iq);
auto i2 = fn.find_last_of('\\', iq);
i = (i == std::string::npos || (i2 != std::string::npos && i2 > i)) ? i2 : i;
if (i == std::string::npos && iq == std::string::npos) {
return fn;
} else if (i == std::string::npos) {
return fn.substr(0, iq);
} else if (iq == std::string::npos) {
return fn.substr(i + 1, fn.length() - i - 1);
} else {
return fn.substr(i + 1, iq - i - 1);
}
}
}
bool fileop::parse_size(std::string size, size_t& fs, bool is_byte) {
const std::regex REG(R"(^(\d+)([kmgtpezy])?(i)?(b)?$)", std::regex::icase);
std::smatch m;
if (!std::regex_search(size, m, REG)) {
return false;
}
auto bs = m[1].str();
size_t base;
if (sscanf(bs.c_str(), "%zu", &base) != 1) {
return false;
}
size_t pow = 0;
if (m[2].matched) {
auto sp = std::tolower(m[2].str()[0]);
if (sp == 'k') pow = 1;
else if (sp == 'm') pow = 2;
else if (sp == 'g') pow = 3;
else if (sp == 't') pow = 4;
else if (sp == 'p') pow = 5;
else if (sp == 'e') pow = 6;
else if (sp == 'z') pow = 7;
else if (sp == 'y') pow = 8;
}
if (m[3].matched) {
pow = 1ull << (pow * 10);
} else {
size_t tmp = 1;
while (pow > 0) {
tmp *= 1000ull;
pow--;
}
pow = tmp;
}
if (m[4].matched) {
auto b = m[4].str();
if (b == "b") {
fs = is_byte ? base * pow / 8ull : base * pow;
} else {
fs = is_byte ? base * pow : base * pow * 8ull;
}
} else {
fs = base * pow;
}
return true;
}
int fileop::open(std::string fn, int& fd, int oflag, int shflag, int pmode) {
#if _WIN32
UINT cp[] = { CP_UTF8, CP_OEMCP, CP_ACP };
int i;
int tmfd;
int err;
for (i = 0; i < 3; i++) {
if (!fileop_internal(fn.c_str(), cp[i], &open_internal, EINVAL, &tmfd, oflag, shflag, pmode)) {
fd = tmfd;
return 0;
}
}
err = ::_sopen_s(&tmfd, fn.c_str(), oflag, shflag, pmode);
fd = tmfd;
return fd == -1 ? err : 0;
#else
fd = ::open(fn.c_str(), oflag, pmode);
return fd == -1 ? errno : 0;
#endif
}
bool fileop::isabs(std::string path) {
if (!path.length()) return false;
#if _WIN32
if (path.length() <= 2) return false;
if (isalpha(path[0]) && path[1] == ':' && (path[2] == '/' || path[2] == '\\')) return true; else return false;
#else
return path[0] == '/' ? true : false;
#endif
}
std::string fileop::join(std::string path, std::string path2) {
auto l1 = path.length(), l2 = path2.length();
if (!l1) return path2;
if (!l2) return path;
if (isabs(path2)) return path2;
#if _WIN32
if (l2 >= 2 && isalpha(path2[0]) && path2[1] == ':') return path2;
if (l1 >= 2 && isalpha(path[0]) && path[1] == ':') {
if (path2[0] == '/' || path2[0] == '\\') return path.substr(0, 2) + path2;
return (path[l1 - 1] == '/' || path[l1 - 1] == '\\') ? path + path2 : path + "\\" + path2;
}
if (path2[0] == '/' || path2[0] == '\\') return path2;
return (path[l1 - 1] == '/' || path[l1 - 1] == '\\') ? path + path2 : path + "\\" + path2;
#else
return path[l1 - 1] == '/' ? path + path2 : path + "/" + path2;
#endif
}
std::string fileop::join(std::initializer_list<std::string> paths) {
std::string path;
for (auto it = paths.begin(); it != paths.end(); it++) {
if (it == paths.begin()) {
path = *it;
} else {
path = join(path, *it);
}
}
return path;
}
bool fileop::isdir(std::string path, bool& result) {
if (!exists(path)) {
result = false;
return true;
}
#if _WIN32
UINT cp[] = { CP_UTF8, CP_OEMCP, CP_ACP };
int i;
for (i = 0; i < 3; i++) {
if (fileop_internal<bool, bool&>(path.c_str(), cp[i], &isdir_internal, false, result)) return true;
}
struct __stat64 stats;
if (_stat64(path.c_str(), &stats)) {
#else
struct stat stats;
if (stat(path.c_str(), &stats)) {
#endif
return false;
}
result = stats.st_mode & S_IFDIR;
return true;
}
#if _WIN32
bool fileop::isdrive(std::string path) {
auto l = path.length();
if (l == 2 && isalpha(path[0]) && path[1] == ':') return true;
if (l == 3 && isalpha(path[0]) && path[1] == ':' && (path[2] == '/' || path[2] == '\\')) return true;
return false;
}
#endif
bool fileop::mkdir(std::string path, int mode) {
#if _WIN32
UINT cp[] = { CP_UTF8, CP_OEMCP, CP_ACP };
int i;
for (i = 0; i < 3; i++) {
if (fileop_internal(path.c_str(), cp[i], &mkdir_internal, false)) return true;
}
return !::_mkdir(path.c_str());
#else
return !::mkdir(path.c_str(), mode);
#endif
}
bool fileop::set_file_time(std::string path, time_t ctime, time_t actime, time_t modtime) {
#if _WIN32
UINT cp[] = { CP_UTF8, CP_OEMCP, CP_ACP };
int i;
HANDLE file;
for (i = 0; i < 3; i++) {
if ((file = fileop_internal(path.c_str(), cp[i], &set_file_time_internal, INVALID_HANDLE_VALUE)) != INVALID_HANDLE_VALUE) {
break;
}
}
if (file == INVALID_HANDLE_VALUE) {
file = CreateFileA(path.c_str(), FILE_WRITE_ATTRIBUTES, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
if (file == INVALID_HANDLE_VALUE) return false;
}
FILETIME c, ac, mod;
time_util::time_t_to_file_time(ctime, &c);
time_util::time_t_to_file_time(actime, &ac);
time_util::time_t_to_file_time(modtime, &mod);
auto re = SetFileTime(file, &c, &ac, &mod);
CloseHandle(file);
return re ? true : false;
#else
struct utimbuf t = { actime, modtime };
return !utime(path.c_str(), &t);
#endif
}
FILE* fileop::fdopen(int fd, std::string mode) {
#if _WIN32
return ::_fdopen(fd, mode.c_str());
#else
return ::fdopen(fd, mode.c_str());
#endif
}
bool fileop::close(int fd) {
#if _WIN32
return !::_close(fd);
#else
return !::close(fd);
#endif
}
bool fileop::fclose(FILE* f) {
if (!f) return false;
return !::fclose(f);
}
bool fileop::mkdirs(std::string path, int mode, bool allow_exists) {
bool exists;
if (!isdir(path, exists)) return false;
if (exists) return allow_exists ? true : false;
std::string dn;
#if _WIN32
std::string sp = "\\";
#else
std::string sp = "/";
#endif
dn = path;
if (!dn.length() || dn == ".") dn = "." + sp;
if (!isdir(dn, exists)) return false;
if (exists) return allow_exists ? true : false;
std::list<std::string> li;
li.push_back(dn);
do {
dn = dirname(dn);
if (dn.length() == 0) {
if (li.size() > 0) {
auto en = *(li.rbegin());
if (en == ("." + sp)) return false;
}
dn = ".";
}
if (!isdir(dn + sp, exists)) return false;
if (!exists) li.push_back(dn + sp);
} while (!exists);
auto it = li.rbegin();
for (; it != li.rend(); it++) {
if (!mkdir(*it, mode)) return false;
}
return true;
}
bool fileop::get_file_size(std::string path, size_t& size) {
#if _WIN32
UINT cp[] = { CP_UTF8, CP_OEMCP, CP_ACP };
int i;
HANDLE file;
for (i = 0; i < 3; i++) {
if ((file = fileop_internal(path.c_str(), cp[i], &get_file_size_internal, INVALID_HANDLE_VALUE)) != INVALID_HANDLE_VALUE) {
break;
}
}
if (file == INVALID_HANDLE_VALUE) {
file = CreateFileA(path.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
if (file == INVALID_HANDLE_VALUE) return false;
}
LARGE_INTEGER si;
auto re = GetFileSizeEx(file, &si);
if (re) size = si.QuadPart;
CloseHandle(file);
return re ? true : false;
#else
struct stat stats;
if (stat(path.c_str(), &stats)) {
return false;
}
size = stats.st_size;
return true;
#endif
}
int fileop::fseek(FILE* f, int64_t offset, int origin) {
#if _WIN32
return ::_fseeki64(f, offset, origin);
#else
#if HAVE_FSEEKO64
return ::fseeko64(f, offset, origin);
#elif HAVE_FSEEKO
return ::fseeko(f, offset, origin);
#else
return ::fseek(f, offset, origin);
#endif
#endif
}
bool fileop::mkdir_for_file(std::string path, int mode) {
return mkdirs(dirname(path), mode, true);
}
int64_t fileop::ftell(FILE* f) {
#if _WIN32
return ::_ftelli64(f);
#else
#if HAVE_FTELLO64
return ::ftello64(f);
#elif HAVE_FTELLO
return ::ftello(f);
#else
return ::ftell(f);
#endif
#endif
}
bool fileop::listdir(std::string path, std::list<std::string>& filelist, bool ignore_hidden_file) {
#if _WIN32
if (!path.length()) path = ".";
path = str_util::str_replace(path, "/", "\\");
path = join(path, "*");
UINT cp[] = { CP_UTF8, CP_OEMCP, CP_ACP };
int i;
std::list<std::string> li;
for (i = 0; i < 3; i++) {
if (fileop_internal<bool, std::list<std::string>&, bool>(path.c_str(), cp[i], &listdir_internal, false, li, ignore_hidden_file)) {
filelist = li;
return true;
}
}
WIN32_FIND_DATAA data;
HANDLE re = FindFirstFileA(path.c_str(), &data);
if (re == INVALID_HANDLE_VALUE) return false;
std::string tfn;
do {
tfn = data.cFileName;
if ((ignore_hidden_file && tfn.find(".") != 0) || (!ignore_hidden_file && tfn != "." && tfn != "..")) li.push_back(tfn);
BOOL r = FindNextFileA(re, &data);
if (!r) {
FindClose(re);
if (GetLastError() == ERROR_NO_MORE_FILES) break;
return false;
}
} while (1);
filelist = li;
return true;
#else
DIR* dir = opendir(path.c_str());
if (!dir) return false;
struct dirent* d = readdir(dir);
std::list<std::string> li;
std::string tfn;
while (d) {
tfn = d->d_name;
if ((ignore_hidden_file && tfn.find(".") != 0) || (!ignore_hidden_file && tfn != "." && tfn != "..")) li.push_back(tfn);
d = readdir(dir);
}
if (errno) {
closedir(dir);
return false;
}
closedir(dir);
filelist = li;
return true;
#endif
}
std::string fileop::filename(std::string path) {
auto loc = path.find_last_of('.');
return loc == -1 ? path : path.substr(0, loc);
}
int fileop::fcloseall() {
#if _WIN32
return ::_fcloseall();
#elif HAVE_FCLOSEALL
return ::fcloseall();
#else
return 0;
#endif
}
std::string fileop::relpath(std::string path, std::string start) {
// 如果 start 为空,使用当前目录
if (start.empty()) {
start = ".";
}
// 将 Windows 风格的路径分隔符统一转换为 '/'
auto normalize_path = [](std::string& p) {
std::string result = p;
for (auto& c : result) {
if (c == '\\') c = '/';
}
// 确保路径末尾没有斜杠
if (!result.empty() && result.back() == '/') {
result.pop_back();
}
return result;
};
std::string norm_path = normalize_path(path);
std::string norm_start = normalize_path(start);
// 如果两个路径相同,返回当前目录
if (norm_path == norm_start) {
return ".";
}
// 如果 path 是绝对路径但 start 不是,或者反之,无法计算相对路径
#ifdef _WIN32
bool path_is_abs = isabs(path);
bool start_is_abs = isabs(start);
// 在 Windows 上,还要检查驱动器号是否相同
if (path_is_abs && start_is_abs) {
if (norm_path.length() >= 2 && norm_start.length() >= 2) {
// 如果驱动器号不同,无法计算相对路径
if (tolower(norm_path[0]) != tolower(norm_start[0]) || norm_path[1] != ':') {
return path; // 返回原路径
}
}
}
#else
bool path_is_abs = !norm_path.empty() && norm_path[0] == '/';
bool start_is_abs = !norm_start.empty() && norm_start[0] == '/';
#endif
if (path_is_abs != start_is_abs) {
return path; // 无法计算相对路径,返回原路径
}
// 分割路径为组件
auto split_path = [](const std::string& p) {
std::vector<std::string> components;
std::string::size_type start = 0;
std::string::size_type end = 0;
while ((end = p.find('/', start)) != std::string::npos) {
if (end != start) {
components.push_back(p.substr(start, end - start));
}
start = end + 1;
}
if (start < p.length()) {
components.push_back(p.substr(start));
}
return components;
};
auto path_components = split_path(norm_path);
auto start_components = split_path(norm_start);
// 找到公共前缀
size_t i = 0;
while (i < path_components.size() && i < start_components.size()) {
#ifdef _WIN32
// Windows 路径不区分大小写
if (str_util::tolower(path_components[i]) != str_util::tolower(start_components[i])) {
break;
}
#else
if (path_components[i] != start_components[i]) {
break;
}
#endif
i++;
}
// 构建相对路径
std::string result;
// 添加 ".." 以表示上级目录
for (size_t j = i; j < start_components.size(); j++) {
if (!result.empty()) {
result += "/";
}
result += "..";
}
// 添加目标路径的非共享部分
for (size_t j = i; j < path_components.size(); j++) {
if (!result.empty()) {
result += "/";
}
result += path_components[j];
}
// 如果结果为空,表示当前目录
if (result.empty()) {
return ".";
}
#ifdef _WIN32
// 在 Windows 上,将斜杠转换回反斜杠
for (auto& c : result) {
if (c == '/') c = '\\';
}
#endif
return result;
}
FILE* fileop::fopen(std::string path, std::string mode) {
#if _WIN32
std::wstring wMode;
if (wchar_util::str_to_wstr(wMode, mode, CP_UTF8)) {
UINT cp[] = { CP_UTF8, CP_OEMCP, CP_ACP };
int i;
for (i = 0; i < 3; i++) {
auto f = fileop_internal<FILE*>(path.c_str(), cp[i], &fopen_internal, nullptr, wMode.c_str());
if (f) return f;
}
}
return ::fopen(path.c_str(), mode.c_str());
#else
return ::fopen(path.c_str(), mode.c_str());
#endif
}
std::string fileop::extname(std::string path) {
auto loc = path.find_last_of('.');
if (loc == std::string::npos) {
return "";
}
return path.substr(loc + 1, path.length() - loc - 1);
}