fs.c 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631
  1. // Public Domain.
  2. #include <stdarg.h>
  3. #include <stdlib.h>
  4. #include <stddef.h> // offsetof
  5. #include <stdio.h> // fprintf, fopen et al.
  6. #include <string.h> // strerror
  7. #include <ctype.h> // isspace
  8. #include <errno.h>
  9. #include <fnmatch.h> // fnmatch
  10. #include <sys/types.h> // opendir
  11. #include <dirent.h> // readdir
  12. #include <unistd.h> // pathconf
  13. #include <sys/stat.h>
  14. #include <wordexp.h>
  15. #include "fs.h"
  16. // given a longer name so as to not conflict with other things
  17. // handles ~ properly
  18. // returns 0 for false, 1 for true, and 0 on any error
  19. int is_path_a_dir(char* path) {
  20. int ret;
  21. struct stat sb;
  22. if(!path) return 0;
  23. if(path[0] == '~') {
  24. char* homedir, *tmp;
  25. homedir = getenv("HOME");
  26. tmp = path_join(homedir, path + 1);
  27. ret = stat(tmp, &sb);
  28. free(tmp);
  29. }
  30. else {
  31. ret = stat(path, &sb);
  32. }
  33. if(ret) return 0;
  34. if(sb.st_mode & S_IFDIR) return 1;
  35. return 0;
  36. }
  37. // handles ~ properly
  38. // returns 0 for false, 1 for true, and 0 on any error
  39. int is_regular_file(char* path) {
  40. int ret;
  41. struct stat sb;
  42. if(!path) return 0;
  43. if(path[0] == '~') {
  44. char* homedir, *tmp;
  45. homedir = getenv("HOME");
  46. tmp = path_join(homedir, path + 1);
  47. ret = stat(tmp, &sb);
  48. free(tmp);
  49. }
  50. else {
  51. ret = stat(path, &sb);
  52. }
  53. if(ret) return 0;
  54. if(sb.st_mode & S_IFREG) return 1;
  55. return 0;
  56. }
  57. // returns negative on error, nonzero if scanning was halted by the callback
  58. int recurse_dirs(
  59. char* path,
  60. readDirCallbackFn fn,
  61. void* data,
  62. int depth,
  63. unsigned int flags
  64. ) {
  65. DIR* derp;
  66. struct dirent* result;
  67. int stop = 0;
  68. if(fn == NULL) {
  69. fprintf(stderr, "Error: readAllDir called with null function pointer.\n");
  70. return -1;
  71. }
  72. derp = opendir(path);
  73. if(derp == NULL) {
  74. fprintf(stderr, "Error opening directory '%s': %s\n", path, strerror(errno));
  75. return -1;
  76. }
  77. while((result = readdir(derp)) && !stop) {
  78. char* n = result->d_name;
  79. unsigned char type = DT_UNKNOWN;
  80. char* fullPath = NULL;
  81. // skip self and parent dir entries
  82. if(n[0] == '.') {
  83. if(n[1] == '.' && n[2] == 0) continue;
  84. if(n[1] == 0) continue;
  85. if(flags & FSU_EXCLUDE_HIDDEN) continue;
  86. }
  87. #ifdef _DIRENT_HAVE_D_TYPE
  88. type = result->d_type; // the way life should be
  89. #else
  90. // do some slow extra bullshit to get the type
  91. fullPath = path_join(path, n);
  92. struct stat upgrade_your_fs;
  93. lstat(fullPath, &upgrade_your_fs);
  94. if(S_ISREG(upgrade_your_fs.st_mode)) type = DT_REG;
  95. else if(S_ISDIR(upgrade_your_fs.st_mode)) type = DT_DIR;
  96. else if(S_ISLNK(upgrade_your_fs.st_mode)) type = DT_LNK;
  97. #endif
  98. if(flags & FSU_NO_FOLLOW_SYMLINKS && type == DT_LNK) {
  99. if(fullPath) free(fullPath);
  100. continue;
  101. }
  102. #ifdef _DIRENT_HAVE_D_TYPE
  103. fullPath = path_join(path, n);
  104. #endif
  105. if(type == DT_DIR) {
  106. if(flags & FSU_INCLUDE_DIRS) {
  107. stop = fn(fullPath, n, data);
  108. }
  109. if(!stop && depth > 0) {
  110. stop |= recurse_dirs(fullPath, fn, data, depth - 1, flags);
  111. }
  112. }
  113. else if(type == DT_REG) {
  114. if(!(flags & FSU_EXCLUDE_FILES)) {
  115. stop = fn(fullPath, n, data);
  116. }
  117. }
  118. free(fullPath);
  119. }
  120. closedir(derp);
  121. return stop;
  122. }
  123. char* path_join_(size_t nargs, ...) {
  124. size_t total = 0;
  125. char* out, *end;
  126. size_t j_len;
  127. char* joiner = "/";
  128. int escape = 0;
  129. if(nargs == 0) return NULL;
  130. // calculate total buffer length
  131. va_list va;
  132. va_start(va, nargs);
  133. for(size_t i = 0; i < nargs; i++) {
  134. char* s = va_arg(va, char*);
  135. if(s) total += strlen(s);
  136. }
  137. va_end(va);
  138. j_len = strlen(joiner);
  139. total += j_len * (nargs - 1);
  140. out = malloc((total + 1) * sizeof(char*));
  141. end = out;
  142. va_start(va, nargs);
  143. for(size_t i = 0; i < nargs; i++) {
  144. char* s = va_arg(va, char*);
  145. size_t l = strlen(s);
  146. if(s) {
  147. if(l > 1) {
  148. escape = s[l-2] == '\\' ? 1 : 0;
  149. }
  150. if(i > 0 && (s[0] == joiner[0])) {
  151. s++;
  152. l--;
  153. }
  154. if(i > 0 && i != nargs-1 && !escape && (s[l-1] == joiner[0])) {
  155. l--;
  156. }
  157. if(i > 0) {
  158. strcpy(end, joiner);
  159. end += j_len;
  160. }
  161. // should be strncpy, but GCC is so fucking stupid that it
  162. // has a warning about using strncpy to do exactly what
  163. // strncpy does if you read the fucking man page.
  164. // fortunately, we are already terminating our strings
  165. // manually so memcpy is a drop-in replacement here.
  166. memcpy(end, s, l);
  167. end += l;
  168. }
  169. }
  170. va_end(va);
  171. *end = 0;
  172. return out;
  173. }
  174. // gets a pointer to the first character of the file extension, or to the null terminator if none
  175. char* path_ext(char* path) {
  176. int i;
  177. int len = strlen(path);
  178. for(i = len - 1; i >= 0; i--) {
  179. char c = path[i];
  180. if(c == '.') return path + i + 1;
  181. else if(c == '/') break;
  182. }
  183. return path + len;
  184. }
  185. // gets a pointer to the first character of the file extension, or to the null terminator if none
  186. // also provides the length of the path without the period and extension
  187. char* path_ext2(char* path, int* end) {
  188. int i;
  189. int len = strlen(path);
  190. for(i = len - 1; i >= 0; i--) {
  191. char c = path[i];
  192. if(c == '.') {
  193. if(end) *end = i > 0 ? i : 0;
  194. return path + i + 1;
  195. }
  196. else if(c == '/') break;
  197. }
  198. if(end) *end = len;
  199. return path + len;
  200. }
  201. // returns a null terminated string. srcLen does NOT include the null terminator
  202. // nulls inside the string are not escaped or removed; the first null is not
  203. // necessarily the terminating null
  204. char* read_whole_file(char* path, size_t* srcLen) {
  205. return readWholeFileExtra(path, 0, srcLen);
  206. }
  207. // reserves extra space in memory just in case you want to append a \n or something
  208. // srcLen reflects the length of the content, not the allocation
  209. char* read_whole_file_extra(char* path, size_t extraAlloc, size_t* srcLen) {
  210. size_t fsize, total_read = 0, bytes_read;
  211. char* contents;
  212. FILE* f;
  213. f = fopen(path, "rb");
  214. if(!f) {
  215. // fprintf(stderr, "Could not open file \"%s\"\n", path);
  216. return NULL;
  217. }
  218. fseek(f, 0, SEEK_END);
  219. fsize = ftell(f);
  220. rewind(f);
  221. contents = malloc(fsize + extraAlloc + 1);
  222. while(total_read < fsize) {
  223. bytes_read = fread(contents + total_read, sizeof(char), fsize - total_read, f);
  224. total_read += bytes_read;
  225. }
  226. contents[fsize] = 0;
  227. fclose(f);
  228. if(srcLen) *srcLen = fsize + 1;
  229. return contents;
  230. }
  231. int write_whole_file(char* path, void* data, size_t len) {
  232. size_t total_written = 0, bytes_written;
  233. FILE* f;
  234. f = fopen(path, "wb");
  235. if(!f) {
  236. // fprintf(stderr, "Could not open file \"%s\"\n", path);
  237. return 1;
  238. }
  239. while(total_written < len) {
  240. bytes_written = fwrite(data + total_written, sizeof(char), len - total_written, f);
  241. total_written += bytes_written;
  242. }
  243. fclose(f);
  244. return 0;
  245. }
  246. // returns a list of the relative file names
  247. char** read_whole_dir(char* path, unsigned int flags, size_t* outLen) {
  248. DIR* derp;
  249. struct dirent* result;
  250. derp = opendir(path);
  251. if(derp == NULL) {
  252. //fprintf(stderr, "Error opening directory '%s': %s\n", path, strerror(errno));
  253. return NULL;
  254. }
  255. size_t on = 0;
  256. size_t oalloc = 16;
  257. char** o = malloc(sizeof(*o) * oalloc);
  258. while(result = readdir(derp)) {
  259. char* n = result->d_name;
  260. unsigned char type = DT_UNKNOWN;
  261. // skip self and parent dir entries
  262. if(n[0] == '.') {
  263. if(n[1] == '.' && n[2] == 0) continue;
  264. if(n[1] == 0) continue;
  265. if(flags & FSU_EXCLUDE_HIDDEN) continue;
  266. }
  267. #ifdef _DIRENT_HAVE_D_TYPE
  268. type = result->d_type; // the way life should be
  269. #else
  270. struct stat upgrade_your_fs;
  271. lstat(n, &upgrade_your_fs);
  272. if(S_ISREG(upgrade_your_fs.st_mode)) type = DT_REG;
  273. else if(S_ISDIR(upgrade_your_fs.st_mode)) type = DT_DIR;
  274. else if(S_ISLNK(upgrade_your_fs.st_mode)) type = DT_LNK;
  275. #endif
  276. if((flags & FSU_NO_FOLLOW_SYMLINKS) && (type == DT_LNK)) {
  277. continue;
  278. }
  279. if(
  280. (type == DT_DIR && (flags & FSU_INCLUDE_DIRS)) ||
  281. (type == DT_REG && !(flags & FSU_EXCLUDE_FILES))
  282. ) {
  283. if(on > oalloc - 1) {
  284. oalloc *= 2;
  285. o = realloc(o, sizeof(*o) * oalloc);
  286. }
  287. o[on++] = strdup(n);
  288. }
  289. }
  290. closedir(derp);
  291. o[on] = NULL;
  292. if(outLen) *outLen = on;
  293. return o;
  294. }
  295. // returns a list of the absolute file names
  296. char** read_whole_dir_abs(char* path, unsigned int flags, size_t* outLen) {
  297. DIR* derp;
  298. struct dirent* result;
  299. char* abspath = resolve_path(path);
  300. if(!abspath) {
  301. if(outLen) *outLen = 0;
  302. return NULL;
  303. }
  304. derp = opendir(abspath);
  305. if(derp == NULL) {
  306. fprintf(stderr, "Error opening directory '%s': %s\n", path, strerror(errno));
  307. return NULL;
  308. }
  309. size_t on = 0;
  310. size_t oalloc = 16;
  311. char** o = malloc(sizeof(*o) * oalloc);
  312. while(result = readdir(derp)) {
  313. char* n = result->d_name;
  314. unsigned char type = DT_UNKNOWN;
  315. char* fullPath = NULL;
  316. // skip self and parent dir entries
  317. if(n[0] == '.') {
  318. if(n[1] == '.' && n[2] == 0) continue;
  319. if(n[1] == 0) continue;
  320. if(flags & FSU_EXCLUDE_HIDDEN) continue;
  321. }
  322. #ifdef _DIRENT_HAVE_D_TYPE
  323. type = result->d_type; // the way life should be
  324. #else
  325. // do some slow extra bullshit to get the type
  326. fullPath = path_join(abspath, n);
  327. struct stat upgrade_your_fs;
  328. lstat(fullPath, &upgrade_your_fs);
  329. if(S_ISREG(upgrade_your_fs.st_mode)) type = DT_REG;
  330. else if(S_ISDIR(upgrade_your_fs.st_mode)) type = DT_DIR;
  331. else if(S_ISLNK(upgrade_your_fs.st_mode)) type = DT_LNK;
  332. #endif
  333. if((flags & FSU_NO_FOLLOW_SYMLINKS) && (type == DT_LNK)) {
  334. if(fullPath) free(fullPath);
  335. continue;
  336. }
  337. #ifdef _DIRENT_HAVE_D_TYPE
  338. fullPath = path_join(abspath, n);
  339. #endif
  340. if(
  341. (type == DT_DIR && (flags & FSU_INCLUDE_DIRS)) ||
  342. (type == DT_REG && !(flags & FSU_EXCLUDE_FILES))
  343. ) {
  344. if(on > oalloc - 1) {
  345. oalloc *= 2;
  346. o = realloc(o, sizeof(*o) * oalloc);
  347. }
  348. o[on++] = fullPath;
  349. }
  350. }
  351. free(abspath);
  352. closedir(derp);
  353. o[on] = NULL;
  354. if(outLen) *outLen = on;
  355. return o;
  356. }
  357. // works like realpath(), except also handles ~/
  358. char* resolve_path(char* in) {
  359. int tmp_was_malloced = 0;
  360. char* out, *tmp;
  361. if(!in) return NULL;
  362. // skip leading whitespace
  363. while(isspace(*in)) in++;
  364. // handle home dir shorthand
  365. if(in[0] == '~') {
  366. char* home = getenv("HOME");
  367. tmp_was_malloced = 1;
  368. tmp = malloc(sizeof(*tmp) * (strlen(home) + strlen(in) + 2));
  369. strcpy(tmp, home);
  370. strcat(tmp, "/"); // just in case
  371. strcat(tmp, in + 1);
  372. }
  373. else tmp = in;
  374. out = realpath(tmp, NULL);
  375. if(tmp_was_malloced) free(tmp);
  376. return out;
  377. }
  378. // works like wordexp, except accepts a list of ;-separated paths
  379. // and returns an array of char*'s, all allocated with normal malloc
  380. char** multi_wordexp_dup(char* input, size_t* out_len) {
  381. char** out;
  382. wordexp_t p;
  383. char* in = input;
  384. char* head = input;
  385. size_t alloc = 128;
  386. char* buf = malloc(alloc * sizeof(*buf));
  387. int flags = WRDE_NOCMD;
  388. while(1) {
  389. if(*in == ';' || *in == 0) {
  390. size_t len = in - head;
  391. if(len) {
  392. if(len + 1 > alloc) {
  393. alloc *= 2;
  394. if(alloc < len + 1) alloc = len + 1;
  395. buf = realloc(buf, alloc * sizeof(*buf));
  396. }
  397. strncpy(buf, head, len);
  398. buf[len] = 0;
  399. wordexp(buf, &p, flags);
  400. flags |= WRDE_APPEND;
  401. }
  402. head = in + 1;
  403. }
  404. else if(*in == '\\') {
  405. in++;
  406. }
  407. if(!*in) break;
  408. in++;
  409. };
  410. // fill the output array
  411. out = malloc((p.we_wordc + 1) * sizeof(*out));
  412. out[p.we_wordc] = NULL;
  413. for(unsigned int i = 0; i < p.we_wordc; i++) {
  414. out[i] = strdup(p.we_wordv[i]);
  415. }
  416. if(out_len) *out_len = p.we_wordc;
  417. free(buf);
  418. wordfree(&p);
  419. return out;
  420. }
  421. static int rglob_fn(char* full_path, char* file_name, void* _results) {
  422. rglob* res = (rglob*)_results;
  423. if(0 == fnmatch(res->pattern, file_name, 0)) {
  424. if(res->len >= res->alloc) {
  425. res->alloc *= 2;
  426. res->entries = realloc(res->entries, sizeof(*res->entries) * res->alloc);
  427. }
  428. res->entries[res->len].type = -1;
  429. res->entries[res->len].full_path = strdup(full_path);
  430. res->entries[res->len].file_name = strdup(file_name);
  431. res->len++;
  432. }
  433. return 0;
  434. }
  435. void recursive_glob(char* base_path, char* pattern, int flags, rglob* results) {
  436. // to pass into recurse_dirs()
  437. results->pattern = pattern;
  438. results->len = 0;
  439. results->alloc = 32;
  440. results->entries = malloc(sizeof(*results->entries) * results->alloc);
  441. recurse_dirs(base_path, rglob_fn, results, -1, flags);
  442. }