489 lines
14 KiB
C
489 lines
14 KiB
C
#include <c_File.h>
|
|
#ifdef _WIN32
|
|
#include <windows.h>
|
|
#include <io.h> // 提供 _get_osfhandle
|
|
#include <direct.h>
|
|
#include <dirent.h>
|
|
#define C_ACCESS(path) _access(path, 0)
|
|
#define C_MAKE_DIR(path) _mkdir(path) // Windows 下创建目录
|
|
#define sys_rmdir(path) _rmdir(path)
|
|
#define PATH_SEP '\\'
|
|
#else
|
|
#include <unistd.h> // 提供 fsync
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#define C_ACCESS(path) access(path, F_OK)
|
|
#define C_MAKE_DIR(path) mkdir(path, 0755)
|
|
#define sys_rmdir(path) rmdir(path)
|
|
#define PATH_SEP '/'
|
|
#endif
|
|
|
|
/* ------------------------------------------------------------------------------------------------------------------ */
|
|
/* */
|
|
|
|
|
|
#if defined(C_PLATFORM_IS_64BIT)
|
|
|
|
// 跨平台 64 位 fseek 封装
|
|
C_STATIC_FORCE_INLINE
|
|
int fn_fseek(FILE* fp, long long offset, int whence) {
|
|
#ifdef _WIN32
|
|
return _fseeki64(fp, (__int64)offset, whence);
|
|
#else
|
|
// 在定义了 _FILE_OFFSET_BITS=64 的情况下,fseeko 接收 64 位的 off_t
|
|
return fseeko(fp, (off_t)offset, whence);
|
|
#endif
|
|
}
|
|
|
|
// 跨平台 64 位 ftell 封装
|
|
C_STATIC_FORCE_INLINE
|
|
long long fn_tell(FILE* fp) {
|
|
#ifdef _WIN32
|
|
return (long long)_ftelli64(fp);
|
|
#else
|
|
return (long long)ftello(fp);
|
|
#endif
|
|
}
|
|
|
|
#elif defined(C_PLATFORM_IS_32BIT)
|
|
|
|
C_STATIC_FORCE_INLINE
|
|
int fn_fseek(FILE* fp, long long offset, int whence) {
|
|
return fseek(fp, (long)offset, whence);
|
|
}
|
|
|
|
C_STATIC_FORCE_INLINE
|
|
long long fn_tell(FILE* fp) {
|
|
return (long long)ftell(fp);
|
|
}
|
|
|
|
#endif
|
|
|
|
/* ------------------------------------------------------------------------------------------------------------------ */
|
|
/* */
|
|
|
|
|
|
c_err_t c_File_Open(c_File_t* file, const char* fileName, const char* mode) {
|
|
if (!file || !fileName || !mode) {
|
|
return C_ERR_FAIL;
|
|
}
|
|
|
|
file->fp = fopen(fileName, mode);
|
|
if (!file->fp) {
|
|
return C_ERR_FAIL;
|
|
}
|
|
|
|
return C_ERR_OK;
|
|
}
|
|
|
|
void c_File_Close(c_File_t* file) {
|
|
if (file && file->fp) {
|
|
fclose(file->fp);
|
|
file->fp = NULL;
|
|
}
|
|
}
|
|
|
|
c_err_t c_File_Read(c_File_t* file, void* buffer, c_size_t buffer_size, c_size_t *read_size) {
|
|
if (!file || !file->fp || !buffer || buffer_size == 0) {
|
|
if (read_size) *read_size = 0;
|
|
return C_ERR_FAIL;
|
|
}
|
|
|
|
// 从文件流中读取 1 字节 * buffer_size
|
|
size_t bytes_read = fread(buffer, 1, (size_t)buffer_size, file->fp);
|
|
|
|
if (read_size) {
|
|
*read_size = (c_size_t)bytes_read;
|
|
}
|
|
|
|
// 如果读取数量小于请求数量,需检查是出错还是到达文件末尾
|
|
if (bytes_read < (size_t)buffer_size) {
|
|
if (ferror(file->fp)) {
|
|
return C_ERR_FAIL; // 发生读取错误
|
|
}
|
|
}
|
|
|
|
return C_ERR_OK;
|
|
}
|
|
|
|
c_err_t c_File_Readline(c_File_t* file, void* buffer, c_size_t buffer_size, c_size_t *read_size) {
|
|
if (!file || !file->fp || !buffer || buffer_size == 0) {
|
|
if (read_size) *read_size = 0;
|
|
return C_ERR_FAIL;
|
|
}
|
|
|
|
// fgets 会自动在末尾加上 '\0',并且最多读取 buffer_size - 1 个字符
|
|
char* result = fgets((char*)buffer, (int)buffer_size, file->fp);
|
|
|
|
if (!result) {
|
|
if (read_size) *read_size = 0;
|
|
return C_ERR_FAIL; // 读取失败或到达文件末尾
|
|
}
|
|
|
|
if (read_size) {
|
|
*read_size = (c_size_t)strlen((char*)buffer);
|
|
}
|
|
|
|
return C_ERR_OK;
|
|
}
|
|
|
|
c_err_t c_File_Write(c_File_t* file, void* buffer, c_size_t buffer_size, c_size_t *write_size) {
|
|
if (!file || !file->fp || !buffer || buffer_size == 0) {
|
|
if (write_size) *write_size = 0;
|
|
return C_ERR_FAIL;
|
|
}
|
|
|
|
size_t bytes_written = fwrite(buffer, 1, (size_t)buffer_size, file->fp);
|
|
|
|
if (write_size) {
|
|
*write_size = (c_size_t)bytes_written;
|
|
}
|
|
|
|
// 如果实际写入字节数不等于预期,说明写入失败(如磁盘满)
|
|
if (bytes_written < (size_t)buffer_size) {
|
|
return C_ERR_FAIL;
|
|
}
|
|
|
|
return C_ERR_OK;
|
|
}
|
|
|
|
c_err_t c_File_Seek(c_File_t* file, long long position) {
|
|
if (!file || !file->fp) {
|
|
return C_ERR_PARAM;
|
|
}
|
|
const int result = fseek(file->fp, (long)position, SEEK_SET);
|
|
return (result == 0) ? C_ERR_OK : C_ERR_FAIL;
|
|
}
|
|
|
|
long long c_File_Size(c_File_t* file) {
|
|
if (!file || !file->fp) {
|
|
return 0;
|
|
}
|
|
|
|
// 1. 保存当前文件指针的位置
|
|
long long current_pos = fn_tell(file->fp);
|
|
if (current_pos == -1LL) return 0;
|
|
|
|
// 2. 将文件指针移到末尾
|
|
if (fn_fseek(file->fp, 0, SEEK_END) != C_ERR_OK) return 0;
|
|
|
|
// 3. 获取末尾位置即为文件大小
|
|
long long size = fn_tell(file->fp);
|
|
|
|
// 4. 恢复原先的文件指针位置
|
|
fn_fseek(file->fp, current_pos, SEEK_SET);
|
|
|
|
return (long long)((size < 0) ? 0 : size);
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------------------------------------------------ */
|
|
/* */
|
|
|
|
c_err_t c_File_Flush(c_File_t* file) {
|
|
if (!file || !file->fp) {
|
|
return C_ERR_FAIL;
|
|
}
|
|
|
|
// Step 1: 先刷新 C 库自带的软件层缓冲区(将数据推入 OS 内核队列)
|
|
if (fflush(file->fp) != 0) {
|
|
return C_ERR_FAIL;
|
|
}
|
|
|
|
// Step 2: 获取底层文件描述符,强行驱动硬件刷盘
|
|
#ifdef _WIN32
|
|
// 从 C 语言的 FILE* 提取 Windows 系统的文件句柄 HANDLE
|
|
int fd = _fileno(file->fp);
|
|
if (fd < 0) return C_ERR_FAIL;
|
|
|
|
HANDLE hFile = (HANDLE)_get_osfhandle(fd);
|
|
if (hFile == INVALID_HANDLE_VALUE) return C_ERR_FAIL;
|
|
|
|
// 强行驱动机械硬盘/SSD硬件将内核缓存写入物理介质
|
|
if (!FlushFileBuffers(hFile)) {
|
|
return C_ERR_FAIL;
|
|
}
|
|
#else
|
|
// Linux / macOS 平台直接提取文件描述符
|
|
int fd = fileno(file->fp);
|
|
if (fd < 0) return C_ERR_FAIL;
|
|
|
|
// 触发 POSIX 的 fsync 系统调用,阻塞直到硬件写入完毕
|
|
if (fsync(fd) != 0) {
|
|
return C_ERR_FAIL;
|
|
}
|
|
#endif
|
|
|
|
return C_ERR_OK;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------------------------------------------------ */
|
|
/* */
|
|
|
|
c_bool_t c_File_IsExist(const char* fileName) {
|
|
if (!fileName) {
|
|
return C_FALSE;
|
|
}
|
|
|
|
// 参数 0 代表 F_OK,即只检查文件是否存在
|
|
// C_ACCESS 返回 0 表示文件存在且可访问,返回 -1 表示不存在或无权限
|
|
if (C_ACCESS(fileName) == F_OK) {
|
|
return C_TRUE;
|
|
}
|
|
|
|
return C_FALSE;
|
|
}
|
|
|
|
/* ------------------------------------------------------------------------------------------------------------------ */
|
|
/* */
|
|
|
|
c_err_t c_File_MkDirs(const char* path) {
|
|
if (path == NULL || strlen(path) == 0) {
|
|
return C_ERR_PARAM;
|
|
}
|
|
|
|
char temp_path[512];
|
|
snprintf(temp_path, sizeof(temp_path), "%s", path);
|
|
size_t len = strlen(temp_path);
|
|
|
|
// 统一将 Windows 的 '\\' 转换为 '/' 方便逐级切分
|
|
for (int i = 0; i < len; i++) {
|
|
if (temp_path[i] == '\\') {
|
|
temp_path[i] = '/';
|
|
}
|
|
}
|
|
|
|
// 确保路径以 '/' 结尾,方便循环逻辑处理
|
|
if (temp_path[len - 1] != '/') {
|
|
if (len + 1 < sizeof(temp_path)) {
|
|
temp_path[len] = '/';
|
|
temp_path[len + 1] = '\0';
|
|
len++;
|
|
} else {
|
|
return C_ERR_FAIL; // 路径超长
|
|
}
|
|
}
|
|
|
|
// 逐级探测并创建目录
|
|
for (int i = 0; i < len; i++) {
|
|
// 每当遇到路径分隔符 '/' 时,尝试截断并创建当前层级目录
|
|
if (temp_path[i] == '/' && i > 0) {
|
|
temp_path[i] = '\0'; // 临时截断字符串
|
|
|
|
// 检查当前级目录是否存在,不存在则创建
|
|
if (C_ACCESS(temp_path) != 0) {
|
|
if (C_MAKE_DIR(temp_path) != 0) {
|
|
return C_ERR_FAIL; // 创建失败
|
|
}
|
|
}
|
|
|
|
temp_path[i] = '/'; // 恢复分隔符
|
|
}
|
|
}
|
|
|
|
return C_SUCCESS;
|
|
}
|
|
|
|
c_err_t c_File_Rmdir(const char* path) {
|
|
if (!path || path[0] == '\0') {
|
|
return C_ERR_PARAM;
|
|
}
|
|
|
|
// 调用操作系统底层移除目录的 API
|
|
int result = sys_rmdir(path);
|
|
|
|
if (result == 0) {
|
|
return C_ERR_OK; // 删除成功
|
|
} else {
|
|
// 删除失败(可能是由于目录不存在、无权限、或者目录非空)
|
|
return C_ERR_FAIL;
|
|
}
|
|
}
|
|
|
|
c_err_t c_File_Rmdirs(const char* path) {
|
|
if (!path || path[0] == '\0') {
|
|
return C_ERR_PARAM;
|
|
}
|
|
|
|
c_err_t status = C_ERR_OK;
|
|
|
|
#if defined(_WIN32) || defined(_WIN64)
|
|
// ==========================================
|
|
// Windows 平台的递归删除实现
|
|
// ==========================================
|
|
char search_path[MAX_PATH];
|
|
// Windows 检索目录需要追加 "\\*"
|
|
snprintf(search_path, sizeof(search_path), "%s\\*", path);
|
|
|
|
WIN32_FIND_DATAA find_data;
|
|
HANDLE h_find = FindFirstFileA(search_path, &find_data);
|
|
|
|
if (h_find == INVALID_HANDLE_VALUE) {
|
|
// 如果目录根本不存在,或者无法打开,尝试直接当做文件 remove 移除(处理符号链接等边界)
|
|
return remove(path) == 0 ? C_ERR_OK : C_ERR_FAIL;
|
|
}
|
|
|
|
do {
|
|
// 排除 Windows 的特殊目录 "." 和 ".."
|
|
if (strcmp(find_data.cFileName, ".") == 0 || strcmp(find_data.cFileName, "..") == 0) {
|
|
continue;
|
|
}
|
|
|
|
// 拼接子项的完整路径
|
|
char sub_path[MAX_PATH];
|
|
snprintf(sub_path, sizeof(sub_path), "%s\\%s", path, find_data.cFileName);
|
|
|
|
if (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
|
|
// 如果子项是目录,递归调用
|
|
status = c_File_Rmdirs(sub_path);
|
|
} else {
|
|
// 如果子项是普通文件,取消只读属性(防止因只读导致删除失败),然后将其删除
|
|
SetFileAttributesA(sub_path, FILE_ATTRIBUTE_NORMAL);
|
|
status = (DeleteFileA(sub_path) != 0) ? C_ERR_OK : C_ERR_FAIL;
|
|
}
|
|
|
|
if (status != C_ERR_OK) {
|
|
break;
|
|
}
|
|
} while (FindNextFileA(h_find, &find_data));
|
|
|
|
FindClose(h_find);
|
|
|
|
#else
|
|
// ==========================================
|
|
// Linux / macOS (POSIX) 平台的递归删除实现
|
|
// ==========================================
|
|
DIR* dir = opendir(path);
|
|
if (!dir) {
|
|
// 无法打开作为目录处理,尝试按普通文件物理删除
|
|
return remove(path) == 0 ? C_ERR_OK : C_ERR_FAIL;
|
|
}
|
|
|
|
struct dirent* entry;
|
|
while ((entry = readdir(dir)) != NULL) {
|
|
// 排除 Linux 的特殊目录 "." 和 ".."
|
|
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
|
|
continue;
|
|
}
|
|
|
|
// 拼接子项的完整路径
|
|
char sub_path[1024];
|
|
snprintf(sub_path, sizeof(sub_path), "%s/%s", path, entry->d_name);
|
|
|
|
struct stat statbuf;
|
|
if (stat(sub_path, &statbuf) == 0) {
|
|
if (S_ISDIR(statbuf.st_mode)) {
|
|
// 如果子项是目录,递归调用
|
|
status = c_File_Rmdirs(sub_path);
|
|
} else {
|
|
// 如果子项是文件,执行普通删除
|
|
status = (remove(sub_path) == 0) ? C_ERR_OK : C_ERR_FAIL;
|
|
}
|
|
}
|
|
|
|
if (status != C_ERR_OK) {
|
|
break;
|
|
}
|
|
}
|
|
closedir(dir);
|
|
#endif
|
|
|
|
// ==========================================
|
|
// 公共收尾:清空了内部所有子项后,最后删除外层空壳目录
|
|
// ==========================================
|
|
if (status == C_ERR_OK) {
|
|
if (sys_rmdir(path) != 0) {
|
|
status = C_ERR_FAIL;
|
|
}
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
c_err_t c_File_Delete(const char* fileName) {
|
|
if (!fileName || fileName[0] == '\0') {
|
|
return C_ERR_PARAM;
|
|
}
|
|
|
|
// 标准 C 库的 remove 能够直接删除文件(在某些平台上也能删除空目录)
|
|
int result = remove(fileName);
|
|
|
|
if (result == 0) {
|
|
return C_ERR_OK; // 删除成功
|
|
} else {
|
|
// 删除失败(文件不存在、或无权限、或文件正被某些操作系统强锁占用)
|
|
return C_ERR_FAIL;
|
|
}
|
|
}
|
|
|
|
#define COPY_BUFFER_SIZE 4096
|
|
|
|
c_err_t c_File_Copy(const char* srcPath, const char* destPath) {
|
|
if (!srcPath || srcPath[0] == '\0' || !destPath || destPath[0] == '\0') {
|
|
return C_ERR_PARAM;
|
|
}
|
|
|
|
FILE* src = fopen(srcPath, "rb");
|
|
if (!src) return C_ERR_NOTFOUND;
|
|
|
|
FILE* dest = fopen(destPath, "wb");
|
|
if (!dest) {
|
|
fclose(src);
|
|
return C_ERR_FAIL;
|
|
}
|
|
|
|
char* buffer = (char*)malloc(COPY_BUFFER_SIZE);
|
|
if (!buffer) {
|
|
fclose(src);
|
|
fclose(dest);
|
|
return C_ERR_FAIL;
|
|
}
|
|
|
|
c_err_t status = C_ERR_OK;
|
|
size_t bytes_read;
|
|
|
|
// 循环读写块,避免一次性读入大文件撑爆堆内存
|
|
while ((bytes_read = fread(buffer, 1, COPY_BUFFER_SIZE, src)) > 0) {
|
|
size_t bytes_written = fwrite(buffer, 1, bytes_read, dest);
|
|
if (bytes_written < bytes_read) {
|
|
status = C_ERR_FAIL; // 磁盘空间不足或写入错误
|
|
break;
|
|
}
|
|
}
|
|
|
|
free(buffer);
|
|
fclose(src);
|
|
fclose(dest);
|
|
|
|
// 如果中途失败,清理掉生成的不完整目标文件
|
|
if (status != C_ERR_OK) {
|
|
remove(destPath);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
c_err_t c_File_Move(const char* oldPath, const char* newPath) {
|
|
if (!oldPath || oldPath[0] == '\0' || !newPath || newPath[0] == '\0') {
|
|
return C_ERR_PARAM;
|
|
}
|
|
|
|
// 1. 尝试使用操作系统原生的轻量级重命名/移动
|
|
if (rename(oldPath, newPath) == 0) {
|
|
return C_ERR_OK;
|
|
}
|
|
|
|
// 2. 跨分区保底策略:如果因为跨文件系统挂载点导致 rename 失败,则执行 复制 + 删除
|
|
c_err_t copy_err = c_File_Copy(oldPath, newPath);
|
|
if (copy_err == C_ERR_OK) {
|
|
if (remove(oldPath) == 0) {
|
|
return C_ERR_OK;
|
|
} else {
|
|
// 如果删原文件失败,为了数据安全,把新拷过去的文件也撤销,避免数据状态不一致
|
|
remove(newPath);
|
|
return C_ERR_FAIL;
|
|
}
|
|
}
|
|
|
|
return C_ERR_FAIL;
|
|
}
|