1/*
2Copyright (c) 2013-2014, Cong Xu, Baudouin Feildel
3All rights reserved.
4
5Redistribution and use in source and binary forms, with or without
6modification, are permitted provided that the following conditions are met:
7
81. Redistributions of source code must retain the above copyright notice, this
9 list of conditions and the following disclaimer.
102. 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
14THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
15ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
16WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
18ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
19(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
21ON 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
23SOFTWARE, 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 _TINYDIR_PATH_EXTRA 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
73typedef 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
87typedef 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
107int tinydir_open(tinydir_dir *dir, const char *path);
108_TINYDIR_FUNC
109int tinydir_open_sorted(tinydir_dir *dir, const char *path);
110_TINYDIR_FUNC
111void tinydir_close(tinydir_dir *dir);
112
113_TINYDIR_FUNC
114int tinydir_next(tinydir_dir *dir);
115_TINYDIR_FUNC
116int tinydir_readfile(const tinydir_dir *dir, tinydir_file *file);
117_TINYDIR_FUNC
118int tinydir_readfile_n(const tinydir_dir *dir, tinydir_file *file, size_t i);
119_TINYDIR_FUNC
120int tinydir_open_subdir_n(tinydir_dir *dir, size_t i);
121
122_TINYDIR_FUNC
123void _tinydir_get_ext(tinydir_file *file);
124_TINYDIR_FUNC
125int _tinydir_file_cmp(const void *a, const void *b);
126
127
128/* definitions*/
129
130_TINYDIR_FUNC
131int 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
180bail:
181 tinydir_close(dir);
182 return -1;
183}
184
185_TINYDIR_FUNC
186int 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
244bail:
245 tinydir_close(dir);
246 return -1;
247}
248
249_TINYDIR_FUNC
250void 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
282int 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
318int 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
407int 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
427int 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
453int 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
531bail:
532 tinydir_close(&dir);
533 return result;
534}
535
536_TINYDIR_FUNC
537void _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
551int _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