1 | /* |
2 | Copyright (c) 2013-2014, Cong Xu, Baudouin Feildel |
3 | All rights reserved. |
4 | |
5 | Redistribution and use in source and binary forms, with or without |
6 | modification, are permitted provided that the following conditions are met: |
7 | |
8 | 1. Redistributions of source code must retain the above copyright notice, this |
9 | list of conditions and the following disclaimer. |
10 | 2. Redistributions in binary form must reproduce the above copyright notice, |
11 | this list of conditions and the following disclaimer in the documentation |
12 | and/or other materials provided with the distribution. |
13 | |
14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
15 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
16 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR |
18 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
19 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
20 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
21 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
22 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
23 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
24 | */ |
25 | #ifndef TINYDIR_H |
26 | #define TINYDIR_H |
27 | |
28 | #include <errno.h> |
29 | #include <stdlib.h> |
30 | #include <string.h> |
31 | #ifdef _WIN32 |
32 | #define WIN32_LEAN_AND_MEAN |
33 | #include <windows.h> |
34 | #ifdef _MSC_VER |
35 | #pragma warning (disable : 4996) |
36 | #endif |
37 | #else |
38 | #include <dirent.h> |
39 | #include <libgen.h> |
40 | #include <sys/stat.h> |
41 | #endif |
42 | |
43 | |
44 | /* types */ |
45 | |
46 | #define _TINYDIR_PATH_MAX 4096 |
47 | #ifdef _WIN32 |
48 | /* extra chars for the "\\*" mask */ |
49 | #define _TINYDIR_PATH_EXTRA 2 |
50 | #else |
51 | #define 0 |
52 | #endif |
53 | #define _TINYDIR_FILENAME_MAX 256 |
54 | |
55 | #ifdef _MSC_VER |
56 | #define _TINYDIR_FUNC static __inline |
57 | #else |
58 | #define _TINYDIR_FUNC static __inline__ |
59 | #endif |
60 | |
61 | /* Allow user to use a custom allocator by defining _TINYDIR_MALLOC and _TINYDIR_FREE. */ |
62 | #if defined(_TINYDIR_MALLOC) && defined(_TINYDIR_FREE) |
63 | #elif !defined(_TINYDIR_MALLOC) && !defined(_TINYDIR_FREE) |
64 | #else |
65 | #error "Either define both alloc and free or none of them!" |
66 | #endif |
67 | |
68 | #if !defined(_TINYDIR_MALLOC) |
69 | #define _TINYDIR_MALLOC(_size) malloc(_size) |
70 | #define _TINYDIR_FREE(_ptr) free(_ptr) |
71 | #endif //!defined(_TINYDIR_MALLOC) |
72 | |
73 | typedef struct |
74 | { |
75 | char path[_TINYDIR_PATH_MAX]; |
76 | char name[_TINYDIR_FILENAME_MAX]; |
77 | char *extension; |
78 | int is_dir; |
79 | int is_reg; |
80 | |
81 | #ifdef _WIN32 |
82 | #else |
83 | struct stat _s; |
84 | #endif |
85 | } tinydir_file; |
86 | |
87 | typedef struct |
88 | { |
89 | char path[_TINYDIR_PATH_MAX]; |
90 | int has_next; |
91 | size_t n_files; |
92 | |
93 | tinydir_file *_files; |
94 | #ifdef _WIN32 |
95 | HANDLE _h; |
96 | WIN32_FIND_DATAA _f; |
97 | #else |
98 | DIR *_d; |
99 | struct dirent *_e; |
100 | #endif |
101 | } tinydir_dir; |
102 | |
103 | |
104 | /* declarations */ |
105 | |
106 | _TINYDIR_FUNC |
107 | int tinydir_open(tinydir_dir *dir, const char *path); |
108 | _TINYDIR_FUNC |
109 | int tinydir_open_sorted(tinydir_dir *dir, const char *path); |
110 | _TINYDIR_FUNC |
111 | void tinydir_close(tinydir_dir *dir); |
112 | |
113 | _TINYDIR_FUNC |
114 | int tinydir_next(tinydir_dir *dir); |
115 | _TINYDIR_FUNC |
116 | int tinydir_readfile(const tinydir_dir *dir, tinydir_file *file); |
117 | _TINYDIR_FUNC |
118 | int tinydir_readfile_n(const tinydir_dir *dir, tinydir_file *file, size_t i); |
119 | _TINYDIR_FUNC |
120 | int tinydir_open_subdir_n(tinydir_dir *dir, size_t i); |
121 | |
122 | _TINYDIR_FUNC |
123 | void _tinydir_get_ext(tinydir_file *file); |
124 | _TINYDIR_FUNC |
125 | int _tinydir_file_cmp(const void *a, const void *b); |
126 | |
127 | |
128 | /* definitions*/ |
129 | |
130 | _TINYDIR_FUNC |
131 | int tinydir_open(tinydir_dir *dir, const char *path) |
132 | { |
133 | if (dir == NULL || path == NULL || strlen(path) == 0) |
134 | { |
135 | errno = EINVAL; |
136 | return -1; |
137 | } |
138 | if (strlen(path) + _TINYDIR_PATH_EXTRA >= _TINYDIR_PATH_MAX) |
139 | { |
140 | errno = ENAMETOOLONG; |
141 | return -1; |
142 | } |
143 | |
144 | /* initialise dir */ |
145 | dir->_files = NULL; |
146 | #ifdef _WIN32 |
147 | dir->_h = INVALID_HANDLE_VALUE; |
148 | #else |
149 | dir->_d = NULL; |
150 | #endif |
151 | tinydir_close(dir); |
152 | |
153 | strcpy(dir->path, path); |
154 | #ifdef _WIN32 |
155 | strcat(dir->path, "\\*" ); |
156 | dir->_h = FindFirstFileA(dir->path, &dir->_f); |
157 | dir->path[strlen(dir->path) - 2] = '\0'; |
158 | if (dir->_h == INVALID_HANDLE_VALUE) |
159 | #else |
160 | dir->_d = opendir(path); |
161 | if (dir->_d == NULL) |
162 | #endif |
163 | { |
164 | errno = ENOENT; |
165 | goto bail; |
166 | } |
167 | |
168 | /* read first file */ |
169 | dir->has_next = 1; |
170 | #ifndef _WIN32 |
171 | dir->_e = readdir(dir->_d); |
172 | if (dir->_e == NULL) |
173 | { |
174 | dir->has_next = 0; |
175 | } |
176 | #endif |
177 | |
178 | return 0; |
179 | |
180 | bail: |
181 | tinydir_close(dir); |
182 | return -1; |
183 | } |
184 | |
185 | _TINYDIR_FUNC |
186 | int tinydir_open_sorted(tinydir_dir *dir, const char *path) |
187 | { |
188 | /* Count the number of files first, to pre-allocate the files array */ |
189 | size_t n_files = 0; |
190 | if (tinydir_open(dir, path) == -1) |
191 | { |
192 | return -1; |
193 | } |
194 | while (dir->has_next) |
195 | { |
196 | n_files++; |
197 | if (tinydir_next(dir) == -1) |
198 | { |
199 | goto bail; |
200 | } |
201 | } |
202 | tinydir_close(dir); |
203 | |
204 | if (tinydir_open(dir, path) == -1) |
205 | { |
206 | return -1; |
207 | } |
208 | |
209 | dir->n_files = 0; |
210 | dir->_files = (tinydir_file *)_TINYDIR_MALLOC(sizeof *dir->_files * n_files); |
211 | if (dir->_files == NULL) |
212 | { |
213 | errno = ENOMEM; |
214 | goto bail; |
215 | } |
216 | while (dir->has_next) |
217 | { |
218 | tinydir_file *p_file; |
219 | dir->n_files++; |
220 | |
221 | p_file = &dir->_files[dir->n_files - 1]; |
222 | if (tinydir_readfile(dir, p_file) == -1) |
223 | { |
224 | goto bail; |
225 | } |
226 | |
227 | if (tinydir_next(dir) == -1) |
228 | { |
229 | goto bail; |
230 | } |
231 | |
232 | /* Just in case the number of files has changed between the first and |
233 | second reads, terminate without writing into unallocated memory */ |
234 | if (dir->n_files == n_files) |
235 | { |
236 | break; |
237 | } |
238 | } |
239 | |
240 | qsort(dir->_files, dir->n_files, sizeof(tinydir_file), _tinydir_file_cmp); |
241 | |
242 | return 0; |
243 | |
244 | bail: |
245 | tinydir_close(dir); |
246 | return -1; |
247 | } |
248 | |
249 | _TINYDIR_FUNC |
250 | void tinydir_close(tinydir_dir *dir) |
251 | { |
252 | if (dir == NULL) |
253 | { |
254 | return; |
255 | } |
256 | |
257 | memset(dir->path, 0, sizeof(dir->path)); |
258 | dir->has_next = 0; |
259 | dir->n_files = 0; |
260 | if (dir->_files != NULL) |
261 | { |
262 | _TINYDIR_FREE(dir->_files); |
263 | } |
264 | dir->_files = NULL; |
265 | #ifdef _WIN32 |
266 | if (dir->_h != INVALID_HANDLE_VALUE) |
267 | { |
268 | FindClose(dir->_h); |
269 | } |
270 | dir->_h = INVALID_HANDLE_VALUE; |
271 | #else |
272 | if (dir->_d) |
273 | { |
274 | closedir(dir->_d); |
275 | } |
276 | dir->_d = NULL; |
277 | dir->_e = NULL; |
278 | #endif |
279 | } |
280 | |
281 | _TINYDIR_FUNC |
282 | int tinydir_next(tinydir_dir *dir) |
283 | { |
284 | if (dir == NULL) |
285 | { |
286 | errno = EINVAL; |
287 | return -1; |
288 | } |
289 | if (!dir->has_next) |
290 | { |
291 | errno = ENOENT; |
292 | return -1; |
293 | } |
294 | |
295 | #ifdef _WIN32 |
296 | if (FindNextFileA(dir->_h, &dir->_f) == 0) |
297 | #else |
298 | dir->_e = readdir(dir->_d); |
299 | if (dir->_e == NULL) |
300 | #endif |
301 | { |
302 | dir->has_next = 0; |
303 | #ifdef _WIN32 |
304 | if (GetLastError() != ERROR_SUCCESS && |
305 | GetLastError() != ERROR_NO_MORE_FILES) |
306 | { |
307 | tinydir_close(dir); |
308 | errno = EIO; |
309 | return -1; |
310 | } |
311 | #endif |
312 | } |
313 | |
314 | return 0; |
315 | } |
316 | |
317 | _TINYDIR_FUNC |
318 | int tinydir_readfile(const tinydir_dir *dir, tinydir_file *file) |
319 | { |
320 | if (dir == NULL || file == NULL) |
321 | { |
322 | errno = EINVAL; |
323 | return -1; |
324 | } |
325 | #ifdef _WIN32 |
326 | if (dir->_h == INVALID_HANDLE_VALUE) |
327 | #else |
328 | if (dir->_e == NULL) |
329 | #endif |
330 | { |
331 | errno = ENOENT; |
332 | return -1; |
333 | } |
334 | if (strlen(dir->path) + |
335 | strlen( |
336 | #ifdef _WIN32 |
337 | dir->_f.cFileName |
338 | #else |
339 | dir->_e->d_name |
340 | #endif |
341 | ) + 1 + _TINYDIR_PATH_EXTRA >= |
342 | _TINYDIR_PATH_MAX) |
343 | { |
344 | /* the path for the file will be too long */ |
345 | errno = ENAMETOOLONG; |
346 | return -1; |
347 | } |
348 | if (strlen( |
349 | #ifdef _WIN32 |
350 | dir->_f.cFileName |
351 | #else |
352 | dir->_e->d_name |
353 | #endif |
354 | ) >= _TINYDIR_FILENAME_MAX) |
355 | { |
356 | errno = ENAMETOOLONG; |
357 | return -1; |
358 | } |
359 | |
360 | strcpy(file->path, dir->path); |
361 | strcat(file->path, "/" ); |
362 | strcpy(file->name, |
363 | #ifdef _WIN32 |
364 | dir->_f.cFileName |
365 | #else |
366 | dir->_e->d_name |
367 | #endif |
368 | ); |
369 | strcat(file->path, file->name); |
370 | #ifndef _WIN32 |
371 | if (stat(file->path, &file->_s) == -1) |
372 | { |
373 | return -1; |
374 | } |
375 | #endif |
376 | _tinydir_get_ext(file); |
377 | |
378 | file->is_dir = |
379 | #ifdef _WIN32 |
380 | !!(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY); |
381 | #else |
382 | S_ISDIR(file->_s.st_mode); |
383 | #endif |
384 | file->is_reg = |
385 | #ifdef _WIN32 |
386 | !!(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_NORMAL) || |
387 | ( |
388 | !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_DEVICE) && |
389 | !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) && |
390 | !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED) && |
391 | #ifdef FILE_ATTRIBUTE_INTEGRITY_STREAM |
392 | !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_INTEGRITY_STREAM) && |
393 | #endif |
394 | #ifdef FILE_ATTRIBUTE_NO_SCRUB_DATA |
395 | !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_NO_SCRUB_DATA) && |
396 | #endif |
397 | !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_OFFLINE) && |
398 | !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY)); |
399 | #else |
400 | S_ISREG(file->_s.st_mode); |
401 | #endif |
402 | |
403 | return 0; |
404 | } |
405 | |
406 | _TINYDIR_FUNC |
407 | int tinydir_readfile_n(const tinydir_dir *dir, tinydir_file *file, size_t i) |
408 | { |
409 | if (dir == NULL || file == NULL) |
410 | { |
411 | errno = EINVAL; |
412 | return -1; |
413 | } |
414 | if (i >= dir->n_files) |
415 | { |
416 | errno = ENOENT; |
417 | return -1; |
418 | } |
419 | |
420 | memcpy(file, &dir->_files[i], sizeof(tinydir_file)); |
421 | _tinydir_get_ext(file); |
422 | |
423 | return 0; |
424 | } |
425 | |
426 | _TINYDIR_FUNC |
427 | int tinydir_open_subdir_n(tinydir_dir *dir, size_t i) |
428 | { |
429 | char path[_TINYDIR_PATH_MAX]; |
430 | if (dir == NULL) |
431 | { |
432 | errno = EINVAL; |
433 | return -1; |
434 | } |
435 | if (i >= dir->n_files || !dir->_files[i].is_dir) |
436 | { |
437 | errno = ENOENT; |
438 | return -1; |
439 | } |
440 | |
441 | strcpy(path, dir->_files[i].path); |
442 | tinydir_close(dir); |
443 | if (tinydir_open_sorted(dir, path) == -1) |
444 | { |
445 | return -1; |
446 | } |
447 | |
448 | return 0; |
449 | } |
450 | |
451 | /* Open a single file given its path */ |
452 | _TINYDIR_FUNC |
453 | int tinydir_file_open(tinydir_file *file, const char *path) |
454 | { |
455 | tinydir_dir dir; |
456 | int result = 0; |
457 | int found = 0; |
458 | char dir_name_buf[_TINYDIR_PATH_MAX]; |
459 | char file_name_buf[_TINYDIR_FILENAME_MAX]; |
460 | char *dir_name; |
461 | char *base_name; |
462 | #ifdef _WIN32 |
463 | char drive_buf[_TINYDIR_PATH_MAX]; |
464 | char ext_buf[_TINYDIR_FILENAME_MAX]; |
465 | #endif |
466 | |
467 | if (file == NULL || path == NULL || strlen(path) == 0) |
468 | { |
469 | errno = EINVAL; |
470 | return -1; |
471 | } |
472 | if (strlen(path) + _TINYDIR_PATH_EXTRA >= _TINYDIR_PATH_MAX) |
473 | { |
474 | errno = ENAMETOOLONG; |
475 | return -1; |
476 | } |
477 | |
478 | /* Get the parent path */ |
479 | #ifdef _WIN32 |
480 | if (_splitpath_s( |
481 | path, |
482 | drive_buf, sizeof drive_buf, |
483 | dir_name_buf, sizeof dir_name_buf, |
484 | file_name_buf, sizeof file_name_buf, |
485 | ext_buf, sizeof ext_buf)) |
486 | { |
487 | errno = EINVAL; |
488 | return -1; |
489 | } |
490 | /* Concatenate the drive letter and dir name to form full dir name */ |
491 | strcat(drive_buf, dir_name_buf); |
492 | dir_name = drive_buf; |
493 | /* Concatenate the file name and extension to form base name */ |
494 | strcat(file_name_buf, ext_buf); |
495 | base_name = file_name_buf; |
496 | #else |
497 | strcpy(dir_name_buf, path); |
498 | dir_name = dirname(dir_name_buf); |
499 | strcpy(file_name_buf, path); |
500 | base_name = basename(file_name_buf); |
501 | #endif |
502 | |
503 | /* Open the parent directory */ |
504 | if (tinydir_open(&dir, dir_name) == -1) |
505 | { |
506 | return -1; |
507 | } |
508 | |
509 | /* Read through the parent directory and look for the file */ |
510 | while (dir.has_next) |
511 | { |
512 | if (tinydir_readfile(&dir, file) == -1) |
513 | { |
514 | result = -1; |
515 | goto bail; |
516 | } |
517 | if (strcmp(file->name, base_name) == 0) |
518 | { |
519 | /* File found */ |
520 | found = 1; |
521 | goto bail; |
522 | } |
523 | tinydir_next(&dir); |
524 | } |
525 | if (!found) |
526 | { |
527 | result = -1; |
528 | errno = ENOENT; |
529 | } |
530 | |
531 | bail: |
532 | tinydir_close(&dir); |
533 | return result; |
534 | } |
535 | |
536 | _TINYDIR_FUNC |
537 | void _tinydir_get_ext(tinydir_file *file) |
538 | { |
539 | char *period = strrchr(file->name, '.'); |
540 | if (period == NULL) |
541 | { |
542 | file->extension = &(file->name[strlen(file->name)]); |
543 | } |
544 | else |
545 | { |
546 | file->extension = period + 1; |
547 | } |
548 | } |
549 | |
550 | _TINYDIR_FUNC |
551 | int _tinydir_file_cmp(const void *a, const void *b) |
552 | { |
553 | const tinydir_file *fa = (const tinydir_file *)a; |
554 | const tinydir_file *fb = (const tinydir_file *)b; |
555 | if (fa->is_dir != fb->is_dir) |
556 | { |
557 | return -(fa->is_dir - fb->is_dir); |
558 | } |
559 | return strncmp(fa->name, fb->name, _TINYDIR_FILENAME_MAX); |
560 | } |
561 | |
562 | #endif |
563 | |