1#include "../include/filepath.h"
3#include "../include/env.h"
4#include "../include/file.h"
5#include "../include/lock.h"
6#include "../include/wintypes.h"
19#define S_ISREG(m) (((m) & _S_IFMT) == _S_IFREG)
22#define S_ISDIR(m) (((m) & _S_IFMT) == _S_IFDIR)
41#define TEMP_PREF_PREFIX_LEN 12
43static inline size_t safe_strlcpy(
char* dst,
const char* src,
size_t size) {
44 if (!dst || !src || size == 0) {
47 size_t n = strnlen(src, size - 1);
55static void random_string(
char* str,
size_t len) {
56 static Lock rand_lock;
57 static atomic_int initialized = 0;
60 if (!atomic_load(&initialized)) {
62 atomic_store(&initialized, 1);
65 const char charset[] =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
66 const size_t charset_size =
sizeof(charset) - 1;
67 unsigned char buffer[64];
72 size_t bytes_needed = len < 64 ? len : 64;
75 HCRYPTPROV hCryptProv;
76 if (!CryptAcquireContextW(&hCryptProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) {
82 if (!CryptGenRandom(hCryptProv, bytes_needed, buffer)) {
83 CryptReleaseContext(hCryptProv, 0);
88 CryptReleaseContext(hCryptProv, 0);
90 ssize_t bytes_read = -1;
93 bytes_read = getrandom(buffer, bytes_needed, 0);
96 int fd = open(
"/dev/urandom", O_RDONLY);
98 bytes_read = read(fd, buffer, bytes_needed);
102 if (bytes_read != (ssize_t)bytes_needed) {
110 for (
size_t i = 0; i < len; i++) {
111 str[i] = charset[buffer[i % bytes_needed] % charset_size];
119 if (!path || *path ==
'\0') {
130 dir->
path = strdup(path);
138 wchar_t wpath[MAX_PATH];
139 if (!MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, MAX_PATH)) {
146 wchar_t search_path[MAX_PATH + 2];
147 if (swprintf(search_path, MAX_PATH + 2, L
"%ls\\*", wpath) < 0) {
150 errno = ENAMETOOLONG;
154 dir->handle = FindFirstFileW(search_path, &dir->find_data);
155 if (dir->handle == INVALID_HANDLE_VALUE) {
158 errno = GetLastError() == ERROR_FILE_NOT_FOUND ? ENOENT : EACCES;
162 dir->
dir = opendir(path);
164 int saved_errno = errno;
179 if (dir->handle != INVALID_HANDLE_VALUE) {
180 FindClose(dir->handle);
199 if (dir->handle == INVALID_HANDLE_VALUE) {
203 if (FindNextFileW(dir->handle, &dir->find_data)) {
205 WideCharToMultiByte(CP_UTF8, 0, dir->find_data.cFileName, -1, dir->name_buf, MAX_PATH, NULL, NULL);
206 return dir->name_buf;
209 struct dirent* entry = readdir(dir->
dir);
211 return entry->d_name;
218static void map_win32_attrs(
const WIN32_FIND_DATAW* fd,
FileAttributes* attr) {
220 if (fd->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
224 if (fd->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) attr->
attrs |=
FATTR_SYMLINK;
228 attr->
size = ((size_t)fd->nFileSizeHigh << 32) | fd->nFileSizeLow;
232 ull.LowPart = fd->ftLastWriteTime.dwLowDateTime;
233 ull.HighPart = fd->ftLastWriteTime.dwHighDateTime;
234 attr->
mtime = (time_t)((ull.QuadPart - 116444736000000000ULL) / 10000000ULL);
238static int map_dirent_attrs(
const struct dirent* entry,
const char* path,
FileAttributes* attr) {
240 if (lstat(path, &st) != 0)
return -1;
242 attr->
size = (size_t)st.st_size;
243 attr->
mtime = st.st_mtime;
247 if (entry->d_name[0] ==
'.') {
252 if (S_ISREG(st.st_mode)) {
255 if (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) {
258 }
else if (S_ISDIR(st.st_mode)) {
260 }
else if (S_ISLNK(st.st_mode)) {
264 else if (S_ISCHR(st.st_mode)) {
269 else if (S_ISBLK(st.st_mode)) {
274 else if (S_ISFIFO(st.st_mode)) {
279 else if (S_ISSOCK(st.st_mode)) {
288static int delete_single_directory(
const char* path) {
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
298 if (rmdir(path) == -1) {
307 if (!path || *path ==
'\0') {
313 if (!CreateDirectoryA(path, NULL)) {
314 if (GetLastError() == ERROR_ALREADY_EXISTS) {
317 errno = GetLastError() == ERROR_ACCESS_DENIED ? EACCES : EIO;
321 if (mkdir(path, 0755) == -1) {
322 if (errno == EEXIST) {
342 if (!attr || !path) {
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
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
366 if (fattr_is_dir(attr)) {
367 if (rmdir(path) != 0) {
371 if (unlink(path) != 0) {
379#define SET_DIR_STATUS(ret) \
380 if ((ret) == DirError) { \
384 if ((ret) == DirStop) { \
399 if (!path || !callback || *path ==
'\0') {
407 char fullpath[FILENAME_MAX];
412 const wchar_t* wname = dir->find_data.cFileName;
413 if (wcscmp(wname, L
".") == 0 || wcscmp(wname, L
"..") == 0)
continue;
416 WideCharToMultiByte(CP_UTF8, 0, wname, -1, name, MAX_PATH, NULL, NULL);
420 map_win32_attrs(&dir->find_data, &attr);
422 if (fattr_is_dir(&attr)) {
435 }
while (FindNextFileW(dir->handle, &dir->find_data));
437 struct dirent* entry;
438 while ((entry = readdir(dir->
dir)) != NULL) {
439 if (strcmp(entry->d_name,
".") == 0 || strcmp(entry->d_name,
"..") == 0)
continue;
443 if (map_dirent_attrs(entry, fullpath, &attr) != 0)
continue;
449 if (fattr_is_dir(&attr)) {
456 WalkDirOption opt = callback(&attr, fullpath, entry->d_name, data);
474 if (!path || *path ==
'\0') {
485 return delete_single_directory(path);
490 if (!oldpath || !newpath || *oldpath ==
'\0' || *newpath ==
'\0') {
494 return rename(oldpath, newpath);
499 if (!path || *path ==
'\0') {
508 if (!path || !count || *path ==
'\0') {
516 size_t capacity = 10;
520 if (!dir)
return NULL;
522 list = (
char**)calloc(capacity,
sizeof(
char*));
527 while ((name =
dir_next(dir)) != NULL) {
528 if (size >= capacity) {
530 char** tmp = (
char**)realloc(list, capacity *
sizeof(
char*));
537 list[size] = strdup(name);
551 for (
size_t i = 0; i < size; i++) {
564 if (!path || !callback || *path ==
'\0')
return;
572 while ((name =
dir_next(dir)) != NULL) {
573 if (strcmp(name,
".") == 0 || strcmp(name,
"..") == 0) {
584 if (!path || *path ==
'\0') {
589 DWORD attr = GetFileAttributesA(path);
590 if (attr == INVALID_FILE_ATTRIBUTES) {
593 return (attr & FILE_ATTRIBUTE_DIRECTORY) != 0;
596 if (stat(path, &st) != 0) {
599 return S_ISDIR(st.st_mode);
605 if (!path || *path ==
'\0') {
610 DWORD attr = GetFileAttributesA(path);
611 if (attr == INVALID_FILE_ATTRIBUTES) {
614 return (attr & FILE_ATTRIBUTE_DIRECTORY) == 0;
617 if (stat(path, &st) != 0) {
620 return S_ISREG(st.st_mode);
626 if (!path || *path ==
'\0') {
635 if (lstat(path, &st) != 0) {
638 return S_ISLNK(st.st_mode);
650 if (!path || !callback || *path ==
'\0') {
658 char fullpath[FILENAME_MAX];
664 const wchar_t* wname = dir->find_data.cFileName;
665 if (wcscmp(wname, L
".") == 0 || wcscmp(wname, L
"..") == 0)
continue;
668 WideCharToMultiByte(CP_UTF8, 0, wname, -1, name, MAX_PATH, NULL, NULL);
676 map_win32_attrs(&dir->find_data, &attr);
686 if (fattr_is_dir(&attr)) {
687 if (
dir_walk(fullpath, callback, data) != 0) {
692 }
while (FindNextFileW(dir->handle, &dir->find_data));
695 struct dirent* entry;
696 while ((entry = readdir(dir->
dir)) != NULL) {
697 if (strcmp(entry->d_name,
".") == 0 || strcmp(entry->d_name,
"..") == 0)
continue;
705 if (map_dirent_attrs(entry, fullpath, &attr) != 0) {
714 WalkDirOption opt = callback(&attr, fullpath, entry->d_name, data);
722 if (fattr_is_dir(&attr)) {
723 if (
dir_walk(fullpath, callback, data) != 0) {
741 if (!attr || !data) {
745 if (fattr_is_file(attr)) {
746 ssize_t* size = (ssize_t*)data;
754 if (!path || *path ==
'\0') {
760 if (
dir_walk(path, dir_size_callback, &size) != 0) {
768 if (!path || *path ==
'\0') {
773 char* temp_path = strdup(path);
781 while (*p ==
'/' || *p ==
'\\') {
784 while (*p !=
'\0' && *p !=
'/' && *p !=
'\\') {
791 if (*temp_path !=
'\0') {
811 wchar_t wtemp[MAX_PATH];
812 DWORD ret = GetTempPathW(MAX_PATH, wtemp);
813 if (ret == 0 || ret > MAX_PATH) {
818 char* temp = (
char*)malloc(MAX_PATH);
823 if (wcstombs(temp, wtemp, MAX_PATH) == (size_t)-1) {
830 const char* temp =
GETENV(
"TMPDIR");
834 char* result = strdup(temp);
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);
862 wchar_t wtmpfile[MAX_PATH];
863 if (mbstowcs(wtmpfile, tmpfile, MAX_PATH) == (
size_t)-1) {
869 int fd = _wcreat(wtmpfile, _S_IREAD | _S_IWRITE);
876 int fd = mkstemp(tmpfile);
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);
905 wchar_t wtmp[MAX_PATH];
906 if (mbstowcs(wtmp, tmp, MAX_PATH) == (
size_t)-1) {
911 if (_wmkdir(wtmp) != 0) {
917 if (mkdtemp(tmp) == NULL) {
927 if (!path || *path ==
'\0') {
933 wchar_t wpath[MAX_PATH];
934 if (mbstowcs(wpath, path, MAX_PATH) == (
size_t)-1) {
939 DWORD attr = GetFileAttributesW(wpath);
940 return attr != INVALID_FILE_ATTRIBUTES;
943 return stat(path, &st) == 0;
949 if (!path || !basename || size == 0) {
950 if (basename) basename[0] =
'\0';
954 const char* base = strrchr(path,
'/');
956 base = strrchr(path,
'\\');
958 base = base ? base + 1 : path;
959 safe_strlcpy(basename, base, size);
964 if (!path || !dirname || size == 0) {
965 if (dirname) dirname[0] =
'\0';
969 const char* base = strrchr(path,
'/');
971 base = strrchr(path,
'\\');
976 size_t len = (size_t)(base - path);
977 safe_strlcpy(dirname, path, len + 1 < size ? len + 1 : size);
983 if (!path || !ext || size == 0) {
984 if (ext) ext[0] =
'\0';
988 const char* dot = strrchr(path,
'.');
989 safe_strlcpy(ext, dot ? dot :
"", size);
992#define BASENAME_MAX 512
996 if (!path || !name || size == 0) {
997 if (name) name[0] =
'\0';
1001 char base[BASENAME_MAX] = {0};
1003 char* dot = strrchr(base,
'.');
1005 size_t base_len = strnlen(base, BASENAME_MAX);
1006 size_t source_len = dot ? (size_t)(dot - base) : base_len;
1009 size_t to_copy = source_len < size - 1 ? source_len : size - 1;
1010 memcpy(name, base, to_copy);
1011 name[to_copy] =
'\0';
1016 if (!path || *path ==
'\0') {
1022 char* abs = _fullpath(NULL, path, 0);
1028 char* abs = realpath(path, NULL);
1038 char* cwd = getcwd(NULL, 0);
1048 if (!path || *path ==
'\0') {
1054 return _unlink(path);
1056 return unlink(path);
1062 if (!oldpath || !newpath || *oldpath ==
'\0' || *newpath ==
'\0') {
1066 return rename(oldpath, newpath);
1072 return GETENV(
"USERPROFILE");
1085 if (path[0] !=
'~') {
1086 return strdup(path);
1095 size_t pathLen = strlen(path);
1096 bool isHome = pathLen == 1 || (pathLen == 2 && (path[1] ==
'/' || path[1] ==
'\\'));
1098 return strdup(home);
1101 size_t len = strlen(home) + pathLen + 1;
1102 char* expanded = (
char*)malloc(len);
1108 const char* suffix = path + 1;
1110 if (*suffix ==
'/' || *suffix ==
'\\') {
1113 snprintf(expanded, len,
"%s%c%s", home,
PATH_SEP, suffix);
1119 if (!path || !expanded || len == 0) {
1120 if (expanded) expanded[0] =
'\0';
1125 if (path[0] !=
'~') {
1126 return safe_strlcpy(expanded, path, len) < len;
1135 size_t pathLen = strlen(path);
1136 bool isHome = pathLen == 1 || (pathLen == 2 && (path[1] ==
'/' || path[1] ==
'\\'));
1138 return safe_strlcpy(expanded, home, len) < len;
1141 size_t homeLen = strlen(home);
1142 if (homeLen + pathLen + 1 > len) {
1143 errno = ENAMETOOLONG;
1148 snprintf(expanded, len,
"%s%s%s", home, path[1] ==
'\\' ?
"\\" :
"/", path + 1);
1150 snprintf(expanded, len,
"%s/%s", home, path + 1);
1157 if (!path1 || !path2) {
1162 size_t len = strlen(path1) + strlen(path2) + 2;
1163 char* joined = (
char*)malloc(len);
1170 snprintf(joined, len,
"%s%s%s", path1, strchr(path1,
'\\') ?
"\\" :
"/", path2);
1172 snprintf(joined, len,
"%s/%s", path1, path2);
1178 if (!path1 || !path2 || !abspath || len == 0) {
1179 if (abspath && len > 0) abspath[0] =
'\0';
1186 const char* sep = strchr(path1,
'\\') ?
"\\" :
"/";
1187 int result = snprintf(abspath, len,
"%s%s%s", path1, sep, path2);
1189 int result = snprintf(abspath, len,
"%s/%s", path1, path2);
1193 if (result < 0 || (
size_t)result >= len) {
1195 abspath[len - 1] =
'\0';
1196 errno = ENAMETOOLONG;
1205 if (!path || !dir || !name ||
dir_size == 0 || name_size == 0) {
1206 if (dir) dir[0] =
'\0';
1207 if (name) name[0] =
'\0';
1211 const char* p = strrchr(path,
'/');
1213 p = strrchr(path,
'\\');
1218 safe_strlcpy(name, path, name_size);
1220 size_t len = (size_t)(p - path);
1222 safe_strlcpy(name, p + 1, name_size);
int populate_file_attrs(const char *path, FileAttributes *attr)
bool is_dir(const char *path)
void filepath_extension(const char *path, char *ext, size_t size)
void filepath_dirname(const char *path, char *dirname, size_t size)
bool is_file(const char *path)
WalkDirOption(* WalkDirCallback)(const FileAttributes *attr, const char *path, const char *name, void *data)
void filepath_nameonly(const char *path, char *name, size_t size)
int filepath_rename(const char *oldpath, const char *newpath)
WARN_UNUSED_RESULT char * make_tempfile(void)
int dir_create(const char *path)
int dir_chdir(const char *path)
WARN_UNUSED_RESULT Directory * dir_open(const char *path)
bool filepath_join_buf(const char *path1, const char *path2, char *abspath, size_t len)
bool path_exists(const char *path)
int filepath_remove(const char *path)
int dir_rename(const char *oldpath, const char *newpath)
const char * user_home_dir(void)
void dir_list_with_callback(const char *path, void(*callback)(const char *name))
WARN_UNUSED_RESULT char * filepath_absolute(const char *path)
WARN_UNUSED_RESULT char * get_cwd(void)
WARN_UNUSED_RESULT char ** dir_list(const char *path, size_t *count)
void filepath_basename(const char *path, char *basename, size_t size)
bool filepath_makedirs(const char *path)
WARN_UNUSED_RESULT char * filepath_join(const char *path1, const char *path2)
ssize_t dir_size(const char *path)
WARN_UNUSED_RESULT char * dir_next(Directory *dir)
WARN_UNUSED_RESULT char * make_tempdir(void)
void dir_close(Directory *dir)
void filepath_split(const char *path, char *dir, char *name, size_t dir_size, size_t name_size)
bool is_symlink(const char *path)
WARN_UNUSED_RESULT char * filepath_expanduser(const char *path)
int dir_walk(const char *path, WalkDirCallback callback, void *data)
WARN_UNUSED_RESULT char * get_tempdir(void)
int dir_walk_depth_first(const char *path, WalkDirCallback callback, void *data)
bool filepath_expanduser_buf(const char *path, char *expanded, size_t len)
int dir_remove(const char *path, bool recursive)
int lock_init(Lock *lock)
int lock_acquire(Lock *lock)
int lock_release(Lock *lock)