1#include "../include/file.h"
16#define HUMAN_SIZE_EPSILON 1e-4
19#define COPY_BUFSIZE 65536
22#define MAX_READALL_SIZE (1ULL << 30)
29static native_handle_t get_native_handle(FILE* stream) {
31 return INVALID_NATIVE_HANDLE;
35 int fd = _fileno(stream);
37 return INVALID_NATIVE_HANDLE;
39 HANDLE handle = (HANDLE)(uintptr_t)_get_osfhandle(fd);
40 return (handle == INVALID_HANDLE_VALUE) ? INVALID_NATIVE_HANDLE : handle;
42 int fd = fileno(stream);
43 return (fd == -1) ? INVALID_NATIVE_HANDLE : fd;
59static time_t filetime_to_unix(
const FILETIME* ft) {
61 ull.LowPart = ft->dwLowDateTime;
62 ull.HighPart = ft->dwHighDateTime;
64 return (time_t)((ull.QuadPart / 10000000ULL) - 11644473600ULL);
75 WIN32_FILE_ATTRIBUTE_DATA file_info;
76 if (!GetFileAttributesExA(path, GetFileExInfoStandard, &file_info)) {
85 .mtime = filetime_to_unix(&file_info.ftLastWriteTime),
89 ULARGE_INTEGER file_size;
90 file_size.LowPart = file_info.nFileSizeLow;
91 file_size.HighPart = file_info.nFileSizeHigh;
92 attr->
size = file_size.QuadPart;
95 if (file_info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
103 if (file_info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
108 if (file_info.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) {
113 const char* ext = strrchr(path,
'.');
114 if (ext && (strcmp(ext,
".exe") == 0 || strcmp(ext,
".bat") == 0 || strcmp(ext,
".cmd") == 0 ||
115 strcmp(ext,
".com") == 0)) {
131 if (lstat(path, &st) != 0) {
138 .size = (uint64_t)st.st_size,
139 .mtime = st.st_mtime,
143 if (S_ISREG(st.st_mode)) {
146 if (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) {
149 }
else if (S_ISDIR(st.st_mode)) {
152 }
else if (S_ISLNK(st.st_mode)) {
157 if (S_ISCHR(st.st_mode)) {
163 if (S_ISBLK(st.st_mode)) {
169 if (S_ISFIFO(st.st_mode)) {
175 if (S_ISSOCK(st.st_mode)) {
181 const char* name = path;
184 const char* slash = strrchr(path,
'/');
185 if (slash && slash[1] !=
'\0') {
190 if (name[0] ==
'.') {
199 if (!filename || !mode) {
209 file->
stream = fopen(filename, mode);
249 if (fflush(file->
stream) != 0) {
254 LARGE_INTEGER li = {.QuadPart = length};
269 if (!buf || len < 8) {
274 int written = snprintf(buf, len,
"0 B");
278 static const char*
const units[] = {
"B",
"KB",
"MB",
"GB",
"TB",
"PB",
"EB"};
279 static const size_t num_units =
sizeof(units) /
sizeof(units[0]);
281 size_t unit_index = 0;
282 double value = (double)size;
284 while (value >= 1024.0 && unit_index < num_units - 1) {
289 double rounded = round(value);
292 if (fabs(value - rounded) < HUMAN_SIZE_EPSILON) {
293 written = snprintf(buf, len,
"%.0f %s", rounded, units[unit_index]);
295 written = snprintf(buf, len,
"%.2f %s", value, units[unit_index]);
302 if (!buffer || size == 0 || count == 0) {
305 return fread(buffer, size, count, file->
stream);
309 if (!buffer || size == 0 || count == 0) {
312 return fwrite(buffer, size, count, file->
stream);
319 size_t len = strlen(str);
320 return (len > 0) ? fwrite(str, 1, len, file->
stream) : 0;
324 if (!buffer || size == 0 || offset < 0) {
330 OVERLAPPED ov = {.Offset = (DWORD)(offset & 0xFFFFFFFF), .OffsetHigh = (DWORD)(offset >> 32), .hEvent = NULL};
333 if (!ReadFile(file->
native_handle, buffer, (DWORD)size, &bytes_read, &ov)) {
334 DWORD error = GetLastError();
335 if (error == ERROR_HANDLE_EOF) {
341 return (ssize_t)bytes_read;
343 return pread(file->
native_handle, buffer, size, (off_t)offset);
348 if (!buffer || size == 0 || offset < 0) {
354 OVERLAPPED ov = {.Offset = (DWORD)(offset & 0xFFFFFFFF), .OffsetHigh = (DWORD)(offset >> 32), .hEvent = NULL};
357 if (!WriteFile(file->
native_handle, buffer, (DWORD)size, &bytes_written, &ov)) {
361 return (ssize_t)bytes_written;
363 return pwrite(file->
native_handle, buffer, size, (off_t)offset);
372 if (fseek(file->
stream, 0, SEEK_END) != 0) {
377 if (current_size < 0) {
379 if (orig_pos >= 0) fseek(file->
stream, (
long)orig_pos, SEEK_SET);
384 if (fseek(file->
stream, 0, SEEK_SET) != 0) {
385 if (orig_pos >= 0) fseek(file->
stream, (
long)orig_pos, SEEK_SET);
389 if (current_size == 0) {
390 if (size_out) *size_out = 0;
395 void* buffer = malloc((
size_t)current_size);
398 if (orig_pos >= 0) fseek(file->
stream, (
long)orig_pos, SEEK_SET);
402 size_t bytes_read =
file_read(file, buffer, 1, (
size_t)current_size);
406 fseek(file->
stream, (
long)orig_pos, SEEK_SET);
409 if (bytes_read != (
size_t)current_size) {
411 errno = ferror(file->
stream) ? EIO : EINVAL;
416 file->attr.
size = (size_t)current_size;
419 *size_out = bytes_read;
426 OVERLAPPED overlapped = {0};
427 if (LockFileEx(file->
native_handle, LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY, 0, MAXDWORD, MAXDWORD,
432 DWORD error = GetLastError();
433 if (error == ERROR_LOCK_VIOLATION) {
440 struct flock fl = {.l_type = F_WRLCK, .l_whence = SEEK_SET, .l_start = 0, .l_len = 0};
446 if (errno == EACCES || errno == EAGAIN) {
455 OVERLAPPED overlapped = {0};
456 if (UnlockFileEx(file->
native_handle, 0, MAXDWORD, MAXDWORD, &overlapped)) {
462 struct flock fl = {.l_type = F_UNLCK, .l_whence = SEEK_SET, .l_start = 0, .l_len = 0};
472 char buffer[COPY_BUFSIZE] = {0};
473 size_t bytes_read = 0;
479 while ((bytes_read =
file_read(src, buffer, 1, COPY_BUFSIZE)) > 0) {
480 if (
file_write(dst, buffer, 1, bytes_read) != bytes_read) {
486 if (ferror(src->
stream)) {
491 if (fflush(dst->
stream) != 0) {
498void*
file_mmap(
const file_t* file,
size_t length,
bool read_access,
bool write_access) {
499 if (length == 0 || (!read_access && !write_access)) {
505 DWORD protect = write_access ? PAGE_READWRITE : PAGE_READONLY;
506 DWORD access = write_access ? FILE_MAP_WRITE : FILE_MAP_READ;
508 HANDLE mapping = CreateFileMapping(file->
native_handle, NULL, protect, (DWORD)(length >> 32), (DWORD)length, NULL);
514 void* addr = MapViewOfFile(mapping, access, 0, 0, length);
515 CloseHandle(mapping);
523 if (read_access) prot |= PROT_READ;
524 if (write_access) prot |= PROT_WRITE;
526 void* addr = mmap(NULL, length, prot, MAP_SHARED, file->
native_handle, 0);
527 return (addr == MAP_FAILED) ? NULL : addr;
548 LARGE_INTEGER zero = {0};
550 if (SetFilePointerEx(file->
native_handle, zero, &pos, FILE_CURRENT)) {
551 return (int64_t)pos.QuadPart;
557 return (pos == (off_t)-1) ? -1 : (int64_t)pos;
563 if (fflush(file->
stream) != 0) {
571 move_method = FILE_BEGIN;
574 move_method = FILE_CURRENT;
577 move_method = FILE_END;
584 LARGE_INTEGER li = {.QuadPart = offset};
585 if (SetFilePointerEx(file->
native_handle, li, NULL, move_method)) {
587 fseek(file->
stream, 0, SEEK_CUR);
593 if (lseek(file->
native_handle, (off_t)offset, whence) == (off_t)-1) {
598 if (fseek(file->
stream, 0, SEEK_CUR) != 0) {
ssize_t file_pwrite(file_t *file, const void *buffer, size_t size, int64_t offset)
void file_close(file_t *file)
file_result_t filesize_tostring(uint64_t size, char *buf, size_t len)
file_result_t file_truncate(file_t *file, int64_t length)
size_t file_read(const file_t *file, void *buffer, size_t size, size_t count)
@ FILE_ERROR_INVALID_ARGS
@ FILE_ERROR_SYSTEM_ERROR
void * file_readall(file_t *file, size_t *size_out)
ssize_t file_pread(const file_t *file, void *buffer, size_t size, int64_t offset)
file_result_t file_open(file_t *file, const char *filename, const char *mode)
file_result_t file_seek(file_t *file, int64_t offset, int whence)
file_result_t file_flush(file_t *file)
void * file_mmap(const file_t *file, size_t length, bool read_access, bool write_access)
size_t file_write_string(file_t *file, const char *str)
int64_t file_tell(const file_t *file)
file_result_t file_copy(const file_t *src, file_t *dst)
file_result_t file_lock(const file_t *file)
size_t file_write(file_t *file, const void *buffer, size_t size, size_t count)
int populate_file_attrs(const char *path, FileAttributes *attr)
file_result_t file_munmap(void *addr, size_t length)
file_result_t file_unlock(const file_t *file)
native_handle_t native_handle