xref: /btstack/3rd-party/tinydir/tinydir.h (revision c1b6583a190ace9247a1f81941d6be4382eb4f1e)
1 /*
2 Copyright (c) 2013-2021, tinydir authors:
3 - Cong Xu
4 - Lautis Sun
5 - Baudouin Feildel
6 - Andargor <[email protected]>
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 #elif defined(__cplusplus)
129 # define _TINYDIR_FUNC static inline
130 #elif defined(__GNUC__)
131 /* Suppress unused function warning */
132 # define _TINYDIR_FUNC __attribute__((unused)) static
133 #else
134 # define _TINYDIR_FUNC static
135 #endif
136 
137 /* readdir_r usage; define TINYDIR_USE_READDIR_R to use it (if supported) */
138 #ifdef TINYDIR_USE_READDIR_R
139 
140 /* readdir_r is a POSIX-only function, and may not be available under various
141  * environments/settings, e.g. MinGW. Use readdir fallback */
142 #if _POSIX_C_SOURCE >= 1 || _XOPEN_SOURCE || _BSD_SOURCE || _SVID_SOURCE ||\
143 	_POSIX_SOURCE
144 # define _TINYDIR_HAS_READDIR_R
145 #endif
146 #if _POSIX_C_SOURCE >= 200112L
147 # define _TINYDIR_HAS_FPATHCONF
148 # include <unistd.h>
149 #endif
150 #if _BSD_SOURCE || _SVID_SOURCE || \
151 	(_POSIX_C_SOURCE >= 200809L || _XOPEN_SOURCE >= 700)
152 # define _TINYDIR_HAS_DIRFD
153 # include <sys/types.h>
154 #endif
155 #if defined _TINYDIR_HAS_FPATHCONF && defined _TINYDIR_HAS_DIRFD &&\
156 	defined _PC_NAME_MAX
157 # define _TINYDIR_USE_FPATHCONF
158 #endif
159 #if defined __MINGW32__ || !defined _TINYDIR_HAS_READDIR_R ||\
160 	!(defined _TINYDIR_USE_FPATHCONF || defined NAME_MAX)
161 # define _TINYDIR_USE_READDIR
162 #endif
163 
164 /* Use readdir by default */
165 #else
166 # define _TINYDIR_USE_READDIR
167 #endif
168 
169 /* MINGW32 has two versions of dirent, ASCII and UNICODE*/
170 #ifndef _MSC_VER
171 #if (defined __MINGW32__) && (defined _UNICODE)
172 #define _TINYDIR_DIR _WDIR
173 #define _tinydir_dirent _wdirent
174 #define _tinydir_opendir _wopendir
175 #define _tinydir_readdir _wreaddir
176 #define _tinydir_closedir _wclosedir
177 #else
178 #define _TINYDIR_DIR DIR
179 #define _tinydir_dirent dirent
180 #define _tinydir_opendir opendir
181 #define _tinydir_readdir readdir
182 #define _tinydir_closedir closedir
183 #endif
184 #endif
185 
186 /* Allow user to use a custom allocator by defining _TINYDIR_MALLOC and _TINYDIR_FREE. */
187 #if    defined(_TINYDIR_MALLOC) &&  defined(_TINYDIR_FREE)
188 #elif !defined(_TINYDIR_MALLOC) && !defined(_TINYDIR_FREE)
189 #else
190 #error "Either define both alloc and free or none of them!"
191 #endif
192 
193 #if !defined(_TINYDIR_MALLOC)
194 	#define _TINYDIR_MALLOC(_size) malloc(_size)
195 	#define _TINYDIR_FREE(_ptr)    free(_ptr)
196 #endif /* !defined(_TINYDIR_MALLOC) */
197 
198 typedef struct tinydir_file
199 {
200 	_tinydir_char_t path[_TINYDIR_PATH_MAX];
201 	_tinydir_char_t name[_TINYDIR_FILENAME_MAX];
202 	_tinydir_char_t *extension;
203 	int is_dir;
204 	int is_reg;
205 
206 #ifndef _MSC_VER
207 #ifdef __MINGW32__
208 	struct _stat _s;
209 #else
210 	struct stat _s;
211 #endif
212 #endif
213 } tinydir_file;
214 
215 typedef struct tinydir_dir
216 {
217 	_tinydir_char_t path[_TINYDIR_PATH_MAX];
218 	int has_next;
219 	size_t n_files;
220 
221 	tinydir_file *_files;
222 #ifdef _MSC_VER
223 	HANDLE _h;
224 	WIN32_FIND_DATA _f;
225 #else
226 	_TINYDIR_DIR *_d;
227 	struct _tinydir_dirent *_e;
228 #ifndef _TINYDIR_USE_READDIR
229 	struct _tinydir_dirent *_ep;
230 #endif
231 #endif
232 } tinydir_dir;
233 
234 
235 /* declarations */
236 
237 _TINYDIR_FUNC
238 int tinydir_open(tinydir_dir *dir, const _tinydir_char_t *path);
239 _TINYDIR_FUNC
240 int tinydir_open_sorted(tinydir_dir *dir, const _tinydir_char_t *path);
241 _TINYDIR_FUNC
242 void tinydir_close(tinydir_dir *dir);
243 
244 _TINYDIR_FUNC
245 int tinydir_next(tinydir_dir *dir);
246 _TINYDIR_FUNC
247 int tinydir_readfile(const tinydir_dir *dir, tinydir_file *file);
248 _TINYDIR_FUNC
249 int tinydir_readfile_n(const tinydir_dir *dir, tinydir_file *file, size_t i);
250 _TINYDIR_FUNC
251 int tinydir_open_subdir_n(tinydir_dir *dir, size_t i);
252 
253 _TINYDIR_FUNC
254 int tinydir_file_open(tinydir_file *file, const _tinydir_char_t *path);
255 _TINYDIR_FUNC
256 void _tinydir_get_ext(tinydir_file *file);
257 _TINYDIR_FUNC
258 int _tinydir_file_cmp(const void *a, const void *b);
259 #ifndef _MSC_VER
260 #ifndef _TINYDIR_USE_READDIR
261 _TINYDIR_FUNC
262 size_t _tinydir_dirent_buf_size(_TINYDIR_DIR *dirp);
263 #endif
264 #endif
265 
266 
267 /* definitions*/
268 
269 _TINYDIR_FUNC
270 int tinydir_open(tinydir_dir *dir, const _tinydir_char_t *path)
271 {
272 #ifndef _MSC_VER
273 #ifndef _TINYDIR_USE_READDIR
274 	int error;
275 	int size;	/* using int size */
276 #endif
277 #else
278 	_tinydir_char_t path_buf[_TINYDIR_PATH_MAX];
279 #endif
280 	_tinydir_char_t *pathp;
281 
282 	if (dir == NULL || path == NULL || _tinydir_strlen(path) == 0)
283 	{
284 		errno = EINVAL;
285 		return -1;
286 	}
287 	if (_tinydir_strlen(path) + _TINYDIR_PATH_EXTRA >= _TINYDIR_PATH_MAX)
288 	{
289 		errno = ENAMETOOLONG;
290 		return -1;
291 	}
292 
293 	/* initialise dir */
294 	dir->_files = NULL;
295 #ifdef _MSC_VER
296 	dir->_h = INVALID_HANDLE_VALUE;
297 #else
298 	dir->_d = NULL;
299 #ifndef _TINYDIR_USE_READDIR
300 	dir->_ep = NULL;
301 #endif
302 #endif
303 	tinydir_close(dir);
304 
305 	_tinydir_strcpy(dir->path, path);
306 	/* Remove trailing slashes */
307 	pathp = &dir->path[_tinydir_strlen(dir->path) - 1];
308 	while (pathp != dir->path && (*pathp == TINYDIR_STRING('\\') || *pathp == TINYDIR_STRING('/')))
309 	{
310 		*pathp = TINYDIR_STRING('\0');
311 		pathp++;
312 	}
313 #ifdef _MSC_VER
314 	_tinydir_strcpy(path_buf, dir->path);
315 	_tinydir_strcat(path_buf, TINYDIR_STRING("\\*"));
316 #if (defined WINAPI_FAMILY) && (WINAPI_FAMILY != WINAPI_FAMILY_DESKTOP_APP)
317 	dir->_h = FindFirstFileEx(path_buf, FindExInfoStandard, &dir->_f, FindExSearchNameMatch, NULL, 0);
318 #else
319 	dir->_h = FindFirstFile(path_buf, &dir->_f);
320 #endif
321 	if (dir->_h == INVALID_HANDLE_VALUE)
322 	{
323 		errno = ENOENT;
324 #else
325 	dir->_d = _tinydir_opendir(path);
326 	if (dir->_d == NULL)
327 	{
328 #endif
329 		goto bail;
330 	}
331 
332 	/* read first file */
333 	dir->has_next = 1;
334 #ifndef _MSC_VER
335 #ifdef _TINYDIR_USE_READDIR
336 	dir->_e = _tinydir_readdir(dir->_d);
337 #else
338 	/* allocate dirent buffer for readdir_r */
339 	size = _tinydir_dirent_buf_size(dir->_d); /* conversion to int */
340 	if (size == -1) return -1;
341 	dir->_ep = (struct _tinydir_dirent*)_TINYDIR_MALLOC(size);
342 	if (dir->_ep == NULL) return -1;
343 
344 	error = readdir_r(dir->_d, dir->_ep, &dir->_e);
345 	if (error != 0) return -1;
346 #endif
347 	if (dir->_e == NULL)
348 	{
349 		dir->has_next = 0;
350 	}
351 #endif
352 
353 	return 0;
354 
355 bail:
356 	tinydir_close(dir);
357 	return -1;
358 }
359 
360 _TINYDIR_FUNC
361 int tinydir_open_sorted(tinydir_dir *dir, const _tinydir_char_t *path)
362 {
363 	/* Count the number of files first, to pre-allocate the files array */
364 	size_t n_files = 0;
365 	if (tinydir_open(dir, path) == -1)
366 	{
367 		return -1;
368 	}
369 	while (dir->has_next)
370 	{
371 		n_files++;
372 		if (tinydir_next(dir) == -1)
373 		{
374 			goto bail;
375 		}
376 	}
377 	tinydir_close(dir);
378 
379 	if (n_files == 0 || tinydir_open(dir, path) == -1)
380 	{
381 		return -1;
382 	}
383 
384 	dir->n_files = 0;
385 	dir->_files = (tinydir_file *)_TINYDIR_MALLOC(sizeof *dir->_files * n_files);
386 	if (dir->_files == NULL)
387 	{
388 		goto bail;
389 	}
390 	while (dir->has_next)
391 	{
392 		tinydir_file *p_file;
393 		dir->n_files++;
394 
395 		p_file = &dir->_files[dir->n_files - 1];
396 		if (tinydir_readfile(dir, p_file) == -1)
397 		{
398 			goto bail;
399 		}
400 
401 		if (tinydir_next(dir) == -1)
402 		{
403 			goto bail;
404 		}
405 
406 		/* Just in case the number of files has changed between the first and
407 		second reads, terminate without writing into unallocated memory */
408 		if (dir->n_files == n_files)
409 		{
410 			break;
411 		}
412 	}
413 
414 	qsort(dir->_files, dir->n_files, sizeof(tinydir_file), _tinydir_file_cmp);
415 
416 	return 0;
417 
418 bail:
419 	tinydir_close(dir);
420 	return -1;
421 }
422 
423 _TINYDIR_FUNC
424 void tinydir_close(tinydir_dir *dir)
425 {
426 	if (dir == NULL)
427 	{
428 		return;
429 	}
430 
431 	memset(dir->path, 0, sizeof(dir->path));
432 	dir->has_next = 0;
433 	dir->n_files = 0;
434 	_TINYDIR_FREE(dir->_files);
435 	dir->_files = NULL;
436 #ifdef _MSC_VER
437 	if (dir->_h != INVALID_HANDLE_VALUE)
438 	{
439 		FindClose(dir->_h);
440 	}
441 	dir->_h = INVALID_HANDLE_VALUE;
442 #else
443 	if (dir->_d)
444 	{
445 		_tinydir_closedir(dir->_d);
446 	}
447 	dir->_d = NULL;
448 	dir->_e = NULL;
449 #ifndef _TINYDIR_USE_READDIR
450 	_TINYDIR_FREE(dir->_ep);
451 	dir->_ep = NULL;
452 #endif
453 #endif
454 }
455 
456 _TINYDIR_FUNC
457 int tinydir_next(tinydir_dir *dir)
458 {
459 	if (dir == NULL)
460 	{
461 		errno = EINVAL;
462 		return -1;
463 	}
464 	if (!dir->has_next)
465 	{
466 		errno = ENOENT;
467 		return -1;
468 	}
469 
470 #ifdef _MSC_VER
471 	if (FindNextFile(dir->_h, &dir->_f) == 0)
472 #else
473 #ifdef _TINYDIR_USE_READDIR
474 	dir->_e = _tinydir_readdir(dir->_d);
475 #else
476 	if (dir->_ep == NULL)
477 	{
478 		return -1;
479 	}
480 	if (readdir_r(dir->_d, dir->_ep, &dir->_e) != 0)
481 	{
482 		return -1;
483 	}
484 #endif
485 	if (dir->_e == NULL)
486 #endif
487 	{
488 		dir->has_next = 0;
489 #ifdef _MSC_VER
490 		if (GetLastError() != ERROR_SUCCESS &&
491 			GetLastError() != ERROR_NO_MORE_FILES)
492 		{
493 			tinydir_close(dir);
494 			errno = EIO;
495 			return -1;
496 		}
497 #endif
498 	}
499 
500 	return 0;
501 }
502 
503 _TINYDIR_FUNC
504 int tinydir_readfile(const tinydir_dir *dir, tinydir_file *file)
505 {
506 	const _tinydir_char_t *filename;
507 	if (dir == NULL || file == NULL)
508 	{
509 		errno = EINVAL;
510 		return -1;
511 	}
512 #ifdef _MSC_VER
513 	if (dir->_h == INVALID_HANDLE_VALUE)
514 #else
515 	if (dir->_e == NULL)
516 #endif
517 	{
518 		errno = ENOENT;
519 		return -1;
520 	}
521 	filename =
522 #ifdef _MSC_VER
523 		dir->_f.cFileName;
524 #else
525 		dir->_e->d_name;
526 #endif
527 	if (_tinydir_strlen(dir->path) +
528 		_tinydir_strlen(filename) + 1 + _TINYDIR_PATH_EXTRA >=
529 		_TINYDIR_PATH_MAX)
530 	{
531 		/* the path for the file will be too long */
532 		errno = ENAMETOOLONG;
533 		return -1;
534 	}
535 	if (_tinydir_strlen(filename) >= _TINYDIR_FILENAME_MAX)
536 	{
537 		errno = ENAMETOOLONG;
538 		return -1;
539 	}
540 
541 	_tinydir_strcpy(file->path, dir->path);
542 	if (_tinydir_strcmp(dir->path, TINYDIR_STRING("/")) != 0)
543 		_tinydir_strcat(file->path, TINYDIR_STRING("/"));
544 	_tinydir_strcpy(file->name, filename);
545 	_tinydir_strcat(file->path, filename);
546 #ifndef _MSC_VER
547 #ifdef __MINGW32__
548 	if (_tstat(
549 #elif (defined _BSD_SOURCE) || (defined _DEFAULT_SOURCE)	\
550 	|| ((defined _XOPEN_SOURCE) && (_XOPEN_SOURCE >= 500))	\
551 	|| ((defined _POSIX_C_SOURCE) && (_POSIX_C_SOURCE >= 200112L)) \
552 	|| ((defined __APPLE__) && (defined __MACH__)) \
553 	|| (defined BSD)
554 	if (lstat(
555 #else
556 	if (stat(
557 #endif
558 		file->path, &file->_s) == -1)
559 	{
560 		return -1;
561 	}
562 #endif
563 	_tinydir_get_ext(file);
564 
565 	file->is_dir =
566 #ifdef _MSC_VER
567 		!!(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
568 #else
569 		S_ISDIR(file->_s.st_mode);
570 #endif
571 	file->is_reg =
572 #ifdef _MSC_VER
573 		!!(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_NORMAL) ||
574 		(
575 			!(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_DEVICE) &&
576 			!(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
577 			!(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED) &&
578 #ifdef FILE_ATTRIBUTE_INTEGRITY_STREAM
579 			!(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_INTEGRITY_STREAM) &&
580 #endif
581 #ifdef FILE_ATTRIBUTE_NO_SCRUB_DATA
582 			!(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_NO_SCRUB_DATA) &&
583 #endif
584 			!(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_OFFLINE) &&
585 			!(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY));
586 #else
587 		S_ISREG(file->_s.st_mode);
588 #endif
589 
590 	return 0;
591 }
592 
593 _TINYDIR_FUNC
594 int tinydir_readfile_n(const tinydir_dir *dir, tinydir_file *file, size_t i)
595 {
596 	if (dir == NULL || file == NULL)
597 	{
598 		errno = EINVAL;
599 		return -1;
600 	}
601 	if (i >= dir->n_files)
602 	{
603 		errno = ENOENT;
604 		return -1;
605 	}
606 
607 	memcpy(file, &dir->_files[i], sizeof(tinydir_file));
608 	_tinydir_get_ext(file);
609 
610 	return 0;
611 }
612 
613 _TINYDIR_FUNC
614 int tinydir_open_subdir_n(tinydir_dir *dir, size_t i)
615 {
616 	_tinydir_char_t path[_TINYDIR_PATH_MAX];
617 	if (dir == NULL)
618 	{
619 		errno = EINVAL;
620 		return -1;
621 	}
622 	if (i >= dir->n_files || !dir->_files[i].is_dir)
623 	{
624 		errno = ENOENT;
625 		return -1;
626 	}
627 
628 	_tinydir_strcpy(path, dir->_files[i].path);
629 	tinydir_close(dir);
630 	if (tinydir_open_sorted(dir, path) == -1)
631 	{
632 		return -1;
633 	}
634 
635 	return 0;
636 }
637 
638 /* Open a single file given its path */
639 _TINYDIR_FUNC
640 int tinydir_file_open(tinydir_file *file, const _tinydir_char_t *path)
641 {
642 	tinydir_dir dir;
643 	int result = 0;
644 	int found = 0;
645 	_tinydir_char_t dir_name_buf[_TINYDIR_PATH_MAX];
646 	_tinydir_char_t file_name_buf[_TINYDIR_PATH_MAX];
647 	_tinydir_char_t *dir_name;
648 	_tinydir_char_t *base_name;
649 #if (defined _MSC_VER || defined __MINGW32__)
650 	_tinydir_char_t drive_buf[_TINYDIR_PATH_MAX];
651 	_tinydir_char_t ext_buf[_TINYDIR_FILENAME_MAX];
652 #endif
653 
654 	if (file == NULL || path == NULL || _tinydir_strlen(path) == 0)
655 	{
656 		errno = EINVAL;
657 		return -1;
658 	}
659 	if (_tinydir_strlen(path) + _TINYDIR_PATH_EXTRA >= _TINYDIR_PATH_MAX)
660 	{
661 		errno = ENAMETOOLONG;
662 		return -1;
663 	}
664 
665 	/* Get the parent path */
666 #if (defined _MSC_VER || defined __MINGW32__)
667 #if ((defined _MSC_VER) && (_MSC_VER >= 1400))
668 	errno = _tsplitpath_s(
669 		path,
670 		drive_buf, _TINYDIR_DRIVE_MAX,
671 		dir_name_buf, _TINYDIR_FILENAME_MAX,
672 		file_name_buf, _TINYDIR_FILENAME_MAX,
673 		ext_buf, _TINYDIR_FILENAME_MAX);
674 #else
675 	_tsplitpath(
676 		path,
677 		drive_buf,
678 		dir_name_buf,
679 		file_name_buf,
680 		ext_buf);
681 #endif
682 
683 	if (errno)
684 	{
685 		return -1;
686 	}
687 
688 /* _splitpath_s not work fine with only filename and widechar support */
689 #ifdef _UNICODE
690 	if (drive_buf[0] == L'\xFEFE')
691 		drive_buf[0] = '\0';
692 	if (dir_name_buf[0] == L'\xFEFE')
693 		dir_name_buf[0] = '\0';
694 #endif
695 
696 	/* Emulate the behavior of dirname by returning "." for dir name if it's
697 	empty */
698 	if (drive_buf[0] == '\0' && dir_name_buf[0] == '\0')
699 	{
700 		_tinydir_strcpy(dir_name_buf, TINYDIR_STRING("."));
701 	}
702 	/* Concatenate the drive letter and dir name to form full dir name */
703 	_tinydir_strcat(drive_buf, dir_name_buf);
704 	dir_name = drive_buf;
705 	/* Concatenate the file name and extension to form base name */
706 	_tinydir_strcat(file_name_buf, ext_buf);
707 	base_name = file_name_buf;
708 #else
709 	_tinydir_strcpy(dir_name_buf, path);
710 	dir_name = dirname(dir_name_buf);
711 	_tinydir_strcpy(file_name_buf, path);
712 	base_name = basename(file_name_buf);
713 #endif
714 
715 	/* Special case: if the path is a root dir, open the parent dir as the file */
716 #if (defined _MSC_VER || defined __MINGW32__)
717 	if (_tinydir_strlen(base_name) == 0)
718 #else
719 	if ((_tinydir_strcmp(base_name, TINYDIR_STRING("/"))) == 0)
720 #endif
721 	{
722 		memset(file, 0, sizeof * file);
723 		file->is_dir = 1;
724 		file->is_reg = 0;
725 		_tinydir_strcpy(file->path, dir_name);
726 		file->extension = file->path + _tinydir_strlen(file->path);
727 		return 0;
728 	}
729 
730 	/* Open the parent directory */
731 	if (tinydir_open(&dir, dir_name) == -1)
732 	{
733 		return -1;
734 	}
735 
736 	/* Read through the parent directory and look for the file */
737 	while (dir.has_next)
738 	{
739 		if (tinydir_readfile(&dir, file) == -1)
740 		{
741 			result = -1;
742 			goto bail;
743 		}
744 		if (_tinydir_strcmp(file->name, base_name) == 0)
745 		{
746 			/* File found */
747 			found = 1;
748 			break;
749 		}
750 		tinydir_next(&dir);
751 	}
752 	if (!found)
753 	{
754 		result = -1;
755 		errno = ENOENT;
756 	}
757 
758 bail:
759 	tinydir_close(&dir);
760 	return result;
761 }
762 
763 _TINYDIR_FUNC
764 void _tinydir_get_ext(tinydir_file *file)
765 {
766 	_tinydir_char_t *period = _tinydir_strrchr(file->name, TINYDIR_STRING('.'));
767 	if (period == NULL)
768 	{
769 		file->extension = &(file->name[_tinydir_strlen(file->name)]);
770 	}
771 	else
772 	{
773 		file->extension = period + 1;
774 	}
775 }
776 
777 _TINYDIR_FUNC
778 int _tinydir_file_cmp(const void *a, const void *b)
779 {
780 	const tinydir_file *fa = (const tinydir_file *)a;
781 	const tinydir_file *fb = (const tinydir_file *)b;
782 	if (fa->is_dir != fb->is_dir)
783 	{
784 		return -(fa->is_dir - fb->is_dir);
785 	}
786 	return _tinydir_strncmp(fa->name, fb->name, _TINYDIR_FILENAME_MAX);
787 }
788 
789 #ifndef _MSC_VER
790 #ifndef _TINYDIR_USE_READDIR
791 /*
792 The following authored by Ben Hutchings <[email protected]>
793 from https://womble.decadent.org.uk/readdir_r-advisory.html
794 */
795 /* Calculate the required buffer size (in bytes) for directory      *
796 * entries read from the given directory handle.  Return -1 if this  *
797 * this cannot be done.                                              *
798 *                                                                   *
799 * This code does not trust values of NAME_MAX that are less than    *
800 * 255, since some systems (including at least HP-UX) incorrectly    *
801 * define it to be a smaller value.                                  */
802 _TINYDIR_FUNC
803 size_t _tinydir_dirent_buf_size(_TINYDIR_DIR *dirp)
804 {
805 	long name_max;
806 	size_t name_end;
807 	/* parameter may be unused */
808 	(void)dirp;
809 
810 #if defined _TINYDIR_USE_FPATHCONF
811 	name_max = fpathconf(dirfd(dirp), _PC_NAME_MAX);
812 	if (name_max == -1)
813 #if defined(NAME_MAX)
814 		name_max = (NAME_MAX > 255) ? NAME_MAX : 255;
815 #else
816 		return (size_t)(-1);
817 #endif
818 #elif defined(NAME_MAX)
819  	name_max = (NAME_MAX > 255) ? NAME_MAX : 255;
820 #else
821 #error "buffer size for readdir_r cannot be determined"
822 #endif
823 	name_end = (size_t)offsetof(struct _tinydir_dirent, d_name) + name_max + 1;
824 	return (name_end > sizeof(struct _tinydir_dirent) ?
825 		name_end : sizeof(struct _tinydir_dirent));
826 }
827 #endif
828 #endif
829 
830 #ifdef __cplusplus
831 }
832 #endif
833 
834 # if defined (_MSC_VER)
835 # pragma warning(pop)
836 # endif
837 
838 #endif
839