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