2 Copyright (c) 2013-2021, tinydir authors:
6 - Andargor <andargor@yahoo.com>
9 Redistribution and use in source and binary forms, with or without
10 modification, are permitted provided that the following conditions are met:
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.
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.
36 #if ((defined _UNICODE) && !(defined UNICODE))
40 #if ((defined UNICODE) && !(defined _UNICODE))
48 # ifndef WIN32_LEAN_AND_MEAN
49 # define WIN32_LEAN_AND_MEAN
53 # pragma warning(push)
54 # pragma warning (disable : 4996)
58 # include <sys/stat.h>
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
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
89 #if (defined _MSC_VER || defined __MINGW32__)
91 # define _TINYDIR_PATH_MAX MAX_PATH
92 #elif defined __linux__
95 # define _TINYDIR_PATH_MAX PATH_MAX
97 #elif defined(__unix__) || (defined(__APPLE__) && defined(__MACH__))
98 # include <sys/param.h>
102 # define _TINYDIR_PATH_MAX PATH_MAX
107 #ifndef _TINYDIR_PATH_MAX
108 #define _TINYDIR_PATH_MAX 4096
112 /* extra chars for the "\\*" mask */
113 # define _TINYDIR_PATH_EXTRA 2
115 # define _TINYDIR_PATH_EXTRA 0
118 #define _TINYDIR_FILENAME_MAX 256
120 #if (defined _MSC_VER || defined __MINGW32__)
121 #define _TINYDIR_DRIVE_MAX 3
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
134 # define _TINYDIR_FUNC static
137 /* readdir_r usage; define TINYDIR_USE_READDIR_R to use it (if supported) */
138 #ifdef TINYDIR_USE_READDIR_R
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 ||\
144 # define _TINYDIR_HAS_READDIR_R
146 #if _POSIX_C_SOURCE >= 200112L
147 # define _TINYDIR_HAS_FPATHCONF
150 #if _BSD_SOURCE || _SVID_SOURCE || \
151 (_POSIX_C_SOURCE >= 200809L || _XOPEN_SOURCE >= 700)
152 # define _TINYDIR_HAS_DIRFD
153 # include <sys/types.h>
155 #if defined _TINYDIR_HAS_FPATHCONF && defined _TINYDIR_HAS_DIRFD &&\
157 # define _TINYDIR_USE_FPATHCONF
159 #if defined __MINGW32__ || !defined _TINYDIR_HAS_READDIR_R ||\
160 !(defined _TINYDIR_USE_FPATHCONF || defined NAME_MAX)
161 # define _TINYDIR_USE_READDIR
164 /* Use readdir by default */
166 # define _TINYDIR_USE_READDIR
169 /* MINGW32 has two versions of dirent, ASCII and UNICODE*/
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
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
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)
190 #error "Either define both alloc and free or none of them!"
193 #if !defined(_TINYDIR_MALLOC)
194 #define _TINYDIR_MALLOC(_size) malloc(_size)
195 #define _TINYDIR_FREE(_ptr) free(_ptr)
196 #endif /* !defined(_TINYDIR_MALLOC) */
198 typedef struct tinydir_file
200 _tinydir_char_t path
[_TINYDIR_PATH_MAX
];
201 _tinydir_char_t name
[_TINYDIR_FILENAME_MAX
];
202 _tinydir_char_t
*extension
;
215 typedef struct tinydir_dir
217 _tinydir_char_t path
[_TINYDIR_PATH_MAX
];
221 tinydir_file
*_files
;
227 struct _tinydir_dirent
*_e
;
228 #ifndef _TINYDIR_USE_READDIR
229 struct _tinydir_dirent
*_ep
;
238 int tinydir_open(tinydir_dir
*dir
, const _tinydir_char_t
*path
);
240 int tinydir_open_sorted(tinydir_dir
*dir
, const _tinydir_char_t
*path
);
242 void tinydir_close(tinydir_dir
*dir
);
245 int tinydir_next(tinydir_dir
*dir
);
247 int tinydir_readfile(const tinydir_dir
*dir
, tinydir_file
*file
);
249 int tinydir_readfile_n(const tinydir_dir
*dir
, tinydir_file
*file
, size_t i
);
251 int tinydir_open_subdir_n(tinydir_dir
*dir
, size_t i
);
254 int tinydir_file_open(tinydir_file
*file
, const _tinydir_char_t
*path
);
256 void _tinydir_get_ext(tinydir_file
*file
);
258 int _tinydir_file_cmp(const void *a
, const void *b
);
260 #ifndef _TINYDIR_USE_READDIR
262 size_t _tinydir_dirent_buf_size(_TINYDIR_DIR
*dirp
);
270 int tinydir_open(tinydir_dir
*dir
, const _tinydir_char_t
*path
)
273 #ifndef _TINYDIR_USE_READDIR
275 int size
; /* using int size */
278 _tinydir_char_t path_buf
[_TINYDIR_PATH_MAX
];
280 _tinydir_char_t
*pathp
;
282 if (dir
== NULL
|| path
== NULL
|| _tinydir_strlen(path
) == 0)
287 if (_tinydir_strlen(path
) + _TINYDIR_PATH_EXTRA
>= _TINYDIR_PATH_MAX
)
289 errno
= ENAMETOOLONG
;
296 dir
->_h
= INVALID_HANDLE_VALUE
;
299 #ifndef _TINYDIR_USE_READDIR
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('/')))
310 *pathp
= TINYDIR_STRING('\0');
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);
319 dir
->_h
= FindFirstFile(path_buf
, &dir
->_f
);
321 if (dir
->_h
== INVALID_HANDLE_VALUE
)
325 dir
->_d
= _tinydir_opendir(path
);
332 /* read first file */
335 #ifdef _TINYDIR_USE_READDIR
336 dir
->_e
= _tinydir_readdir(dir
->_d
);
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;
344 error
= readdir_r(dir
->_d
, dir
->_ep
, &dir
->_e
);
345 if (error
!= 0) return -1;
361 int tinydir_open_sorted(tinydir_dir
*dir
, const _tinydir_char_t
*path
)
363 /* Count the number of files first, to pre-allocate the files array */
365 if (tinydir_open(dir
, path
) == -1)
369 while (dir
->has_next
)
372 if (tinydir_next(dir
) == -1)
379 if (n_files
== 0 || tinydir_open(dir
, path
) == -1)
385 dir
->_files
= (tinydir_file
*)_TINYDIR_MALLOC(sizeof *dir
->_files
* n_files
);
386 if (dir
->_files
== NULL
)
390 while (dir
->has_next
)
392 tinydir_file
*p_file
;
395 p_file
= &dir
->_files
[dir
->n_files
- 1];
396 if (tinydir_readfile(dir
, p_file
) == -1)
401 if (tinydir_next(dir
) == -1)
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
)
414 qsort(dir
->_files
, dir
->n_files
, sizeof(tinydir_file
), _tinydir_file_cmp
);
424 void tinydir_close(tinydir_dir
*dir
)
431 memset(dir
->path
, 0, sizeof(dir
->path
));
434 _TINYDIR_FREE(dir
->_files
);
437 if (dir
->_h
!= INVALID_HANDLE_VALUE
)
441 dir
->_h
= INVALID_HANDLE_VALUE
;
445 _tinydir_closedir(dir
->_d
);
449 #ifndef _TINYDIR_USE_READDIR
450 _TINYDIR_FREE(dir
->_ep
);
457 int tinydir_next(tinydir_dir
*dir
)
471 if (FindNextFile(dir
->_h
, &dir
->_f
) == 0)
473 #ifdef _TINYDIR_USE_READDIR
474 dir
->_e
= _tinydir_readdir(dir
->_d
);
476 if (dir
->_ep
== NULL
)
480 if (readdir_r(dir
->_d
, dir
->_ep
, &dir
->_e
) != 0)
490 if (GetLastError() != ERROR_SUCCESS
&&
491 GetLastError() != ERROR_NO_MORE_FILES
)
504 int tinydir_readfile(const tinydir_dir
*dir
, tinydir_file
*file
)
506 const _tinydir_char_t
*filename
;
507 if (dir
== NULL
|| file
== NULL
)
513 if (dir
->_h
== INVALID_HANDLE_VALUE
)
527 if (_tinydir_strlen(dir
->path
) +
528 _tinydir_strlen(filename
) + 1 + _TINYDIR_PATH_EXTRA
>=
531 /* the path for the file will be too long */
532 errno
= ENAMETOOLONG
;
535 if (_tinydir_strlen(filename
) >= _TINYDIR_FILENAME_MAX
)
537 errno
= ENAMETOOLONG
;
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
);
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__)) \
558 file
->path
, &file
->_s
) == -1)
563 _tinydir_get_ext(file
);
567 !!(dir
->_f
.dwFileAttributes
& FILE_ATTRIBUTE_DIRECTORY
);
569 S_ISDIR(file
->_s
.st_mode
);
573 !!(dir
->_f
.dwFileAttributes
& FILE_ATTRIBUTE_NORMAL
) ||
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
) &&
581 #ifdef FILE_ATTRIBUTE_NO_SCRUB_DATA
582 !(dir
->_f
.dwFileAttributes
& FILE_ATTRIBUTE_NO_SCRUB_DATA
) &&
584 !(dir
->_f
.dwFileAttributes
& FILE_ATTRIBUTE_OFFLINE
) &&
585 !(dir
->_f
.dwFileAttributes
& FILE_ATTRIBUTE_TEMPORARY
));
587 S_ISREG(file
->_s
.st_mode
);
594 int tinydir_readfile_n(const tinydir_dir
*dir
, tinydir_file
*file
, size_t i
)
596 if (dir
== NULL
|| file
== NULL
)
601 if (i
>= dir
->n_files
)
607 memcpy(file
, &dir
->_files
[i
], sizeof(tinydir_file
));
608 _tinydir_get_ext(file
);
614 int tinydir_open_subdir_n(tinydir_dir
*dir
, size_t i
)
616 _tinydir_char_t path
[_TINYDIR_PATH_MAX
];
622 if (i
>= dir
->n_files
|| !dir
->_files
[i
].is_dir
)
628 _tinydir_strcpy(path
, dir
->_files
[i
].path
);
630 if (tinydir_open_sorted(dir
, path
) == -1)
638 /* Open a single file given its path */
640 int tinydir_file_open(tinydir_file
*file
, const _tinydir_char_t
*path
)
645 _tinydir_char_t dir_name_buf
[_TINYDIR_PATH_MAX
];
646 _tinydir_char_t file_name_buf
[_TINYDIR_FILENAME_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
];
654 if (file
== NULL
|| path
== NULL
|| _tinydir_strlen(path
) == 0)
659 if (_tinydir_strlen(path
) + _TINYDIR_PATH_EXTRA
>= _TINYDIR_PATH_MAX
)
661 errno
= ENAMETOOLONG
;
665 /* Get the parent path */
666 #if (defined _MSC_VER || defined __MINGW32__)
667 #if ((defined _MSC_VER) && (_MSC_VER >= 1400))
668 errno
= _tsplitpath_s(
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
);
688 /* _splitpath_s not work fine with only filename and widechar support */
690 if (drive_buf
[0] == L
'\xFEFE')
692 if (dir_name_buf
[0] == L
'\xFEFE')
693 dir_name_buf
[0] = '\0';
696 /* Emulate the behavior of dirname by returning "." for dir name if it's
698 if (drive_buf
[0] == '\0' && dir_name_buf
[0] == '\0')
700 _tinydir_strcpy(dir_name_buf
, TINYDIR_STRING("."));
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
;
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
);
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)
719 if ((_tinydir_strcmp(base_name
, TINYDIR_STRING("/"))) == 0)
722 memset(file
, 0, sizeof * file
);
725 _tinydir_strcpy(file
->path
, dir_name
);
726 file
->extension
= file
->path
+ _tinydir_strlen(file
->path
);
730 /* Open the parent directory */
731 if (tinydir_open(&dir
, dir_name
) == -1)
736 /* Read through the parent directory and look for the file */
739 if (tinydir_readfile(&dir
, file
) == -1)
744 if (_tinydir_strcmp(file
->name
, base_name
) == 0)
764 void _tinydir_get_ext(tinydir_file
*file
)
766 _tinydir_char_t
*period
= _tinydir_strrchr(file
->name
, TINYDIR_STRING('.'));
769 file
->extension
= &(file
->name
[_tinydir_strlen(file
->name
)]);
773 file
->extension
= period
+ 1;
778 int _tinydir_file_cmp(const void *a
, const void *b
)
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
)
784 return -(fa
->is_dir
- fb
->is_dir
);
786 return _tinydir_strncmp(fa
->name
, fb
->name
, _TINYDIR_FILENAME_MAX
);
790 #ifndef _TINYDIR_USE_READDIR
792 The following authored by Ben Hutchings <ben@decadent.org.uk>
793 from https://womble.decadent.org.uk/readdir_r-advisory.html
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. *
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. */
803 size_t _tinydir_dirent_buf_size(_TINYDIR_DIR
*dirp
)
807 /* parameter may be unused */
810 #if defined _TINYDIR_USE_FPATHCONF
811 name_max
= fpathconf(dirfd(dirp
), _PC_NAME_MAX
);
813 #if defined(NAME_MAX)
814 name_max
= (NAME_MAX
> 255) ? NAME_MAX
: 255;
818 #elif defined(NAME_MAX)
819 name_max
= (NAME_MAX
> 255) ? NAME_MAX
: 255;
821 #error "buffer size for readdir_r cannot be determined"
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
));
834 # if defined (_MSC_VER)
835 # pragma warning(pop)