solidc
Robust collection of general-purpose cross-platform C libraries and data structures designed for rapid and safe development in C
Loading...
Searching...
No Matches
file.c
1#include "../include/file.h"
2
3#include <errno.h>
4#include <math.h>
5#include <stdlib.h>
6#include <string.h>
7
8#ifdef _WIN32
9#include <io.h>
10#else
11#include <pwd.h>
12#include <sys/types.h>
13#endif
14
16#define HUMAN_SIZE_EPSILON 1e-4
17
19#define COPY_BUFSIZE 65536
20
22#define MAX_READALL_SIZE (1ULL << 30)
23
29static native_handle_t get_native_handle(FILE* stream) {
30 if (!stream) {
31 return INVALID_NATIVE_HANDLE;
32 }
33
34#ifdef _WIN32
35 int fd = _fileno(stream);
36 if (fd == -1) {
37 return INVALID_NATIVE_HANDLE;
38 }
39 HANDLE handle = (HANDLE)(uintptr_t)_get_osfhandle(fd);
40 return (handle == INVALID_HANDLE_VALUE) ? INVALID_NATIVE_HANDLE : handle;
41#else
42 int fd = fileno(stream);
43 return (fd == -1) ? INVALID_NATIVE_HANDLE : fd;
44#endif
45}
46
52#ifdef _WIN32
53
59static time_t filetime_to_unix(const FILETIME* ft) {
60 ULARGE_INTEGER ull;
61 ull.LowPart = ft->dwLowDateTime;
62 ull.HighPart = ft->dwHighDateTime;
63 // Convert from 100-nanosecond intervals since 1601 to seconds since 1970
64 return (time_t)((ull.QuadPart / 10000000ULL) - 11644473600ULL);
65}
66
74int populate_file_attrs(const char* path, FileAttributes* attr) {
75 WIN32_FILE_ATTRIBUTE_DATA file_info;
76 if (!GetFileAttributesExA(path, GetFileExInfoStandard, &file_info)) {
77 errno = ENOENT;
78 return -1;
79 }
80
81 // Initialize structure
82 *attr = (FileAttributes){
84 .size = 0,
85 .mtime = filetime_to_unix(&file_info.ftLastWriteTime),
86 };
87
88 // Calculate file size
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;
93
94 // Determine file type
95 if (file_info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
96 attr->attrs |= FATTR_DIR;
97 attr->size = 0; // Directories have no meaningful size on Windows
98 } else {
99 attr->attrs |= FATTR_FILE;
100 }
101
102 // Check for reparse points (symlinks, junctions, etc.)
103 if (file_info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {
104 attr->attrs |= FATTR_SYMLINK;
105 }
106
107 // Check for hidden files
108 if (file_info.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) {
109 attr->attrs |= FATTR_HIDDEN;
110 }
111
112 // On Windows, executability is determined by file extension
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)) {
116 attr->attrs |= FATTR_EXECUTABLE;
117 }
118
119 return 0;
120}
121
122#else // Unix/Linux/macOS
123
124int populate_file_attrs(const char* path, FileAttributes* attr) {
125 if (!attr) {
126 errno = EINVAL;
127 return -1;
128 }
129
130 struct stat st;
131 if (lstat(path, &st) != 0) {
132 return -1; // errno is set by lstat
133 }
134
135 // Initialize structure
136 *attr = (FileAttributes){
137 .attrs = FATTR_NONE,
138 .size = (uint64_t)st.st_size,
139 .mtime = st.st_mtime,
140 };
141
142 // Determine file type
143 if (S_ISREG(st.st_mode)) {
144 attr->attrs |= FATTR_FILE;
145 // Check if the file is executable by User, Group, or Other.
146 if (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) {
147 attr->attrs |= FATTR_EXECUTABLE;
148 }
149 } else if (S_ISDIR(st.st_mode)) {
150 attr->attrs |= FATTR_DIR;
151 attr->size = 0; // Directory size is not meaningful
152 } else if (S_ISLNK(st.st_mode)) {
153 attr->attrs |= FATTR_SYMLINK;
154 }
155
156#ifdef S_ISCHR
157 if (S_ISCHR(st.st_mode)) {
158 attr->attrs |= FATTR_CHARDEV;
159 }
160#endif
161
162#ifdef S_ISBLK
163 if (S_ISBLK(st.st_mode)) {
164 attr->attrs |= FATTR_BLOCKDEV;
165 }
166#endif
167
168#ifdef S_ISFIFO
169 if (S_ISFIFO(st.st_mode)) {
170 attr->attrs |= FATTR_FIFO;
171 }
172#endif
173
174#ifdef S_ISSOCK
175 if (S_ISSOCK(st.st_mode)) {
176 attr->attrs |= FATTR_SOCKET;
177 }
178#endif
179
180 // Check if hidden (starts with '.' on Unix)
181 const char* name = path;
182
183 if (path) {
184 const char* slash = strrchr(path, '/');
185 if (slash && slash[1] != '\0') {
186 name = slash + 1;
187 }
188 }
189
190 if (name[0] == '.') {
191 attr->attrs |= FATTR_HIDDEN;
192 }
193
194 return 0;
195}
196#endif // _WIN32
197
198file_result_t file_open(file_t* file, const char* filename, const char* mode) {
199 if (!filename || !mode) {
200 errno = EINVAL;
202 }
203
204 // Initialize structure to safe state
205 file->stream = NULL;
206 file->native_handle = INVALID_NATIVE_HANDLE;
207
208 // Open the file stream
209 file->stream = fopen(filename, mode);
210 if (!file->stream) {
211 return FILE_ERROR_OPEN_FAILED; // errno set by fopen
212 }
213
214 // Get native handle
215 file->native_handle = get_native_handle(file->stream);
216 if (file->native_handle == INVALID_NATIVE_HANDLE) {
217 fclose(file->stream);
218 file->stream = NULL;
219 errno = EBADF;
221 }
222
223 // Populate file attributes.
224 if (populate_file_attrs(filename, &file->attr) != 0) {
225 fclose(file->stream);
226 file->stream = NULL;
227 errno = EBADF;
229 }
230
231 return FILE_SUCCESS;
232}
233
234void file_close(file_t* file) {
235 if (file->stream) {
236 fclose(file->stream);
237 file->stream = NULL;
238 }
239 file->native_handle = INVALID_NATIVE_HANDLE;
240}
241
242file_result_t file_truncate(file_t* file, int64_t length) {
243 if (length < 0) {
244 errno = EINVAL;
246 }
247
248 // Flush any pending writes before truncation
249 if (fflush(file->stream) != 0) {
251 }
252
253#ifdef _WIN32
254 LARGE_INTEGER li = {.QuadPart = length};
255 if (!SetFilePointerEx(file->native_handle, li, NULL, FILE_BEGIN) || !SetEndOfFile(file->native_handle)) {
256 errno = EIO;
258 }
259#else
260 if (ftruncate(file->native_handle, (off_t)length) != 0) {
261 return FILE_ERROR_IO_FAILED; // errno already set
262 }
263#endif
264
265 return FILE_SUCCESS;
266}
267
268file_result_t filesize_tostring(uint64_t size, char* buf, size_t len) {
269 if (!buf || len < 8) { // Minimum for "1024.00 B"
271 }
272
273 if (size == 0) {
274 int written = snprintf(buf, len, "0 B");
275 return (written > 0 && (size_t)written < len) ? FILE_SUCCESS : FILE_ERROR_INVALID_ARGS;
276 }
277
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]);
280
281 size_t unit_index = 0;
282 double value = (double)size;
283
284 while (value >= 1024.0 && unit_index < num_units - 1) {
285 value /= 1024.0;
286 unit_index++;
287 }
288
289 double rounded = round(value);
290 int written = -1;
291
292 if (fabs(value - rounded) < HUMAN_SIZE_EPSILON) {
293 written = snprintf(buf, len, "%.0f %s", rounded, units[unit_index]);
294 } else {
295 written = snprintf(buf, len, "%.2f %s", value, units[unit_index]);
296 }
297
298 return (written > 0 && (size_t)written < len) ? FILE_SUCCESS : FILE_ERROR_INVALID_ARGS;
299}
300
301size_t file_read(const file_t* file, void* buffer, size_t size, size_t count) {
302 if (!buffer || size == 0 || count == 0) {
303 return 0;
304 }
305 return fread(buffer, size, count, file->stream);
306}
307
308size_t file_write(file_t* file, const void* buffer, size_t size, size_t count) {
309 if (!buffer || size == 0 || count == 0) {
310 return 0;
311 }
312 return fwrite(buffer, size, count, file->stream);
313}
314
315size_t file_write_string(file_t* file, const char* str) {
316 if (!str) {
317 return 0;
318 }
319 size_t len = strlen(str);
320 return (len > 0) ? fwrite(str, 1, len, file->stream) : 0;
321}
322
323ssize_t file_pread(const file_t* file, void* buffer, size_t size, int64_t offset) {
324 if (!buffer || size == 0 || offset < 0) {
325 errno = EINVAL;
326 return -1;
327 }
328
329#ifdef _WIN32
330 OVERLAPPED ov = {.Offset = (DWORD)(offset & 0xFFFFFFFF), .OffsetHigh = (DWORD)(offset >> 32), .hEvent = NULL};
331
332 DWORD bytes_read;
333 if (!ReadFile(file->native_handle, buffer, (DWORD)size, &bytes_read, &ov)) {
334 DWORD error = GetLastError();
335 if (error == ERROR_HANDLE_EOF) {
336 return 0; // EOF
337 }
338 errno = EIO;
339 return -1;
340 }
341 return (ssize_t)bytes_read;
342#else
343 return pread(file->native_handle, buffer, size, (off_t)offset);
344#endif
345}
346
347ssize_t file_pwrite(file_t* file, const void* buffer, size_t size, int64_t offset) {
348 if (!buffer || size == 0 || offset < 0) {
349 errno = EINVAL;
350 return -1;
351 }
352
353#ifdef _WIN32
354 OVERLAPPED ov = {.Offset = (DWORD)(offset & 0xFFFFFFFF), .OffsetHigh = (DWORD)(offset >> 32), .hEvent = NULL};
355
356 DWORD bytes_written;
357 if (!WriteFile(file->native_handle, buffer, (DWORD)size, &bytes_written, &ov)) {
358 errno = EIO;
359 return -1;
360 }
361 return (ssize_t)bytes_written;
362#else
363 return pwrite(file->native_handle, buffer, size, (off_t)offset);
364#endif
365}
366
367void* file_readall(file_t* file, size_t* size_out) {
368 /* Save the caller's current stream position so we can restore it later. */
369 int64_t orig_pos = file_tell(file);
370
371 /* Seek to the end to get the actual, up-to-date byte count. */
372 if (fseek(file->stream, 0, SEEK_END) != 0) {
373 return NULL;
374 }
375
376 int64_t current_size = file_tell(file);
377 if (current_size < 0) {
378 /* Restore position on error and bail. */
379 if (orig_pos >= 0) fseek(file->stream, (long)orig_pos, SEEK_SET);
380 return NULL;
381 }
382
383 /* Rewind to the beginning before reading. */
384 if (fseek(file->stream, 0, SEEK_SET) != 0) {
385 if (orig_pos >= 0) fseek(file->stream, (long)orig_pos, SEEK_SET);
386 return NULL;
387 }
388
389 if (current_size == 0) {
390 if (size_out) *size_out = 0;
391 /* Return a valid non-NULL pointer for zero-sized files */
392 return malloc(1);
393 }
394
395 void* buffer = malloc((size_t)current_size);
396 if (!buffer) {
397 errno = ENOMEM;
398 if (orig_pos >= 0) fseek(file->stream, (long)orig_pos, SEEK_SET);
399 return NULL;
400 }
401
402 size_t bytes_read = file_read(file, buffer, 1, (size_t)current_size);
403
404 /* Restore the original stream position (best-effort). */
405 if (orig_pos >= 0) {
406 fseek(file->stream, (long)orig_pos, SEEK_SET);
407 }
408
409 if (bytes_read != (size_t)current_size) {
410 free(buffer);
411 errno = ferror(file->stream) ? EIO : EINVAL;
412 return NULL;
413 }
414
415 // Update attrs.size with most recent size after reading, since file could have changed since open
416 file->attr.size = (size_t)current_size;
417
418 if (size_out) {
419 *size_out = bytes_read;
420 }
421 return buffer;
422}
423
425#ifdef _WIN32
426 OVERLAPPED overlapped = {0};
427 if (LockFileEx(file->native_handle, LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY, 0, MAXDWORD, MAXDWORD,
428 &overlapped)) {
429 return FILE_SUCCESS;
430 }
431
432 DWORD error = GetLastError();
433 if (error == ERROR_LOCK_VIOLATION) {
434 errno = EACCES;
436 }
437 errno = EIO;
439#else
440 struct flock fl = {.l_type = F_WRLCK, .l_whence = SEEK_SET, .l_start = 0, .l_len = 0};
441
442 if (fcntl(file->native_handle, F_SETLK, &fl) == 0) {
443 return FILE_SUCCESS;
444 }
445
446 if (errno == EACCES || errno == EAGAIN) {
448 }
450#endif
451}
452
454#ifdef _WIN32
455 OVERLAPPED overlapped = {0};
456 if (UnlockFileEx(file->native_handle, 0, MAXDWORD, MAXDWORD, &overlapped)) {
457 return FILE_SUCCESS;
458 }
459 errno = EIO;
461#else
462 struct flock fl = {.l_type = F_UNLCK, .l_whence = SEEK_SET, .l_start = 0, .l_len = 0};
463
464 if (fcntl(file->native_handle, F_SETLK, &fl) == 0) {
465 return FILE_SUCCESS;
466 }
468#endif
469}
470
472 char buffer[COPY_BUFSIZE] = {0};
473 size_t bytes_read = 0;
474
475 // Clear any previous errors
476 clearerr(src->stream);
477 clearerr(dst->stream);
478
479 while ((bytes_read = file_read(src, buffer, 1, COPY_BUFSIZE)) > 0) {
480 if (file_write(dst, buffer, 1, bytes_read) != bytes_read) {
482 }
483 }
484
485 // Check for read error
486 if (ferror(src->stream)) {
488 }
489
490 // Flush destination
491 if (fflush(dst->stream) != 0) {
493 }
494
495 return FILE_SUCCESS;
496}
497
498void* file_mmap(const file_t* file, size_t length, bool read_access, bool write_access) {
499 if (length == 0 || (!read_access && !write_access)) {
500 errno = EINVAL;
501 return NULL;
502 }
503
504#ifdef _WIN32
505 DWORD protect = write_access ? PAGE_READWRITE : PAGE_READONLY;
506 DWORD access = write_access ? FILE_MAP_WRITE : FILE_MAP_READ;
507
508 HANDLE mapping = CreateFileMapping(file->native_handle, NULL, protect, (DWORD)(length >> 32), (DWORD)length, NULL);
509 if (!mapping) {
510 errno = EIO;
511 return NULL;
512 }
513
514 void* addr = MapViewOfFile(mapping, access, 0, 0, length);
515 CloseHandle(mapping);
516
517 if (!addr) {
518 errno = EIO;
519 }
520 return addr;
521#else
522 int prot = 0;
523 if (read_access) prot |= PROT_READ;
524 if (write_access) prot |= PROT_WRITE;
525
526 void* addr = mmap(NULL, length, prot, MAP_SHARED, file->native_handle, 0);
527 return (addr == MAP_FAILED) ? NULL : addr;
528#endif
529}
530
531file_result_t file_munmap(void* addr, size_t length) {
532 if (!addr) {
534 }
535
536#ifdef _WIN32
537 (void)length; // Unused on Windows
538 return UnmapViewOfFile(addr) ? FILE_SUCCESS : FILE_ERROR_SYSTEM_ERROR;
539#else
540 return (munmap(addr, length) == 0) ? FILE_SUCCESS : FILE_ERROR_SYSTEM_ERROR;
541#endif
542}
543
544file_result_t file_flush(file_t* file) { return (fflush(file->stream) == 0) ? FILE_SUCCESS : FILE_ERROR_IO_FAILED; }
545
546int64_t file_tell(const file_t* file) {
547#ifdef _WIN32
548 LARGE_INTEGER zero = {0};
549 LARGE_INTEGER pos;
550 if (SetFilePointerEx(file->native_handle, zero, &pos, FILE_CURRENT)) {
551 return (int64_t)pos.QuadPart;
552 }
553 errno = EIO;
554 return -1;
555#else
556 off_t pos = lseek(file->native_handle, 0, SEEK_CUR);
557 return (pos == (off_t)-1) ? -1 : (int64_t)pos;
558#endif
559}
560
561file_result_t file_seek(file_t* file, int64_t offset, int whence) {
562 // Flush any pending writes before seeking
563 if (fflush(file->stream) != 0) {
565 }
566
567#ifdef _WIN32
568 DWORD move_method;
569 switch (whence) {
570 case SEEK_SET:
571 move_method = FILE_BEGIN;
572 break;
573 case SEEK_CUR:
574 move_method = FILE_CURRENT;
575 break;
576 case SEEK_END:
577 move_method = FILE_END;
578 break;
579 default:
580 errno = EINVAL;
582 }
583
584 LARGE_INTEGER li = {.QuadPart = offset};
585 if (SetFilePointerEx(file->native_handle, li, NULL, move_method)) {
586 // Sync the FILE* stream position
587 fseek(file->stream, 0, SEEK_CUR);
588 return FILE_SUCCESS;
589 }
590 errno = EIO;
592#else
593 if (lseek(file->native_handle, (off_t)offset, whence) == (off_t)-1) {
594 return FILE_ERROR_IO_FAILED; // errno already set
595 }
596
597 // Sync the FILE* stream position
598 if (fseek(file->stream, 0, SEEK_CUR) != 0) {
600 }
601
602 return FILE_SUCCESS;
603#endif
604}
ssize_t file_pwrite(file_t *file, const void *buffer, size_t size, int64_t offset)
Definition file.c:347
void file_close(file_t *file)
Definition file.c:234
file_result_t filesize_tostring(uint64_t size, char *buf, size_t len)
Definition file.c:268
file_result_t file_truncate(file_t *file, int64_t length)
Definition file.c:242
size_t file_read(const file_t *file, void *buffer, size_t size, size_t count)
Definition file.c:301
file_result_t
Definition file.h:140
@ FILE_ERROR_INVALID_ARGS
Definition file.h:142
@ FILE_ERROR_SYSTEM_ERROR
Definition file.h:147
@ FILE_ERROR_LOCK_FAILED
Definition file.h:145
@ FILE_ERROR_OPEN_FAILED
Definition file.h:143
@ FILE_SUCCESS
Definition file.h:141
@ FILE_ERROR_IO_FAILED
Definition file.h:144
void * file_readall(file_t *file, size_t *size_out)
Definition file.c:367
ssize_t file_pread(const file_t *file, void *buffer, size_t size, int64_t offset)
Definition file.c:323
file_result_t file_open(file_t *file, const char *filename, const char *mode)
Definition file.c:198
file_result_t file_seek(file_t *file, int64_t offset, int whence)
Definition file.c:561
file_result_t file_flush(file_t *file)
Definition file.c:544
void * file_mmap(const file_t *file, size_t length, bool read_access, bool write_access)
Definition file.c:498
size_t file_write_string(file_t *file, const char *str)
Definition file.c:315
int64_t file_tell(const file_t *file)
Definition file.c:546
@ FATTR_DIR
Definition file.h:59
@ FATTR_HIDDEN
Definition file.h:65
@ FATTR_NONE
Definition file.h:57
@ FATTR_FILE
Definition file.h:58
@ FATTR_BLOCKDEV
Definition file.h:62
@ FATTR_SYMLINK
Definition file.h:60
@ FATTR_FIFO
Definition file.h:63
@ FATTR_SOCKET
Definition file.h:64
@ FATTR_EXECUTABLE
Definition file.h:66
@ FATTR_CHARDEV
Definition file.h:61
file_result_t file_copy(const file_t *src, file_t *dst)
Definition file.c:471
file_result_t file_lock(const file_t *file)
Definition file.c:424
size_t file_write(file_t *file, const void *buffer, size_t size, size_t count)
Definition file.c:308
int populate_file_attrs(const char *path, FileAttributes *attr)
Definition file.c:124
file_result_t file_munmap(void *addr, size_t length)
Definition file.c:531
file_result_t file_unlock(const file_t *file)
Definition file.c:453
size_t size
Definition file.h:75
uint32_t attrs
Definition file.h:72
Definition file.h:133
FILE * stream
Definition file.h:134
native_handle_t native_handle
Definition file.h:136