1 /* SPDX-License-Identifier: MIT 2 * Dirent interface for Microsoft Visual Studio 3 * Version 1.21 4 * Copyright (C) 2006-2012 Toni Ronkko 5 * https://github.com/tronkko/dirent 6 */ 7 8 #ifndef DIRENT_H 9 #define DIRENT_H 10 11 /* 12 * Include windows.h without Windows Sockets 1.1 to prevent conflicts with 13 * Windows Sockets 2.0. 14 */ 15 #ifndef WIN32_LEAN_AND_MEAN 16 # define WIN32_LEAN_AND_MEAN 17 #endif 18 19 #include <windows.h> 20 21 #include <stdio.h> 22 #include <stdarg.h> 23 #include <wchar.h> 24 #include <string.h> 25 #include <stdlib.h> 26 #include <malloc.h> 27 #include <sys/types.h> 28 #include <sys/stat.h> 29 #include <errno.h> 30 31 /* Maximum length of file name */ 32 #if !defined(PATH_MAX) 33 # define PATH_MAX MAX_PATH 34 #endif 35 36 /* File type flags for d_type */ 37 #define DT_UNKNOWN 0 38 #define DT_REG S_IFREG 39 #define DT_DIR S_IFDIR 40 #define DT_CHR S_IFCHR 41 42 /* 43 * File type macros. Note that block devices, sockets and links cannot be 44 * distinguished on Windows and the macros S_ISBLK, S_ISSOCK and S_ISLNK are 45 * only defined for compatibility. These macros should always return false 46 * on Windows. 47 */ 48 #if !defined(S_ISDIR) 49 # define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) 50 #endif 51 #if !defined(S_ISREG) 52 # define S_ISREG(mode) (((mode) & S_IFMT) == S_IFREG) 53 #endif 54 55 /* Wide-character version */ 56 struct _wdirent { 57 /* Always zero */ 58 long d_ino; 59 60 /* Structure size */ 61 unsigned short d_reclen; 62 63 /* Length of name without \0 */ 64 size_t d_namlen; 65 66 /* File type */ 67 int d_type; 68 69 /* File name */ 70 wchar_t d_name[PATH_MAX]; 71 }; 72 typedef struct _wdirent _wdirent; 73 74 struct _WDIR { 75 /* Current directory entry */ 76 struct _wdirent ent; 77 78 /* Private file data */ 79 WIN32_FIND_DATAW data; 80 81 /* True if data is valid */ 82 int cached; 83 84 /* Win32 search handle */ 85 HANDLE handle; 86 87 /* Initial directory name */ 88 wchar_t *patt; 89 }; 90 typedef struct _WDIR _WDIR; 91 92 static _WDIR *_wopendir(const wchar_t *dirname); 93 static int _wclosedir(_WDIR *dirp); 94 95 /* For compatibility with Symbian */ 96 #define wdirent _wdirent 97 #define WDIR _WDIR 98 #define wopendir _wopendir 99 #define wclosedir _wclosedir 100 101 /* Multi-byte character versions */ 102 struct dirent { 103 /* Always zero */ 104 long d_ino; 105 106 /* Structure size */ 107 unsigned short d_reclen; 108 109 /* Length of name without \0 */ 110 size_t d_namlen; 111 112 /* File type */ 113 int d_type; 114 115 /* File name */ 116 char d_name[PATH_MAX]; 117 }; 118 typedef struct dirent dirent; 119 120 struct DIR { 121 struct dirent ent; 122 struct _WDIR *wdirp; 123 }; 124 typedef struct DIR DIR; 125 126 static DIR *opendir(const char *dirname); 127 static struct dirent *readdir(DIR *dirp); 128 static int closedir(DIR *dirp); 129 130 /* Internal utility functions */ 131 static WIN32_FIND_DATAW *dirent_first(_WDIR *dirp); 132 static WIN32_FIND_DATAW *dirent_next(_WDIR *dirp); 133 134 static int dirent_mbstowcs_s( 135 size_t *pReturnValue, 136 wchar_t *wcstr, 137 size_t sizeInWords, 138 const char *mbstr, 139 size_t count); 140 141 static int dirent_wcstombs_s( 142 size_t *pReturnValue, 143 char *mbstr, 144 size_t sizeInBytes, 145 const wchar_t *wcstr, 146 size_t count); 147 148 static void dirent_set_errno(int error); 149 150 /* 151 * Open directory stream DIRNAME for read and return a pointer to the 152 * internal working area that is used to retrieve individual directory 153 * entries. 154 */ 155 static _WDIR* 156 _wopendir(const wchar_t *dirname) 157 { 158 _WDIR *dirp = NULL; 159 int error; 160 161 /* Must have directory name */ 162 if (dirname == NULL || dirname[0] == '\0') { 163 dirent_set_errno(ENOENT); 164 return NULL; 165 } 166 167 /* Allocate new _WDIR structure */ 168 dirp = (_WDIR *)malloc(sizeof(struct _WDIR)); 169 if (dirp != NULL) { 170 DWORD n; 171 172 /* Reset _WDIR structure */ 173 dirp->handle = INVALID_HANDLE_VALUE; 174 dirp->patt = NULL; 175 dirp->cached = 0; 176 177 /* Compute the length of full path plus zero terminator 178 * 179 * Note that on WinRT there's no way to convert relative paths 180 * into absolute paths, so just assume its an absolute path. 181 */ 182 #if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP) 183 n = wcslen(dirname); 184 #else 185 n = GetFullPathNameW(dirname, 0, NULL, NULL); 186 #endif 187 188 /* Allocate room for absolute directory name and search 189 * pattern 190 */ 191 dirp->patt = (wchar_t *)malloc(sizeof(wchar_t) * n + 16); 192 if (dirp->patt) { 193 /* Convert relative directory name to an 194 * absolute one. This allows rewinddir() to 195 * function correctly even when current working 196 * directory is changed between opendir() 197 * and rewinddir(). 198 * 199 * Note that on WinRT there's no way to convert 200 * relative paths into absolute paths, so just 201 * assume its an absolute path. 202 */ 203 #if defined(WINAPI_FAMILY) && \ 204 (WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP) 205 wcsncpy_s(dirp->patt, n + 1, dirname, n); 206 #else 207 n = GetFullPathNameW(dirname, n, dirp->patt, NULL); 208 #endif 209 if (n > 0) { 210 wchar_t *p; 211 212 /* Append search pattern \* to the directory 213 * name 214 */ 215 p = dirp->patt + n; 216 if (dirp->patt < p) { 217 switch (p[-1]) { 218 case '\\': 219 case '/': 220 case ':': 221 /* Directory ends in path separator, 222 * e.g.c:\temp\ 223 */ 224 /*NOP*/; 225 break; 226 227 default: 228 /* Directory name doesn't end in path 229 * separator 230 */ 231 *p++ = '\\'; 232 } 233 } 234 *p++ = '*'; 235 *p = '\0'; 236 237 /* Open directory stream and retrieve the first 238 * entry 239 */ 240 if (dirent_first(dirp)) { 241 /* Directory stream opened successfully */ 242 error = 0; 243 } else { 244 /* Cannot retrieve first entry */ 245 error = 1; 246 dirent_set_errno(ENOENT); 247 } 248 249 } else { 250 /* Cannot retrieve full path name */ 251 dirent_set_errno(ENOENT); 252 error = 1; 253 } 254 255 } else { 256 /* Cannot allocate memory for search pattern */ 257 error = 1; 258 } 259 260 } else { 261 /* Cannot allocate _WDIR structure */ 262 error = 1; 263 } 264 265 /* Clean up in case of error */ 266 if (error && dirp) { 267 _wclosedir(dirp); 268 dirp = NULL; 269 } 270 271 return dirp; 272 } 273 274 /* 275 * Close directory stream opened by opendir() function. 276 * This invalidates the DIR structure as well as any directory 277 * entry read previously by _wreaddir(). 278 */ 279 static int 280 _wclosedir(_WDIR *dirp) 281 { 282 int ok; 283 if (dirp) { 284 285 /* Release search handle */ 286 if (dirp->handle != INVALID_HANDLE_VALUE) { 287 FindClose(dirp->handle); 288 dirp->handle = INVALID_HANDLE_VALUE; 289 } 290 291 /* Release search pattern */ 292 if (dirp->patt) { 293 free(dirp->patt); 294 dirp->patt = NULL; 295 } 296 297 /* Release directory structure */ 298 free(dirp); 299 ok = /*success*/0; 300 301 } else { 302 /* Invalid directory stream */ 303 dirent_set_errno(EBADF); 304 ok = /*failure*/-1; 305 } 306 return ok; 307 } 308 309 /* Get first directory entry (internal) */ 310 static WIN32_FIND_DATAW* 311 dirent_first(_WDIR *dirp) 312 { 313 WIN32_FIND_DATAW *datap; 314 315 /* Open directory and retrieve the first entry */ 316 dirp->handle = FindFirstFileExW( 317 dirp->patt, FindExInfoStandard, &dirp->data, 318 FindExSearchNameMatch, NULL, 0); 319 if (dirp->handle != INVALID_HANDLE_VALUE) { 320 321 /* a directory entry is now waiting in memory */ 322 datap = &dirp->data; 323 dirp->cached = 1; 324 325 } else { 326 327 /* Failed to re-open directory: no directory entry in memory */ 328 dirp->cached = 0; 329 datap = NULL; 330 331 } 332 return datap; 333 } 334 335 /* Get next directory entry (internal) */ 336 static WIN32_FIND_DATAW* 337 dirent_next(_WDIR *dirp) 338 { 339 WIN32_FIND_DATAW *p; 340 341 /* Get next directory entry */ 342 if (dirp->cached != 0) { 343 344 /* A valid directory entry already in memory */ 345 p = &dirp->data; 346 dirp->cached = 0; 347 348 } else if (dirp->handle != INVALID_HANDLE_VALUE) { 349 350 /* Get the next directory entry from stream */ 351 if (FindNextFileW(dirp->handle, &dirp->data) != FALSE) { 352 /* Got a file */ 353 p = &dirp->data; 354 } else { 355 /* The very last entry has been processed 356 *or an error occurred 357 */ 358 FindClose(dirp->handle); 359 dirp->handle = INVALID_HANDLE_VALUE; 360 p = NULL; 361 } 362 363 } else { 364 365 /* End of directory stream reached */ 366 p = NULL; 367 368 } 369 370 return p; 371 } 372 373 /* 374 * Open directory stream using plain old C-string. 375 */ 376 static DIR* 377 opendir(const char *dirname) 378 { 379 struct DIR *dirp; 380 int error; 381 382 /* Must have directory name */ 383 if (dirname == NULL || dirname[0] == '\0') { 384 dirent_set_errno(ENOENT); 385 return NULL; 386 } 387 388 /* Allocate memory for DIR structure */ 389 dirp = (DIR *)malloc(sizeof(struct DIR)); 390 if (dirp) { 391 wchar_t wname[PATH_MAX]; 392 size_t n; 393 394 /* Convert directory name to wide-character string */ 395 error = dirent_mbstowcs_s(&n, wname, PATH_MAX, 396 dirname, PATH_MAX); 397 if (!error) { 398 399 /* Open directory stream using wide-character name */ 400 dirp->wdirp = _wopendir(wname); 401 if (dirp->wdirp) { 402 /* Directory stream opened */ 403 error = 0; 404 } else { 405 /* Failed to open directory stream */ 406 error = 1; 407 } 408 409 } else { 410 /* 411 * Cannot convert file name to wide-character string. 412 * This occurs if the string contains invalid multi-byte 413 * sequences or the output buffer is too small to 414 * contain the resulting string. 415 */ 416 error = 1; 417 } 418 419 } else { 420 /* Cannot allocate DIR structure */ 421 error = 1; 422 } 423 424 /* Clean up in case of error */ 425 if (error && dirp) { 426 free(dirp); 427 dirp = NULL; 428 } 429 430 return dirp; 431 } 432 433 /* 434 * Read next directory entry. 435 * 436 * When working with text consoles, please note that file names 437 * returned by readdir() are represented in the default ANSI code 438 * page while any output toconsole is typically formatted on another 439 * code page. Thus, non-ASCII characters in file names will not usually 440 * display correctly on console. The problem can be fixed in two ways: 441 * (1) change the character set of console to 1252 using chcp utility 442 * and use Lucida Console font, or (2) use _cprintf function when 443 * writing to console. The _cprintf() will re-encode ANSI strings to the 444 * console code page so many non-ASCII characters will display correctly. 445 */ 446 static struct dirent* 447 readdir(DIR *dirp) 448 { 449 WIN32_FIND_DATAW *datap; 450 struct dirent *entp; 451 452 /* Read next directory entry */ 453 datap = dirent_next(dirp->wdirp); 454 if (datap) { 455 size_t n; 456 int error; 457 458 /* Attempt to convert file name to multi-byte string */ 459 error = dirent_wcstombs_s(&n, dirp->ent.d_name, 460 PATH_MAX, datap->cFileName, PATH_MAX); 461 462 /* 463 * If the file name cannot be represented by a multi-byte 464 * string, then attempt to use old 8+3 file name. 465 * This allows traditional Unix-code to access some file 466 * names despite of unicode characters, although file names 467 * may seem unfamiliar to the user. 468 * 469 * Be ware that the code below cannot come up with a short 470 * file name unless the file system provides one. At least 471 * VirtualBox shared folders fail to do this. 472 */ 473 if (error && datap->cAlternateFileName[0] != '\0') { 474 error = dirent_wcstombs_s( 475 &n, dirp->ent.d_name, PATH_MAX, 476 datap->cAlternateFileName, PATH_MAX); 477 } 478 479 if (!error) { 480 DWORD attr; 481 482 /* Initialize directory entry for return */ 483 entp = &dirp->ent; 484 485 /* Length of file name excluding zero terminator */ 486 entp->d_namlen = n - 1; 487 488 /* File attributes */ 489 attr = datap->dwFileAttributes; 490 if ((attr & FILE_ATTRIBUTE_DEVICE) != 0) 491 entp->d_type = DT_CHR; 492 else if ((attr & FILE_ATTRIBUTE_DIRECTORY) != 0) 493 entp->d_type = DT_DIR; 494 else 495 entp->d_type = DT_REG; 496 497 /* Reset dummy fields */ 498 entp->d_ino = 0; 499 entp->d_reclen = sizeof(struct dirent); 500 501 } else { 502 /* 503 * Cannot convert file name to multi-byte string so 504 * construct an erroneous directory entry and return 505 * that. Note that we cannot return NULL as that would 506 * stop the processing of directory entries completely. 507 */ 508 entp = &dirp->ent; 509 entp->d_name[0] = '?'; 510 entp->d_name[1] = '\0'; 511 entp->d_namlen = 1; 512 entp->d_type = DT_UNKNOWN; 513 entp->d_ino = 0; 514 entp->d_reclen = 0; 515 } 516 517 } else { 518 /* No more directory entries */ 519 entp = NULL; 520 } 521 522 return entp; 523 } 524 525 /* 526 * Close directory stream. 527 */ 528 static int 529 closedir(DIR *dirp) 530 { 531 int ok; 532 if (dirp) { 533 534 /* Close wide-character directory stream */ 535 ok = _wclosedir(dirp->wdirp); 536 dirp->wdirp = NULL; 537 538 /* Release multi-byte character version */ 539 free(dirp); 540 541 } else { 542 543 /* Invalid directory stream */ 544 dirent_set_errno(EBADF); 545 ok = /*failure*/-1; 546 547 } 548 return ok; 549 } 550 551 /* Convert multi-byte string to wide character string */ 552 static int 553 dirent_mbstowcs_s( 554 size_t *pReturnValue, 555 wchar_t *wcstr, 556 size_t sizeInWords, 557 const char *mbstr, 558 size_t count) 559 { 560 int error; 561 562 #if defined(_MSC_VER) && _MSC_VER >= 1400 563 /* Microsoft Visual Studio 2005 or later */ 564 error = mbstowcs_s(pReturnValue, wcstr, 565 sizeInWords, mbstr, count); 566 #else 567 568 /* Older Visual Studio or non-Microsoft compiler */ 569 size_t n; 570 571 /* Convert to wide-character string (or count characters) */ 572 n = mbstowcs(wcstr, mbstr, sizeInWords); 573 if (!wcstr || n < count) { 574 575 /* Zero-terminate output buffer */ 576 if (wcstr && sizeInWords) { 577 if (n >= sizeInWords) 578 n = sizeInWords - 1; 579 wcstr[n] = 0; 580 } 581 582 /* Length of resulting multi-byte string WITH zero 583 *terminator 584 */ 585 if (pReturnValue) 586 *pReturnValue = n + 1; 587 588 /* Success */ 589 error = 0; 590 591 } else { 592 593 /* Could not convert string */ 594 error = 1; 595 596 } 597 #endif 598 599 return error; 600 } 601 602 /* Convert wide-character string to multi-byte string */ 603 static int 604 dirent_wcstombs_s( 605 size_t *pReturnValue, 606 char *mbstr, 607 size_t sizeInBytes, /* max size of mbstr */ 608 const wchar_t *wcstr, 609 size_t count) 610 { 611 int error; 612 613 #if defined(_MSC_VER) && _MSC_VER >= 1400 614 /* Microsoft Visual Studio 2005 or later */ 615 error = wcstombs_s(pReturnValue, mbstr, sizeInBytes, wcstr, count); 616 #else 617 /* Older Visual Studio or non-Microsoft compiler */ 618 size_t n; 619 620 /* Convert to multi-byte string 621 * (or count the number of bytes needed) 622 */ 623 n = wcstombs(mbstr, wcstr, sizeInBytes); 624 if (!mbstr || n < count) { 625 /* Zero-terminate output buffer */ 626 if (mbstr && sizeInBytes) { 627 if (n >= sizeInBytes) 628 n = sizeInBytes - 1; 629 mbstr[n] = '\0'; 630 } 631 /* Length of resulting multi-bytes string WITH 632 *zero-terminator 633 */ 634 if (pReturnValue) 635 *pReturnValue = n + 1; 636 /* Success */ 637 error = 0; 638 } else { 639 /* Cannot convert string */ 640 error = 1; 641 } 642 #endif 643 644 return error; 645 } 646 647 /* Set errno variable */ 648 static void 649 dirent_set_errno(int error) 650 { 651 #if defined(_MSC_VER) && _MSC_VER >= 1400 652 /* Microsoft Visual Studio 2005 and later */ 653 _set_errno(error); 654 #else 655 656 /* Non-Microsoft compiler or older Microsoft compiler */ 657 errno = error; 658 #endif 659 } 660 661 #endif /*DIRENT_H*/ 662