197 lines
8.9 KiB
C
197 lines
8.9 KiB
C
#include "file_io.h"
|
|
#include "platform_compat.h"
|
|
#include "debug_config.h"
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#include <limits.h>
|
|
|
|
#ifdef _WIN32
|
|
/* Общие пути API — UTF-8; ANSI Win32 API не применяется. */
|
|
int file_io_wide(const char *path, wchar_t wide[FILE_IO_PATH_MAX]) {
|
|
if (path && *path && strlen(path) < FILE_IO_PATH_MAX
|
|
&& MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, path, -1, wide, FILE_IO_PATH_MAX)) return 0;
|
|
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "file invalid UTF-8/oversized path winerr=%lu", GetLastError());
|
|
return -1;
|
|
}
|
|
#endif
|
|
|
|
int file_io_inspect(const char *path, int follow, struct file_io_info *info) {
|
|
if (!path || !*path || strlen(path) >= FILE_IO_PATH_MAX || !info) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "file inspect invalid path/output"); return -1;
|
|
}
|
|
struct file_io_info result = {0};
|
|
#ifdef _WIN32
|
|
wchar_t wide[FILE_IO_PATH_MAX];
|
|
if (file_io_wide(path, wide)) return -1;
|
|
DWORD attributes = GetFileAttributesW(wide);
|
|
if (attributes == INVALID_FILE_ATTRIBUTES) {
|
|
DWORD error = GetLastError();
|
|
if (error == ERROR_FILE_NOT_FOUND || error == ERROR_PATH_NOT_FOUND) return 1;
|
|
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "file inspect failed path=%s winerr=%lu", path, error); return -1;
|
|
}
|
|
if (!follow && (attributes & FILE_ATTRIBUTE_REPARSE_POINT)) result.type = FILE_IO_LINK;
|
|
else {
|
|
HANDLE handle = CreateFileW(wide, FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
|
|
NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
|
|
BY_HANDLE_FILE_INFORMATION data;
|
|
int valid = handle != INVALID_HANDLE_VALUE && GetFileInformationByHandle(handle, &data);
|
|
if (valid) {
|
|
result.type = GetFileType(handle) != FILE_TYPE_DISK ? FILE_IO_OTHER
|
|
: (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? FILE_IO_DIRECTORY : FILE_IO_REGULAR;
|
|
uint64_t size = ((uint64_t)data.nFileSizeHigh << 32) | data.nFileSizeLow;
|
|
valid = size <= INT64_MAX;
|
|
result.size = (int64_t)size;
|
|
result.identity[0] = data.dwVolumeSerialNumber;
|
|
result.identity[1] = ((uint64_t)data.nFileIndexHigh << 32) | data.nFileIndexLow;
|
|
}
|
|
DWORD error = GetLastError();
|
|
if (handle != INVALID_HANDLE_VALUE) CloseHandle(handle);
|
|
if (!valid) { DEBUG_ERROR(DEBUG_CATEGORY_SYS, "file handle inspect failed path=%s winerr=%lu", path, error); return -1; }
|
|
}
|
|
#else
|
|
struct stat data;
|
|
if ((follow ? stat(path, &data) : lstat(path, &data))) {
|
|
if (errno == ENOENT) return 1;
|
|
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "file inspect failed path=%s errno=%d", path, errno); return -1;
|
|
}
|
|
result.type = S_ISREG(data.st_mode) ? FILE_IO_REGULAR : S_ISDIR(data.st_mode) ? FILE_IO_DIRECTORY
|
|
: S_ISLNK(data.st_mode) ? FILE_IO_LINK : FILE_IO_OTHER;
|
|
if (data.st_size < 0 || (uint64_t)data.st_size > INT64_MAX) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "file size out of range path=%s", path); return -1;
|
|
}
|
|
result.size = (int64_t)data.st_size;
|
|
result.identity[0] = data.st_dev; result.identity[1] = data.st_ino;
|
|
#endif
|
|
*info = result;
|
|
return 0;
|
|
}
|
|
|
|
FILE *file_io_open_read(const char *path) {
|
|
struct file_io_info info;
|
|
if (file_io_inspect(path, 1, &info) || info.type != FILE_IO_REGULAR) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "file source is not regular/readable path=%s", path ? path : "(null)"); return NULL;
|
|
}
|
|
#ifdef _WIN32
|
|
wchar_t wide[FILE_IO_PATH_MAX];
|
|
if (file_io_wide(path, wide)) return NULL;
|
|
FILE *file = _wfopen(wide, L"rb");
|
|
#else
|
|
FILE *file = fopen(path, "rb");
|
|
#endif
|
|
if (!file) DEBUG_ERROR(DEBUG_CATEGORY_SYS, "file open read failed path=%s errno=%d", path, errno);
|
|
return file;
|
|
}
|
|
|
|
FILE *file_io_open_update(const char *path) {
|
|
struct file_io_info info;
|
|
if (file_io_inspect(path, 0, &info) || info.type != FILE_IO_REGULAR) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "file update requires regular file path=%s", path ? path : "(null)"); return NULL;
|
|
}
|
|
#ifdef _WIN32
|
|
wchar_t wide[FILE_IO_PATH_MAX];
|
|
if (file_io_wide(path, wide)) return NULL;
|
|
FILE *file = _wfopen(wide, L"r+b");
|
|
#else
|
|
FILE *file = fopen(path, "r+b");
|
|
#endif
|
|
if (!file) DEBUG_ERROR(DEBUG_CATEGORY_SYS, "file update open failed path=%s errno=%d", path, errno);
|
|
return file;
|
|
}
|
|
|
|
int file_io_paths(const char *source, const char *destination) {
|
|
struct file_io_info input, output;
|
|
if (file_io_inspect(source, 1, &input) || input.type != FILE_IO_REGULAR) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "file invalid source path=%s", source ? source : "(null)"); return -1;
|
|
}
|
|
int status = file_io_inspect(destination, 0, &output);
|
|
if (status == 1) return 0;
|
|
if (status < 0) return -1;
|
|
if (output.type != FILE_IO_REGULAR) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "file destination is not regular path=%s type=%d", destination, output.type); return -1;
|
|
}
|
|
return !memcmp(input.identity, output.identity, sizeof(input.identity));
|
|
}
|
|
|
|
int file_io_temp_open(struct file_io_temp *output, const char *destination) {
|
|
if (!output || output->file || output->temporary[0] || !destination || !*destination
|
|
|| strlen(destination) + 38 >= FILE_IO_PATH_MAX) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "file temporary invalid owner/path"); return -1;
|
|
}
|
|
for (unsigned attempt = 0; attempt < 8; attempt++) {
|
|
uint8_t random[16]; char suffix[33];
|
|
if (random_bytes(random, sizeof(random))) { DEBUG_ERROR(DEBUG_CATEGORY_SYS, "file temporary RNG failed"); return -1; }
|
|
for (unsigned i = 0; i < sizeof(random); i++) snprintf(suffix + i * 2, 3, "%02x", random[i]);
|
|
snprintf(output->temporary, sizeof(output->temporary), "%s.tmp.%s", destination, suffix);
|
|
#ifdef _WIN32
|
|
wchar_t wide[FILE_IO_PATH_MAX];
|
|
if (file_io_wide(output->temporary, wide)) { output->temporary[0] = 0; return -1; }
|
|
int fd = _wopen(wide, _O_CREAT | _O_EXCL | _O_RDWR | _O_BINARY, _S_IREAD | _S_IWRITE);
|
|
#else
|
|
int fd = open(output->temporary, O_CREAT | O_EXCL | O_RDWR, 0600);
|
|
#endif
|
|
if (fd < 0) {
|
|
output->temporary[0] = 0;
|
|
if (errno == EEXIST) continue;
|
|
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "file temporary open failed destination=%s errno=%d", destination, errno); return -1;
|
|
}
|
|
#ifdef _WIN32
|
|
output->file = _fdopen(fd, "wb+");
|
|
if (!output->file) _close(fd);
|
|
#else
|
|
output->file = fdopen(fd, "wb+");
|
|
if (!output->file) close(fd);
|
|
#endif
|
|
if (!output->file) { DEBUG_ERROR(DEBUG_CATEGORY_SYS, "file fdopen failed path=%s errno=%d", output->temporary, errno); return -1; }
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_SYS, "file temporary opened path=%s", output->temporary);
|
|
return 0;
|
|
}
|
|
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "file temporary collision limit destination=%s", destination); return -1;
|
|
}
|
|
|
|
int file_io_temp_finish(struct file_io_temp *output) {
|
|
if (!output || !output->file) { DEBUG_ERROR(DEBUG_CATEGORY_SYS, "file finish without open file"); return -1; }
|
|
int error = fflush(output->file) ? (errno ? errno : EIO) : 0;
|
|
if (fclose(output->file) && !error) error = errno ? errno : EIO;
|
|
output->file = NULL;
|
|
if (error) { DEBUG_ERROR(DEBUG_CATEGORY_SYS, "file flush/close failed path=%s errno=%d", output->temporary, error); return -1; }
|
|
return 0;
|
|
}
|
|
|
|
void file_io_temp_discard(struct file_io_temp *output) {
|
|
if (!output) return;
|
|
if (output->file) {
|
|
if (fclose(output->file)) DEBUG_WARN(DEBUG_CATEGORY_SYS, "file cleanup close failed errno=%d", errno);
|
|
output->file = NULL;
|
|
}
|
|
if (!output->temporary[0]) return;
|
|
#ifdef _WIN32
|
|
wchar_t wide[FILE_IO_PATH_MAX];
|
|
int error = file_io_wide(output->temporary, wide) ? -1 : _wremove(wide);
|
|
#else
|
|
int error = remove(output->temporary);
|
|
#endif
|
|
if (error && errno != ENOENT) DEBUG_WARN(DEBUG_CATEGORY_SYS, "file cleanup remove failed path=%s errno=%d", output->temporary, errno);
|
|
output->temporary[0] = 0;
|
|
}
|
|
|
|
int file_io_temp_publish(struct file_io_temp *output, const char *source, const char *destination) {
|
|
if (!output || output->file || !output->temporary[0] || file_io_paths(source, destination)) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "file publish invalid state/paths"); return -1;
|
|
}
|
|
#ifdef _WIN32
|
|
wchar_t from[FILE_IO_PATH_MAX], to[FILE_IO_PATH_MAX];
|
|
if (file_io_wide(output->temporary, from) || file_io_wide(destination, to)) return -1;
|
|
if (!MoveFileExW(from, to, MOVEFILE_REPLACE_EXISTING)) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "file publish failed destination=%s winerr=%lu", destination, GetLastError()); return -1;
|
|
}
|
|
#else
|
|
if (rename(output->temporary, destination)) {
|
|
DEBUG_ERROR(DEBUG_CATEGORY_SYS, "file publish failed destination=%s errno=%d", destination, errno); return -1;
|
|
}
|
|
#endif
|
|
output->temporary[0] = 0;
|
|
DEBUG_DEBUG(DEBUG_CATEGORY_SYS, "file published destination=%s", destination);
|
|
return 0;
|
|
}
|