1 /*
2 Copyright (c) 2013-2017, tinydir authors:
3 - Cong Xu
4 - Lautis Sun
5 - Baudouin Feildel
6 - Andargor <andargor@yahoo.com>
7 All rights reserved.
8 
9 Redistribution and use in source and binary forms, with or without
10 modification, are permitted provided that the following conditions are met:
11 
12 1. Redistributions of source code must retain the above copyright notice, this
13    list of conditions and the following disclaimer.
14 2. Redistributions in binary form must reproduce the above copyright notice,
15    this list of conditions and the following disclaimer in the documentation
16    and/or other materials provided with the distribution.
17 
18 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
19 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
22 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
27 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29 #ifndef TINYDIR_H
30 #define TINYDIR_H
31 
32 #ifdef __cplusplus
33 extern "C" {
34 #endif
35 
36 #if ((defined _UNICODE) && !(defined UNICODE))
37 #define UNICODE
38 #endif
39 
40 #if ((defined UNICODE) && !(defined _UNICODE))
41 #define _UNICODE
42 #endif
43 
44 #include <errno.h>
45 #include <stdlib.h>
46 #include <string.h>
47 #ifdef _MSC_VER
48 # define WIN32_LEAN_AND_MEAN
49 # include <windows.h>
50 # include <tchar.h>
51 # pragma warning(push)
52 # pragma warning (disable : 4996)
53 #else
54 # include <dirent.h>
55 # include <libgen.h>
56 # include <sys/stat.h>
57 # include <stddef.h>
58 #endif
59 #ifdef __MINGW32__
60 # include <tchar.h>
61 #endif
62 
63 
64 /* types */
65 
66 /* Windows UNICODE wide character support */
67 #if defined _MSC_VER || defined __MINGW32__
68 # define _tinydir_char_t TCHAR
69 # define TINYDIR_STRING(s) _TEXT(s)
70 # define _tinydir_strlen _tcslen
71 # define _tinydir_strcpy _tcscpy
72 # define _tinydir_strcat _tcscat
73 # define _tinydir_strcmp _tcscmp
74 # define _tinydir_strrchr _tcsrchr
75 # define _tinydir_strncmp _tcsncmp
76 #else
77 # define _tinydir_char_t char
78 # define TINYDIR_STRING(s) s
79 # define _tinydir_strlen strlen
80 # define _tinydir_strcpy strcpy
81 # define _tinydir_strcat strcat
82 # define _tinydir_strcmp strcmp
83 # define _tinydir_strrchr strrchr
84 # define _tinydir_strncmp strncmp
85 #endif
86 
87 #if (defined _MSC_VER || defined __MINGW32__)
88 # include <windows.h>
89 # define _TINYDIR_PATH_MAX MAX_PATH
90 #elif defined  __linux__
91 # include <limits.h>
92 # define _TINYDIR_PATH_MAX PATH_MAX
93 #elif defined(__unix__) || (defined(__APPLE__) && defined(__MACH__))
94 # include <sys/param.h>
95 # if defined(BSD)
96 #  include <limits.h>
97 #  define _TINYDIR_PATH_MAX PATH_MAX
98 # endif
99 #endif
100 
101 #ifndef _TINYDIR_PATH_MAX
102 #define _TINYDIR_PATH_MAX 4096
103 #endif
104 
105 #ifdef _MSC_VER
106 /* extra chars for the "\\*" mask */
107 # define _TINYDIR_PATH_EXTRA 2
108 #else
109 # define _TINYDIR_PATH_EXTRA 0
110 #endif
111 
112 #define _TINYDIR_FILENAME_MAX 256
113 
114 #if (defined _MSC_VER || defined __MINGW32__)
115 #define _TINYDIR_DRIVE_MAX 3
116 #endif
117 
118 #ifdef _MSC_VER
119 # define _TINYDIR_FUNC static __inline
120 #elif !defined __STDC_VERSION__ || __STDC_VERSION__ < 199901L
121 # define _TINYDIR_FUNC static __inline__
122 #else
123 # define _TINYDIR_FUNC static inline
124 #endif
125 
126 /* readdir_r usage; define TINYDIR_USE_READDIR_R to use it (if supported) */
127 #ifdef TINYDIR_USE_READDIR_R
128 
129 /* readdir_r is a POSIX-only function, and may not be available under various
130  * environments/settings, e.g. MinGW. Use readdir fallback */
131 #if _POSIX_C_SOURCE >= 1 || _XOPEN_SOURCE || _BSD_SOURCE || _SVID_SOURCE ||\
132 	_POSIX_SOURCE
133 # define _TINYDIR_HAS_READDIR_R
134 #endif
135 #if _POSIX_C_SOURCE >= 200112L
136 # define _TINYDIR_HAS_FPATHCONF
137 # include <unistd.h>
138 #endif
139 #if _BSD_SOURCE || _SVID_SOURCE || \
140 	(_POSIX_C_SOURCE >= 200809L || _XOPEN_SOURCE >= 700)
141 # define _TINYDIR_HAS_DIRFD
142 # include <sys/types.h>
143 #endif
144 #if defined _TINYDIR_HAS_FPATHCONF && defined _TINYDIR_HAS_DIRFD &&\
145 	defined _PC_NAME_MAX
146 # define _TINYDIR_USE_FPATHCONF
147 #endif
148 #if defined __MINGW32__ || !defined _TINYDIR_HAS_READDIR_R ||\
149 	!(defined _TINYDIR_USE_FPATHCONF || defined NAME_MAX)
150 # define _TINYDIR_USE_READDIR
151 #endif
152 
153 /* Use readdir by default */
154 #else
155 # define _TINYDIR_USE_READDIR
156 #endif
157 
158 /* MINGW32 has two versions of dirent, ASCII and UNICODE*/
159 #ifndef _MSC_VER
160 #if (defined __MINGW32__) && (defined _UNICODE)
161 #define _TINYDIR_DIR _WDIR
162 #define _tinydir_dirent _wdirent
163 #define _tinydir_opendir _wopendir
164 #define _tinydir_readdir _wreaddir
165 #define _tinydir_closedir _wclosedir
166 #else
167 #define _TINYDIR_DIR DIR
168 #define _tinydir_dirent dirent
169 #define _tinydir_opendir opendir
170 #define _tinydir_readdir readdir
171 #define _tinydir_closedir closedir
172 #endif
173 #endif
174 
175 /* Allow user to use a custom allocator by defining _TINYDIR_MALLOC and _TINYDIR_FREE. */
176 #if    defined(_TINYDIR_MALLOC) &&  defined(_TINYDIR_FREE)
177 #elif !defined(_TINYDIR_MALLOC) && !defined(_TINYDIR_FREE)
178 #else
179 #error "Either define both alloc and free or none of them!"
180 #endif
181 
182 #if !defined(_TINYDIR_MALLOC)
183 	#define _TINYDIR_MALLOC(_size) malloc(_size)
184 	#define _TINYDIR_FREE(_ptr)    free(_ptr)
185 #endif /* !defined(_TINYDIR_MALLOC) */
186 
187 typedef struct tinydir_file
188 {
189 	_tinydir_char_t path[_TINYDIR_PATH_MAX];
190 	_tinydir_char_t name[_TINYDIR_FILENAME_MAX];
191 	_tinydir_char_t *extension;
192 	int is_dir;
193 	int is_reg;
194 
195 #ifndef _MSC_VER
196 #ifdef __MINGW32__
197 	struct _stat _s;
198 #else
199 	struct stat _s;
200 #endif
201 #endif
202 } tinydir_file;
203 
204 typedef struct tinydir_dir
205 {
206 	_tinydir_char_t path[_TINYDIR_PATH_MAX];
207 	int has_next;
208 	size_t n_files;
209 
210 	tinydir_file *_files;
211 #ifdef _MSC_VER
212 	HANDLE _h;
213 	WIN32_FIND_DATA _f;
214 #else
215 	_TINYDIR_DIR *_d;
216 	struct _tinydir_dirent *_e;
217 #ifndef _TINYDIR_USE_READDIR
218 	struct _tinydir_dirent *_ep;
219 #endif
220 #endif
221 } tinydir_dir;
222 
223 
224 /* declarations */
225 
226 _TINYDIR_FUNC
227 int tinydir_open(tinydir_dir *dir, const _tinydir_char_t *path);
228 _TINYDIR_FUNC
229 int tinydir_open_sorted(tinydir_dir *dir, const _tinydir_char_t *path);
230 _TINYDIR_FUNC
231 void tinydir_close(tinydir_dir *dir);
232 
233 _TINYDIR_FUNC
234 int tinydir_next(tinydir_dir *dir);
235 _TINYDIR_FUNC
236 int tinydir_readfile(const tinydir_dir *dir, tinydir_file *file);
237 _TINYDIR_FUNC
238 int tinydir_readfile_n(const tinydir_dir *dir, tinydir_file *file, size_t i);
239 _TINYDIR_FUNC
240 int tinydir_open_subdir_n(tinydir_dir *dir, size_t i);
241 
242 _TINYDIR_FUNC
243 int tinydir_file_open(tinydir_file *file, const _tinydir_char_t *path);
244 _TINYDIR_FUNC
245 void _tinydir_get_ext(tinydir_file *file);
246 _TINYDIR_FUNC
247 int _tinydir_file_cmp(const void *a, const void *b);
248 #ifndef _MSC_VER
249 #ifndef _TINYDIR_USE_READDIR
250 _TINYDIR_FUNC
251 size_t _tinydir_dirent_buf_size(_TINYDIR_DIR *dirp);
252 #endif
253 #endif
254 
255 
256 /* definitions*/
257 
258 _TINYDIR_FUNC
tinydir_open(tinydir_dir * dir,const _tinydir_char_t * path)259 int tinydir_open(tinydir_dir *dir, const _tinydir_char_t *path)
260 {
261 #ifndef _MSC_VER
262 #ifndef _TINYDIR_USE_READDIR
263 	int error;
264 	int size;	/* using int size */
265 #endif
266 #else
267 	_tinydir_char_t path_buf[_TINYDIR_PATH_MAX];
268 #endif
269 	_tinydir_char_t *pathp;
270 
271 	if (dir == NULL || path == NULL || _tinydir_strlen(path) == 0)
272 	{
273 		errno = EINVAL;
274 		return -1;
275 	}
276 	if (_tinydir_strlen(path) + _TINYDIR_PATH_EXTRA >= _TINYDIR_PATH_MAX)
277 	{
278 		errno = ENAMETOOLONG;
279 		return -1;
280 	}
281 
282 	/* initialise dir */
283 	dir->_files = NULL;
284 #ifdef _MSC_VER
285 	dir->_h = INVALID_HANDLE_VALUE;
286 #else
287 	dir->_d = NULL;
288 #ifndef _TINYDIR_USE_READDIR
289 	dir->_ep = NULL;
290 #endif
291 #endif
292 	tinydir_close(dir);
293 
294 	_tinydir_strcpy(dir->path, path);
295 	/* Remove trailing slashes */
296 	pathp = &dir->path[_tinydir_strlen(dir->path) - 1];
297 	while (pathp != dir->path && (*pathp == TINYDIR_STRING('\\') || *pathp == TINYDIR_STRING('/')))
298 	{
299 		*pathp = TINYDIR_STRING('\0');
300 		pathp++;
301 	}
302 #ifdef _MSC_VER
303 	_tinydir_strcpy(path_buf, dir->path);
304 	_tinydir_strcat(path_buf, TINYDIR_STRING("\\*"));
305 #if (defined WINAPI_FAMILY) && (WINAPI_FAMILY != WINAPI_FAMILY_DESKTOP_APP)
306 	dir->_h = FindFirstFileEx(path_buf, FindExInfoStandard, &dir->_f, FindExSearchNameMatch, NULL, 0);
307 #else
308 	dir->_h = FindFirstFile(path_buf, &dir->_f);
309 #endif
310 	if (dir->_h == INVALID_HANDLE_VALUE)
311 	{
312 		errno = ENOENT;
313 #else
314 	dir->_d = _tinydir_opendir(path);
315 	if (dir->_d == NULL)
316 	{
317 #endif
318 		goto bail;
319 	}
320 
321 	/* read first file */
322 	dir->has_next = 1;
323 #ifndef _MSC_VER
324 #ifdef _TINYDIR_USE_READDIR
325 	dir->_e = _tinydir_readdir(dir->_d);
326 #else
327 	/* allocate dirent buffer for readdir_r */
328 	size = _tinydir_dirent_buf_size(dir->_d); /* conversion to int */
329 	if (size == -1) return -1;
330 	dir->_ep = (struct _tinydir_dirent*)_TINYDIR_MALLOC(size);
331 	if (dir->_ep == NULL) return -1;
332 
333 	error = readdir_r(dir->_d, dir->_ep, &dir->_e);
334 	if (error != 0) return -1;
335 #endif
336 	if (dir->_e == NULL)
337 	{
338 		dir->has_next = 0;
339 	}
340 #endif
341 
342 	return 0;
343 
344 bail:
345 	tinydir_close(dir);
346 	return -1;
347 }
348 
349 _TINYDIR_FUNC
350 int tinydir_open_sorted(tinydir_dir *dir, const _tinydir_char_t *path)
351 {
352 	/* Count the number of files first, to pre-allocate the files array */
353 	size_t n_files = 0;
354 	if (tinydir_open(dir, path) == -1)
355 	{
356 		return -1;
357 	}
358 	while (dir->has_next)
359 	{
360 		n_files++;
361 		if (tinydir_next(dir) == -1)
362 		{
363 			goto bail;
364 		}
365 	}
366 	tinydir_close(dir);
367 
368 	if (tinydir_open(dir, path) == -1)
369 	{
370 		return -1;
371 	}
372 
373 	dir->n_files = 0;
374 	dir->_files = (tinydir_file *)_TINYDIR_MALLOC(sizeof *dir->_files * n_files);
375 	if (dir->_files == NULL)
376 	{
377 		goto bail;
378 	}
379 	while (dir->has_next)
380 	{
381 		tinydir_file *p_file;
382 		dir->n_files++;
383 
384 		p_file = &dir->_files[dir->n_files - 1];
385 		if (tinydir_readfile(dir, p_file) == -1)
386 		{
387 			goto bail;
388 		}
389 
390 		if (tinydir_next(dir) == -1)
391 		{
392 			goto bail;
393 		}
394 
395 		/* Just in case the number of files has changed between the first and
396 		second reads, terminate without writing into unallocated memory */
397 		if (dir->n_files == n_files)
398 		{
399 			break;
400 		}
401 	}
402 
403 	qsort(dir->_files, dir->n_files, sizeof(tinydir_file), _tinydir_file_cmp);
404 
405 	return 0;
406 
407 bail:
408 	tinydir_close(dir);
409 	return -1;
410 }
411 
412 _TINYDIR_FUNC
413 void tinydir_close(tinydir_dir *dir)
414 {
415 	if (dir == NULL)
416 	{
417 		return;
418 	}
419 
420 	memset(dir->path, 0, sizeof(dir->path));
421 	dir->has_next = 0;
422 	dir->n_files = 0;
423 	_TINYDIR_FREE(dir->_files);
424 	dir->_files = NULL;
425 #ifdef _MSC_VER
426 	if (dir->_h != INVALID_HANDLE_VALUE)
427 	{
428 		FindClose(dir->_h);
429 	}
430 	dir->_h = INVALID_HANDLE_VALUE;
431 #else
432 	if (dir->_d)
433 	{
434 		_tinydir_closedir(dir->_d);
435 	}
436 	dir->_d = NULL;
437 	dir->_e = NULL;
438 #ifndef _TINYDIR_USE_READDIR
439 	_TINYDIR_FREE(dir->_ep);
440 	dir->_ep = NULL;
441 #endif
442 #endif
443 }
444 
445 _TINYDIR_FUNC
446 int tinydir_next(tinydir_dir *dir)
447 {
448 	if (dir == NULL)
449 	{
450 		errno = EINVAL;
451 		return -1;
452 	}
453 	if (!dir->has_next)
454 	{
455 		errno = ENOENT;
456 		return -1;
457 	}
458 
459 #ifdef _MSC_VER
460 	if (FindNextFile(dir->_h, &dir->_f) == 0)
461 #else
462 #ifdef _TINYDIR_USE_READDIR
463 	dir->_e = _tinydir_readdir(dir->_d);
464 #else
465 	if (dir->_ep == NULL)
466 	{
467 		return -1;
468 	}
469 	if (readdir_r(dir->_d, dir->_ep, &dir->_e) != 0)
470 	{
471 		return -1;
472 	}
473 #endif
474 	if (dir->_e == NULL)
475 #endif
476 	{
477 		dir->has_next = 0;
478 #ifdef _MSC_VER
479 		if (GetLastError() != ERROR_SUCCESS &&
480 			GetLastError() != ERROR_NO_MORE_FILES)
481 		{
482 			tinydir_close(dir);
483 			errno = EIO;
484 			return -1;
485 		}
486 #endif
487 	}
488 
489 	return 0;
490 }
491 
492 _TINYDIR_FUNC
493 int tinydir_readfile(const tinydir_dir *dir, tinydir_file *file)
494 {
495 	if (dir == NULL || file == NULL)
496 	{
497 		errno = EINVAL;
498 		return -1;
499 	}
500 #ifdef _MSC_VER
501 	if (dir->_h == INVALID_HANDLE_VALUE)
502 #else
503 	if (dir->_e == NULL)
504 #endif
505 	{
506 		errno = ENOENT;
507 		return -1;
508 	}
509 	if (_tinydir_strlen(dir->path) +
510 		_tinydir_strlen(
511 #ifdef _MSC_VER
512 			dir->_f.cFileName
513 #else
514 			dir->_e->d_name
515 #endif
516 		) + 1 + _TINYDIR_PATH_EXTRA >=
517 		_TINYDIR_PATH_MAX)
518 	{
519 		/* the path for the file will be too long */
520 		errno = ENAMETOOLONG;
521 		return -1;
522 	}
523 	if (_tinydir_strlen(
524 #ifdef _MSC_VER
525 			dir->_f.cFileName
526 #else
527 			dir->_e->d_name
528 #endif
529 		) >= _TINYDIR_FILENAME_MAX)
530 	{
531 		errno = ENAMETOOLONG;
532 		return -1;
533 	}
534 
535 	_tinydir_strcpy(file->path, dir->path);
536 	_tinydir_strcat(file->path, TINYDIR_STRING("/"));
537 	_tinydir_strcpy(file->name,
538 #ifdef _MSC_VER
539 		dir->_f.cFileName
540 #else
541 		dir->_e->d_name
542 #endif
543 	);
544 	_tinydir_strcat(file->path, file->name);
545 #ifndef _MSC_VER
546 #ifdef __MINGW32__
547 	if (_tstat(
548 #else
549 	if (stat(
550 #endif
551 		file->path, &file->_s) == -1)
552 	{
553 		return -1;
554 	}
555 #endif
556 	_tinydir_get_ext(file);
557 
558 	file->is_dir =
559 #ifdef _MSC_VER
560 		!!(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
561 #else
562 		S_ISDIR(file->_s.st_mode);
563 #endif
564 	file->is_reg =
565 #ifdef _MSC_VER
566 		!!(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_NORMAL) ||
567 		(
568 			!(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_DEVICE) &&
569 			!(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
570 			!(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED) &&
571 #ifdef FILE_ATTRIBUTE_INTEGRITY_STREAM
572 			!(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_INTEGRITY_STREAM) &&
573 #endif
574 #ifdef FILE_ATTRIBUTE_NO_SCRUB_DATA
575 			!(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_NO_SCRUB_DATA) &&
576 #endif
577 			!(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_OFFLINE) &&
578 			!(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY));
579 #else
580 		S_ISREG(file->_s.st_mode);
581 #endif
582 
583 	return 0;
584 }
585 
586 _TINYDIR_FUNC
587 int tinydir_readfile_n(const tinydir_dir *dir, tinydir_file *file, size_t i)
588 {
589 	if (dir == NULL || file == NULL)
590 	{
591 		errno = EINVAL;
592 		return -1;
593 	}
594 	if (i >= dir->n_files)
595 	{
596 		errno = ENOENT;
597 		return -1;
598 	}
599 
600 	memcpy(file, &dir->_files[i], sizeof(tinydir_file));
601 	_tinydir_get_ext(file);
602 
603 	return 0;
604 }
605 
606 _TINYDIR_FUNC
607 int tinydir_open_subdir_n(tinydir_dir *dir, size_t i)
608 {
609 	_tinydir_char_t path[_TINYDIR_PATH_MAX];
610 	if (dir == NULL)
611 	{
612 		errno = EINVAL;
613 		return -1;
614 	}
615 	if (i >= dir->n_files || !dir->_files[i].is_dir)
616 	{
617 		errno = ENOENT;
618 		return -1;
619 	}
620 
621 	_tinydir_strcpy(path, dir->_files[i].path);
622 	tinydir_close(dir);
623 	if (tinydir_open_sorted(dir, path) == -1)
624 	{
625 		return -1;
626 	}
627 
628 	return 0;
629 }
630 
631 /* Open a single file given its path */
632 _TINYDIR_FUNC
633 int tinydir_file_open(tinydir_file *file, const _tinydir_char_t *path)
634 {
635 	tinydir_dir dir;
636 	int result = 0;
637 	int found = 0;
638 	_tinydir_char_t dir_name_buf[_TINYDIR_PATH_MAX];
639 	_tinydir_char_t file_name_buf[_TINYDIR_FILENAME_MAX];
640 	_tinydir_char_t *dir_name;
641 	_tinydir_char_t *base_name;
642 #if (defined _MSC_VER || defined __MINGW32__)
643 	_tinydir_char_t drive_buf[_TINYDIR_PATH_MAX];
644 	_tinydir_char_t ext_buf[_TINYDIR_FILENAME_MAX];
645 #endif
646 
647 	if (file == NULL || path == NULL || _tinydir_strlen(path) == 0)
648 	{
649 		errno = EINVAL;
650 		return -1;
651 	}
652 	if (_tinydir_strlen(path) + _TINYDIR_PATH_EXTRA >= _TINYDIR_PATH_MAX)
653 	{
654 		errno = ENAMETOOLONG;
655 		return -1;
656 	}
657 
658 	/* Get the parent path */
659 #if (defined _MSC_VER || defined __MINGW32__)
660 #if ((defined _MSC_VER) && (_MSC_VER >= 1400))
661 		_tsplitpath_s(
662 			path,
663 			drive_buf, _TINYDIR_DRIVE_MAX,
664 			dir_name_buf, _TINYDIR_FILENAME_MAX,
665 			file_name_buf, _TINYDIR_FILENAME_MAX,
666 			ext_buf, _TINYDIR_FILENAME_MAX);
667 #else
668 		_tsplitpath(
669 			path,
670 			drive_buf,
671 			dir_name_buf,
672 			file_name_buf,
673 			ext_buf);
674 #endif
675 
676 /* _splitpath_s not work fine with only filename and widechar support */
677 #ifdef _UNICODE
678 		if (drive_buf[0] == L'\xFEFE')
679 			drive_buf[0] = '\0';
680 		if (dir_name_buf[0] == L'\xFEFE')
681 			dir_name_buf[0] = '\0';
682 #endif
683 
684 	if (errno)
685 	{
686 		errno = EINVAL;
687 		return -1;
688 	}
689 	/* Emulate the behavior of dirname by returning "." for dir name if it's
690 	empty */
691 	if (drive_buf[0] == '\0' && dir_name_buf[0] == '\0')
692 	{
693 		_tinydir_strcpy(dir_name_buf, TINYDIR_STRING("."));
694 	}
695 	/* Concatenate the drive letter and dir name to form full dir name */
696 	_tinydir_strcat(drive_buf, dir_name_buf);
697 	dir_name = drive_buf;
698 	/* Concatenate the file name and extension to form base name */
699 	_tinydir_strcat(file_name_buf, ext_buf);
700 	base_name = file_name_buf;
701 #else
702 	_tinydir_strcpy(dir_name_buf, path);
703 	dir_name = dirname(dir_name_buf);
704 	_tinydir_strcpy(file_name_buf, path);
705 	base_name =basename(file_name_buf);
706 #endif
707 
708 	/* Open the parent directory */
709 	if (tinydir_open(&dir, dir_name) == -1)
710 	{
711 		return -1;
712 	}
713 
714 	/* Read through the parent directory and look for the file */
715 	while (dir.has_next)
716 	{
717 		if (tinydir_readfile(&dir, file) == -1)
718 		{
719 			result = -1;
720 			goto bail;
721 		}
722 		if (_tinydir_strcmp(file->name, base_name) == 0)
723 		{
724 			/* File found */
725 			found = 1;
726 			break;
727 		}
728 		tinydir_next(&dir);
729 	}
730 	if (!found)
731 	{
732 		result = -1;
733 		errno = ENOENT;
734 	}
735 
736 bail:
737 	tinydir_close(&dir);
738 	return result;
739 }
740 
741 _TINYDIR_FUNC
742 void _tinydir_get_ext(tinydir_file *file)
743 {
744 	_tinydir_char_t *period = _tinydir_strrchr(file->name, TINYDIR_STRING('.'));
745 	if (period == NULL)
746 	{
747 		file->extension = &(file->name[_tinydir_strlen(file->name)]);
748 	}
749 	else
750 	{
751 		file->extension = period + 1;
752 	}
753 }
754 
755 _TINYDIR_FUNC
756 int _tinydir_file_cmp(const void *a, const void *b)
757 {
758 	const tinydir_file *fa = (const tinydir_file *)a;
759 	const tinydir_file *fb = (const tinydir_file *)b;
760 	if (fa->is_dir != fb->is_dir)
761 	{
762 		return -(fa->is_dir - fb->is_dir);
763 	}
764 	return _tinydir_strncmp(fa->name, fb->name, _TINYDIR_FILENAME_MAX);
765 }
766 
767 #ifndef _MSC_VER
768 #ifndef _TINYDIR_USE_READDIR
769 /*
770 The following authored by Ben Hutchings <ben@decadent.org.uk>
771 from https://womble.decadent.org.uk/readdir_r-advisory.html
772 */
773 /* Calculate the required buffer size (in bytes) for directory      *
774 * entries read from the given directory handle.  Return -1 if this  *
775 * this cannot be done.                                              *
776 *                                                                   *
777 * This code does not trust values of NAME_MAX that are less than    *
778 * 255, since some systems (including at least HP-UX) incorrectly    *
779 * define it to be a smaller value.                                  */
780 _TINYDIR_FUNC
781 size_t _tinydir_dirent_buf_size(_TINYDIR_DIR *dirp)
782 {
783 	long name_max;
784 	size_t name_end;
785 	/* parameter may be unused */
786 	(void)dirp;
787 
788 #if defined _TINYDIR_USE_FPATHCONF
789 	name_max = fpathconf(dirfd(dirp), _PC_NAME_MAX);
790 	if (name_max == -1)
791 #if defined(NAME_MAX)
792 		name_max = (NAME_MAX > 255) ? NAME_MAX : 255;
793 #else
794 		return (size_t)(-1);
795 #endif
796 #elif defined(NAME_MAX)
797  	name_max = (NAME_MAX > 255) ? NAME_MAX : 255;
798 #else
799 #error "buffer size for readdir_r cannot be determined"
800 #endif
801 	name_end = (size_t)offsetof(struct _tinydir_dirent, d_name) + name_max + 1;
802 	return (name_end > sizeof(struct _tinydir_dirent) ?
803 		name_end : sizeof(struct _tinydir_dirent));
804 }
805 #endif
806 #endif
807 
808 #ifdef __cplusplus
809 }
810 #endif
811 
812 # if defined (_MSC_VER)
813 # pragma warning(pop)
814 # endif
815 
816 #endif
817