1 /*
2 Copyright (c) 2013-2019, 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 # ifndef WIN32_LEAN_AND_MEAN
49 #  define WIN32_LEAN_AND_MEAN
50 # endif
51 # include <windows.h>
52 # include <tchar.h>
53 # pragma warning(push)
54 # pragma warning (disable : 4996)
55 #else
56 # include <dirent.h>
57 # include <libgen.h>
58 # include <sys/stat.h>
59 # include <stddef.h>
60 #endif
61 #ifdef __MINGW32__
62 # include <tchar.h>
63 #endif
64 
65 
66 /* types */
67 
68 /* Windows UNICODE wide character support */
69 #if defined _MSC_VER || defined __MINGW32__
70 # define _tinydir_char_t TCHAR
71 # define TINYDIR_STRING(s) _TEXT(s)
72 # define _tinydir_strlen _tcslen
73 # define _tinydir_strcpy _tcscpy
74 # define _tinydir_strcat _tcscat
75 # define _tinydir_strcmp _tcscmp
76 # define _tinydir_strrchr _tcsrchr
77 # define _tinydir_strncmp _tcsncmp
78 #else
79 # define _tinydir_char_t char
80 # define TINYDIR_STRING(s) s
81 # define _tinydir_strlen strlen
82 # define _tinydir_strcpy strcpy
83 # define _tinydir_strcat strcat
84 # define _tinydir_strcmp strcmp
85 # define _tinydir_strrchr strrchr
86 # define _tinydir_strncmp strncmp
87 #endif
88 
89 #if (defined _MSC_VER || defined __MINGW32__)
90 # include <windows.h>
91 # define _TINYDIR_PATH_MAX MAX_PATH
92 #elif defined  __linux__
93 # include <limits.h>
94 # ifdef PATH_MAX
95 #  define _TINYDIR_PATH_MAX PATH_MAX
96 # endif
97 #elif defined(__unix__) || (defined(__APPLE__) && defined(__MACH__))
98 # include <sys/param.h>
99 # if defined(BSD)
100 #  include <limits.h>
101 #  ifdef PATH_MAX
102 #   define _TINYDIR_PATH_MAX PATH_MAX
103 #  endif
104 # endif
105 #endif
106 
107 #ifndef _TINYDIR_PATH_MAX
108 #define _TINYDIR_PATH_MAX 4096
109 #endif
110 
111 #ifdef _MSC_VER
112 /* extra chars for the "\\*" mask */
113 # define _TINYDIR_PATH_EXTRA 2
114 #else
115 # define _TINYDIR_PATH_EXTRA 0
116 #endif
117 
118 #define _TINYDIR_FILENAME_MAX 256
119 
120 #if (defined _MSC_VER || defined __MINGW32__)
121 #define _TINYDIR_DRIVE_MAX 3
122 #endif
123 
124 #ifdef _MSC_VER
125 # define _TINYDIR_FUNC static __inline
126 #elif !defined __STDC_VERSION__ || __STDC_VERSION__ < 199901L
127 # define _TINYDIR_FUNC static __inline__
128 #else
129 # define _TINYDIR_FUNC static inline
130 #endif
131 
132 /* readdir_r usage; define TINYDIR_USE_READDIR_R to use it (if supported) */
133 #ifdef TINYDIR_USE_READDIR_R
134 
135 /* readdir_r is a POSIX-only function, and may not be available under various
136  * environments/settings, e.g. MinGW. Use readdir fallback */
137 #if _POSIX_C_SOURCE >= 1 || _XOPEN_SOURCE || _BSD_SOURCE || _SVID_SOURCE ||\
138 	_POSIX_SOURCE
139 # define _TINYDIR_HAS_READDIR_R
140 #endif
141 #if _POSIX_C_SOURCE >= 200112L
142 # define _TINYDIR_HAS_FPATHCONF
143 # include <unistd.h>
144 #endif
145 #if _BSD_SOURCE || _SVID_SOURCE || \
146 	(_POSIX_C_SOURCE >= 200809L || _XOPEN_SOURCE >= 700)
147 # define _TINYDIR_HAS_DIRFD
148 # include <sys/types.h>
149 #endif
150 #if defined _TINYDIR_HAS_FPATHCONF && defined _TINYDIR_HAS_DIRFD &&\
151 	defined _PC_NAME_MAX
152 # define _TINYDIR_USE_FPATHCONF
153 #endif
154 #if defined __MINGW32__ || !defined _TINYDIR_HAS_READDIR_R ||\
155 	!(defined _TINYDIR_USE_FPATHCONF || defined NAME_MAX)
156 # define _TINYDIR_USE_READDIR
157 #endif
158 
159 /* Use readdir by default */
160 #else
161 # define _TINYDIR_USE_READDIR
162 #endif
163 
164 /* MINGW32 has two versions of dirent, ASCII and UNICODE*/
165 #ifndef _MSC_VER
166 #if (defined __MINGW32__) && (defined _UNICODE)
167 #define _TINYDIR_DIR _WDIR
168 #define _tinydir_dirent _wdirent
169 #define _tinydir_opendir _wopendir
170 #define _tinydir_readdir _wreaddir
171 #define _tinydir_closedir _wclosedir
172 #else
173 #define _TINYDIR_DIR DIR
174 #define _tinydir_dirent dirent
175 #define _tinydir_opendir opendir
176 #define _tinydir_readdir readdir
177 #define _tinydir_closedir closedir
178 #endif
179 #endif
180 
181 /* Allow user to use a custom allocator by defining _TINYDIR_MALLOC and _TINYDIR_FREE. */
182 #if    defined(_TINYDIR_MALLOC) &&  defined(_TINYDIR_FREE)
183 #elif !defined(_TINYDIR_MALLOC) && !defined(_TINYDIR_FREE)
184 #else
185 #error "Either define both alloc and free or none of them!"
186 #endif
187 
188 #if !defined(_TINYDIR_MALLOC)
189 	#define _TINYDIR_MALLOC(_size) malloc(_size)
190 	#define _TINYDIR_FREE(_ptr)    free(_ptr)
191 #endif /* !defined(_TINYDIR_MALLOC) */
192 
193 typedef struct tinydir_file
194 {
195 	_tinydir_char_t path[_TINYDIR_PATH_MAX];
196 	_tinydir_char_t name[_TINYDIR_FILENAME_MAX];
197 	_tinydir_char_t *extension;
198 	int is_dir;
199 	int is_reg;
200 
201 #ifndef _MSC_VER
202 #ifdef __MINGW32__
203 	struct _stat _s;
204 #else
205 	struct stat _s;
206 #endif
207 #endif
208 } tinydir_file;
209 
210 typedef struct tinydir_dir
211 {
212 	_tinydir_char_t path[_TINYDIR_PATH_MAX];
213 	int has_next;
214 	size_t n_files;
215 
216 	tinydir_file *_files;
217 #ifdef _MSC_VER
218 	HANDLE _h;
219 	WIN32_FIND_DATA _f;
220 #else
221 	_TINYDIR_DIR *_d;
222 	struct _tinydir_dirent *_e;
223 #ifndef _TINYDIR_USE_READDIR
224 	struct _tinydir_dirent *_ep;
225 #endif
226 #endif
227 } tinydir_dir;
228 
229 
230 /* declarations */
231 
232 _TINYDIR_FUNC
233 int tinydir_open(tinydir_dir *dir, const _tinydir_char_t *path);
234 _TINYDIR_FUNC
235 int tinydir_open_sorted(tinydir_dir *dir, const _tinydir_char_t *path);
236 _TINYDIR_FUNC
237 void tinydir_close(tinydir_dir *dir);
238 
239 _TINYDIR_FUNC
240 int tinydir_next(tinydir_dir *dir);
241 _TINYDIR_FUNC
242 int tinydir_readfile(const tinydir_dir *dir, tinydir_file *file);
243 _TINYDIR_FUNC
244 int tinydir_readfile_n(const tinydir_dir *dir, tinydir_file *file, size_t i);
245 _TINYDIR_FUNC
246 int tinydir_open_subdir_n(tinydir_dir *dir, size_t i);
247 
248 _TINYDIR_FUNC
249 int tinydir_file_open(tinydir_file *file, const _tinydir_char_t *path);
250 _TINYDIR_FUNC
251 void _tinydir_get_ext(tinydir_file *file);
252 _TINYDIR_FUNC
253 int _tinydir_file_cmp(const void *a, const void *b);
254 #ifndef _MSC_VER
255 #ifndef _TINYDIR_USE_READDIR
256 _TINYDIR_FUNC
257 size_t _tinydir_dirent_buf_size(_TINYDIR_DIR *dirp);
258 #endif
259 #endif
260 
261 
262 /* definitions*/
263 
264 _TINYDIR_FUNC
tinydir_open(tinydir_dir * dir,const _tinydir_char_t * path)265 int tinydir_open(tinydir_dir *dir, const _tinydir_char_t *path)
266 {
267 #ifndef _MSC_VER
268 #ifndef _TINYDIR_USE_READDIR
269 	int error;
270 	int size;	/* using int size */
271 #endif
272 #else
273 	_tinydir_char_t path_buf[_TINYDIR_PATH_MAX];
274 #endif
275 	_tinydir_char_t *pathp;
276 
277 	if (dir == NULL || path == NULL || _tinydir_strlen(path) == 0)
278 	{
279 		errno = EINVAL;
280 		return -1;
281 	}
282 	if (_tinydir_strlen(path) + _TINYDIR_PATH_EXTRA >= _TINYDIR_PATH_MAX)
283 	{
284 		errno = ENAMETOOLONG;
285 		return -1;
286 	}
287 
288 	/* initialise dir */
289 	dir->_files = NULL;
290 #ifdef _MSC_VER
291 	dir->_h = INVALID_HANDLE_VALUE;
292 #else
293 	dir->_d = NULL;
294 #ifndef _TINYDIR_USE_READDIR
295 	dir->_ep = NULL;
296 #endif
297 #endif
298 	tinydir_close(dir);
299 
300 	_tinydir_strcpy(dir->path, path);
301 	/* Remove trailing slashes */
302 	pathp = &dir->path[_tinydir_strlen(dir->path) - 1];
303 	while (pathp != dir->path && (*pathp == TINYDIR_STRING('\\') || *pathp == TINYDIR_STRING('/')))
304 	{
305 		*pathp = TINYDIR_STRING('\0');
306 		pathp++;
307 	}
308 #ifdef _MSC_VER
309 	_tinydir_strcpy(path_buf, dir->path);
310 	_tinydir_strcat(path_buf, TINYDIR_STRING("\\*"));
311 #if (defined WINAPI_FAMILY) && (WINAPI_FAMILY != WINAPI_FAMILY_DESKTOP_APP)
312 	dir->_h = FindFirstFileEx(path_buf, FindExInfoStandard, &dir->_f, FindExSearchNameMatch, NULL, 0);
313 #else
314 	dir->_h = FindFirstFile(path_buf, &dir->_f);
315 #endif
316 	if (dir->_h == INVALID_HANDLE_VALUE)
317 	{
318 		errno = ENOENT;
319 #else
320 	dir->_d = _tinydir_opendir(path);
321 	if (dir->_d == NULL)
322 	{
323 #endif
324 		goto bail;
325 	}
326 
327 	/* read first file */
328 	dir->has_next = 1;
329 #ifndef _MSC_VER
330 #ifdef _TINYDIR_USE_READDIR
331 	dir->_e = _tinydir_readdir(dir->_d);
332 #else
333 	/* allocate dirent buffer for readdir_r */
334 	size = _tinydir_dirent_buf_size(dir->_d); /* conversion to int */
335 	if (size == -1) return -1;
336 	dir->_ep = (struct _tinydir_dirent*)_TINYDIR_MALLOC(size);
337 	if (dir->_ep == NULL) return -1;
338 
339 	error = readdir_r(dir->_d, dir->_ep, &dir->_e);
340 	if (error != 0) return -1;
341 #endif
342 	if (dir->_e == NULL)
343 	{
344 		dir->has_next = 0;
345 	}
346 #endif
347 
348 	return 0;
349 
350 bail:
351 	tinydir_close(dir);
352 	return -1;
353 }
354 
355 _TINYDIR_FUNC
356 int tinydir_open_sorted(tinydir_dir *dir, const _tinydir_char_t *path)
357 {
358 	/* Count the number of files first, to pre-allocate the files array */
359 	size_t n_files = 0;
360 	if (tinydir_open(dir, path) == -1)
361 	{
362 		return -1;
363 	}
364 	while (dir->has_next)
365 	{
366 		n_files++;
367 		if (tinydir_next(dir) == -1)
368 		{
369 			goto bail;
370 		}
371 	}
372 	tinydir_close(dir);
373 
374 	if (n_files == 0 || tinydir_open(dir, path) == -1)
375 	{
376 		return -1;
377 	}
378 
379 	dir->n_files = 0;
380 	dir->_files = (tinydir_file *)_TINYDIR_MALLOC(sizeof *dir->_files * n_files);
381 	if (dir->_files == NULL)
382 	{
383 		goto bail;
384 	}
385 	while (dir->has_next)
386 	{
387 		tinydir_file *p_file;
388 		dir->n_files++;
389 
390 		p_file = &dir->_files[dir->n_files - 1];
391 		if (tinydir_readfile(dir, p_file) == -1)
392 		{
393 			goto bail;
394 		}
395 
396 		if (tinydir_next(dir) == -1)
397 		{
398 			goto bail;
399 		}
400 
401 		/* Just in case the number of files has changed between the first and
402 		second reads, terminate without writing into unallocated memory */
403 		if (dir->n_files == n_files)
404 		{
405 			break;
406 		}
407 	}
408 
409 	qsort(dir->_files, dir->n_files, sizeof(tinydir_file), _tinydir_file_cmp);
410 
411 	return 0;
412 
413 bail:
414 	tinydir_close(dir);
415 	return -1;
416 }
417 
418 _TINYDIR_FUNC
419 void tinydir_close(tinydir_dir *dir)
420 {
421 	if (dir == NULL)
422 	{
423 		return;
424 	}
425 
426 	memset(dir->path, 0, sizeof(dir->path));
427 	dir->has_next = 0;
428 	dir->n_files = 0;
429 	_TINYDIR_FREE(dir->_files);
430 	dir->_files = NULL;
431 #ifdef _MSC_VER
432 	if (dir->_h != INVALID_HANDLE_VALUE)
433 	{
434 		FindClose(dir->_h);
435 	}
436 	dir->_h = INVALID_HANDLE_VALUE;
437 #else
438 	if (dir->_d)
439 	{
440 		_tinydir_closedir(dir->_d);
441 	}
442 	dir->_d = NULL;
443 	dir->_e = NULL;
444 #ifndef _TINYDIR_USE_READDIR
445 	_TINYDIR_FREE(dir->_ep);
446 	dir->_ep = NULL;
447 #endif
448 #endif
449 }
450 
451 _TINYDIR_FUNC
452 int tinydir_next(tinydir_dir *dir)
453 {
454 	if (dir == NULL)
455 	{
456 		errno = EINVAL;
457 		return -1;
458 	}
459 	if (!dir->has_next)
460 	{
461 		errno = ENOENT;
462 		return -1;
463 	}
464 
465 #ifdef _MSC_VER
466 	if (FindNextFile(dir->_h, &dir->_f) == 0)
467 #else
468 #ifdef _TINYDIR_USE_READDIR
469 	dir->_e = _tinydir_readdir(dir->_d);
470 #else
471 	if (dir->_ep == NULL)
472 	{
473 		return -1;
474 	}
475 	if (readdir_r(dir->_d, dir->_ep, &dir->_e) != 0)
476 	{
477 		return -1;
478 	}
479 #endif
480 	if (dir->_e == NULL)
481 #endif
482 	{
483 		dir->has_next = 0;
484 #ifdef _MSC_VER
485 		if (GetLastError() != ERROR_SUCCESS &&
486 			GetLastError() != ERROR_NO_MORE_FILES)
487 		{
488 			tinydir_close(dir);
489 			errno = EIO;
490 			return -1;
491 		}
492 #endif
493 	}
494 
495 	return 0;
496 }
497 
498 _TINYDIR_FUNC
499 int tinydir_readfile(const tinydir_dir *dir, tinydir_file *file)
500 {
501 	const _tinydir_char_t *filename;
502 	if (dir == NULL || file == NULL)
503 	{
504 		errno = EINVAL;
505 		return -1;
506 	}
507 #ifdef _MSC_VER
508 	if (dir->_h == INVALID_HANDLE_VALUE)
509 #else
510 	if (dir->_e == NULL)
511 #endif
512 	{
513 		errno = ENOENT;
514 		return -1;
515 	}
516 	filename =
517 #ifdef _MSC_VER
518 		dir->_f.cFileName;
519 #else
520 		dir->_e->d_name;
521 #endif
522 	if (_tinydir_strlen(dir->path) +
523 		_tinydir_strlen(filename) + 1 + _TINYDIR_PATH_EXTRA >=
524 		_TINYDIR_PATH_MAX)
525 	{
526 		/* the path for the file will be too long */
527 		errno = ENAMETOOLONG;
528 		return -1;
529 	}
530 	if (_tinydir_strlen(filename) >= _TINYDIR_FILENAME_MAX)
531 	{
532 		errno = ENAMETOOLONG;
533 		return -1;
534 	}
535 
536 	_tinydir_strcpy(file->path, dir->path);
537 	if (_tinydir_strcmp(dir->path, TINYDIR_STRING("/")) != 0)
538 		_tinydir_strcat(file->path, TINYDIR_STRING("/"));
539 	_tinydir_strcpy(file->name, filename);
540 	_tinydir_strcat(file->path, filename);
541 #ifndef _MSC_VER
542 #ifdef __MINGW32__
543 	if (_tstat(
544 #elif (defined _BSD_SOURCE) || (defined _DEFAULT_SOURCE)	\
545 	|| ((defined _XOPEN_SOURCE) && (_XOPEN_SOURCE >= 500))	\
546 	|| ((defined _POSIX_C_SOURCE) && (_POSIX_C_SOURCE >= 200112L))
547 	if (lstat(
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 	errno = _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 	if (errno)
677 	{
678 		return -1;
679 	}
680 
681 /* _splitpath_s not work fine with only filename and widechar support */
682 #ifdef _UNICODE
683 	if (drive_buf[0] == L'\xFEFE')
684 		drive_buf[0] = '\0';
685 	if (dir_name_buf[0] == L'\xFEFE')
686 		dir_name_buf[0] = '\0';
687 #endif
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 	/* Special case: if the path is a root dir, open the parent dir as the file */
709 #if (defined _MSC_VER || defined __MINGW32__)
710 	if (_tinydir_strlen(base_name) == 0)
711 #else
712 	if ((_tinydir_strcmp(base_name, TINYDIR_STRING("/"))) == 0)
713 #endif
714 	{
715 		memset(file, 0, sizeof * file);
716 		file->is_dir = 1;
717 		file->is_reg = 0;
718 		_tinydir_strcpy(file->path, dir_name);
719 		file->extension = file->path + _tinydir_strlen(file->path);
720 		return 0;
721 	}
722 
723 	/* Open the parent directory */
724 	if (tinydir_open(&dir, dir_name) == -1)
725 	{
726 		return -1;
727 	}
728 
729 	/* Read through the parent directory and look for the file */
730 	while (dir.has_next)
731 	{
732 		if (tinydir_readfile(&dir, file) == -1)
733 		{
734 			result = -1;
735 			goto bail;
736 		}
737 		if (_tinydir_strcmp(file->name, base_name) == 0)
738 		{
739 			/* File found */
740 			found = 1;
741 			break;
742 		}
743 		tinydir_next(&dir);
744 	}
745 	if (!found)
746 	{
747 		result = -1;
748 		errno = ENOENT;
749 	}
750 
751 bail:
752 	tinydir_close(&dir);
753 	return result;
754 }
755 
756 _TINYDIR_FUNC
757 void _tinydir_get_ext(tinydir_file *file)
758 {
759 	_tinydir_char_t *period = _tinydir_strrchr(file->name, TINYDIR_STRING('.'));
760 	if (period == NULL)
761 	{
762 		file->extension = &(file->name[_tinydir_strlen(file->name)]);
763 	}
764 	else
765 	{
766 		file->extension = period + 1;
767 	}
768 }
769 
770 _TINYDIR_FUNC
771 int _tinydir_file_cmp(const void *a, const void *b)
772 {
773 	const tinydir_file *fa = (const tinydir_file *)a;
774 	const tinydir_file *fb = (const tinydir_file *)b;
775 	if (fa->is_dir != fb->is_dir)
776 	{
777 		return -(fa->is_dir - fb->is_dir);
778 	}
779 	return _tinydir_strncmp(fa->name, fb->name, _TINYDIR_FILENAME_MAX);
780 }
781 
782 #ifndef _MSC_VER
783 #ifndef _TINYDIR_USE_READDIR
784 /*
785 The following authored by Ben Hutchings <ben@decadent.org.uk>
786 from https://womble.decadent.org.uk/readdir_r-advisory.html
787 */
788 /* Calculate the required buffer size (in bytes) for directory      *
789 * entries read from the given directory handle.  Return -1 if this  *
790 * this cannot be done.                                              *
791 *                                                                   *
792 * This code does not trust values of NAME_MAX that are less than    *
793 * 255, since some systems (including at least HP-UX) incorrectly    *
794 * define it to be a smaller value.                                  */
795 _TINYDIR_FUNC
796 size_t _tinydir_dirent_buf_size(_TINYDIR_DIR *dirp)
797 {
798 	long name_max;
799 	size_t name_end;
800 	/* parameter may be unused */
801 	(void)dirp;
802 
803 #if defined _TINYDIR_USE_FPATHCONF
804 	name_max = fpathconf(dirfd(dirp), _PC_NAME_MAX);
805 	if (name_max == -1)
806 #if defined(NAME_MAX)
807 		name_max = (NAME_MAX > 255) ? NAME_MAX : 255;
808 #else
809 		return (size_t)(-1);
810 #endif
811 #elif defined(NAME_MAX)
812  	name_max = (NAME_MAX > 255) ? NAME_MAX : 255;
813 #else
814 #error "buffer size for readdir_r cannot be determined"
815 #endif
816 	name_end = (size_t)offsetof(struct _tinydir_dirent, d_name) + name_max + 1;
817 	return (name_end > sizeof(struct _tinydir_dirent) ?
818 		name_end : sizeof(struct _tinydir_dirent));
819 }
820 #endif
821 #endif
822 
823 #ifdef __cplusplus
824 }
825 #endif
826 
827 # if defined (_MSC_VER)
828 # pragma warning(pop)
829 # endif
830 
831 #endif
832