path.cpp 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. /*
  2. Path handling utilities.
  3. */
  4. String remove_extension_from_path(String const &s) {
  5. for (isize i = s.len-1; i >= 0; i--) {
  6. if (s[i] == '.') {
  7. return substring(s, 0, i);
  8. }
  9. }
  10. return s;
  11. }
  12. String remove_directory_from_path(String const &s) {
  13. isize len = 0;
  14. for (isize i = s.len-1; i >= 0; i--) {
  15. if (s[i] == '/' ||
  16. s[i] == '\\') {
  17. break;
  18. }
  19. len += 1;
  20. }
  21. return substring(s, s.len-len, s.len);
  22. }
  23. bool path_is_directory(String path);
  24. String directory_from_path(String const &s) {
  25. if (path_is_directory(s)) {
  26. return s;
  27. }
  28. isize i = s.len-1;
  29. for (; i >= 0; i--) {
  30. if (s[i] == '/' ||
  31. s[i] == '\\') {
  32. break;
  33. }
  34. }
  35. if (i >= 0) {
  36. return substring(s, 0, i);
  37. }
  38. return substring(s, 0, 0);
  39. }
  40. #if defined(GB_SYSTEM_WINDOWS)
  41. bool path_is_directory(String path) {
  42. gbAllocator a = heap_allocator();
  43. String16 wstr = string_to_string16(a, path);
  44. defer (gb_free(a, wstr.text));
  45. i32 attribs = GetFileAttributesW(wstr.text);
  46. if (attribs < 0) return false;
  47. return (attribs & FILE_ATTRIBUTE_DIRECTORY) != 0;
  48. }
  49. #else
  50. bool path_is_directory(String path) {
  51. gbAllocator a = heap_allocator();
  52. char *copy = cast(char *)copy_string(a, path).text;
  53. defer (gb_free(a, copy));
  54. struct stat s;
  55. if (stat(copy, &s) == 0) {
  56. return (s.st_mode & S_IFDIR) != 0;
  57. }
  58. return false;
  59. }
  60. #endif
  61. String path_to_full_path(gbAllocator a, String path) {
  62. gbAllocator ha = heap_allocator();
  63. char *path_c = gb_alloc_str_len(ha, cast(char *)path.text, path.len);
  64. defer (gb_free(ha, path_c));
  65. char *fullpath = gb_path_get_full_name(a, path_c);
  66. String res = string_trim_whitespace(make_string_c(fullpath));
  67. #if defined(GB_SYSTEM_WINDOWS)
  68. for (isize i = 0; i < res.len; i++) {
  69. if (res.text[i] == '\\') {
  70. res.text[i] = '/';
  71. }
  72. }
  73. #endif
  74. return copy_string(a, res);
  75. }
  76. struct Path {
  77. String basename;
  78. String name;
  79. String ext;
  80. };
  81. // NOTE(Jeroen): Naively turns a Path into a string.
  82. String path_to_string(gbAllocator a, Path path) {
  83. if (path.basename.len + path.name.len + path.ext.len == 0) {
  84. return make_string(nullptr, 0);
  85. }
  86. isize len = path.basename.len + 1 + path.name.len + 1;
  87. if (path.ext.len > 0) {
  88. len += path.ext.len + 1;
  89. }
  90. u8 *str = gb_alloc_array(a, u8, len);
  91. isize i = 0;
  92. gb_memmove(str+i, path.basename.text, path.basename.len); i += path.basename.len;
  93. gb_memmove(str+i, "/", 1); i += 1;
  94. gb_memmove(str+i, path.name.text, path.name.len); i += path.name.len;
  95. if (path.ext.len > 0) {
  96. gb_memmove(str+i, ".", 1); i += 1;
  97. gb_memmove(str+i, path.ext.text, path.ext.len); i += path.ext.len;
  98. }
  99. str[i] = 0;
  100. String res = make_string(str, i);
  101. res = string_trim_whitespace(res);
  102. return res;
  103. }
  104. // NOTE(Jeroen): Naively turns a Path into a string, then normalizes it using `path_to_full_path`.
  105. String path_to_full_path(gbAllocator a, Path path) {
  106. String temp = path_to_string(heap_allocator(), path);
  107. defer (gb_free(heap_allocator(), temp.text));
  108. return path_to_full_path(a, temp);
  109. }
  110. // NOTE(Jeroen): Takes a path like "odin" or "W:\Odin", turns it into a full path,
  111. // and then breaks it into its components to make a Path.
  112. Path path_from_string(gbAllocator a, String const &path) {
  113. Path res = {};
  114. if (path.len == 0) return res;
  115. String fullpath = path_to_full_path(a, path);
  116. defer (gb_free(heap_allocator(), fullpath.text));
  117. res.basename = directory_from_path(fullpath);
  118. res.basename = copy_string(a, res.basename);
  119. if (path_is_directory(fullpath)) {
  120. // It's a directory. We don't need to tinker with the name and extension.
  121. // It could have a superfluous trailing `/`. Remove it if so.
  122. if (res.basename.len > 0 && res.basename.text[res.basename.len - 1] == '/') {
  123. res.basename.len--;
  124. }
  125. return res;
  126. }
  127. isize name_start = (res.basename.len > 0) ? res.basename.len + 1 : res.basename.len;
  128. res.name = substring(fullpath, name_start, fullpath.len);
  129. res.name = remove_extension_from_path(res.name);
  130. res.name = copy_string(a, res.name);
  131. res.ext = path_extension(fullpath, false); // false says not to include the dot.
  132. res.ext = copy_string(a, res.ext);
  133. return res;
  134. }
  135. // NOTE(Jeroen): Takes a path String and returns the last path element.
  136. String last_path_element(String const &path) {
  137. isize count = 0;
  138. u8 * start = (u8 *)(&path.text[path.len - 1]);
  139. for (isize length = path.len; length > 0 && path.text[length - 1] != '/'; length--) {
  140. count++;
  141. start--;
  142. }
  143. if (count > 0) {
  144. start++; // Advance past the `/` and return the substring.
  145. String res = make_string(start, count);
  146. return res;
  147. }
  148. // Must be a root path like `/` or `C:/`, return empty String.
  149. return STR_LIT("");
  150. }
  151. bool path_is_directory(Path path) {
  152. String path_string = path_to_full_path(heap_allocator(), path);
  153. defer (gb_free(heap_allocator(), path_string.text));
  154. return path_is_directory(path_string);
  155. }
  156. struct FileInfo {
  157. String name;
  158. String fullpath;
  159. i64 size;
  160. bool is_dir;
  161. };
  162. enum ReadDirectoryError {
  163. ReadDirectory_None,
  164. ReadDirectory_InvalidPath,
  165. ReadDirectory_NotExists,
  166. ReadDirectory_Permission,
  167. ReadDirectory_NotDir,
  168. ReadDirectory_Empty,
  169. ReadDirectory_Unknown,
  170. ReadDirectory_COUNT,
  171. };
  172. i64 get_file_size(String path) {
  173. char *c_str = alloc_cstring(heap_allocator(), path);
  174. defer (gb_free(heap_allocator(), c_str));
  175. gbFile f = {};
  176. gbFileError err = gb_file_open(&f, c_str);
  177. defer (gb_file_close(&f));
  178. if (err != gbFileError_None) {
  179. return -1;
  180. }
  181. return gb_file_size(&f);
  182. }
  183. #if defined(GB_SYSTEM_WINDOWS)
  184. ReadDirectoryError read_directory(String path, Array<FileInfo> *fi) {
  185. GB_ASSERT(fi != nullptr);
  186. gbAllocator a = heap_allocator();
  187. while (path.len > 0) {
  188. Rune end = path[path.len-1];
  189. if (end == '/') {
  190. path.len -= 1;
  191. } else if (end == '\\') {
  192. path.len -= 1;
  193. } else {
  194. break;
  195. }
  196. }
  197. if (path.len == 0) {
  198. return ReadDirectory_InvalidPath;
  199. }
  200. {
  201. char *c_str = alloc_cstring(a, path);
  202. defer (gb_free(a, c_str));
  203. gbFile f = {};
  204. gbFileError file_err = gb_file_open(&f, c_str);
  205. defer (gb_file_close(&f));
  206. switch (file_err) {
  207. case gbFileError_Invalid: return ReadDirectory_InvalidPath;
  208. case gbFileError_NotExists: return ReadDirectory_NotExists;
  209. // case gbFileError_Permission: return ReadDirectory_Permission;
  210. }
  211. }
  212. if (!path_is_directory(path)) {
  213. return ReadDirectory_NotDir;
  214. }
  215. char *new_path = gb_alloc_array(a, char, path.len+3);
  216. defer (gb_free(a, new_path));
  217. gb_memmove(new_path, path.text, path.len);
  218. gb_memmove(new_path+path.len, "/*", 2);
  219. new_path[path.len+2] = 0;
  220. String np = make_string(cast(u8 *)new_path, path.len+2);
  221. String16 wstr = string_to_string16(a, np);
  222. defer (gb_free(a, wstr.text));
  223. WIN32_FIND_DATAW file_data = {};
  224. HANDLE find_file = FindFirstFileW(wstr.text, &file_data);
  225. if (find_file == INVALID_HANDLE_VALUE) {
  226. return ReadDirectory_Unknown;
  227. }
  228. defer (FindClose(find_file));
  229. array_init(fi, a, 0, 100);
  230. do {
  231. wchar_t *filename_w = file_data.cFileName;
  232. i64 size = cast(i64)file_data.nFileSizeLow;
  233. size |= (cast(i64)file_data.nFileSizeHigh) << 32;
  234. String name = string16_to_string(a, make_string16_c(filename_w));
  235. if (name == "." || name == "..") {
  236. gb_free(a, name.text);
  237. continue;
  238. }
  239. String filepath = {};
  240. filepath.len = path.len+1+name.len;
  241. filepath.text = gb_alloc_array(a, u8, filepath.len+1);
  242. defer (gb_free(a, filepath.text));
  243. gb_memmove(filepath.text, path.text, path.len);
  244. gb_memmove(filepath.text+path.len, "/", 1);
  245. gb_memmove(filepath.text+path.len+1, name.text, name.len);
  246. FileInfo info = {};
  247. info.name = name;
  248. info.fullpath = path_to_full_path(a, filepath);
  249. info.size = size;
  250. info.is_dir = (file_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
  251. array_add(fi, info);
  252. } while (FindNextFileW(find_file, &file_data));
  253. if (fi->count == 0) {
  254. return ReadDirectory_Empty;
  255. }
  256. return ReadDirectory_None;
  257. }
  258. #elif defined(GB_SYSTEM_LINUX) || defined(GB_SYSTEM_OSX) || defined(GB_SYSTEM_FREEBSD) || defined(GB_SYSTEM_OPENBSD)
  259. #include <dirent.h>
  260. ReadDirectoryError read_directory(String path, Array<FileInfo> *fi) {
  261. GB_ASSERT(fi != nullptr);
  262. gbAllocator a = heap_allocator();
  263. char *c_path = alloc_cstring(a, path);
  264. defer (gb_free(a, c_path));
  265. DIR *dir = opendir(c_path);
  266. if (!dir) {
  267. switch (errno) {
  268. case ENOENT:
  269. return ReadDirectory_NotExists;
  270. case EACCES:
  271. return ReadDirectory_Permission;
  272. case ENOTDIR:
  273. return ReadDirectory_NotDir;
  274. default:
  275. // ENOMEM: out of memory
  276. // EMFILE: per-process limit on open fds reached
  277. // ENFILE: system-wide limit on total open files reached
  278. return ReadDirectory_Unknown;
  279. }
  280. GB_PANIC("unreachable");
  281. }
  282. array_init(fi, a, 0, 100);
  283. for (;;) {
  284. struct dirent *entry = readdir(dir);
  285. if (entry == nullptr) {
  286. break;
  287. }
  288. String name = make_string_c(entry->d_name);
  289. if (name == "." || name == "..") {
  290. continue;
  291. }
  292. String filepath = {};
  293. filepath.len = path.len+1+name.len;
  294. filepath.text = gb_alloc_array(a, u8, filepath.len+1);
  295. defer (gb_free(a, filepath.text));
  296. gb_memmove(filepath.text, path.text, path.len);
  297. gb_memmove(filepath.text+path.len, "/", 1);
  298. gb_memmove(filepath.text+path.len+1, name.text, name.len);
  299. filepath.text[filepath.len] = 0;
  300. struct stat dir_stat = {};
  301. if (stat((char *)filepath.text, &dir_stat)) {
  302. continue;
  303. }
  304. if (S_ISDIR(dir_stat.st_mode)) {
  305. continue;
  306. }
  307. i64 size = dir_stat.st_size;
  308. FileInfo info = {};
  309. info.name = name;
  310. info.fullpath = path_to_full_path(a, filepath);
  311. info.size = size;
  312. array_add(fi, info);
  313. }
  314. if (fi->count == 0) {
  315. return ReadDirectory_Empty;
  316. }
  317. return ReadDirectory_None;
  318. }
  319. #else
  320. #error Implement read_directory
  321. #endif