mirror of
https://github.com/odin-lang/Odin.git
synced 2025-12-29 09:24:33 +00:00
518 lines
12 KiB
C++
518 lines
12 KiB
C++
/*
|
|
Path handling utilities.
|
|
*/
|
|
#if !defined(GB_SYSTEM_WINDOWS)
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
gb_internal String remove_extension_from_path(String const &s) {
|
|
if (s.len != 0 && s.text[s.len-1] == '.') {
|
|
return s;
|
|
}
|
|
for (isize i = s.len-1; i >= 0; i--) {
|
|
if (s[i] == '.') {
|
|
return substring(s, 0, i);
|
|
}
|
|
}
|
|
return s;
|
|
}
|
|
|
|
gb_internal String remove_directory_from_path(String const &s) {
|
|
isize len = 0;
|
|
for (isize i = s.len-1; i >= 0; i--) {
|
|
if (s[i] == '/' ||
|
|
s[i] == '\\') {
|
|
break;
|
|
}
|
|
len += 1;
|
|
}
|
|
return substring(s, s.len-len, s.len);
|
|
}
|
|
|
|
|
|
#if defined(GB_SYSTEM_WINDOWS)
|
|
gb_global SRWLOCK cwd_lock;
|
|
|
|
String get_working_directory(gbAllocator allocator) {
|
|
AcquireSRWLockExclusive(&cwd_lock);
|
|
|
|
TEMPORARY_ALLOCATOR_GUARD();
|
|
|
|
DWORD sz_utf16 = GetCurrentDirectoryW(0, nullptr);
|
|
wchar_t *dir_buf_wstr = gb_alloc_array(temporary_allocator(), wchar_t, sz_utf16);
|
|
if (dir_buf_wstr == nullptr) {
|
|
ReleaseSRWLockExclusive(&cwd_lock);
|
|
return {};
|
|
}
|
|
|
|
DWORD n = GetCurrentDirectoryW(sz_utf16, dir_buf_wstr);
|
|
GB_ASSERT(n+1 == sz_utf16);
|
|
ReleaseSRWLockExclusive(&cwd_lock);
|
|
|
|
|
|
isize buf_len = sz_utf16*4;
|
|
u8 *buf = gb_alloc_array(allocator, u8, buf_len);
|
|
gb_ucs2_to_utf8(buf, buf_len, cast(u16 *)dir_buf_wstr);
|
|
|
|
return make_string_c(cast(char const *)buf);
|
|
}
|
|
|
|
bool set_working_directory(String dir) {
|
|
bool ok = false;
|
|
TEMPORARY_ALLOCATOR_GUARD();
|
|
|
|
char const *cdir = alloc_cstring(temporary_allocator(), dir);
|
|
wchar_t *wstr = gb__alloc_utf8_to_ucs2(temporary_allocator(), cdir, nullptr);
|
|
|
|
AcquireSRWLockExclusive(&cwd_lock);
|
|
|
|
ok = SetCurrentDirectoryW(wstr);
|
|
|
|
ReleaseSRWLockExclusive(&cwd_lock);
|
|
|
|
return ok;
|
|
}
|
|
|
|
#else
|
|
|
|
String get_working_directory(gbAllocator allocator) {
|
|
TEMPORARY_ALLOCATOR_GUARD();
|
|
|
|
auto buf = array_make<char>(temporary_allocator());
|
|
size_t size = PATH_MAX;
|
|
|
|
char const *cwd = nullptr;
|
|
for (; cwd == nullptr; size *= 2) {
|
|
array_resize(&buf, size);
|
|
|
|
cwd = getcwd(buf.data, buf.count);
|
|
if (cwd == nullptr && errno != ERANGE) {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
return copy_string(allocator, make_string_c(cwd));
|
|
}
|
|
|
|
bool set_working_directory(String dir) {
|
|
TEMPORARY_ALLOCATOR_GUARD();
|
|
char const *cdir = alloc_cstring(temporary_allocator(), dir);
|
|
return !chdir(cdir);
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
gb_internal bool path_is_directory(String path);
|
|
|
|
gb_internal String directory_from_path(String const &s) {
|
|
if (path_is_directory(s)) {
|
|
return s;
|
|
}
|
|
|
|
isize i = s.len-1;
|
|
for (; i >= 0; i--) {
|
|
if (s[i] == '/' ||
|
|
s[i] == '\\') {
|
|
break;
|
|
}
|
|
}
|
|
if (i >= 0) {
|
|
return substring(s, 0, i);
|
|
}
|
|
return substring(s, 0, 0);
|
|
}
|
|
|
|
#if defined(GB_SYSTEM_WINDOWS)
|
|
gb_internal bool path_is_directory(String path) {
|
|
gbAllocator a = heap_allocator();
|
|
String16 wstr = string_to_string16(a, path);
|
|
defer (gb_free(a, wstr.text));
|
|
|
|
i32 attribs = GetFileAttributesW(wstr.text);
|
|
if (attribs < 0) return false;
|
|
|
|
return (attribs & FILE_ATTRIBUTE_DIRECTORY) != 0;
|
|
}
|
|
|
|
#else
|
|
gb_internal bool path_is_directory(String path) {
|
|
gbAllocator a = heap_allocator();
|
|
char *copy = cast(char *)copy_string(a, path).text;
|
|
defer (gb_free(a, copy));
|
|
|
|
struct stat s;
|
|
if (stat(copy, &s) == 0) {
|
|
return (s.st_mode & S_IFDIR) != 0;
|
|
}
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
|
|
gb_internal String path_to_full_path(gbAllocator a, String path) {
|
|
gbAllocator ha = heap_allocator();
|
|
char *path_c = gb_alloc_str_len(ha, cast(char *)path.text, path.len);
|
|
defer (gb_free(ha, path_c));
|
|
|
|
char *fullpath = gb_path_get_full_name(a, path_c);
|
|
String res = string_trim_whitespace(make_string_c(fullpath));
|
|
#if defined(GB_SYSTEM_WINDOWS)
|
|
for (isize i = 0; i < res.len; i++) {
|
|
if (res.text[i] == '\\') {
|
|
res.text[i] = '/';
|
|
}
|
|
}
|
|
#endif
|
|
return copy_string(a, res);
|
|
}
|
|
|
|
struct Path {
|
|
String basename;
|
|
String name;
|
|
String ext;
|
|
};
|
|
|
|
// NOTE(Jeroen): Naively turns a Path into a string.
|
|
gb_internal String path_to_string(gbAllocator a, Path path) {
|
|
if (path.basename.len + path.name.len + path.ext.len == 0) {
|
|
return make_string(nullptr, 0);
|
|
}
|
|
|
|
isize len = path.basename.len + 1 + path.name.len + 1;
|
|
if (path.ext.len > 0) {
|
|
len += path.ext.len + 1;
|
|
}
|
|
|
|
u8 *str = gb_alloc_array(a, u8, len);
|
|
|
|
isize i = 0;
|
|
gb_memmove(str+i, path.basename.text, path.basename.len); i += path.basename.len;
|
|
|
|
gb_memmove(str+i, "/", 1); i += 1;
|
|
|
|
gb_memmove(str+i, path.name.text, path.name.len); i += path.name.len;
|
|
if (path.ext.len > 0) {
|
|
gb_memmove(str+i, ".", 1); i += 1;
|
|
gb_memmove(str+i, path.ext.text, path.ext.len); i += path.ext.len;
|
|
}
|
|
str[i] = 0;
|
|
|
|
String res = make_string(str, i);
|
|
res = string_trim_whitespace(res);
|
|
return res;
|
|
}
|
|
|
|
gb_internal String quote_path(gbAllocator a, Path path) {
|
|
String temp = path_to_string(a, path);
|
|
String quoted = concatenate3_strings(a, str_lit("\""), temp, str_lit("\""));
|
|
gb_free(a, temp.text);
|
|
return quoted;
|
|
}
|
|
|
|
// NOTE(Jeroen): Naively turns a Path into a string, then normalizes it using `path_to_full_path`.
|
|
gb_internal String path_to_full_path(gbAllocator a, Path path) {
|
|
String temp = path_to_string(heap_allocator(), path);
|
|
defer (gb_free(heap_allocator(), temp.text));
|
|
|
|
return path_to_full_path(a, temp);
|
|
}
|
|
|
|
// NOTE(Jeroen): Takes a path like "odin" or "W:\Odin", turns it into a full path,
|
|
// and then breaks it into its components to make a Path.
|
|
gb_internal Path path_from_string(gbAllocator a, String const &path) {
|
|
Path res = {};
|
|
|
|
if (path.len == 0) return res;
|
|
|
|
String fullpath = path_to_full_path(a, path);
|
|
defer (gb_free(heap_allocator(), fullpath.text));
|
|
|
|
res.basename = directory_from_path(fullpath);
|
|
res.basename = copy_string(a, res.basename);
|
|
|
|
if (path_is_directory(fullpath)) {
|
|
// It's a directory. We don't need to tinker with the name and extension.
|
|
// It could have a superfluous trailing `/`. Remove it if so.
|
|
if (res.basename.len > 0 && res.basename.text[res.basename.len - 1] == '/') {
|
|
res.basename.len--;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
// Note(Dragos): Is the copy_string required if it's a substring?
|
|
isize name_start = (res.basename.len > 0) ? res.basename.len + 1 : res.basename.len;
|
|
res.name = substring(fullpath, name_start, fullpath.len);
|
|
res.name = remove_extension_from_path(res.name);
|
|
res.name = copy_string(a, res.name);
|
|
|
|
res.ext = path_extension(fullpath, false); // false says not to include the dot.
|
|
res.ext = copy_string(a, res.ext);
|
|
return res;
|
|
}
|
|
|
|
// NOTE(Jeroen): Takes a path String and returns the last path element.
|
|
gb_internal String last_path_element(String const &path) {
|
|
isize count = 0;
|
|
u8 * start = (u8 *)(&path.text[path.len - 1]);
|
|
for (isize length = path.len; length > 0 && path.text[length - 1] != '/'; length--) {
|
|
count++;
|
|
start--;
|
|
}
|
|
if (count > 0) {
|
|
start++; // Advance past the `/` and return the substring.
|
|
String res = make_string(start, count);
|
|
return res;
|
|
}
|
|
// Must be a root path like `/` or `C:/`, return empty String.
|
|
return STR_LIT("");
|
|
}
|
|
|
|
gb_internal bool path_is_directory(Path path) {
|
|
String path_string = path_to_full_path(heap_allocator(), path);
|
|
defer (gb_free(heap_allocator(), path_string.text));
|
|
|
|
return path_is_directory(path_string);
|
|
}
|
|
|
|
struct FileInfo {
|
|
String name;
|
|
String fullpath;
|
|
i64 size;
|
|
bool is_dir;
|
|
};
|
|
|
|
enum ReadDirectoryError {
|
|
ReadDirectory_None,
|
|
|
|
ReadDirectory_InvalidPath,
|
|
ReadDirectory_NotExists,
|
|
ReadDirectory_Permission,
|
|
ReadDirectory_NotDir,
|
|
ReadDirectory_Empty,
|
|
ReadDirectory_Unknown,
|
|
|
|
ReadDirectory_COUNT,
|
|
};
|
|
|
|
gb_internal i64 get_file_size(String path) {
|
|
char *c_str = alloc_cstring(heap_allocator(), path);
|
|
defer (gb_free(heap_allocator(), c_str));
|
|
|
|
gbFile f = {};
|
|
gbFileError err = gb_file_open(&f, c_str);
|
|
defer (gb_file_close(&f));
|
|
if (err != gbFileError_None) {
|
|
return -1;
|
|
}
|
|
return gb_file_size(&f);
|
|
}
|
|
|
|
|
|
#if defined(GB_SYSTEM_WINDOWS)
|
|
gb_internal ReadDirectoryError read_directory(String path, Array<FileInfo> *fi) {
|
|
GB_ASSERT(fi != nullptr);
|
|
|
|
|
|
while (path.len > 0) {
|
|
Rune end = path[path.len-1];
|
|
if (end == '/') {
|
|
path.len -= 1;
|
|
} else if (end == '\\') {
|
|
path.len -= 1;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (path.len == 0) {
|
|
return ReadDirectory_InvalidPath;
|
|
}
|
|
{
|
|
char *c_str = alloc_cstring(temporary_allocator(), path);
|
|
gbFile f = {};
|
|
gbFileError file_err = gb_file_open(&f, c_str);
|
|
defer (gb_file_close(&f));
|
|
|
|
switch (file_err) {
|
|
case gbFileError_Invalid: return ReadDirectory_InvalidPath;
|
|
case gbFileError_NotExists: return ReadDirectory_NotExists;
|
|
// case gbFileError_Permission: return ReadDirectory_Permission;
|
|
}
|
|
}
|
|
|
|
if (!path_is_directory(path)) {
|
|
return ReadDirectory_NotDir;
|
|
}
|
|
|
|
|
|
gbAllocator a = heap_allocator();
|
|
char *new_path = gb_alloc_array(a, char, path.len+3);
|
|
defer (gb_free(a, new_path));
|
|
|
|
gb_memmove(new_path, path.text, path.len);
|
|
gb_memmove(new_path+path.len, "/*", 2);
|
|
new_path[path.len+2] = 0;
|
|
|
|
String np = make_string(cast(u8 *)new_path, path.len+2);
|
|
String16 wstr = string_to_string16(a, np);
|
|
defer (gb_free(a, wstr.text));
|
|
|
|
WIN32_FIND_DATAW file_data = {};
|
|
HANDLE find_file = FindFirstFileW(wstr.text, &file_data);
|
|
if (find_file == INVALID_HANDLE_VALUE) {
|
|
return ReadDirectory_Unknown;
|
|
}
|
|
defer (FindClose(find_file));
|
|
|
|
array_init(fi, a, 0, 100);
|
|
|
|
do {
|
|
wchar_t *filename_w = file_data.cFileName;
|
|
u64 size = cast(u64)file_data.nFileSizeLow;
|
|
size |= (cast(u64)file_data.nFileSizeHigh) << 32;
|
|
String name = string16_to_string(a, make_string16_c(filename_w));
|
|
if (name == "." || name == "..") {
|
|
gb_free(a, name.text);
|
|
continue;
|
|
}
|
|
|
|
String filepath = {};
|
|
filepath.len = path.len+1+name.len;
|
|
filepath.text = gb_alloc_array(a, u8, filepath.len+1);
|
|
defer (gb_free(a, filepath.text));
|
|
gb_memmove(filepath.text, path.text, path.len);
|
|
gb_memmove(filepath.text+path.len, "/", 1);
|
|
gb_memmove(filepath.text+path.len+1, name.text, name.len);
|
|
|
|
FileInfo info = {};
|
|
info.name = name;
|
|
info.fullpath = path_to_full_path(a, filepath);
|
|
info.size = cast(i64)size;
|
|
info.is_dir = (file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
|
|
array_add(fi, info);
|
|
} while (FindNextFileW(find_file, &file_data));
|
|
|
|
if (fi->count == 0) {
|
|
return ReadDirectory_Empty;
|
|
}
|
|
|
|
return ReadDirectory_None;
|
|
}
|
|
#elif defined(GB_SYSTEM_LINUX) || defined(GB_SYSTEM_OSX) || defined(GB_SYSTEM_FREEBSD) || defined(GB_SYSTEM_OPENBSD) || defined(GB_SYSTEM_NETBSD) || defined(GB_SYSTEM_HAIKU)
|
|
|
|
#include <dirent.h>
|
|
|
|
gb_internal ReadDirectoryError read_directory(String path, Array<FileInfo> *fi) {
|
|
GB_ASSERT(fi != nullptr);
|
|
|
|
gbAllocator a = heap_allocator();
|
|
|
|
char *c_path = alloc_cstring(a, path);
|
|
defer (gb_free(a, c_path));
|
|
|
|
DIR *dir = opendir(c_path);
|
|
if (!dir) {
|
|
switch (errno) {
|
|
case ENOENT:
|
|
return ReadDirectory_NotExists;
|
|
case EACCES:
|
|
return ReadDirectory_Permission;
|
|
case ENOTDIR:
|
|
return ReadDirectory_NotDir;
|
|
default:
|
|
// ENOMEM: out of memory
|
|
// EMFILE: per-process limit on open fds reached
|
|
// ENFILE: system-wide limit on total open files reached
|
|
return ReadDirectory_Unknown;
|
|
}
|
|
GB_PANIC("unreachable");
|
|
}
|
|
|
|
array_init(fi, a, 0, 100);
|
|
|
|
for (;;) {
|
|
struct dirent *entry = readdir(dir);
|
|
if (entry == nullptr) {
|
|
break;
|
|
}
|
|
|
|
String name = make_string_c(entry->d_name);
|
|
if (name == "." || name == "..") {
|
|
continue;
|
|
}
|
|
|
|
String filepath = {};
|
|
filepath.len = path.len+1+name.len;
|
|
filepath.text = gb_alloc_array(a, u8, filepath.len+1);
|
|
defer (gb_free(a, filepath.text));
|
|
gb_memmove(filepath.text, path.text, path.len);
|
|
gb_memmove(filepath.text+path.len, "/", 1);
|
|
gb_memmove(filepath.text+path.len+1, name.text, name.len);
|
|
filepath.text[filepath.len] = 0;
|
|
|
|
|
|
struct stat dir_stat = {};
|
|
|
|
if (stat((char *)filepath.text, &dir_stat)) {
|
|
continue;
|
|
}
|
|
|
|
i64 size = dir_stat.st_size;
|
|
|
|
FileInfo info = {};
|
|
info.name = copy_string(a, name);
|
|
info.fullpath = path_to_full_path(a, filepath);
|
|
info.size = size;
|
|
info.is_dir = S_ISDIR(dir_stat.st_mode);
|
|
array_add(fi, info);
|
|
}
|
|
|
|
if (fi->count == 0) {
|
|
return ReadDirectory_Empty;
|
|
}
|
|
|
|
return ReadDirectory_None;
|
|
}
|
|
|
|
|
|
#else
|
|
#error Implement read_directory
|
|
#endif
|
|
|
|
#if !defined(GB_SYSTEM_WINDOWS)
|
|
gb_internal bool write_directory(String path) {
|
|
char const *pathname = (char *) path.text;
|
|
|
|
if (access(pathname, W_OK) < 0) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
#else
|
|
gb_internal bool write_directory(String path) {
|
|
String16 wstr = string_to_string16(heap_allocator(), path);
|
|
LPCWSTR wdirectory_name = wstr.text;
|
|
|
|
HANDLE directory = CreateFileW(wdirectory_name,
|
|
GENERIC_WRITE,
|
|
0,
|
|
NULL,
|
|
OPEN_EXISTING,
|
|
FILE_FLAG_BACKUP_SEMANTICS,
|
|
NULL);
|
|
|
|
if (directory == INVALID_HANDLE_VALUE) {
|
|
DWORD error_code = GetLastError();
|
|
if (error_code == ERROR_ACCESS_DENIED) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
CloseHandle(directory);
|
|
return true;
|
|
}
|
|
#endif
|