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
filepath.c
1#include "../include/filepath.h"
2
3#include "../include/env.h"
4#include "../include/file.h"
5#include "../include/lock.h"
6#include "../include/wintypes.h"
7
8#include <errno.h>
9#include <stdatomic.h>
10#include <stdlib.h>
11#include <string.h>
12#include <time.h>
13
14#ifdef _WIN32
15#include <io.h> // for _access
16#include <windows.h>
17
18#ifndef S_ISREG
19#define S_ISREG(m) (((m) & _S_IFMT) == _S_IFREG)
20#endif
21#ifndef S_ISDIR
22#define S_ISDIR(m) (((m) & _S_IFMT) == _S_IFDIR)
23#endif
24
25// Windows doesn't have R_OK, W_OK, X_OK
26#ifndef R_OK
27#define R_OK 4
28#endif
29#ifndef W_OK
30#define W_OK 2
31#endif
32#ifndef X_OK
33#define X_OK 1
34#endif
35#else
36#include <sys/stat.h> // for stat, lstat, S_ISDIR, S_ISREG, etc.
37#include <unistd.h> // for access
38#endif
39
40// Length of the temporary directory prefix
41#define TEMP_PREF_PREFIX_LEN 12
42
43static inline size_t safe_strlcpy(char* dst, const char* src, size_t size) {
44 if (!dst || !src || size == 0) {
45 return 0;
46 }
47 size_t n = strnlen(src, size - 1);
48 memcpy(dst, src, n);
49 dst[n] = '\0';
50 return n;
51}
52
53// Generate a random string for temporary file/directory names.
54// len must be < 64 bytes.
55static void random_string(char* str, size_t len) {
56 static Lock rand_lock;
57 static atomic_int initialized = 0;
58
59 // Initialize lock in a thread-safe manner
60 if (!atomic_load(&initialized)) {
61 lock_init(&rand_lock);
62 atomic_store(&initialized, 1);
63 }
64
65 const char charset[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
66 const size_t charset_size = sizeof(charset) - 1; // Exclude null terminator
67 unsigned char buffer[64]; // Buffer for random bytes (sufficient for typical len)
68
69 lock_acquire(&rand_lock);
70
71 // Ensure we don't write beyond requested length
72 size_t bytes_needed = len < 64 ? len : 64;
73
74#ifdef _WIN32
75 HCRYPTPROV hCryptProv;
76 if (!CryptAcquireContextW(&hCryptProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) {
77 str[0] = '\0'; // Fallback to empty string on error
78 lock_release(&rand_lock);
79 return;
80 }
81
82 if (!CryptGenRandom(hCryptProv, bytes_needed, buffer)) {
83 CryptReleaseContext(hCryptProv, 0);
84 str[0] = '\0';
85 lock_release(&rand_lock);
86 return;
87 }
88 CryptReleaseContext(hCryptProv, 0);
89#else
90 ssize_t bytes_read = -1;
91#ifdef __linux__
92 // Try getrandom (Linux-specific, preferred)
93 bytes_read = getrandom(buffer, bytes_needed, 0);
94#else
95 // Fallback to /dev/urandom for other POSIX systems
96 int fd = open("/dev/urandom", O_RDONLY);
97 if (fd != -1) {
98 bytes_read = read(fd, buffer, bytes_needed);
99 close(fd);
100 }
101#endif
102 if (bytes_read != (ssize_t)bytes_needed) {
103 str[0] = '\0'; // Fallback to empty string on error
104 lock_release(&rand_lock);
105 return;
106 }
107#endif
108
109 // Convert random bytes to characters from charset
110 for (size_t i = 0; i < len; i++) {
111 str[i] = charset[buffer[i % bytes_needed] % charset_size];
112 }
113 str[len] = '\0';
114 lock_release(&rand_lock);
115}
116
117// Open a directory
118Directory* dir_open(const char* path) {
119 if (!path || *path == '\0') {
120 errno = EINVAL;
121 return NULL;
122 }
123
124 Directory* dir = (Directory*)calloc(1, sizeof(Directory)); // Use calloc for zero-initialization
125 if (!dir) {
126 errno = ENOMEM;
127 return NULL;
128 }
129
130 dir->path = strdup(path);
131 if (!dir->path) {
132 free(dir);
133 errno = ENOMEM;
134 return NULL;
135 }
136
137#ifdef _WIN32
138 wchar_t wpath[MAX_PATH];
139 if (!MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, MAX_PATH)) {
140 free(dir->path);
141 free(dir);
142 errno = EINVAL; // More specific error code
143 return NULL;
144 }
145
146 wchar_t search_path[MAX_PATH + 2];
147 if (swprintf(search_path, MAX_PATH + 2, L"%ls\\*", wpath) < 0) {
148 free(dir->path);
149 free(dir);
150 errno = ENAMETOOLONG;
151 return NULL;
152 }
153
154 dir->handle = FindFirstFileW(search_path, &dir->find_data);
155 if (dir->handle == INVALID_HANDLE_VALUE) {
156 free(dir->path);
157 free(dir);
158 errno = GetLastError() == ERROR_FILE_NOT_FOUND ? ENOENT : EACCES;
159 return NULL;
160 }
161#else
162 dir->dir = opendir(path);
163 if (!dir->dir) {
164 int saved_errno = errno;
165 free(dir->path);
166 free(dir);
167 errno = saved_errno;
168 return NULL;
169 }
170#endif
171 return dir;
172}
173
174// Close a directory
176 if (!dir) return;
177
178#ifdef _WIN32
179 if (dir->handle != INVALID_HANDLE_VALUE) {
180 FindClose(dir->handle);
181 }
182#else
183 if (dir->dir) {
184 closedir(dir->dir);
185 }
186#endif
187 free(dir->path);
188 free(dir);
189}
190
191// Read the next entry in the directory
192char* dir_next(Directory* dir) {
193 if (!dir) {
194 errno = EINVAL;
195 return NULL;
196 }
197
198#ifdef _WIN32
199 if (dir->handle == INVALID_HANDLE_VALUE) {
200 errno = EBADF;
201 return NULL;
202 }
203 if (FindNextFileW(dir->handle, &dir->find_data)) {
204 // Convert wide-char filename to UTF-8 directly into the struct buffer
205 WideCharToMultiByte(CP_UTF8, 0, dir->find_data.cFileName, -1, dir->name_buf, MAX_PATH, NULL, NULL);
206 return dir->name_buf;
207 }
208#else
209 struct dirent* entry = readdir(dir->dir);
210 if (entry) {
211 return entry->d_name;
212 }
213#endif
214 return NULL;
215}
216
217#ifdef _WIN32
218static void map_win32_attrs(const WIN32_FIND_DATAW* fd, FileAttributes* attr) {
219 attr->attrs = FATTR_NONE;
220 if (fd->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
221 attr->attrs |= FATTR_DIR;
222 else
223 attr->attrs |= FATTR_FILE;
224 if (fd->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) attr->attrs |= FATTR_SYMLINK;
225 if (fd->dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) attr->attrs |= FATTR_HIDDEN;
226 if (fd->cFileName[0] == '.') attr->attrs |= FATTR_HIDDEN;
227
228 attr->size = ((size_t)fd->nFileSizeHigh << 32) | fd->nFileSizeLow;
229
230 // Convert Windows FileTime to Unix mtime (simplified)
231 ULARGE_INTEGER ull;
232 ull.LowPart = fd->ftLastWriteTime.dwLowDateTime;
233 ull.HighPart = fd->ftLastWriteTime.dwHighDateTime;
234 attr->mtime = (time_t)((ull.QuadPart - 116444736000000000ULL) / 10000000ULL);
235}
236#else
237
238static int map_dirent_attrs(const struct dirent* entry, const char* path, FileAttributes* attr) {
239 struct stat st;
240 if (lstat(path, &st) != 0) return -1;
241
242 attr->size = (size_t)st.st_size;
243 attr->mtime = st.st_mtime;
244 attr->attrs = FATTR_NONE;
245
246 // Check for hidden file based on name
247 if (entry->d_name[0] == '.') {
248 attr->attrs |= FATTR_HIDDEN;
249 }
250
251 // Use standard POSIX macros on st_mode instead of non-standard DT_ constants
252 if (S_ISREG(st.st_mode)) {
253 attr->attrs |= FATTR_FILE;
254 // Optional: Check executable bits here if needed
255 if (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) {
256 attr->attrs |= FATTR_EXECUTABLE;
257 }
258 } else if (S_ISDIR(st.st_mode)) {
259 attr->attrs |= FATTR_DIR;
260 } else if (S_ISLNK(st.st_mode)) {
261 attr->attrs |= FATTR_SYMLINK;
262 }
263#ifdef S_ISCHR
264 else if (S_ISCHR(st.st_mode)) {
265 attr->attrs |= FATTR_CHARDEV;
266 }
267#endif
268#ifdef S_ISBLK
269 else if (S_ISBLK(st.st_mode)) {
270 attr->attrs |= FATTR_BLOCKDEV;
271 }
272#endif
273#ifdef S_ISFIFO
274 else if (S_ISFIFO(st.st_mode)) {
275 attr->attrs |= FATTR_FIFO;
276 }
277#endif
278#ifdef S_ISSOCK
279 else if (S_ISSOCK(st.st_mode)) {
280 attr->attrs |= FATTR_SOCKET;
281 }
282#endif
283 return 0;
284}
285
286#endif
287
288static int delete_single_directory(const char* path) {
289#ifdef _WIN32
290 if (!RemoveDirectoryA(path)) {
291 DWORD err = GetLastError();
292 errno = (err == ERROR_DIR_NOT_EMPTY) ? ENOTEMPTY
293 : (err == ERROR_PATH_NOT_FOUND || err == ERROR_FILE_NOT_FOUND) ? ENOENT
294 : EACCES;
295 return -1;
296 }
297#else
298 if (rmdir(path) == -1) {
299 return -1;
300 }
301#endif
302 return 0;
303}
304
305// Create a directory
306int dir_create(const char* path) {
307 if (!path || *path == '\0') {
308 errno = EINVAL;
309 return -1;
310 }
311
312#ifdef _WIN32
313 if (!CreateDirectoryA(path, NULL)) {
314 if (GetLastError() == ERROR_ALREADY_EXISTS) {
315 return 0;
316 }
317 errno = GetLastError() == ERROR_ACCESS_DENIED ? EACCES : EIO;
318 return -1;
319 }
320#else
321 if (mkdir(path, 0755) == -1) {
322 if (errno == EEXIST) {
323 return 0;
324 }
325 return -1;
326 }
327#endif
328 return 0;
329}
330
338static WalkDirOption dir_remove_callback(const FileAttributes* attr, const char* path, const char* name, void* data) {
339 (void)data;
340 (void)name;
341
342 if (!attr || !path) {
343 errno = EINVAL;
344 return DirError;
345 }
346
347#ifdef _WIN32
348 if (fattr_is_dir(attr)) {
349 if (!RemoveDirectoryA(path)) {
350 DWORD err = GetLastError();
351 errno = (err == ERROR_DIR_NOT_EMPTY) ? ENOTEMPTY
352 : (err == ERROR_PATH_NOT_FOUND || err == ERROR_FILE_NOT_FOUND) ? ENOENT
353 : EACCES;
354 return DirError;
355 }
356 } else {
357 if (!DeleteFileA(path)) {
358 DWORD err = GetLastError();
359 errno = (err == ERROR_PATH_NOT_FOUND || err == ERROR_FILE_NOT_FOUND) ? ENOENT
360 : (err == ERROR_ACCESS_DENIED) ? EACCES
361 : EIO;
362 return DirError;
363 }
364 }
365#else
366 if (fattr_is_dir(attr)) {
367 if (rmdir(path) != 0) {
368 return DirError; // errno already set by rmdir
369 }
370 } else {
371 if (unlink(path) != 0) {
372 return DirError; // errno already set by unlink
373 }
374 }
375#endif
376 return DirContinue;
377}
378
379#define SET_DIR_STATUS(ret) \
380 if ((ret) == DirError) { \
381 status = -1; \
382 break; \
383 } \
384 if ((ret) == DirStop) { \
385 status = 0; \
386 break; \
387 }
388
398int dir_walk_depth_first(const char* path, WalkDirCallback callback, void* data) {
399 if (!path || !callback || *path == '\0') {
400 errno = EINVAL;
401 return -1;
402 }
403
404 Directory* dir = dir_open(path);
405 if (!dir) return -1;
406
407 char fullpath[FILENAME_MAX];
408 int status = 0;
409
410#ifdef _WIN32
411 do {
412 const wchar_t* wname = dir->find_data.cFileName;
413 if (wcscmp(wname, L".") == 0 || wcscmp(wname, L"..") == 0) continue;
414
415 char name[MAX_PATH];
416 WideCharToMultiByte(CP_UTF8, 0, wname, -1, name, MAX_PATH, NULL, NULL);
417 filepath_join_buf(path, name, fullpath, sizeof(fullpath));
418
419 FileAttributes attr;
420 map_win32_attrs(&dir->find_data, &attr);
421
422 if (fattr_is_dir(&attr)) {
423 if (dir_walk_depth_first(fullpath, callback, data) != 0) {
424 status = -1;
425 break;
426 }
427 }
428
429 WalkDirOption opt = callback(&attr, fullpath, name, data);
430 if (opt == DirStop) break;
431 if (opt == DirError) {
432 status = -1;
433 break;
434 }
435 } while (FindNextFileW(dir->handle, &dir->find_data));
436#else
437 struct dirent* entry;
438 while ((entry = readdir(dir->dir)) != NULL) {
439 if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue;
440
441 filepath_join_buf(path, entry->d_name, fullpath, sizeof(fullpath));
442 FileAttributes attr;
443 if (map_dirent_attrs(entry, fullpath, &attr) != 0) continue;
444
445 if (attr.attrs == FATTR_NONE) {
446 populate_file_attrs(fullpath, &attr);
447 }
448
449 if (fattr_is_dir(&attr)) {
450 if (dir_walk_depth_first(fullpath, callback, data) != 0) {
451 status = -1;
452 break;
453 }
454 }
455
456 WalkDirOption opt = callback(&attr, fullpath, entry->d_name, data);
457 if (opt == DirStop) break;
458 if (opt == DirError) {
459 status = -1;
460 break;
461 }
462 }
463#endif
464
465 dir_close(dir);
466 return status;
467}
468
469// Remove a directory, with optional recursive deletion
470// When recursive is true, deletes all files, subdirectories, and symbolic links (POSIX)
471// within path, including empty subdirectories. Does not affect parent directories,
472// as dir_walk skips "." and "..".
473int dir_remove(const char* path, bool recursive) {
474 if (!path || *path == '\0') {
475 errno = EINVAL;
476 return -1;
477 }
478
479 if (recursive) {
480 if (dir_walk_depth_first(path, dir_remove_callback, NULL) != 0) {
481 return -1;
482 };
483 // fallthrough and remove root directory.
484 }
485 return delete_single_directory(path);
486}
487
488// Rename a directory
489int dir_rename(const char* oldpath, const char* newpath) {
490 if (!oldpath || !newpath || *oldpath == '\0' || *newpath == '\0') {
491 errno = EINVAL;
492 return -1;
493 }
494 return rename(oldpath, newpath);
495}
496
497// Change the current working directory
498int dir_chdir(const char* path) {
499 if (!path || *path == '\0') {
500 errno = EINVAL;
501 return -1;
502 }
503 return chdir(path);
504}
505
506// List files in a directory with unified error handling
507char** dir_list(const char* path, size_t* count) {
508 if (!path || !count || *path == '\0') {
509 errno = EINVAL;
510 return NULL;
511 }
512
513 Directory* dir = NULL;
514 char** list = NULL;
515 size_t size = 0;
516 size_t capacity = 10;
517 char* name = NULL;
518
519 dir = dir_open(path);
520 if (!dir) return NULL;
521
522 list = (char**)calloc(capacity, sizeof(char*));
523 if (!list) {
524 goto error;
525 }
526
527 while ((name = dir_next(dir)) != NULL) {
528 if (size >= capacity) {
529 capacity *= 2;
530 char** tmp = (char**)realloc(list, capacity * sizeof(char*));
531 if (!tmp) {
532 goto error;
533 }
534 list = tmp;
535 }
536
537 list[size] = strdup(name);
538 if (!list[size]) {
539 goto error;
540 }
541 size++;
542 }
543
544 dir_close(dir);
545 *count = size;
546 return list;
547
548error:
549 if (list) {
550 // Free all successfully duplicated names
551 for (size_t i = 0; i < size; i++) {
552 free(list[i]);
553 }
554 free(list);
555 }
556
557 if (dir) dir_close(dir);
558
559 return NULL;
560}
561
562// List files with callback
563void dir_list_with_callback(const char* path, void (*callback)(const char* name)) {
564 if (!path || !callback || *path == '\0') return;
565
566 Directory* dir = dir_open(path);
567 if (!dir) {
568 return;
569 }
570
571 char* name = NULL;
572 while ((name = dir_next(dir)) != NULL) {
573 if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) {
574 continue;
575 }
576 callback(name);
577 }
578
579 dir_close(dir);
580}
581
582// Check if path is a directory
583bool is_dir(const char* path) {
584 if (!path || *path == '\0') {
585 return false;
586 }
587
588#ifdef _WIN32
589 DWORD attr = GetFileAttributesA(path);
590 if (attr == INVALID_FILE_ATTRIBUTES) {
591 return false;
592 }
593 return (attr & FILE_ATTRIBUTE_DIRECTORY) != 0;
594#else
595 struct stat st;
596 if (stat(path, &st) != 0) {
597 return false;
598 }
599 return S_ISDIR(st.st_mode);
600#endif
601}
602
603// Check if path is a file
604bool is_file(const char* path) {
605 if (!path || *path == '\0') {
606 return false;
607 }
608
609#ifdef _WIN32
610 DWORD attr = GetFileAttributesA(path);
611 if (attr == INVALID_FILE_ATTRIBUTES) {
612 return false;
613 }
614 return (attr & FILE_ATTRIBUTE_DIRECTORY) == 0;
615#else
616 struct stat st;
617 if (stat(path, &st) != 0) {
618 return false;
619 }
620 return S_ISREG(st.st_mode);
621#endif
622}
623
624// Check if path is a symbolic link
625bool is_symlink(const char* path) {
626 if (!path || *path == '\0') {
627 return false;
628 }
629
630#ifdef _WIN32
631 // Windows supports symbolic links since Vista, but we keep original behavior
632 return false;
633#else
634 struct stat st;
635 if (lstat(path, &st) != 0) {
636 return false;
637 }
638 return S_ISLNK(st.st_mode);
639#endif
640}
641
649int dir_walk(const char* path, WalkDirCallback callback, void* data) {
650 if (!path || !callback || *path == '\0') {
651 errno = EINVAL;
652 return -1;
653 }
654
655 Directory* dir = dir_open(path);
656 if (!dir) return -1;
657
658 char fullpath[FILENAME_MAX];
659 int status = 0;
660
661#ifdef _WIN32
662 // Windows iteration
663 do {
664 const wchar_t* wname = dir->find_data.cFileName;
665 if (wcscmp(wname, L".") == 0 || wcscmp(wname, L"..") == 0) continue;
666
667 char name[MAX_PATH];
668 WideCharToMultiByte(CP_UTF8, 0, wname, -1, name, MAX_PATH, NULL, NULL);
669
670 if (!filepath_join_buf(path, name, fullpath, sizeof(fullpath))) {
671 status = -1;
672 break;
673 }
674
675 FileAttributes attr;
676 map_win32_attrs(&dir->find_data, &attr);
677
678 WalkDirOption opt = callback(&attr, fullpath, name, data);
679 if (opt == DirStop) break;
680 if (opt == DirError) {
681 status = -1;
682 break;
683 }
684 if (opt == DirSkip) continue;
685
686 if (fattr_is_dir(&attr)) {
687 if (dir_walk(fullpath, callback, data) != 0) {
688 status = -1;
689 break;
690 }
691 }
692 } while (FindNextFileW(dir->handle, &dir->find_data));
693#else
694 // POSIX iteration
695 struct dirent* entry;
696 while ((entry = readdir(dir->dir)) != NULL) {
697 if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue;
698
699 if (!filepath_join_buf(path, entry->d_name, fullpath, sizeof(fullpath))) {
700 status = -1;
701 break;
702 }
703
704 FileAttributes attr;
705 if (map_dirent_attrs(entry, fullpath, &attr) != 0) {
706 continue;
707 };
708
709 // Fallback for filesystems that don't support d_type (DT_UNKNOWN)
710 if (attr.attrs == FATTR_NONE) {
711 if (populate_file_attrs(fullpath, &attr) != 0) continue;
712 }
713
714 WalkDirOption opt = callback(&attr, fullpath, entry->d_name, data);
715 if (opt == DirStop) break;
716 if (opt == DirError) {
717 status = -1;
718 break;
719 }
720 if (opt == DirSkip) continue;
721
722 if (fattr_is_dir(&attr)) {
723 if (dir_walk(fullpath, callback, data) != 0) {
724 status = -1;
725 break;
726 }
727 }
728 }
729#endif
730
731 dir_close(dir);
732 return status;
733}
734
735// Callback for directory size calculation
736static inline WalkDirOption dir_size_callback(const FileAttributes* attr, const char* path, const char* name,
737 void* data) {
738 (void)path;
739 (void)name;
740
741 if (!attr || !data) {
742 return DirStop;
743 }
744
745 if (fattr_is_file(attr)) {
746 ssize_t* size = (ssize_t*)data;
747 *size += attr->size;
748 }
749 return DirContinue;
750}
751
752// Get directory size
753ssize_t dir_size(const char* path) {
754 if (!path || *path == '\0') {
755 errno = EINVAL;
756 return -1;
757 }
758
759 ssize_t size = 0;
760 if (dir_walk(path, dir_size_callback, &size) != 0) {
761 return -1;
762 }
763 return size;
764}
765
766// Create directories recursively
767bool filepath_makedirs(const char* path) {
768 if (!path || *path == '\0') {
769 errno = EINVAL;
770 return false;
771 }
772
773 char* temp_path = strdup(path);
774 if (!temp_path) {
775 errno = ENOMEM;
776 return false;
777 }
778
779 char* p = temp_path;
780 while (*p != '\0') {
781 while (*p == '/' || *p == '\\') {
782 p++;
783 }
784 while (*p != '\0' && *p != '/' && *p != '\\') {
785 p++;
786 }
787
788 char old = *p;
789 *p = '\0';
790
791 if (*temp_path != '\0') {
792 if (dir_create(temp_path) != 0) {
793 free(temp_path);
794 return false;
795 }
796 }
797
798 *p = old;
799 if (*p != '\0') {
800 p++;
801 }
802 }
803
804 free(temp_path);
805 return true;
806}
807
808// Get temporary directory
809char* get_tempdir(void) {
810#ifdef _WIN32
811 wchar_t wtemp[MAX_PATH];
812 DWORD ret = GetTempPathW(MAX_PATH, wtemp);
813 if (ret == 0 || ret > MAX_PATH) {
814 errno = EIO;
815 return NULL;
816 }
817
818 char* temp = (char*)malloc(MAX_PATH);
819 if (!temp) {
820 errno = ENOMEM;
821 return NULL;
822 }
823 if (wcstombs(temp, wtemp, MAX_PATH) == (size_t)-1) {
824 free(temp);
825 errno = EINVAL;
826 return NULL;
827 }
828 return temp;
829#else
830 const char* temp = GETENV("TMPDIR");
831 if (!temp) {
832 temp = "/tmp";
833 }
834 char* result = strdup(temp);
835 if (!result) {
836 errno = ENOMEM;
837 return NULL;
838 }
839 return result;
840#endif
841}
842
843// Make a temporary file
844char* make_tempfile(void) {
845 char* tmpdir = get_tempdir();
846 if (!tmpdir) {
847 return NULL;
848 }
849
850 char pattern[TEMP_PREF_PREFIX_LEN + 7] = {0};
851 random_string(pattern, TEMP_PREF_PREFIX_LEN);
852 safe_strlcpy(pattern + TEMP_PREF_PREFIX_LEN, "XXXXXX", 7);
853
854 char* tmpfile = filepath_join(tmpdir, pattern);
855 free(tmpdir);
856 if (!tmpfile) {
857 errno = ENOMEM;
858 return NULL;
859 }
860
861#ifdef _WIN32
862 wchar_t wtmpfile[MAX_PATH];
863 if (mbstowcs(wtmpfile, tmpfile, MAX_PATH) == (size_t)-1) {
864 free(tmpfile);
865 errno = EINVAL;
866 return NULL;
867 }
868
869 int fd = _wcreat(wtmpfile, _S_IREAD | _S_IWRITE);
870 if (fd == -1) {
871 free(tmpfile);
872 return NULL;
873 }
874 _close(fd);
875#else
876 int fd = mkstemp(tmpfile);
877 if (fd == -1) {
878 free(tmpfile);
879 return NULL;
880 }
881 close(fd);
882#endif
883 return tmpfile;
884}
885
886// Make a temporary directory
887char* make_tempdir(void) {
888 char* tmpdir = get_tempdir();
889 if (!tmpdir) {
890 return NULL;
891 }
892
893 char pattern[TEMP_PREF_PREFIX_LEN + 7] = {0};
894 random_string(pattern, TEMP_PREF_PREFIX_LEN);
895 safe_strlcpy(pattern + TEMP_PREF_PREFIX_LEN, "XXXXXX", 7);
896
897 char* tmp = filepath_join(tmpdir, pattern);
898 free(tmpdir);
899 if (!tmp) {
900 errno = ENOMEM;
901 return NULL;
902 }
903
904#ifdef _WIN32
905 wchar_t wtmp[MAX_PATH];
906 if (mbstowcs(wtmp, tmp, MAX_PATH) == (size_t)-1) {
907 free(tmp);
908 errno = EINVAL;
909 return NULL;
910 }
911 if (_wmkdir(wtmp) != 0) {
912 free(tmp);
913 return NULL;
914 }
915#else
916
917 if (mkdtemp(tmp) == NULL) {
918 free(tmp);
919 return NULL;
920 }
921#endif
922 return tmp;
923}
924
925// Check if path exists
926bool path_exists(const char* path) {
927 if (!path || *path == '\0') {
928 errno = EINVAL;
929 return false;
930 }
931
932#ifdef _WIN32
933 wchar_t wpath[MAX_PATH];
934 if (mbstowcs(wpath, path, MAX_PATH) == (size_t)-1) {
935 errno = EINVAL;
936 return false;
937 }
938
939 DWORD attr = GetFileAttributesW(wpath);
940 return attr != INVALID_FILE_ATTRIBUTES;
941#else
942 struct stat st;
943 return stat(path, &st) == 0;
944#endif
945}
946
947// Get basename of path
948void filepath_basename(const char* path, char* basename, size_t size) {
949 if (!path || !basename || size == 0) {
950 if (basename) basename[0] = '\0';
951 return;
952 }
953
954 const char* base = strrchr(path, '/');
955 if (!base) {
956 base = strrchr(path, '\\');
957 }
958 base = base ? base + 1 : path;
959 safe_strlcpy(basename, base, size);
960}
961
962// Get dirname of path
963void filepath_dirname(const char* path, char* dirname, size_t size) {
964 if (!path || !dirname || size == 0) {
965 if (dirname) dirname[0] = '\0';
966 return;
967 }
968
969 const char* base = strrchr(path, '/');
970 if (!base) {
971 base = strrchr(path, '\\');
972 }
973 if (!base) {
974 dirname[0] = '\0';
975 } else {
976 size_t len = (size_t)(base - path);
977 safe_strlcpy(dirname, path, len + 1 < size ? len + 1 : size);
978 }
979}
980
981// Get file extension
982void filepath_extension(const char* path, char* ext, size_t size) {
983 if (!path || !ext || size == 0) {
984 if (ext) ext[0] = '\0';
985 return;
986 }
987
988 const char* dot = strrchr(path, '.');
989 safe_strlcpy(ext, dot ? dot : "", size);
990}
991
992#define BASENAME_MAX 512
993
994// Get filename without extension
995void filepath_nameonly(const char* path, char* name, size_t size) {
996 if (!path || !name || size == 0) {
997 if (name) name[0] = '\0';
998 return;
999 }
1000
1001 char base[BASENAME_MAX] = {0};
1002 filepath_basename(path, base, BASENAME_MAX);
1003 char* dot = strrchr(base, '.');
1004
1005 size_t base_len = strnlen(base, BASENAME_MAX);
1006 size_t source_len = dot ? (size_t)(dot - base) : base_len;
1007
1008 // Don't exceed destination size
1009 size_t to_copy = source_len < size - 1 ? source_len : size - 1;
1010 memcpy(name, base, to_copy);
1011 name[to_copy] = '\0';
1012}
1013
1014// Get absolute path
1015char* filepath_absolute(const char* path) {
1016 if (!path || *path == '\0') {
1017 errno = EINVAL;
1018 return NULL;
1019 }
1020
1021#ifdef _WIN32
1022 char* abs = _fullpath(NULL, path, 0);
1023 if (!abs) {
1024 errno = ENOMEM;
1025 return NULL;
1026 }
1027#else
1028 char* abs = realpath(path, NULL);
1029 if (!abs) {
1030 return NULL;
1031 }
1032#endif
1033 return abs;
1034}
1035
1036// Get current working directory
1037char* get_cwd(void) {
1038 char* cwd = getcwd(NULL, 0);
1039 if (!cwd) {
1040 errno = ENOMEM;
1041 return NULL;
1042 }
1043 return cwd;
1044}
1045
1046// Remove a file
1047int filepath_remove(const char* path) {
1048 if (!path || *path == '\0') {
1049 errno = EINVAL;
1050 return -1;
1051 }
1052
1053#ifdef _WIN32
1054 return _unlink(path);
1055#else
1056 return unlink(path);
1057#endif
1058}
1059
1060// Rename a file
1061int filepath_rename(const char* oldpath, const char* newpath) {
1062 if (!oldpath || !newpath || *oldpath == '\0' || *newpath == '\0') {
1063 errno = EINVAL;
1064 return -1;
1065 }
1066 return rename(oldpath, newpath);
1067}
1068
1069// Get user home directory
1070const char* user_home_dir(void) {
1071#ifdef _WIN32
1072 return GETENV("USERPROFILE");
1073#else
1074 return GETENV("HOME");
1075#endif
1076}
1077
1078// Expand user home directory
1079char* filepath_expanduser(const char* path) {
1080 if (!path) {
1081 errno = EINVAL;
1082 return NULL;
1083 }
1084
1085 if (path[0] != '~') {
1086 return strdup(path);
1087 }
1088
1089 const char* home = user_home_dir();
1090 if (!home) {
1091 errno = ENOENT;
1092 return NULL;
1093 }
1094
1095 size_t pathLen = strlen(path);
1096 bool isHome = pathLen == 1 || (pathLen == 2 && (path[1] == '/' || path[1] == '\\'));
1097 if (isHome) {
1098 return strdup(home);
1099 }
1100
1101 size_t len = strlen(home) + pathLen + 1;
1102 char* expanded = (char*)malloc(len);
1103 if (!expanded) {
1104 errno = ENOMEM;
1105 return NULL;
1106 }
1107
1108 const char* suffix = path + 1;
1109 // Skip leading separator after ~
1110 if (*suffix == '/' || *suffix == '\\') {
1111 suffix++;
1112 }
1113 snprintf(expanded, len, "%s%c%s", home, PATH_SEP, suffix);
1114 return expanded;
1115}
1116
1117// Expand user home directory into buffer
1118bool filepath_expanduser_buf(const char* path, char* expanded, size_t len) {
1119 if (!path || !expanded || len == 0) {
1120 if (expanded) expanded[0] = '\0';
1121 errno = EINVAL;
1122 return false;
1123 }
1124
1125 if (path[0] != '~') {
1126 return safe_strlcpy(expanded, path, len) < len;
1127 }
1128
1129 const char* home = user_home_dir();
1130 if (!home) {
1131 errno = ENOENT;
1132 return false;
1133 }
1134
1135 size_t pathLen = strlen(path);
1136 bool isHome = pathLen == 1 || (pathLen == 2 && (path[1] == '/' || path[1] == '\\'));
1137 if (isHome) {
1138 return safe_strlcpy(expanded, home, len) < len;
1139 }
1140
1141 size_t homeLen = strlen(home);
1142 if (homeLen + pathLen + 1 > len) {
1143 errno = ENAMETOOLONG;
1144 return false;
1145 }
1146
1147#ifdef _WIN32
1148 snprintf(expanded, len, "%s%s%s", home, path[1] == '\\' ? "\\" : "/", path + 1);
1149#else
1150 snprintf(expanded, len, "%s/%s", home, path + 1);
1151#endif
1152 return true;
1153}
1154
1155// Join paths
1156char* filepath_join(const char* path1, const char* path2) {
1157 if (!path1 || !path2) {
1158 errno = EINVAL;
1159 return NULL;
1160 }
1161
1162 size_t len = strlen(path1) + strlen(path2) + 2;
1163 char* joined = (char*)malloc(len);
1164 if (!joined) {
1165 errno = ENOMEM;
1166 return NULL;
1167 }
1168
1169#ifdef _WIN32
1170 snprintf(joined, len, "%s%s%s", path1, strchr(path1, '\\') ? "\\" : "/", path2);
1171#else
1172 snprintf(joined, len, "%s/%s", path1, path2);
1173#endif
1174 return joined;
1175}
1176
1177bool filepath_join_buf(const char* path1, const char* path2, char* abspath, size_t len) {
1178 if (!path1 || !path2 || !abspath || len == 0) {
1179 if (abspath && len > 0) abspath[0] = '\0';
1180 errno = EINVAL;
1181 return false;
1182 }
1183
1184#ifdef _WIN32
1185 // On Windows, decide which separator to use based on what path1 already uses
1186 const char* sep = strchr(path1, '\\') ? "\\" : "/";
1187 int result = snprintf(abspath, len, "%s%s%s", path1, sep, path2);
1188#else
1189 int result = snprintf(abspath, len, "%s/%s", path1, path2);
1190#endif
1191
1192 // Check for encoding error or truncation
1193 if (result < 0 || (size_t)result >= len) {
1194 // Ensure null-termination if truncated (snprintf does this, but good practice to be sure)
1195 abspath[len - 1] = '\0';
1196 errno = ENAMETOOLONG;
1197 return false;
1198 }
1199
1200 return true;
1201}
1202
1203// Split path into directory and filename
1204void filepath_split(const char* path, char* dir, char* name, size_t dir_size, size_t name_size) {
1205 if (!path || !dir || !name || dir_size == 0 || name_size == 0) {
1206 if (dir) dir[0] = '\0';
1207 if (name) name[0] = '\0';
1208 return;
1209 }
1210
1211 const char* p = strrchr(path, '/');
1212 if (!p) {
1213 p = strrchr(path, '\\');
1214 }
1215
1216 if (!p) {
1217 dir[0] = '\0';
1218 safe_strlcpy(name, path, name_size);
1219 } else {
1220 size_t len = (size_t)(p - path);
1221 safe_strlcpy(dir, path, len + 1 < dir_size ? len + 1 : dir_size);
1222 safe_strlcpy(name, p + 1, name_size);
1223 }
1224}
#define GETENV(name)
Definition env.h:35
@ 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
int populate_file_attrs(const char *path, FileAttributes *attr)
Definition file.c:124
bool is_dir(const char *path)
Definition filepath.c:583
WalkDirOption
Definition filepath.h:246
@ DirSkip
Definition filepath.h:249
@ DirError
Definition filepath.h:250
@ DirStop
Definition filepath.h:248
@ DirContinue
Definition filepath.h:247
void filepath_extension(const char *path, char *ext, size_t size)
Definition filepath.c:982
void filepath_dirname(const char *path, char *dirname, size_t size)
Definition filepath.c:963
bool is_file(const char *path)
Definition filepath.c:604
WalkDirOption(* WalkDirCallback)(const FileAttributes *attr, const char *path, const char *name, void *data)
Definition filepath.h:263
void filepath_nameonly(const char *path, char *name, size_t size)
Definition filepath.c:995
int filepath_rename(const char *oldpath, const char *newpath)
Definition filepath.c:1061
WARN_UNUSED_RESULT char * make_tempfile(void)
Definition filepath.c:844
int dir_create(const char *path)
Definition filepath.c:306
int dir_chdir(const char *path)
Definition filepath.c:498
WARN_UNUSED_RESULT Directory * dir_open(const char *path)
Definition filepath.c:118
bool filepath_join_buf(const char *path1, const char *path2, char *abspath, size_t len)
Definition filepath.c:1177
bool path_exists(const char *path)
Definition filepath.c:926
#define PATH_SEP
Definition filepath.h:44
int filepath_remove(const char *path)
Definition filepath.c:1047
int dir_rename(const char *oldpath, const char *newpath)
Definition filepath.c:489
const char * user_home_dir(void)
Definition filepath.c:1070
void dir_list_with_callback(const char *path, void(*callback)(const char *name))
Definition filepath.c:563
WARN_UNUSED_RESULT char * filepath_absolute(const char *path)
Definition filepath.c:1015
WARN_UNUSED_RESULT char * get_cwd(void)
Definition filepath.c:1037
WARN_UNUSED_RESULT char ** dir_list(const char *path, size_t *count)
Definition filepath.c:507
void filepath_basename(const char *path, char *basename, size_t size)
Definition filepath.c:948
bool filepath_makedirs(const char *path)
Definition filepath.c:767
WARN_UNUSED_RESULT char * filepath_join(const char *path1, const char *path2)
Definition filepath.c:1156
ssize_t dir_size(const char *path)
Definition filepath.c:753
WARN_UNUSED_RESULT char * dir_next(Directory *dir)
Definition filepath.c:192
WARN_UNUSED_RESULT char * make_tempdir(void)
Definition filepath.c:887
void dir_close(Directory *dir)
Definition filepath.c:175
void filepath_split(const char *path, char *dir, char *name, size_t dir_size, size_t name_size)
Definition filepath.c:1204
bool is_symlink(const char *path)
Definition filepath.c:625
WARN_UNUSED_RESULT char * filepath_expanduser(const char *path)
Definition filepath.c:1079
int dir_walk(const char *path, WalkDirCallback callback, void *data)
Definition filepath.c:649
WARN_UNUSED_RESULT char * get_tempdir(void)
Definition filepath.c:809
int dir_walk_depth_first(const char *path, WalkDirCallback callback, void *data)
Definition filepath.c:398
bool filepath_expanduser_buf(const char *path, char *expanded, size_t len)
Definition filepath.c:1118
int dir_remove(const char *path, bool recursive)
Definition filepath.c:473
pthread_mutex_t Lock
Definition lock.h:32
int lock_init(Lock *lock)
Definition lock.c:132
int lock_acquire(Lock *lock)
Definition lock.c:145
int lock_release(Lock *lock)
Definition lock.c:168
DIR * dir
Definition filepath.h:71
char * path
Definition filepath.h:65
time_t mtime
Definition file.h:78
size_t size
Definition file.h:75
uint32_t attrs
Definition file.h:72