diff options
Diffstat (limited to 'dirent')
42 files changed, 5325 insertions, 0 deletions
diff --git a/dirent/.gitignore b/dirent/.gitignore new file mode 100644 index 0000000..7def436 --- /dev/null +++ b/dirent/.gitignore @@ -0,0 +1,27 @@ +/CMakeCache.txt +/CMakeFiles +/CTestTestfile.cmake +/DartConfiguration.tcl +/Makefile +/Testing +/Win32 +/cmake_install.cmake +/find +/locate +/ls +/scandir +/cat +/dir +/du +/t-compile +/t-dirent +/t-scandir +/t-cplusplus +/t-unicode +/t-strverscmp +/t-telldir +/t-utf8 +/updatedb +/*.filters +/*.vcxproj +/*.dir diff --git a/dirent/CMakeLists.txt b/dirent/CMakeLists.txt new file mode 100644 index 0000000..0e002c8 --- /dev/null +++ b/dirent/CMakeLists.txt @@ -0,0 +1,64 @@ +cmake_minimum_required(VERSION 2.8.12) +project(dirent LANGUAGES C CXX) + +# User options +option(DIRENT_BUILD_TESTS "Build bundled tests" ON) +message(STATUS "Build test and example programs: ${DIRENT_BUILD_TESTS}") + +# Initialize C and C++ compilers +enable_language(C CXX) + +# Compile in debug mode by default +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Debug + CACHE STRING + "Type of build: None Debug Release RelWithDebInfo MinSizeRel." + FORCE + ) +endif(NOT CMAKE_BUILD_TYPE) + +# Use the include directory only on Windows +if(WIN32) + include_directories(${CMAKE_SOURCE_DIR}/include) +endif(WIN32) + +# Install dirent.h +if(WIN32) + install(FILES include/dirent.h DESTINATION include) +endif(WIN32) + +# Add distclean target +add_custom_target(distclean + COMMAND ${CMAKE_BUILD_TOOL} clean + COMMAND ${CMAKE_COMMAND} -P ${CMAKE_SOURCE_DIR}/distclean.cmake +) + +# Build example programs +if(DIRENT_BUILD_TESTS) + add_executable(find examples/find.c) + add_executable(ls examples/ls.c) + add_executable(locate examples/locate.c) + add_executable(updatedb examples/updatedb.c) + add_executable(scandir examples/scandir.c) + add_executable(cat examples/cat.c) + add_executable(dir examples/dir.c) + add_executable(du examples/du.c) + + # Build test programs + include(CTest) + add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure -C ${CMAKE_CFG_INTDIR}) + function(add_test_executable TEST_NAME) + add_executable(${TEST_NAME} EXCLUDE_FROM_ALL ${ARGN}) + add_test(NAME ${TEST_NAME} WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} COMMAND $<TARGET_FILE:${TEST_NAME}>) + add_dependencies(check ${TEST_NAME}) + endfunction(add_test_executable) + + add_test_executable(t-compile tests/t-compile.c) + add_test_executable(t-dirent tests/t-dirent.c) + add_test_executable(t-scandir tests/t-scandir.c) + add_test_executable(t-unicode tests/t-unicode.c) + add_test_executable(t-cplusplus tests/t-cplusplus.cpp) + add_test_executable(t-telldir tests/t-telldir.c) + add_test_executable(t-strverscmp tests/t-strverscmp.c) + add_test_executable(t-utf8 tests/t-utf8.c) +endif(DIRENT_BUILD_TESTS) diff --git a/dirent/CONTRIBUTING.md b/dirent/CONTRIBUTING.md new file mode 100644 index 0000000..bff76c3 --- /dev/null +++ b/dirent/CONTRIBUTING.md @@ -0,0 +1,41 @@ +# Contributing to Dirent + +Dirent is an open source project and we love to receive contributions from our +community -- you! There are many ways to contribute, from writing tutorials +or blog posts, improving the documentation, submitting bug reports and +feature requests, or writing code which can be incorporated into Dirent. + +For example, we are looking for contributions which + +1. Improve portability of code developed for Unix/Linux to Microsoft Windows. +2. Make Dirent easier to use and/or more useful for programmers working on + Microsoft Windows platform. +3. Remove compiler warnings or fix bugs. + +## How to Suggest a Feature + +We use Github to host code, to track issues and to discuss about Dirent. If +you would like to suggest a feature, then open an issue at Github. + +## How to Report a Bug + +If you have trouble using Dirent, then try to repeat the problem with the +latest version found at Github. If the problem remains, then open an issue +at Github. + +## How to Submit a Fix or an Enhancement + +If you wish to contribute code, then fork the repo and test the change in your +private repo first. If the change works for you and you would like to donate +the change back to the community, then make a pull request at Github. + +Before making a pull request, please consider that: + +1. We can only accept contributions which are compatible with [MIT Software + License](LICENSE). +2. By making a pull request, you agree to transfer all copyrights to us. +3. Our code will be reviewed and edited by us. In particular, we may + reformat your code according to the [Linux Kernel Coding + Style](https://www.kernel.org/doc/html/v4.10/process/coding-style.html) + + diff --git a/dirent/ChangeLog b/dirent/ChangeLog new file mode 100644 index 0000000..d79cea2 --- /dev/null +++ b/dirent/ChangeLog @@ -0,0 +1,129 @@ +2018-05-08 Toni Rönkkö + + * Version 1.23.2: fixes bad scandir prototype. + +2017-08-27 Toni Rönkkö + + * Version 1.23: support readdir_r and scandir functions. + +2017-07-18 Toni Rönkkö + + * Created release branches v1.22 and v1.21 to Git. Published version + 1.22 at softagalleria.net. + +2016-09-11 Toni Rönkkö + + * Version 1.22: added support for CMake. Thanks to Paul Fultz II. + +2014-09-25 Toni Rönkkö + + * Version 1.21: compiles correctly under Open Watcom. Thanks to + Virgil Banowetz for a patch! + +2014-04-07 Toni Rönkkö + + * Version 1.20.1: the zip file from the previous version did not open + correctly with Microsoft's compressed folders. Thanks to Alexandre + for info! + +2014-03-17 Toni Ronkko + + * Version 1.20: dirent.h compiles correctly in 64-bit architecture. + Thanks to Aaron Simmons! + +2014-03-03 Toni Ronkko + + * Version 1.13.2: define DT_LNK for compatibility with Unix + programs. Thanks to Joel Bruick for suggestion! + +2013-01-27 Toni Ronkko + + * Version 1.13.1: patch from Edward Berner fixes set_errno() on + Windows NT 4.0. + + * Revised wcstombs() and mbstowcs() wrappers to make sure that they do + not write past their target string. + + * PATH_MAX from windows.h includes zero terminator so there is no + need to add one extra byte to variables and structures. + +2012-12-12 Toni Ronkko + + * Version 1.13: use the traditional 8+3 file naming scheme if a file + name cannot be represented in the default ANSI code page. Now + compiles again with MSVC 6.0. Thanks to Konstantin Khomoutov for + testing. + +2012-10-01 Toni Ronkko + + * Version 1.12.1: renamed wide-character DIR structure _wDIR to + _WDIR (with capital W) in order to maintain compatibility with MingW. + +2012-09-30 Toni Ronkko + + * Version 1.12: define PATH_MAX and NAME_MAX. Added wide-character + variants _wDIR, _wdirent, _wopendir(), _wreaddir(), _wclosedir() and + _wrewinddir(). Thanks to Edgar Buerkle and Jan Nijtmans for ideas + and code. + + * Now avoiding windows.h. This allows dirent.h to be integrated + more easily into programs using winsock. Thanks to Fernando + Azaldegui. + +2011-03-15 Toni Ronkko + + * Version 1.11: defined FILE_ATTRIBUTE_DEVICE for MSVC 6.0. + +2010-08-11 Toni Ronkko + + * Version 1.10: added d_type and d_namlen fields to dirent structure. + The former is especially useful for determining whether directory + entry represents a file or a directory. For more information, see + http://www.delorie.com/gnu/docs/glibc/libc_270.html + + * Improved conformance to the standards. For example, errno is now + set properly on failure and assert() is never used. Thanks to Peter + Brockam for suggestions. + + * Fixed a bug in rewinddir(): when using relative directory names, + change of working directory no longer causes rewinddir() to fail. + +2009-12-15 John Cunningham + + * Version 1.9: added rewinddir member function + +2008-01-18 Toni Ronkko + + * Version 1.8: Using FindFirstFileA and WIN32_FIND_DATAA to avoid + converting string between multi-byte and unicode representations. + This makes the code simpler and also allows the code to be compiled + under MingW. Thanks to Azriel Fasten for the suggestion. + +2007-03-04 Toni Ronkko + + * Bug fix: due to the strncpy_s() function this file only compiled in + Visual Studio 2005. Using the new string functions only when the + compiler version allows. + +2006-11-02 Toni Ronkko + + * Major update: removed support for Watcom C, MS-DOS and Turbo C to + simplify the file, updated the code to compile cleanly on Visual + Studio 2005 with both unicode and multi-byte character strings, + removed rewinddir() as it had a bug. + +2006-08-20 Toni Ronkko + + * Removed all remarks about MSVC 1.0, which is antiqued now. + Simplified comments by removing SGML tags. + +2002-05-14 Toni Ronkko + + * Embedded the function definitions directly to the header so that no + source modules need to be included in the Visual Studio project. + Removed all the dependencies to other projects so that this header + file can be used independently. + +1998-05-28 Toni Ronkko + + * First version. diff --git a/dirent/LICENSE b/dirent/LICENSE new file mode 100644 index 0000000..af04360 --- /dev/null +++ b/dirent/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 1998-2019 Toni Ronkko + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/dirent/README.md b/dirent/README.md new file mode 100644 index 0000000..93e21d6 --- /dev/null +++ b/dirent/README.md @@ -0,0 +1,176 @@ +# Dirent + +Dirent is a programming interface for retrieving information about files and +directories in C and C++ languages. This project provides a Dirent interface +for Microsoft Visual Studio. + + +# Installation + +Download the latest Dirent installation package from +[GitHub](https://github.com/tronkko/dirent/releases) and +unpack the installation file with 7-zip, for example. The installation +package contains ``include/dirent.h`` file as well as a few example and test +programs. + +To make Dirent available to all C/C++ projects in your machine, simply copy +``include/dirent.h`` file to the system include directory, e.g. +``C:\Program Files\Microsoft Visual Studio 9.0\VC\include``. Everything you +need is included in the single ``dirent.h`` file, and you can start using +Dirent immediately -- there is no need to add files to your Visual Studio +project. + +Alternatively, if you wish to distribute ``dirent.h`` alongside with your own +project, then copy ``include/dirent.h`` file to a new sub-directory within +your project and add that directory to include path on Windows while omitting +the directory under Linux/UNIX. This allows your project to be compiled +against native ``dirent.h`` on Linux/UNIX while substituting the functionality +on Microsoft Windows. + + +# Example Programs + +The installation package contains example programs: + +Program | Purpose +-------- | ----------------------------------------------------------------- +ls | List files in a directory, e.g. ls "c:\Program Files" +find | Find files in subdirectories, e.g. find "c:\Program Files\CMake" +updatedb | Build database of files in a drive, e.g. updatedb c:\ +locate | Locate a file from database, e.g. locate notepad +scandir | Printed sorted list of file names in a directory, e.g. scandir . +du | Compute disk usage, e.g. du "C:\Program Files" +cat | Print a text file to screen, e.g. cat include/dirent.h + +In order to build the example programs, first install +[CMake](https://cmake.org/) to your machine. Then, open command prompt and +create a temporary directory ``c:\temp\dirent`` for the build files as + +``` +c:\ +mkdir temp +mkdir temp\dirent +cd temp\dirent +``` + +Generate build files as + +``` +cmake d:\dirent +``` + +where ``d:\dirent`` is the root directory of the Dirent package containing +this README.md file. + +Once CMake is finished, open Visual Studio, load the generated ``dirent.sln`` +file from the build directory and build the whole solution. + +Once the build completes, open command prompt and cd to the Debug directory to +run the example programs. For example: + +``` +cd c:\temp\dirent\Debug +.\ls . +``` + +Visual Studio project also contains a solution named ``check`` which can be +used to verify that Dirent API works as expected. Just build the solution +from Visual Studio to run the test programs. + + +# UTF-8 Support + +By default, file and directory names in the Dirent API are expressed in the +currently selected windows codepage. If you wish to use UTF-8 character +encoding, then replace the main function with \_main function and convert +wide-character arguments to UTF-8 strings as demonstrated in the snippet +below. + +``` +/* This is your true main function */ +static int +_main(int argc, wchar_t *argv[]) +{ + /* ... */ +} + +/* Convert arguments to UTF-8 */ +#ifdef _MSC_VER +int +wmain(int argc, wchar_t *argv[]) +{ + /* Select UTF-8 locale */ + setlocale(LC_ALL, ".utf8"); + SetConsoleCP(CP_UTF8); + SetConsoleOutputCP(CP_UTF8); + + /* Allocate memory for multi-byte argv table */ + char **mbargv; + mbargv = (char**) malloc(argc * sizeof(char*)); + if (!mbargv) { + puts("Out of memory"); + exit(3); + } + + /* Convert each argument in argv to UTF-8 */ + for (int i = 0; i < argc; i++) { + size_t n; + wcstombs_s(&n, NULL, 0, argv[i], 0); + + /* Allocate room for ith argument */ + mbargv[i] = (char*) malloc(n); + if (!mbargv[i]) { + puts("Out of memory"); + exit(3); + } + + /* Convert ith argument to utf-8 */ + wcstombs_s(NULL, mbargv[i], n, argv[i], n); + } + + /* Pass UTF-8 converted arguments to the main program */ + int errorcode = _main(argc, mbargv); + + /* Release UTF-8 arguments */ + for (int i = 0; i < argc; i++) { + free(mbargv[i]); + } + + /* Release the argument table */ + free(mbargv); + return errorcode; +} +#else +int +main(int argc, char *argv[]) +{ + return _main(argc, argv); +} +#endif +``` + +For more information on UTF-8 support, please see setlocale in Visual Studio +[C runtime library reference](https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/setlocale-wsetlocale?view=msvc-160#utf-8-support). + + +# Contributing + +We love to receive contributions from you. See the +[CONTRIBUTING](CONTRIBUTING.md) file for details. + + +# Copying + +Dirent may be freely distributed under the MIT license. See the +[LICENSE](LICENSE) file for details. + + +# Alternatives to Dirent + +I ported Dirent to Microsoft Windows in 1998 when only a few alternatives +were available. However, the situation has changed since then and nowadays +both [Cygwin](http://www.cygwin.com) and [MingW](http://www.mingw.org) +allow you to compile a great number of UNIX programs in Microsoft Windows. +They both provide a full Dirent API as well as many other UNIX APIs. MingW +can even be used for commercial applications! + diff --git a/dirent/distclean.cmake b/dirent/distclean.cmake new file mode 100644 index 0000000..60e69ad --- /dev/null +++ b/dirent/distclean.cmake @@ -0,0 +1,62 @@ +# Remove CMake generated temporary files +set (cmake_generated + ${CMAKE_BINARY_DIR}/ALL_BUILD.vcxproj + ${CMAKE_BINARY_DIR}/ALL_BUILD.vcxproj.filters + ${CMAKE_BINARY_DIR}/CMakeCache.txt + ${CMAKE_BINARY_DIR}/CMakeFiles + ${CMAKE_BINARY_DIR}/CTestTestfile.cmake + ${CMAKE_BINARY_DIR}/Continuous.vcxproj + ${CMAKE_BINARY_DIR}/Continuous.vcxproj.filters + ${CMAKE_BINARY_DIR}/DartConfiguration.tcl + ${CMAKE_BINARY_DIR}/Debug + ${CMAKE_BINARY_DIR}/Experimental.vcxproj + ${CMAKE_BINARY_DIR}/Experimental.vcxproj.filters + ${CMAKE_BINARY_DIR}/INSTALL.vcxproj + ${CMAKE_BINARY_DIR}/INSTALL.vcxproj.filters + ${CMAKE_BINARY_DIR}/Makefile + ${CMAKE_BINARY_DIR}/Nightly.vcxproj + ${CMAKE_BINARY_DIR}/Nightly.vcxproj.filters + ${CMAKE_BINARY_DIR}/NightlyMemoryCheck.vcxproj + ${CMAKE_BINARY_DIR}/NightlyMemoryCheck.vcxproj.filters + ${CMAKE_BINARY_DIR}/RUN_TESTS.vcxproj + ${CMAKE_BINARY_DIR}/RUN_TESTS.vcxproj.filters + ${CMAKE_BINARY_DIR}/Testing + ${CMAKE_BINARY_DIR}/Win32 + ${CMAKE_BINARY_DIR}/ZERO_CHECK.vcxproj + ${CMAKE_BINARY_DIR}/ZERO_CHECK.vcxproj.filters + ${CMAKE_BINARY_DIR}/check.vcxproj + ${CMAKE_BINARY_DIR}/check.vcxproj.filters + ${CMAKE_BINARY_DIR}/cmake_install.cmake + ${CMAKE_BINARY_DIR}/dirent.sln + ${CMAKE_BINARY_DIR}/distclean.vcxproj + ${CMAKE_BINARY_DIR}/distclean.vcxproj.filters + ${CMAKE_BINARY_DIR}/find + ${CMAKE_BINARY_DIR}/find.dir + ${CMAKE_BINARY_DIR}/find.vcxproj + ${CMAKE_BINARY_DIR}/find.vcxproj.filters + ${CMAKE_BINARY_DIR}/locate + ${CMAKE_BINARY_DIR}/locate.dir + ${CMAKE_BINARY_DIR}/locate.vcxproj + ${CMAKE_BINARY_DIR}/locate.vcxproj.filters + ${CMAKE_BINARY_DIR}/ls + ${CMAKE_BINARY_DIR}/ls.dir + ${CMAKE_BINARY_DIR}/ls.vcxproj + ${CMAKE_BINARY_DIR}/ls.vcxproj.filters + ${CMAKE_BINARY_DIR}/t-compile + ${CMAKE_BINARY_DIR}/t-compile.dir + ${CMAKE_BINARY_DIR}/t-compile.vcxproj + ${CMAKE_BINARY_DIR}/t-compile.vcxproj.filters + ${CMAKE_BINARY_DIR}/t-dirent + ${CMAKE_BINARY_DIR}/t-dirent.dir + ${CMAKE_BINARY_DIR}/t-dirent.vcxproj + ${CMAKE_BINARY_DIR}/t-dirent.vcxproj.filters + ${CMAKE_BINARY_DIR}/updatedb + ${CMAKE_BINARY_DIR}/updatedb.dir + ${CMAKE_BINARY_DIR}/updatedb.vcxproj + ${CMAKE_BINARY_DIR}/updatedb.vcxproj.filters +) +foreach (file ${cmake_generated}) + if (EXISTS ${file}) + file (REMOVE_RECURSE ${file}) + endif() +endforeach (file) diff --git a/dirent/examples/cat.c b/dirent/examples/cat.c new file mode 100644 index 0000000..5346922 --- /dev/null +++ b/dirent/examples/cat.c @@ -0,0 +1,127 @@ +/* + * Output contents of a file. + * + * Compile this file with Visual Studio and run the produced command in + * console with a file name argument. For example, command + * + * cat include\dirent.h + * + * will output the dirent.h to screen. + * + * Copyright (C) 1998-2019 Toni Ronkko + * This file is part of dirent. Dirent may be freely distributed + * under the MIT license. For all details and documentation, see + * https://github.com/tronkko/dirent + */ +#define _CRT_SECURE_NO_WARNINGS +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <dirent.h> +#include <errno.h> +#include <locale.h> + +static void output_file(const char *fn); +static int _main(int argc, char *argv[]); + +static int +_main(int argc, char *argv[]) +{ + /* Require at least one file */ + if (argc == 1) { + fprintf(stderr, "Usage: cat filename\n"); + return EXIT_FAILURE; + } + + /* For each file name argument in command line */ + int i = 1; + while (i < argc) { + output_file(argv[i]); + i++; + } + return EXIT_SUCCESS; +} + +/* + * Output file to screen + */ +static void +output_file(const char *fn) +{ + /* Open file */ + FILE *fp = fopen(fn, "r"); + if (!fp) { + /* Could not open directory */ + fprintf(stderr, "Cannot open %s (%s)\n", fn, strerror(errno)); + exit(EXIT_FAILURE); + } + + /* Output file to screen */ + size_t n; + do { + /* Read some bytes from file */ + char buffer[4096]; + n = fread(buffer, 1, 4096, fp); + + /* Output bytes to screen */ + fwrite(buffer, 1, n, stdout); + } while (n != 0); + + /* Close file */ + fclose(fp); +} + +/* Stub for converting arguments to UTF-8 on Windows */ +#ifdef _MSC_VER +int +wmain(int argc, wchar_t *argv[]) +{ + /* Select UTF-8 locale */ + setlocale(LC_ALL, ".utf8"); + SetConsoleCP(CP_UTF8); + SetConsoleOutputCP(CP_UTF8); + + /* Allocate memory for multi-byte argv table */ + char **mbargv; + mbargv = (char**) malloc(argc * sizeof(char*)); + if (!mbargv) { + puts("Out of memory"); + exit(3); + } + + /* Convert each argument in argv to UTF-8 */ + for (int i = 0; i < argc; i++) { + size_t n; + wcstombs_s(&n, NULL, 0, argv[i], 0); + + /* Allocate room for ith argument */ + mbargv[i] = (char*) malloc(n + 1); + if (!mbargv[i]) { + puts("Out of memory"); + exit(3); + } + + /* Convert ith argument to utf-8 */ + wcstombs_s(NULL, mbargv[i], n + 1, argv[i], n); + } + + /* Pass UTF-8 converted arguments to the main program */ + int errorcode = _main(argc, mbargv); + + /* Release UTF-8 arguments */ + for (int i = 0; i < argc; i++) { + free(mbargv[i]); + } + + /* Release the argument table */ + free(mbargv); + return errorcode; +} +#else +int +main(int argc, char *argv[]) +{ + return _main(argc, argv); +} +#endif + diff --git a/dirent/examples/dir.c b/dirent/examples/dir.c new file mode 100644 index 0000000..46e38de --- /dev/null +++ b/dirent/examples/dir.c @@ -0,0 +1,234 @@ +/* + * List files with date, type and size. + * + * Compile this file with Visual Studio and run the produced command in + * console with a directory name argument. For example, command + * + * dir "C:\Users\User 1\Documents" + * + * might output something like + * + * Directory of c:\Users\User 1\Documents + * + * 2021-06-06 20:04 <DIR> . + * 2021-07-20 13:42 <DIR> .. + * 2020-06-21 15:00 402 desktop.ini + * 2020-06-21 15:00 <DIR> Omat kuvatiedostot + * 2020-06-21 15:00 <DIR> Omat musiikkitiedostot + * 2020-06-21 15:00 <DIR> Omat videotiedostot + * 2018-12-21 18:34 <DIR> Visual Studio 2017 + * 3 File(s) 402 bytes + * 7 Dir(s) + * + * The dir command provided by this file is only an example: the command + * does not have any fancy options. + * + * Copyright (C) 2006-2012 Toni Ronkko + * This file is part of dirent. Dirent may be freely distributed + * under the MIT license. For all details and documentation, see + * https://github.com/tronkko/dirent + */ +#define _CRT_SECURE_NO_WARNINGS +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <dirent.h> +#include <errno.h> +#include <locale.h> +#include <time.h> +#include <sys/stat.h> + +#define ERR_MSG_LEN 256 + +static void list_directory(const char* dirname); +static void fail(const char* dirname); +static int _main(int argc, char *argv[]); + +static int +_main(int argc, char* argv[]) +{ + /* For each directory in command line */ + int i = 1; + while (i < argc) { + list_directory(argv[i]); + i++; + } + + /* List current working directory if no arguments on command line */ + if (argc == 1) + list_directory("."); + + return EXIT_SUCCESS; +} + +/* List files and file sizes in directory */ +static void +list_directory(const char* dirname) +{ + char path[PATH_MAX + 2]; + char *p = path; + char *end = &path[PATH_MAX]; + + /* Copy directory name to path */ + const char *src = dirname; + while (p < end && *src != '\0') { + *p++ = *src++; + } + + /* Get final character of directory name */ + char c = (path < p ? p[-1] : ':'); + + /* Append directory separator if not already there */ + if (c != ':' && c != '/' && c != '\\') + *p++ = '/'; + + /* Print directory name to screen */ + printf("Directory of %s\n\n", dirname); + + /* Open directory stream */ + DIR *dir = opendir(dirname); + if (!dir) { + /* Could not open directory */ + fprintf(stderr, + "Cannot open %s (%s)\n", dirname, strerror(errno)); + exit(EXIT_FAILURE); + } + + /* Loop through file names */ + int filecount = 0; + int dircount = 0; + long long bytecount = 0; + struct dirent *ent; + while ((ent = readdir(dir)) != NULL) { + /* Append file name to path */ + char *q = p; + src = ent->d_name; + while (q < end && *src != '\0') { + *q++ = *src++; + } + *q = '\0'; + + /* Get file properties such as size and modification time */ + struct stat stbuf; + if (stat(path, &stbuf) == /*error*/-1) { + fail(path); + } + + /* Get file type from stat buffer */ + const char *type; + if (S_ISDIR(stbuf.st_mode)) { + /* Directory */ + type = "<DIR>"; + } else if (S_ISREG(stbuf.st_mode)) { + /* Regular file */ + type = ""; + } else if (S_ISLNK(stbuf.st_mode)) { + /* Link */ + type = "<LNK>"; + } else { + /* Named pipe, socket, character device or else */ + type = "<UNK>"; + } + + /* Get last modification date as a string */ + struct tm *tp = localtime(&stbuf.st_mtime); + char mtime[40]; + sprintf(mtime, "%04d-%02d-%02d %02d:%02d", + tp->tm_year + 1900, + tp->tm_mon + 1, + tp->tm_mday, + tp->tm_hour, + tp->tm_min); + + /* Get file size as a string */ + char size[40]; + if (S_ISREG(stbuf.st_mode)) { + sprintf(size, "%lld", (long long) stbuf.st_size); + } else { + size[0] = '\0'; + } + + /* Output file info */ + printf("%-20s %-5s %12s %s\n", + mtime, type, size, ent->d_name); + + /* Compute totals */ + if (S_ISREG(stbuf.st_mode)) { + filecount++; + bytecount += (long long) stbuf.st_size; + } + if (S_ISDIR(stbuf.st_mode)) { + dircount++; + } + } + + /* Output sums */ + printf("%20d File(s) %12lld bytes\n", filecount, bytecount); + printf("%20d Dir(s)\n", dircount); +} + +/* Print error message and exit with error condition */ +static void +fail(const char* msg) +{ + /* Output error message */ + perror(msg); + + /* Exit the program immediately */ + exit(EXIT_FAILURE); +} + +/* Stub for converting arguments to UTF-8 on Windows */ +#ifdef _MSC_VER +int +wmain(int argc, wchar_t *argv[]) +{ + /* Select UTF-8 locale */ + setlocale(LC_ALL, ".utf8"); + SetConsoleCP(CP_UTF8); + SetConsoleOutputCP(CP_UTF8); + + /* Allocate memory for multi-byte argv table */ + char **mbargv; + mbargv = (char**) malloc(argc * sizeof(char*)); + if (!mbargv) { + puts("Out of memory"); + exit(3); + } + + /* Convert each argument in argv to UTF-8 */ + for (int i = 0; i < argc; i++) { + size_t n; + wcstombs_s(&n, NULL, 0, argv[i], 0); + + /* Allocate room for ith argument */ + mbargv[i] = (char*) malloc(n + 1); + if (!mbargv[i]) { + puts("Out of memory"); + exit(3); + } + + /* Convert ith argument to utf-8 */ + wcstombs_s(NULL, mbargv[i], n + 1, argv[i], n); + } + + /* Pass UTF-8 converted arguments to the main program */ + int errorcode = _main(argc, mbargv); + + /* Release UTF-8 arguments */ + for (int i = 0; i < argc; i++) { + free(mbargv[i]); + } + + /* Release the argument table */ + free(mbargv); + return errorcode; +} +#else +int +main(int argc, char *argv[]) +{ + return _main(argc, argv); +} +#endif + diff --git a/dirent/examples/du.c b/dirent/examples/du.c new file mode 100644 index 0000000..a5832c4 --- /dev/null +++ b/dirent/examples/du.c @@ -0,0 +1,199 @@ +/* + * Compute disk usage of files and sub-directories in bytes. + * + * Compile this file with Visual Studio and run the produced command in + * console with a directory name argument. For example, command + * + * du "c:\Program Files" + * + * might produce listing such as + * + * 5204927 7-Zip + * 140046882 CCleaner + * 83140342 CMake + * 2685264 Internet Explorer + * 686314712 LibreOffice + * 214025459 Mozilla Firefox + * 174753900 VideoLAN + * + * If you compare this program to a genuine du command in Linux, then be ware + * directories themselves consume some space in Linux. This program, however, + * only counts the files and hence the size will always be smaller than that + * reported by Linux du. + * + * Copyright (C) 1998-2019 Toni Ronkko + * This file is part of dirent. Dirent may be freely distributed + * under the MIT license. For all details and documentation, see + * https://github.com/tronkko/dirent + */ +#define _CRT_SECURE_NO_WARNINGS +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <dirent.h> +#include <sys/stat.h> +#include <errno.h> +#include <locale.h> + +static long long list_directory(const char *dirname, int level); +static int _main(int argc, char *argv[]); + +static int +_main(int argc, char *argv[]) +{ + /* For each directory in command line */ + int i = 1; + while (i < argc) { + list_directory(argv[i], 0); + i++; + } + + /* List current working directory if no arguments on command line */ + if (argc == 1) + list_directory(".", 0); + + return EXIT_SUCCESS; +} + +/* Find files and subdirectories recursively; list their sizes */ +static long long +list_directory(const char *dirname, int level) +{ + char buffer[PATH_MAX + 2]; + char *p = buffer; + char *end = &buffer[PATH_MAX]; + + /* Copy directory name to buffer */ + const char *src = dirname; + while (p < end && *src != '\0') { + *p++ = *src++; + } + *p = '\0'; + + /* Get final character of directory name */ + char c; + if (buffer < p) + c = p[-1]; + else + c = ':'; + + /* Append directory separator if not already there */ + if (c != ':' && c != '/' && c != '\\') + *p++ = '/'; + + /* Open directory stream */ + DIR *dir = opendir(dirname); + if (!dir) { + fprintf(stderr, + "Cannot open %s (%s)\n", dirname, strerror(errno)); + return 0LL; + } + + /* Compute total disk usage of all files and directories */ + struct stat stbuf; + struct dirent *ent; + long long total = 0; + while ((ent = readdir(dir)) != NULL) { + /* Skip pseudo directories . and .. */ + if (strcmp(ent->d_name, ".") == 0 + || strcmp(ent->d_name, "..") == 0) + continue; + + /* Skip links as they consume no space */ + if (ent->d_type == DT_LNK) + continue; + + /* Skip device entries */ + if (ent->d_type != DT_REG && ent->d_type != DT_DIR) + continue; + + /* Append file name to buffer */ + src = ent->d_name; + char *q = p; + while (q < end && *src != '\0') { + *q++ = *src++; + } + *q = '\0'; + + /* Add file size */ + long long size = 0; + if (ent->d_type == DT_REG) { + if (stat(buffer, &stbuf) == /*Error*/-1) { + fprintf(stderr, "Cannot access %s\n", buffer); + continue; + } + size += (long long) stbuf.st_size; + } + + /* Compute size of subdirectories recursively */ + if (ent->d_type == DT_DIR) + size += list_directory(buffer, level + 1); + + /* Update total size of directory */ + total += size; + + /* Output file/directory size in bytes */ + if (level == 0) + printf("%-10lld %s\n", size, ent->d_name); + } + + closedir(dir); + + /* Return total size of directory */ + return total; +} + +/* Stub for converting arguments to UTF-8 on Windows */ +#ifdef _MSC_VER +int +wmain(int argc, wchar_t *argv[]) +{ + /* Select UTF-8 locale */ + setlocale(LC_ALL, ".utf8"); + SetConsoleCP(CP_UTF8); + SetConsoleOutputCP(CP_UTF8); + + /* Allocate memory for multi-byte argv table */ + char **mbargv; + mbargv = (char**) malloc(argc * sizeof(char*)); + if (!mbargv) { + puts("Out of memory"); + exit(3); + } + + /* Convert each argument in argv to UTF-8 */ + for (int i = 0; i < argc; i++) { + size_t n; + wcstombs_s(&n, NULL, 0, argv[i], 0); + + /* Allocate room for ith argument */ + mbargv[i] = (char*) malloc(n + 1); + if (!mbargv[i]) { + puts("Out of memory"); + exit(3); + } + + /* Convert ith argument to utf-8 */ + wcstombs_s(NULL, mbargv[i], n + 1, argv[i], n); + } + + /* Pass UTF-8 converted arguments to the main program */ + int errorcode = _main(argc, mbargv); + + /* Release UTF-8 arguments */ + for (int i = 0; i < argc; i++) { + free(mbargv[i]); + } + + /* Release the argument table */ + free(mbargv); + return errorcode; +} +#else +int +main(int argc, char *argv[]) +{ + return _main(argc, argv); +} +#endif + diff --git a/dirent/examples/find.c b/dirent/examples/find.c new file mode 100644 index 0000000..3e53d49 --- /dev/null +++ b/dirent/examples/find.c @@ -0,0 +1,184 @@ +/* + * List files and directories recursively. + * + * Compile this file with Visual Studio and run the produced command in + * console with a directory name argument. For example, command + * + * find "C:\Program Files" + * + * will output thousands of file names such as + * + * c:\Program Files/7-Zip/7-zip.chm + * c:\Program Files/7-Zip/7-zip.dll + * c:\Program Files/7-Zip/7z.dll + * c:\Program Files/Adobe/Reader 10.0/Reader/logsession.dll + * c:\Program Files/Adobe/Reader 10.0/Reader/LogTransport2.exe + * c:\Program Files/Windows NT/Accessories/wordpad.exe + * c:\Program Files/Windows NT/Accessories/write.wpc + * + * The find command provided by this file is only an example: the command does + * not provide options to restrict the output to certain files as the Linux + * version does. + * + * Copyright (C) 1998-2019 Toni Ronkko + * This file is part of dirent. Dirent may be freely distributed + * under the MIT license. For all details and documentation, see + * https://github.com/tronkko/dirent + */ +#define _CRT_SECURE_NO_WARNINGS +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <dirent.h> +#include <errno.h> +#include <locale.h> + +static int find_directory(const char *dirname); +static int _main(int argc, char *argv[]); + +int +_main(int argc, char *argv[]) +{ + /* For each directory in command line */ + int i = 1; + while (i < argc) { + if (!find_directory(argv[i])) + exit(EXIT_FAILURE); + i++; + } + + /* List current working directory if no arguments on command line */ + if (argc == 1) + find_directory("."); + + return EXIT_SUCCESS; +} + +/* Find files and subdirectories recursively */ +static int +find_directory(const char *dirname) +{ + char buffer[PATH_MAX + 2]; + char *p = buffer; + char *end = &buffer[PATH_MAX]; + + /* Copy directory name to buffer */ + const char *src = dirname; + while (p < end && *src != '\0') { + *p++ = *src++; + } + *p = '\0'; + + /* Open directory stream */ + DIR *dir = opendir(dirname); + if (!dir) { + /* Could not open directory */ + fprintf(stderr, + "Cannot open %s (%s)\n", dirname, strerror(errno)); + return /*failure*/ 0; + } + + /* Print all files and directories within the directory */ + struct dirent *ent; + while ((ent = readdir(dir)) != NULL) { + char *q = p; + char c; + + /* Get final character of directory name */ + if (buffer < q) + c = q[-1]; + else + c = ':'; + + /* Append directory separator if not already there */ + if (c != ':' && c != '/' && c != '\\') + *q++ = '/'; + + /* Append file name */ + src = ent->d_name; + while (q < end && *src != '\0') { + *q++ = *src++; + } + *q = '\0'; + + /* Decide what to do with the directory entry */ + switch (ent->d_type) { + case DT_LNK: + case DT_REG: + /* Output file name with directory */ + printf("%s\n", buffer); + break; + + case DT_DIR: + /* Scan sub-directory recursively */ + if (strcmp(ent->d_name, ".") != 0 + && strcmp(ent->d_name, "..") != 0) { + find_directory(buffer); + } + break; + + default: + /* Ignore device entries */ + /*NOP*/; + } + + } + + closedir(dir); + return /*success*/ 1; +} + +/* Stub for converting arguments to UTF-8 on Windows */ +#ifdef _MSC_VER +int +wmain(int argc, wchar_t *argv[]) +{ + /* Select UTF-8 locale */ + setlocale(LC_ALL, ".utf8"); + SetConsoleCP(CP_UTF8); + SetConsoleOutputCP(CP_UTF8); + + /* Allocate memory for multi-byte argv table */ + char **mbargv; + mbargv = (char**) malloc(argc * sizeof(char*)); + if (!mbargv) { + puts("Out of memory"); + exit(3); + } + + /* Convert each argument in argv to UTF-8 */ + for (int i = 0; i < argc; i++) { + size_t n; + wcstombs_s(&n, NULL, 0, argv[i], 0); + + /* Allocate room for ith argument */ + mbargv[i] = (char*) malloc(n + 1); + if (!mbargv[i]) { + puts("Out of memory"); + exit(3); + } + + /* Convert ith argument to utf-8 */ + wcstombs_s(NULL, mbargv[i], n + 1, argv[i], n); + } + + /* Pass UTF-8 converted arguments to the main program */ + int errorcode = _main(argc, mbargv); + + /* Release UTF-8 arguments */ + for (int i = 0; i < argc; i++) { + free(mbargv[i]); + } + + /* Release the argument table */ + free(mbargv); + return errorcode; +} +#else +int +main(int argc, char *argv[]) +{ + return _main(argc, argv); +} +#endif + diff --git a/dirent/examples/locate.c b/dirent/examples/locate.c new file mode 100644 index 0000000..60bea4d --- /dev/null +++ b/dirent/examples/locate.c @@ -0,0 +1,262 @@ +/* + * Find file name from locatedb database. + * + * Compile and run updatedb command first to create locate.db file. Then, + * compile this program with Visual Studio and run the program in console with + * a file name argument. For example, the command + * + * locate autoexec + * + * might output something like + * + * c:/AUTOEXEC.BAT + * c:/WINDOWS/repair/autoexec.nt + * c:/WINDOWS/system32/AUTOEXEC.NT + * + * Be ware that this file uses wide-character API which is not compatible + * with Linux or other major Unixes. + * + * Copyright (C) 1998-2019 Toni Ronkko + * This file is part of dirent. Dirent may be freely distributed + * under the MIT license. For all details and documentation, see + * https://github.com/tronkko/dirent + */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <wchar.h> +#ifdef WIN32 +# include <io.h> +# include <fcntl.h> +#endif +#include <dirent.h> + +/* File name and location of database file */ +#define DB_LOCATION L"locate.db" + +/* Forward-decl */ +static int db_locate(const wchar_t *pattern); +static int db_match(const wchar_t *fn, const wchar_t *pattern); +static void db_open(void); +static void db_close(void); +static int db_read(wchar_t *buffer, size_t max); + +/* Module local variables */ +static FILE *db = NULL; + +#ifdef _MSC_VER +int +wmain(int argc, wchar_t *argv[]) +{ + /* Prepare for unicode output */ + _setmode(_fileno(stdout), _O_U16TEXT); + + /* For each pattern in command line */ + int i = 1; + while (i < argc) { + int count = 0; + + /* Find files matching pattern */ + count = db_locate(argv[i]); + + /* Output warning if string is not found */ + if (count == 0) { + wprintf(L"%s not found\n", argv[i]); + } + + i++; + } + + if (argc < 2) { + wprintf(L"Usage: locate pattern\n"); + exit(EXIT_FAILURE); + } + return EXIT_SUCCESS; +} +#else +int +main(int argc, char *argv[]) +{ + printf("locate only works on Microsoft Windows\n"); + return EXIT_SUCCESS; +} +#endif + +/* Match pattern against files in locate.db file */ +static int +db_locate(const wchar_t *pattern) +{ + int count = 0; + +#ifdef WIN32 + wchar_t buffer[PATH_MAX + 1]; + + /* Open locate.db for read */ + db_open(); + + /* Read one directory and file name at a time from database file */ + while (db_read(buffer, PATH_MAX + 1)) { + /* See if file name in buffer matches the search pattern */ + if (db_match(buffer, pattern)) { + /* Match found => output file name and path */ + wprintf(L"%s\n", buffer); + count++; + } + } + + db_close(); +#endif + + return count; +} + +/* Match pattern against file name */ +static int +db_match(const wchar_t *fn, const wchar_t *pattern) +{ + int found = 0; + +#ifdef WIN32 + /* Locate zero-terminator from fn */ + wchar_t *p = wcschr(fn, '\0'); + + /* Find base name from buffer */ + int done = 0; + while (fn < p && !done) { + switch (p[-1]) { + case ':': + case '/': + case '\\': + /* Final path separator found */ + done = 1; + break; + + default: + /* No path separator yet */ + p--; + } + } + + /* Convert base name to lower case */ + int i = 0; + wchar_t base[PATH_MAX + 1]; + while (i < PATH_MAX && p[i] != '\0') { + base[i] = towlower(p[i]); + i++; + } + base[i] = '\0'; + + /* Convert search pattern to lower case */ + i = 0; + wchar_t patt[PATH_MAX + 1]; + while (i < PATH_MAX && pattern[i] != '\0') { + patt[i] = towlower(pattern[i]); + i++; + } + patt[i] = '\0'; + + /* See if file name matches pattern */ + if (wcsstr(base, patt) != NULL) { + found = 1; + } else { + found = 0; + } +#endif + + return found; +} + +/* + * Read line from locate.db. This function is same as fgetws() except + * that new-line at the end of line is not included. + */ +static int +db_read(wchar_t *buffer, size_t max) +{ + int ok = 0; + +#ifdef WIN32 + size_t i = 0; + wchar_t c; + int done = 0; + + if (!db) { + wprintf(L"Database not open\n"); + exit(EXIT_SUCCESS); + } + + do { + /* Read wide-character from stream */ + c = fgetwc(db); + + /* Determine how to process character */ + switch (c) { + case '\r': + /* Ignore, should be handled by run-time libraries */ + /*NOP*/; + break; + + case '\n': + /* End of string => return file name */ + done = 1; + ok = 1; + break; + + case /*EOF*/WEOF: + /* End of file */ + done = 1; + if (i == 0) { + /* No data in buffer => return false */ + ok = 0; + } else { + /* Data in buffer => return file name */ + ok = 1; + } + break; + + default: + /* Store character */ + if (i < max - 1) { + buffer[i++] = c; + } else { + buffer[max - 1] = '\0'; + wprintf(L"Buffer too small: %s", buffer); + exit(EXIT_FAILURE); + } + } + } while (!done); + + /* Zero-terminate buffer */ + buffer[i] = '\0'; +#endif + + return ok; +} + +/* Open database file locate.db */ +static void +db_open(void) +{ +#ifdef WIN32 + if (db) + return; + + /* Open file for writing */ + errno_t error = _wfopen_s(&db, DB_LOCATION, L"rt, ccs=UNICODE"); + if (error) { + wprintf(L"Cannot open %s\n", DB_LOCATION); + exit(EXIT_FAILURE); + } +#endif +} + +/* Close database file */ +static void +db_close(void) +{ + if (!db) + return; + + fclose(db); + db = NULL; +} diff --git a/dirent/examples/ls.c b/dirent/examples/ls.c new file mode 100644 index 0000000..3fb627e --- /dev/null +++ b/dirent/examples/ls.c @@ -0,0 +1,148 @@ +/* + * List contents of a directory. + * + * Compile this file with Visual Studio and run the produced command in + * console with a directory name argument. For example, command + * + * ls "c:\Program Files" + * + * might output something like + * + * ./ + * ../ + * 7-Zip/ + * Internet Explorer/ + * Microsoft Visual Studio 9.0/ + * Microsoft.NET/ + * Mozilla Firefox/ + * + * The ls command provided by this file is only an example: the command does + * not have any fancy options like "ls -al" in Linux and the command does not + * support file name matching like "ls *.c". + * + * Copyright (C) 1998-2019 Toni Ronkko + * This file is part of dirent. Dirent may be freely distributed + * under the MIT license. For all details and documentation, see + * https://github.com/tronkko/dirent + */ +#define _CRT_SECURE_NO_WARNINGS +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <dirent.h> +#include <errno.h> +#include <locale.h> + +static void list_directory(const char *dirname); +static int _main(int argc, char *argv[]); + +static int +_main(int argc, char *argv[]) +{ + /* For each directory in command line */ + int i = 1; + while (i < argc) { + list_directory(argv[i]); + i++; + } + + /* List current working directory if no arguments on command line */ + if (argc == 1) + list_directory("."); + + return EXIT_SUCCESS; +} + +/* + * List files and directories within a directory. + */ +static void +list_directory(const char *dirname) +{ + /* Open directory stream */ + DIR *dir = opendir(dirname); + if (!dir) { + /* Could not open directory */ + fprintf(stderr, + "Cannot open %s (%s)\n", dirname, strerror(errno)); + exit(EXIT_FAILURE); + } + + /* Print all files and directories within the directory */ + struct dirent *ent; + while ((ent = readdir(dir)) != NULL) { + switch (ent->d_type) { + case DT_REG: + printf("%s\n", ent->d_name); + break; + + case DT_DIR: + printf("%s/\n", ent->d_name); + break; + + case DT_LNK: + printf("%s@\n", ent->d_name); + break; + + default: + printf("%s*\n", ent->d_name); + } + } + + closedir(dir); +} + +/* Stub for converting arguments to UTF-8 on Windows */ +#ifdef _MSC_VER +int +wmain(int argc, wchar_t *argv[]) +{ + /* Select UTF-8 locale */ + setlocale(LC_ALL, ".utf8"); + SetConsoleCP(CP_UTF8); + SetConsoleOutputCP(CP_UTF8); + + /* Allocate memory for multi-byte argv table */ + char **mbargv; + mbargv = (char**) malloc(argc * sizeof(char*)); + if (!mbargv) { + puts("Out of memory"); + exit(3); + } + + /* Convert each argument in argv to UTF-8 */ + for (int i = 0; i < argc; i++) { + size_t n; + wcstombs_s(&n, NULL, 0, argv[i], 0); + + /* Allocate room for ith argument */ + mbargv[i] = (char*) malloc(n + 1); + if (!mbargv[i]) { + puts("Out of memory"); + exit(3); + } + + /* Convert ith argument to utf-8 */ + wcstombs_s(NULL, mbargv[i], n + 1, argv[i], n); + } + + /* Pass UTF-8 converted arguments to the main program */ + int errorcode = _main(argc, mbargv); + + /* Release UTF-8 arguments */ + for (int i = 0; i < argc; i++) { + free(mbargv[i]); + } + + /* Release the argument table */ + free(mbargv); + return errorcode; +} +#else +int +main(int argc, char *argv[]) +{ + return _main(argc, argv); +} +#endif + diff --git a/dirent/examples/scandir.c b/dirent/examples/scandir.c new file mode 100644 index 0000000..65dc11f --- /dev/null +++ b/dirent/examples/scandir.c @@ -0,0 +1,152 @@ +/* + * List contents of a directory in an alphabetical order. + * + * Compile this file with Visual Studio and run the produced command in + * console with a directory name argument. For example, command + * + * scandir "c:\Program Files" + * + * might output something like + * + * ./ + * ../ + * 7-Zip/ + * Internet Explorer/ + * Microsoft Visual Studio 9.0/ + * Microsoft.NET/ + * Mozilla Firefox/ + * + * Copyright (C) 1998-2019 Toni Ronkko + * This file is part of dirent. Dirent may be freely distributed + * under the MIT license. For all details and documentation, see + * https://github.com/tronkko/dirent + */ +#define _CRT_SECURE_NO_WARNINGS +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <dirent.h> +#include <errno.h> +#include <locale.h> + +static void list_directory(const char *dirname); +static int _main(int argc, char *argv[]); + +static int +_main(int argc, char *argv[]) +{ + /* For each directory in command line */ + int i = 1; + while (i < argc) { + list_directory(argv[i]); + i++; + } + + /* List current working directory if no arguments on command line */ + if (argc == 1) + list_directory("."); + + return EXIT_SUCCESS; +} + +/* + * List files and directories within a directory. + */ +static void +list_directory( + const char *dirname) +{ + /* Scan files in directory */ + struct dirent **files; + int n = scandir(dirname, &files, NULL, alphasort); + if (n < 0) { + fprintf(stderr, + "Cannot open %s (%s)\n", dirname, strerror(errno)); + exit(EXIT_FAILURE); + } + + /* Loop through file names */ + for (int i = 0; i < n; i++) { + /* Get pointer to file entry */ + struct dirent *ent = files[i]; + + /* Output file name */ + switch (ent->d_type) { + case DT_REG: + printf("%s\n", ent->d_name); + break; + + case DT_DIR: + printf("%s/\n", ent->d_name); + break; + + case DT_LNK: + printf("%s@\n", ent->d_name); + break; + + default: + printf("%s*\n", ent->d_name); + } + } + + /* Release file names */ + for (int i = 0; i < n; i++) { + free(files[i]); + } + free(files); +} + +/* Stub for converting arguments to UTF-8 on Windows */ +#ifdef _MSC_VER +int +wmain(int argc, wchar_t *argv[]) +{ + /* Select UTF-8 locale */ + setlocale(LC_ALL, ".utf8"); + SetConsoleCP(CP_UTF8); + SetConsoleOutputCP(CP_UTF8); + + /* Allocate memory for multi-byte argv table */ + char **mbargv; + mbargv = (char**) malloc(argc * sizeof(char*)); + if (!mbargv) { + puts("Out of memory"); + exit(3); + } + + /* Convert each argument in argv to UTF-8 */ + for (int i = 0; i < argc; i++) { + size_t n; + wcstombs_s(&n, NULL, 0, argv[i], 0); + + /* Allocate room for ith argument */ + mbargv[i] = (char*) malloc(n + 1); + if (!mbargv[i]) { + puts("Out of memory"); + exit(3); + } + + /* Convert ith argument to utf-8 */ + wcstombs_s(NULL, mbargv[i], n + 1, argv[i], n); + } + + /* Pass UTF-8 converted arguments to the main program */ + int errorcode = _main(argc, mbargv); + + /* Release UTF-8 arguments */ + for (int i = 0; i < argc; i++) { + free(mbargv[i]); + } + + /* Release the argument table */ + free(mbargv); + return errorcode; +} +#else +int +main(int argc, char *argv[]) +{ + return _main(argc, argv); +} +#endif + diff --git a/dirent/examples/updatedb.c b/dirent/examples/updatedb.c new file mode 100644 index 0000000..c7ec652 --- /dev/null +++ b/dirent/examples/updatedb.c @@ -0,0 +1,206 @@ +/* + * Build database of file and directory names. + * + * Compile this file with Visual Studio and run the produced command in + * console with a directory name argument. For example, command + * + * updatedb C:\ + * + * will produce the file locate.db with one file name per line such as + * + * c:\Program Files/7-Zip/7-zip.chm + * c:\Program Files/7-Zip/7-zip.dll + * c:\Program Files/7-Zip/7z.dll + * c:\Program Files/Adobe/Reader 10.0/Reader/logsession.dll + * c:\Program Files/Adobe/Reader 10.0/Reader/LogTransport2.exe + * c:\Program Files/Windows NT/Accessories/wordpad.exe + * c:\Program Files/Windows NT/Accessories/write.wpc + * + * Be ware that this file uses wide-character API which is not compatible + * with Linux or other major Unixes. + * + * Copyright (C) 1998-2019 Toni Ronkko + * This file is part of dirent. Dirent may be freely distributed + * under the MIT license. For all details and documentation, see + * https://github.com/tronkko/dirent + */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <wchar.h> +#ifdef WIN32 +# include <io.h> +# include <fcntl.h> +#endif +#include <dirent.h> + +/* File name and location of database file */ +#define DB_LOCATION L"locate.db" + +/* Forward-decl */ +static int update_directory(const wchar_t *dirname); +static void db_open(void); +static void db_close(void); +static void db_store(const wchar_t *dirname); + +/* Module local variables */ +static FILE *db = NULL; + +#ifdef _MSC_VER +int +wmain(int argc, wchar_t *argv[]) +{ + /* Prepare for unicode output */ + _setmode(_fileno(stdout), _O_U16TEXT); + + /* Open locate.db */ + db_open(); + + /* For each directory in command line */ + int i = 1; + while (i < argc) { + /* Scan directory for files */ + int ok = update_directory(argv[i]); + if (!ok) { + wprintf(L"Cannot open directory %s\n", argv[i]); + exit(EXIT_FAILURE); + } + + i++; + } + + /* Use current working directory if no arguments on command line */ + if (argc == 1) + update_directory(L"."); + + db_close(); + return EXIT_SUCCESS; +} +#else +int +main(int argc, char *argv[]) +{ + printf("updatedb only works on Microsoft Windows\n"); + return EXIT_SUCCESS; +} +#endif + +/* Find files recursively */ +static int +update_directory(const wchar_t *dirname) +{ +#ifdef WIN32 + wchar_t buffer[PATH_MAX + 2]; + wchar_t *p = buffer; + wchar_t *end = &buffer[PATH_MAX]; + + /* Copy directory name to buffer */ + const wchar_t *src = dirname; + while (p < end && *src != '\0') { + *p++ = *src++; + } + *p = '\0'; + + /* Open directory stream */ + _WDIR *dir = _wopendir(dirname); + if (!dir) { + /* Cannot open directory */ + return /*failure*/ 0; + } + + /* Print all files and directories within the directory */ + struct _wdirent *ent; + while ((ent = _wreaddir (dir)) != NULL) { + wchar_t *q = p; + wchar_t c; + + /* Get final character of directory name */ + if (buffer < q) + c = q[-1]; + else + c = ':'; + + /* Append directory separator if not already there */ + if (c != ':' && c != '/' && c != '\\') + *q++ = '/'; + + /* Append file name */ + src = ent->d_name; + while (q < end && *src != '\0') { + *q++ = *src++; + } + *q = '\0'; + + /* Decide what to do with the directory entry */ + switch (ent->d_type) { + case DT_REG: + /* Store file name */ + db_store(buffer); + break; + + case DT_DIR: + /* Scan sub-directory recursively */ + if (wcscmp(ent->d_name, L".") != 0 + && wcscmp(ent->d_name, L"..") != 0) { + update_directory(buffer); + } + break; + + default: + /* Do not device entries */ + /*NOP*/; + } + + } + + wclosedir(dir); + return /*success*/ 1; +#else + return /*failure*/ 0; +#endif +} + +/* Store file name to locate.db */ +static void +db_store(const wchar_t *dirname) +{ +#ifdef WIN32 + if (!db) { + wprintf(L"Database not open\n"); + exit(EXIT_FAILURE); + } + + /* Output line to file */ + fwprintf(db, L"%s\n", dirname); +#endif +} + +/* Open database file locate.db */ +static void +db_open(void) +{ +#ifdef WIN32 + if (db) + return; + + /* Open file for writing */ + errno_t error = _wfopen_s(&db, DB_LOCATION, L"wt, ccs=UNICODE"); + if (error) { + wprintf(L"Cannot open %s\n", DB_LOCATION); + exit(EXIT_FAILURE); + } +#endif +} + +/* Close database file */ +static void +db_close( + void) +{ + if (!db) + return; + + /* Close file */ + fclose(db); + db = NULL; +} diff --git a/dirent/include/dirent.h b/dirent/include/dirent.h new file mode 100644 index 0000000..557485a --- /dev/null +++ b/dirent/include/dirent.h @@ -0,0 +1,1212 @@ +/* + * Dirent interface for Microsoft Visual Studio + * + * Copyright (C) 1998-2019 Toni Ronkko + * This file is part of dirent. Dirent may be freely distributed + * under the MIT license. For all details and documentation, see + * https://github.com/tronkko/dirent + */ +#ifndef DIRENT_H +#define DIRENT_H + +/* Hide warnings about unreferenced local functions */ +#if defined(__clang__) +# pragma clang diagnostic ignored "-Wunused-function" +#elif defined(_MSC_VER) +# pragma warning(disable:4505) +#elif defined(__GNUC__) +# pragma GCC diagnostic ignored "-Wunused-function" +#endif + +/* + * Include windows.h without Windows Sockets 1.1 to prevent conflicts with + * Windows Sockets 2.0. + */ +#ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +#endif +#include <windows.h> + +#include <stdio.h> +#include <stdarg.h> +#include <wchar.h> +#include <string.h> +#include <stdlib.h> +#include <malloc.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <errno.h> +#include <ctype.h> + +/* Indicates that d_type field is available in dirent structure */ +#define _DIRENT_HAVE_D_TYPE + +/* Indicates that d_namlen field is available in dirent structure */ +#define _DIRENT_HAVE_D_NAMLEN + +/* Entries missing from MSVC 6.0 */ +#if !defined(FILE_ATTRIBUTE_DEVICE) +# define FILE_ATTRIBUTE_DEVICE 0x40 +#endif + +/* File type and permission flags for stat(), general mask */ +#if !defined(S_IFMT) +# define S_IFMT _S_IFMT +#endif + +/* Directory bit */ +#if !defined(S_IFDIR) +# define S_IFDIR _S_IFDIR +#endif + +/* Character device bit */ +#if !defined(S_IFCHR) +# define S_IFCHR _S_IFCHR +#endif + +/* Pipe bit */ +#if !defined(S_IFFIFO) +# define S_IFFIFO _S_IFFIFO +#endif + +/* Regular file bit */ +#if !defined(S_IFREG) +# define S_IFREG _S_IFREG +#endif + +/* Read permission */ +#if !defined(S_IREAD) +# define S_IREAD _S_IREAD +#endif + +/* Write permission */ +#if !defined(S_IWRITE) +# define S_IWRITE _S_IWRITE +#endif + +/* Execute permission */ +#if !defined(S_IEXEC) +# define S_IEXEC _S_IEXEC +#endif + +/* Pipe */ +#if !defined(S_IFIFO) +# define S_IFIFO _S_IFIFO +#endif + +/* Block device */ +#if !defined(S_IFBLK) +# define S_IFBLK 0 +#endif + +/* Link */ +#if !defined(S_IFLNK) +# define S_IFLNK 0 +#endif + +/* Socket */ +#if !defined(S_IFSOCK) +# define S_IFSOCK 0 +#endif + +/* Read user permission */ +#if !defined(S_IRUSR) +# define S_IRUSR S_IREAD +#endif + +/* Write user permission */ +#if !defined(S_IWUSR) +# define S_IWUSR S_IWRITE +#endif + +/* Execute user permission */ +#if !defined(S_IXUSR) +# define S_IXUSR 0 +#endif + +/* Read group permission */ +#if !defined(S_IRGRP) +# define S_IRGRP 0 +#endif + +/* Write group permission */ +#if !defined(S_IWGRP) +# define S_IWGRP 0 +#endif + +/* Execute group permission */ +#if !defined(S_IXGRP) +# define S_IXGRP 0 +#endif + +/* Read others permission */ +#if !defined(S_IROTH) +# define S_IROTH 0 +#endif + +/* Write others permission */ +#if !defined(S_IWOTH) +# define S_IWOTH 0 +#endif + +/* Execute others permission */ +#if !defined(S_IXOTH) +# define S_IXOTH 0 +#endif + +/* Maximum length of file name */ +#if !defined(PATH_MAX) +# define PATH_MAX MAX_PATH +#endif +#if !defined(FILENAME_MAX) +# define FILENAME_MAX MAX_PATH +#endif +#if !defined(NAME_MAX) +# define NAME_MAX FILENAME_MAX +#endif + +/* File type flags for d_type */ +#define DT_UNKNOWN 0 +#define DT_REG S_IFREG +#define DT_DIR S_IFDIR +#define DT_FIFO S_IFIFO +#define DT_SOCK S_IFSOCK +#define DT_CHR S_IFCHR +#define DT_BLK S_IFBLK +#define DT_LNK S_IFLNK + +/* Macros for converting between st_mode and d_type */ +#define IFTODT(mode) ((mode) & S_IFMT) +#define DTTOIF(type) (type) + +/* + * File type macros. Note that block devices, sockets and links cannot be + * distinguished on Windows and the macros S_ISBLK, S_ISSOCK and S_ISLNK are + * only defined for compatibility. These macros should always return false + * on Windows. + */ +#if !defined(S_ISFIFO) +# define S_ISFIFO(mode) (((mode) & S_IFMT) == S_IFIFO) +#endif +#if !defined(S_ISDIR) +# define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) +#endif +#if !defined(S_ISREG) +# define S_ISREG(mode) (((mode) & S_IFMT) == S_IFREG) +#endif +#if !defined(S_ISLNK) +# define S_ISLNK(mode) (((mode) & S_IFMT) == S_IFLNK) +#endif +#if !defined(S_ISSOCK) +# define S_ISSOCK(mode) (((mode) & S_IFMT) == S_IFSOCK) +#endif +#if !defined(S_ISCHR) +# define S_ISCHR(mode) (((mode) & S_IFMT) == S_IFCHR) +#endif +#if !defined(S_ISBLK) +# define S_ISBLK(mode) (((mode) & S_IFMT) == S_IFBLK) +#endif + +/* Return the exact length of the file name without zero terminator */ +#define _D_EXACT_NAMLEN(p) ((p)->d_namlen) + +/* Return the maximum size of a file name */ +#define _D_ALLOC_NAMLEN(p) ((PATH_MAX)+1) + + +#ifdef __cplusplus +extern "C" { +#endif + + +/* Wide-character version */ +struct _wdirent { + /* Always zero */ + long d_ino; + + /* Position of next file in a directory stream */ + long d_off; + + /* Structure size */ + unsigned short d_reclen; + + /* Length of name without \0 */ + size_t d_namlen; + + /* File type */ + int d_type; + + /* File name */ + wchar_t d_name[PATH_MAX+1]; +}; +typedef struct _wdirent _wdirent; + +struct _WDIR { + /* Current directory entry */ + struct _wdirent ent; + + /* Private file data */ + WIN32_FIND_DATAW data; + + /* True if data is valid */ + int cached; + + /* True if next entry is invalid */ + int invalid; + + /* Win32 search handle */ + HANDLE handle; + + /* Initial directory name */ + wchar_t *patt; +}; +typedef struct _WDIR _WDIR; + +/* Multi-byte character version */ +struct dirent { + /* Always zero */ + long d_ino; + + /* Position of next file in a directory stream */ + long d_off; + + /* Structure size */ + unsigned short d_reclen; + + /* Length of name without \0 */ + size_t d_namlen; + + /* File type */ + int d_type; + + /* File name */ + char d_name[PATH_MAX+1]; +}; +typedef struct dirent dirent; + +struct DIR { + struct dirent ent; + struct _WDIR *wdirp; +}; +typedef struct DIR DIR; + + +/* Dirent functions */ +static DIR *opendir(const char *dirname); +static _WDIR *_wopendir(const wchar_t *dirname); + +static struct dirent *readdir(DIR *dirp); +static struct _wdirent *_wreaddir(_WDIR *dirp); + +static int readdir_r( + DIR *dirp, struct dirent *entry, struct dirent **result); +static int _wreaddir_r( + _WDIR *dirp, struct _wdirent *entry, struct _wdirent **result); + +static int closedir(DIR *dirp); +static int _wclosedir(_WDIR *dirp); + +static void rewinddir(DIR *dirp); +static void _wrewinddir(_WDIR *dirp); + +static long telldir(DIR *dirp); +static long _wtelldir(_WDIR *dirp); + +static void seekdir(DIR *dirp, long loc); +static void _wseekdir(_WDIR *dirp, long loc); + +static int scandir(const char *dirname, struct dirent ***namelist, + int (*filter)(const struct dirent*), + int (*compare)(const struct dirent**, const struct dirent**)); + +static int alphasort(const struct dirent **a, const struct dirent **b); + +static int versionsort(const struct dirent **a, const struct dirent **b); + +static int strverscmp(const char *a, const char *b); + +/* For compatibility with Symbian */ +#define wdirent _wdirent +#define WDIR _WDIR +#define wopendir _wopendir +#define wreaddir _wreaddir +#define wclosedir _wclosedir +#define wrewinddir _wrewinddir +#define wtelldir _wtelldir +#define wseekdir _wseekdir + +/* Compatibility with older Microsoft compilers and non-Microsoft compilers */ +#if !defined(_MSC_VER) || _MSC_VER < 1400 +# define wcstombs_s dirent_wcstombs_s +# define mbstowcs_s dirent_mbstowcs_s +#endif + +/* Optimize dirent_set_errno() away on modern Microsoft compilers */ +#if defined(_MSC_VER) && _MSC_VER >= 1400 +# define dirent_set_errno _set_errno +#endif + + +/* Internal utility functions */ +static WIN32_FIND_DATAW *dirent_first(_WDIR *dirp); +static WIN32_FIND_DATAW *dirent_next(_WDIR *dirp); +static long dirent_hash(WIN32_FIND_DATAW *datap); + +#if !defined(_MSC_VER) || _MSC_VER < 1400 +static int dirent_mbstowcs_s( + size_t *pReturnValue, wchar_t *wcstr, size_t sizeInWords, + const char *mbstr, size_t count); +#endif + +#if !defined(_MSC_VER) || _MSC_VER < 1400 +static int dirent_wcstombs_s( + size_t *pReturnValue, char *mbstr, size_t sizeInBytes, + const wchar_t *wcstr, size_t count); +#endif + +#if !defined(_MSC_VER) || _MSC_VER < 1400 +static void dirent_set_errno(int error); +#endif + + +/* + * Open directory stream DIRNAME for read and return a pointer to the + * internal working area that is used to retrieve individual directory + * entries. + */ +static _WDIR * +_wopendir(const wchar_t *dirname) +{ + wchar_t *p; + + /* Must have directory name */ + if (dirname == NULL || dirname[0] == '\0') { + dirent_set_errno(ENOENT); + return NULL; + } + + /* Allocate new _WDIR structure */ + _WDIR *dirp = (_WDIR*) malloc(sizeof(struct _WDIR)); + if (!dirp) + return NULL; + + /* Reset _WDIR structure */ + dirp->handle = INVALID_HANDLE_VALUE; + dirp->patt = NULL; + dirp->cached = 0; + dirp->invalid = 0; + + /* + * Compute the length of full path plus zero terminator + * + * Note that on WinRT there's no way to convert relative paths + * into absolute paths, so just assume it is an absolute path. + */ +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) + /* Desktop */ + DWORD n = GetFullPathNameW(dirname, 0, NULL, NULL); +#else + /* WinRT */ + size_t n = wcslen(dirname); +#endif + + /* Allocate room for absolute directory name and search pattern */ + dirp->patt = (wchar_t*) malloc(sizeof(wchar_t) * n + 16); + if (dirp->patt == NULL) + goto exit_closedir; + + /* + * Convert relative directory name to an absolute one. This + * allows rewinddir() to function correctly even when current + * working directory is changed between opendir() and rewinddir(). + * + * Note that on WinRT there's no way to convert relative paths + * into absolute paths, so just assume it is an absolute path. + */ +#if WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP) + /* Desktop */ + n = GetFullPathNameW(dirname, n, dirp->patt, NULL); + if (n <= 0) + goto exit_closedir; +#else + /* WinRT */ + wcsncpy_s(dirp->patt, n+1, dirname, n); +#endif + + /* Append search pattern \* to the directory name */ + p = dirp->patt + n; + switch (p[-1]) { + case '\\': + case '/': + case ':': + /* Directory ends in path separator, e.g. c:\temp\ */ + /*NOP*/; + break; + + default: + /* Directory name doesn't end in path separator */ + *p++ = '\\'; + } + *p++ = '*'; + *p = '\0'; + + /* Open directory stream and retrieve the first entry */ + if (!dirent_first(dirp)) + goto exit_closedir; + + /* Success */ + return dirp; + + /* Failure */ +exit_closedir: + _wclosedir(dirp); + return NULL; +} + +/* + * Read next directory entry. + * + * Returns pointer to static directory entry which may be overwritten by + * subsequent calls to _wreaddir(). + */ +static struct _wdirent * +_wreaddir(_WDIR *dirp) +{ + /* + * Read directory entry to buffer. We can safely ignore the return + * value as entry will be set to NULL in case of error. + */ + struct _wdirent *entry; + (void) _wreaddir_r(dirp, &dirp->ent, &entry); + + /* Return pointer to statically allocated directory entry */ + return entry; +} + +/* + * Read next directory entry. + * + * Returns zero on success. If end of directory stream is reached, then sets + * result to NULL and returns zero. + */ +static int +_wreaddir_r( + _WDIR *dirp, struct _wdirent *entry, struct _wdirent **result) +{ + /* Validate directory handle */ + if (!dirp || dirp->handle == INVALID_HANDLE_VALUE || !dirp->patt) { + dirent_set_errno(EBADF); + *result = NULL; + return -1; + } + + /* Read next directory entry */ + WIN32_FIND_DATAW *datap = dirent_next(dirp); + if (!datap) { + /* Return NULL to indicate end of directory */ + *result = NULL; + return /*OK*/0; + } + + /* + * Copy file name as wide-character string. If the file name is too + * long to fit in to the destination buffer, then truncate file name + * to PATH_MAX characters and zero-terminate the buffer. + */ + size_t i = 0; + while (i < PATH_MAX && datap->cFileName[i] != 0) { + entry->d_name[i] = datap->cFileName[i]; + i++; + } + entry->d_name[i] = 0; + + /* Length of file name excluding zero terminator */ + entry->d_namlen = i; + + /* Determine file type */ + DWORD attr = datap->dwFileAttributes; + if ((attr & FILE_ATTRIBUTE_DEVICE) != 0) + entry->d_type = DT_CHR; + else if ((attr & FILE_ATTRIBUTE_DIRECTORY) != 0) + entry->d_type = DT_DIR; + else + entry->d_type = DT_REG; + + /* Read the next directory entry to cache */ + datap = dirent_next(dirp); + if (datap) { + /* Compute 31-bit hash of the next directory entry */ + entry->d_off = dirent_hash(datap); + + /* Push the next directory entry back to cache */ + dirp->cached = 1; + } else { + /* End of directory stream */ + entry->d_off = (long) ((~0UL) >> 1); + } + + /* Reset other fields */ + entry->d_ino = 0; + entry->d_reclen = sizeof(struct _wdirent); + + /* Set result address */ + *result = entry; + return /*OK*/0; +} + +/* + * Close directory stream opened by opendir() function. This invalidates the + * DIR structure as well as any directory entry read previously by + * _wreaddir(). + */ +static int +_wclosedir(_WDIR *dirp) +{ + if (!dirp) { + dirent_set_errno(EBADF); + return /*failure*/-1; + } + + /* + * Release search handle if we have one. Being able to handle + * partially initialized _WDIR structure allows us to use this + * function to handle errors occuring within _wopendir. + */ + if (dirp->handle != INVALID_HANDLE_VALUE) { + FindClose(dirp->handle); + } + + /* + * Release search pattern. Note that we don't need to care if + * dirp->patt is NULL or not: function free is guaranteed to act + * appropriately. + */ + free(dirp->patt); + + /* Release directory structure */ + free(dirp); + return /*success*/0; +} + +/* + * Rewind directory stream such that _wreaddir() returns the very first + * file name again. + */ +static void _wrewinddir(_WDIR* dirp) +{ + /* Check directory pointer */ + if (!dirp || dirp->handle == INVALID_HANDLE_VALUE || !dirp->patt) + return; + + /* Release existing search handle */ + FindClose(dirp->handle); + + /* Open new search handle */ + dirent_first(dirp); +} + +/* Get first directory entry */ +static WIN32_FIND_DATAW * +dirent_first(_WDIR *dirp) +{ + /* Open directory and retrieve the first entry */ + dirp->handle = FindFirstFileExW( + dirp->patt, FindExInfoStandard, &dirp->data, + FindExSearchNameMatch, NULL, 0); + if (dirp->handle == INVALID_HANDLE_VALUE) + goto error; + + /* A directory entry is now waiting in memory */ + dirp->cached = 1; + return &dirp->data; + +error: + /* Failed to open directory: no directory entry in memory */ + dirp->cached = 0; + dirp->invalid = 1; + + /* Set error code */ + DWORD errorcode = GetLastError(); + switch (errorcode) { + case ERROR_ACCESS_DENIED: + /* No read access to directory */ + dirent_set_errno(EACCES); + break; + + case ERROR_DIRECTORY: + /* Directory name is invalid */ + dirent_set_errno(ENOTDIR); + break; + + case ERROR_PATH_NOT_FOUND: + default: + /* Cannot find the file */ + dirent_set_errno(ENOENT); + } + return NULL; +} + +/* Get next directory entry */ +static WIN32_FIND_DATAW * +dirent_next(_WDIR *dirp) +{ + /* Return NULL if seek position was invalid */ + if (dirp->invalid) + return NULL; + + /* Is the next directory entry already in cache? */ + if (dirp->cached) { + /* Yes, a valid directory entry found in memory */ + dirp->cached = 0; + return &dirp->data; + } + + /* Read the next directory entry from stream */ + if (FindNextFileW(dirp->handle, &dirp->data) == FALSE) { + /* End of directory stream */ + return NULL; + } + + /* Success */ + return &dirp->data; +} + +/* + * Compute 31-bit hash of file name. + * + * See djb2 at http://www.cse.yorku.ca/~oz/hash.html + */ +static long +dirent_hash(WIN32_FIND_DATAW *datap) +{ + unsigned long hash = 5381; + unsigned long c; + const wchar_t *p = datap->cFileName; + const wchar_t *e = p + MAX_PATH; + while (p != e && (c = *p++) != 0) { + hash = (hash << 5) + hash + c; + } + + return (long) (hash & ((~0UL) >> 1)); +} + +/* Open directory stream using plain old C-string */ +static DIR *opendir(const char *dirname) +{ + /* Must have directory name */ + if (dirname == NULL || dirname[0] == '\0') { + dirent_set_errno(ENOENT); + return NULL; + } + + /* Allocate memory for DIR structure */ + struct DIR *dirp = (DIR*) malloc(sizeof(struct DIR)); + if (!dirp) + return NULL; + + /* Convert directory name to wide-character string */ + wchar_t wname[PATH_MAX + 1]; + size_t n; + int error = mbstowcs_s(&n, wname, PATH_MAX + 1, dirname, PATH_MAX+1); + if (error) + goto exit_failure; + + /* Open directory stream using wide-character name */ + dirp->wdirp = _wopendir(wname); + if (!dirp->wdirp) + goto exit_failure; + + /* Success */ + return dirp; + + /* Failure */ +exit_failure: + free(dirp); + return NULL; +} + +/* Read next directory entry */ +static struct dirent * +readdir(DIR *dirp) +{ + /* + * Read directory entry to buffer. We can safely ignore the return + * value as entry will be set to NULL in case of error. + */ + struct dirent *entry; + (void) readdir_r(dirp, &dirp->ent, &entry); + + /* Return pointer to statically allocated directory entry */ + return entry; +} + +/* + * Read next directory entry into called-allocated buffer. + * + * Returns zero on success. If the end of directory stream is reached, then + * sets result to NULL and returns zero. + */ +static int +readdir_r( + DIR *dirp, struct dirent *entry, struct dirent **result) +{ + /* Read next directory entry */ + WIN32_FIND_DATAW *datap = dirent_next(dirp->wdirp); + if (!datap) { + /* No more directory entries */ + *result = NULL; + return /*OK*/0; + } + + /* Attempt to convert file name to multi-byte string */ + size_t n; + int error = wcstombs_s( + &n, entry->d_name, PATH_MAX + 1, + datap->cFileName, PATH_MAX + 1); + + /* + * If the file name cannot be represented by a multi-byte string, then + * attempt to use old 8+3 file name. This allows the program to + * access files although file names may seem unfamiliar to the user. + * + * Be ware that the code below cannot come up with a short file name + * unless the file system provides one. At least VirtualBox shared + * folders fail to do this. + */ + if (error && datap->cAlternateFileName[0] != '\0') { + error = wcstombs_s( + &n, entry->d_name, PATH_MAX + 1, + datap->cAlternateFileName, PATH_MAX + 1); + } + + if (!error) { + /* Length of file name excluding zero terminator */ + entry->d_namlen = n - 1; + + /* Determine file type */ + DWORD attr = datap->dwFileAttributes; + if ((attr & FILE_ATTRIBUTE_DEVICE) != 0) + entry->d_type = DT_CHR; + else if ((attr & FILE_ATTRIBUTE_DIRECTORY) != 0) + entry->d_type = DT_DIR; + else + entry->d_type = DT_REG; + + /* Get offset of next file */ + datap = dirent_next(dirp->wdirp); + if (datap) { + /* Compute 31-bit hash of the next directory entry */ + entry->d_off = dirent_hash(datap); + + /* Push the next directory entry back to cache */ + dirp->wdirp->cached = 1; + } else { + /* End of directory stream */ + entry->d_off = (long) ((~0UL) >> 1); + } + + /* Reset fields */ + entry->d_ino = 0; + entry->d_reclen = sizeof(struct dirent); + } else { + /* + * Cannot convert file name to multi-byte string so construct + * an erroneous directory entry and return that. Note that + * we cannot return NULL as that would stop the processing + * of directory entries completely. + */ + entry->d_name[0] = '?'; + entry->d_name[1] = '\0'; + entry->d_namlen = 1; + entry->d_type = DT_UNKNOWN; + entry->d_ino = 0; + entry->d_off = -1; + entry->d_reclen = 0; + } + + /* Return pointer to directory entry */ + *result = entry; + return /*OK*/0; +} + +/* Close directory stream */ +static int +closedir(DIR *dirp) +{ + int ok; + + if (!dirp) + goto exit_failure; + + /* Close wide-character directory stream */ + ok = _wclosedir(dirp->wdirp); + dirp->wdirp = NULL; + + /* Release multi-byte character version */ + free(dirp); + return ok; + +exit_failure: + /* Invalid directory stream */ + dirent_set_errno(EBADF); + return /*failure*/-1; +} + +/* Rewind directory stream to beginning */ +static void +rewinddir(DIR *dirp) +{ + if (!dirp) + return; + + /* Rewind wide-character string directory stream */ + _wrewinddir(dirp->wdirp); +} + +/* Get position of directory stream */ +static long +_wtelldir(_WDIR *dirp) +{ + if (!dirp || dirp->handle == INVALID_HANDLE_VALUE) { + dirent_set_errno(EBADF); + return /*failure*/-1; + } + + /* Read next file entry */ + WIN32_FIND_DATAW *datap = dirent_next(dirp); + if (!datap) { + /* End of directory stream */ + return (long) ((~0UL) >> 1); + } + + /* Store file entry to cache for readdir() */ + dirp->cached = 1; + + /* Return the 31-bit hash code to be used as stream position */ + return dirent_hash(datap); +} + +/* Get position of directory stream */ +static long +telldir(DIR *dirp) +{ + if (!dirp) { + dirent_set_errno(EBADF); + return -1; + } + + return _wtelldir(dirp->wdirp); +} + +/* Seek directory stream to offset */ +static void +_wseekdir(_WDIR *dirp, long loc) +{ + /* Directory must be open */ + if (!dirp || dirp->handle == INVALID_HANDLE_VALUE) + goto exit_failure; + + /* Ensure that seek position is valid */ + if (loc < 0) + goto exit_failure; + + /* Restart directory stream from the beginning */ + FindClose(dirp->handle); + if (!dirent_first(dirp)) + goto exit_failure; + + /* Reset invalid flag so that we can read from the stream again */ + dirp->invalid = 0; + + /* + * Read directory entries from the beginning until the hash matches a + * file name. Be ware that hash code is only 31 bits longs and + * duplicates are possible: the hash code cannot return the position + * with 100.00% accuracy! Moreover, the method is slow for large + * directories. + */ + long hash; + do { + /* Read next directory entry */ + WIN32_FIND_DATAW *datap = dirent_next(dirp); + if (!datap) { + /* + * End of directory stream was reached before finding + * the requested location. Perhaps the file in + * question was deleted or moved out of the directory. + */ + goto exit_failure; + } + + /* Does the file name match the hash? */ + hash = dirent_hash(datap); + } while (hash != loc); + + /* + * File name matches the hash! Push the directory entry back to cache + * from where next readdir() will return it. + */ + dirp->cached = 1; + dirp->invalid = 0; + return; + +exit_failure: + /* Ensure that readdir will return NULL */ + dirp->invalid = 1; +} + +/* Seek directory stream to offset */ +static void +seekdir(DIR *dirp, long loc) +{ + if (!dirp) + return; + + _wseekdir(dirp->wdirp, loc); +} + +/* Scan directory for entries */ +static int +scandir( + const char *dirname, struct dirent ***namelist, + int (*filter)(const struct dirent*), + int (*compare)(const struct dirent**, const struct dirent**)) +{ + int result; + + /* Open directory stream */ + DIR *dir = opendir(dirname); + if (!dir) { + /* Cannot open directory */ + return /*Error*/ -1; + } + + /* Read directory entries to memory */ + struct dirent *tmp = NULL; + struct dirent **files = NULL; + size_t size = 0; + size_t allocated = 0; + while (1) { + /* Allocate room for a temporary directory entry */ + if (!tmp) { + tmp = (struct dirent*) malloc(sizeof(struct dirent)); + if (!tmp) + goto exit_failure; + } + + /* Read directory entry to temporary area */ + struct dirent *entry; + if (readdir_r(dir, tmp, &entry) != /*OK*/0) + goto exit_failure; + + /* Stop if we already read the last directory entry */ + if (entry == NULL) + goto exit_success; + + /* Determine whether to include the entry in results */ + if (filter && !filter(tmp)) + continue; + + /* Enlarge pointer table to make room for another pointer */ + if (size >= allocated) { + /* Compute number of entries in the new table */ + size_t num_entries = size * 2 + 16; + + /* Allocate new pointer table or enlarge existing */ + void *p = realloc(files, sizeof(void*) * num_entries); + if (!p) + goto exit_failure; + + /* Got the memory */ + files = (dirent**) p; + allocated = num_entries; + } + + /* Store the temporary entry to ptr table */ + files[size++] = tmp; + tmp = NULL; + } + +exit_failure: + /* Release allocated entries */ + for (size_t i = 0; i < size; i++) { + free(files[i]); + } + + /* Release the pointer table */ + free(files); + files = NULL; + + /* Exit with error code */ + result = /*error*/ -1; + goto exit_status; + +exit_success: + /* Sort directory entries */ + qsort(files, size, sizeof(void*), + (int (*) (const void*, const void*)) compare); + + /* Pass pointer table to caller */ + if (namelist) + *namelist = files; + + /* Return the number of directory entries read */ + result = (int) size; + +exit_status: + /* Release temporary directory entry, if we had one */ + free(tmp); + + /* Close directory stream */ + closedir(dir); + return result; +} + +/* Alphabetical sorting */ +static int +alphasort(const struct dirent **a, const struct dirent **b) +{ + return strcoll((*a)->d_name, (*b)->d_name); +} + +/* Sort versions */ +static int +versionsort(const struct dirent **a, const struct dirent **b) +{ + return strverscmp((*a)->d_name, (*b)->d_name); +} + +/* Compare strings */ +static int +strverscmp(const char *a, const char *b) +{ + size_t i = 0; + size_t j; + + /* Find first difference */ + while (a[i] == b[i]) { + if (a[i] == '\0') { + /* No difference */ + return 0; + } + ++i; + } + + /* Count backwards and find the leftmost digit */ + j = i; + while (j > 0 && isdigit(a[j-1])) { + --j; + } + + /* Determine mode of comparison */ + if (a[j] == '0' || b[j] == '0') { + /* Find the next non-zero digit */ + while (a[j] == '0' && a[j] == b[j]) { + j++; + } + + /* String with more digits is smaller, e.g 002 < 01 */ + if (isdigit(a[j])) { + if (!isdigit(b[j])) { + return -1; + } + } else if (isdigit(b[j])) { + return 1; + } + } else if (isdigit(a[j]) && isdigit(b[j])) { + /* Numeric comparison */ + size_t k1 = j; + size_t k2 = j; + + /* Compute number of digits in each string */ + while (isdigit(a[k1])) { + k1++; + } + while (isdigit(b[k2])) { + k2++; + } + + /* Number with more digits is bigger, e.g 999 < 1000 */ + if (k1 < k2) + return -1; + else if (k1 > k2) + return 1; + } + + /* Alphabetical comparison */ + return (int) ((unsigned char) a[i]) - ((unsigned char) b[i]); +} + +/* Convert multi-byte string to wide character string */ +#if !defined(_MSC_VER) || _MSC_VER < 1400 +static int +dirent_mbstowcs_s( + size_t *pReturnValue, wchar_t *wcstr, + size_t sizeInWords, const char *mbstr, size_t count) +{ + /* Older Visual Studio or non-Microsoft compiler */ + size_t n = mbstowcs(wcstr, mbstr, sizeInWords); + if (wcstr && n >= count) + return /*error*/ 1; + + /* Zero-terminate output buffer */ + if (wcstr && sizeInWords) { + if (n >= sizeInWords) + n = sizeInWords - 1; + wcstr[n] = 0; + } + + /* Length of multi-byte string with zero terminator */ + if (pReturnValue) { + *pReturnValue = n + 1; + } + + /* Success */ + return 0; +} +#endif + +/* Convert wide-character string to multi-byte string */ +#if !defined(_MSC_VER) || _MSC_VER < 1400 +static int +dirent_wcstombs_s( + size_t *pReturnValue, char *mbstr, + size_t sizeInBytes, const wchar_t *wcstr, size_t count) +{ + /* Older Visual Studio or non-Microsoft compiler */ + size_t n = wcstombs(mbstr, wcstr, sizeInBytes); + if (mbstr && n >= count) + return /*error*/1; + + /* Zero-terminate output buffer */ + if (mbstr && sizeInBytes) { + if (n >= sizeInBytes) { + n = sizeInBytes - 1; + } + mbstr[n] = '\0'; + } + + /* Length of resulting multi-bytes string WITH zero-terminator */ + if (pReturnValue) { + *pReturnValue = n + 1; + } + + /* Success */ + return 0; +} +#endif + +/* Set errno variable */ +#if !defined(_MSC_VER) || _MSC_VER < 1400 +static void +dirent_set_errno(int error) +{ + /* Non-Microsoft compiler or older Microsoft compiler */ + errno = error; +} +#endif + +#ifdef __cplusplus +} +#endif +#endif /*DIRENT_H*/ diff --git a/dirent/tests/1/dir/readme.txt b/dirent/tests/1/dir/readme.txt new file mode 100644 index 0000000..e59af2f --- /dev/null +++ b/dirent/tests/1/dir/readme.txt @@ -0,0 +1,3 @@ +This file ensures that the directory dir will be created accordingly when +you unzip dirent to your computer. The directory itself is needed by the +test program t-dirent. diff --git a/dirent/tests/1/file b/dirent/tests/1/file new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/dirent/tests/1/file diff --git a/dirent/tests/2/Testfile-1.2.3.dat b/dirent/tests/2/Testfile-1.2.3.dat new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/dirent/tests/2/Testfile-1.2.3.dat diff --git a/dirent/tests/2/file.txt b/dirent/tests/2/file.txt new file mode 100644 index 0000000..d32e004 --- /dev/null +++ b/dirent/tests/2/file.txt @@ -0,0 +1 @@ +This dummy file is needed by the test program t-dirent. diff --git a/dirent/tests/3/3zero.dat b/dirent/tests/3/3zero.dat new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/dirent/tests/3/3zero.dat diff --git a/dirent/tests/3/666.dat b/dirent/tests/3/666.dat new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/dirent/tests/3/666.dat diff --git a/dirent/tests/3/Qwerty-my-aunt.dat b/dirent/tests/3/Qwerty-my-aunt.dat new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/dirent/tests/3/Qwerty-my-aunt.dat diff --git a/dirent/tests/3/README.txt b/dirent/tests/3/README.txt new file mode 100644 index 0000000..a07591c --- /dev/null +++ b/dirent/tests/3/README.txt @@ -0,0 +1,2 @@ +This directory contains some random files for the t-scandir test program. The +files are empty and only the file names matter. diff --git a/dirent/tests/3/aaa.dat b/dirent/tests/3/aaa.dat new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/dirent/tests/3/aaa.dat diff --git a/dirent/tests/3/dirent.dat b/dirent/tests/3/dirent.dat new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/dirent/tests/3/dirent.dat diff --git a/dirent/tests/3/empty.dat b/dirent/tests/3/empty.dat new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/dirent/tests/3/empty.dat diff --git a/dirent/tests/3/sane-1.12.0.dat b/dirent/tests/3/sane-1.12.0.dat new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/dirent/tests/3/sane-1.12.0.dat diff --git a/dirent/tests/3/sane-1.2.30.dat b/dirent/tests/3/sane-1.2.30.dat new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/dirent/tests/3/sane-1.2.30.dat diff --git a/dirent/tests/3/sane-1.2.4.dat b/dirent/tests/3/sane-1.2.4.dat new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/dirent/tests/3/sane-1.2.4.dat diff --git a/dirent/tests/3/zebra.dat b/dirent/tests/3/zebra.dat new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/dirent/tests/3/zebra.dat diff --git a/dirent/tests/4/asidofilusphosphorus b/dirent/tests/4/asidofilusphosphorus new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/dirent/tests/4/asidofilusphosphorus diff --git a/dirent/tests/4/bubblebob b/dirent/tests/4/bubblebob new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/dirent/tests/4/bubblebob diff --git a/dirent/tests/4/celsiusfortissimo b/dirent/tests/4/celsiusfortissimo new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/dirent/tests/4/celsiusfortissimo diff --git a/dirent/tests/t-compile.c b/dirent/tests/t-compile.c new file mode 100644 index 0000000..90d8e50 --- /dev/null +++ b/dirent/tests/t-compile.c @@ -0,0 +1,46 @@ +/* + * Test program to make sure that dirent compiles cleanly with winsock. + * + * Copyright (C) 1998-2019 Toni Ronkko + * This file is part of dirent. Dirent may be freely distributed + * under the MIT license. For all details and documentation, see + * https://github.com/tronkko/dirent + */ +#include <dirent.h> +#ifdef WIN32 +# include <winsock2.h> +# include <ws2tcpip.h> +#endif +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +int +main(int argc, char *argv[]) +{ + struct dirent *dirp = NULL; + + (void) argc; + (void) argv; + +#ifdef _DIRENT_HAVE_D_TYPE + printf("Has d_type\n"); +#endif +#ifdef _DIRENT_HAVE_D_NAMLEN + printf("Has d_namlen\n"); +#endif +#ifdef _D_EXACT_NAMLEN + printf("Has _D_EXACT_NAMLEN\n"); +#endif +#ifdef _D_ALLOC_NAMLEN + printf("Has _D_ALLOC_NAMLEN\n"); +#endif +#ifdef _D_ALLOC_NAMLEN + printf("Has _D_ALLOC_NAMLEN\n"); +#endif + printf("Length of d_name with terminator: %d\n", + (int) sizeof(dirp->d_name)); + + printf("OK\n"); + return EXIT_SUCCESS; +} diff --git a/dirent/tests/t-cplusplus.cpp b/dirent/tests/t-cplusplus.cpp new file mode 100644 index 0000000..8a8bad4 --- /dev/null +++ b/dirent/tests/t-cplusplus.cpp @@ -0,0 +1,155 @@ +/* + * Test program to make sure that dirent compiles cleanly with C++ + * + * Copyright (C) 1998-2019 Toni Ronkko + * This file is part of dirent. Dirent may be freely distributed + * under the MIT license. For all details and documentation, see + * https://github.com/tronkko/dirent + */ +#include <iostream> +#include <string.h> +#include <dirent.h> +#include <assert.h> +using namespace std; + +/* Filter and sort functions */ +static int only_readme(const struct dirent *entry); +static void test_retrieval(void); +static void test_scan(void); + +int +main(int argc, char *argv[]) +{ + (void) argc; + (void) argv; + + test_retrieval(); + test_scan(); + + cout << "OK" << endl; + return EXIT_SUCCESS; +} + +/* Test basic directory retrieval */ +static void +test_retrieval(void) +{ + /* Open directory */ + DIR *dir = opendir("tests/1"); + if (dir == NULL) { + cerr << "Directory tests/1 not found" << endl; + abort(); + } + + /* Read directory entries */ + struct dirent *ent; + int found = 0; + while ((ent = readdir(dir)) != NULL) { + /* Check each file */ + if (strcmp(ent->d_name, ".") == 0) { + /* Directory itself */ +#ifdef _DIRENT_HAVE_D_TYPE + assert(ent->d_type == DT_DIR); +#endif +#ifdef _DIRENT_HAVE_D_NAMLEN + assert(ent->d_namlen == 1); +#endif +#ifdef _D_EXACT_NAMLEN + assert(_D_EXACT_NAMLEN(ent) == 1); +#endif +#ifdef _D_ALLOC_NAMLEN + assert(_D_ALLOC_NAMLEN(ent) > 1); +#endif + found += 1; + } else if (strcmp(ent->d_name, "..") == 0) { + /* Parent directory */ +#ifdef _DIRENT_HAVE_D_TYPE + assert(ent->d_type == DT_DIR); +#endif +#ifdef _DIRENT_HAVE_D_NAMLEN + assert(ent->d_namlen == 2); +#endif +#ifdef _D_EXACT_NAMLEN + assert(_D_EXACT_NAMLEN(ent) == 2); +#endif +#ifdef _D_ALLOC_NAMLEN + assert(_D_ALLOC_NAMLEN(ent) > 2); +#endif + found += 2; + } else if (strcmp(ent->d_name, "file") == 0) { + /* Regular file */ +#ifdef _DIRENT_HAVE_D_TYPE + assert(ent->d_type == DT_REG); +#endif +#ifdef _DIRENT_HAVE_D_NAMLEN + assert(ent->d_namlen == 4); +#endif +#ifdef _D_EXACT_NAMLEN + assert(_D_EXACT_NAMLEN(ent) == 4); +#endif +#ifdef _D_ALLOC_NAMLEN + assert(_D_ALLOC_NAMLEN(ent) > 4); +#endif + found += 4; + } else if (strcmp(ent->d_name, "dir") == 0) { + /* Just a directory */ +#ifdef _DIRENT_HAVE_D_TYPE + assert(ent->d_type == DT_DIR); +#endif +#ifdef _DIRENT_HAVE_D_NAMLEN + assert(ent->d_namlen == 3); +#endif +#ifdef _D_EXACT_NAMLEN + assert(_D_EXACT_NAMLEN(ent) == 3); +#endif +#ifdef _D_ALLOC_NAMLEN + assert(_D_ALLOC_NAMLEN(ent) > 3); +#endif + found += 8; + } else { + /* Other file */ + cerr << "Unexpected file " << ent->d_name << endl; + abort(); + } + } + + /* Make sure that all files were found */ + assert(found == 0xf); + + closedir(dir); +} + +/* Text basic scan with simple filter function */ +static void +test_scan(void) +{ + struct dirent **files; + + /* Read directory entries */ + int n = scandir("tests/3", &files, only_readme, alphasort); + assert(n == 1); + + /* Make sure that the filter works */ + assert(strcmp(files[0]->d_name, "README.txt") == 0); + + /* Release file names */ + for (int i = 0; i < n; i++) { + free(files[i]); + } + free(files); +} + +/* Only pass README.txt file */ +static int +only_readme(const struct dirent *entry) +{ + int pass; + + if (strcmp (entry->d_name, "README.txt") == 0) { + pass = 1; + } else { + pass = 0; + } + + return pass; +} diff --git a/dirent/tests/t-dirent.c b/dirent/tests/t-dirent.c new file mode 100644 index 0000000..95f151a --- /dev/null +++ b/dirent/tests/t-dirent.c @@ -0,0 +1,608 @@ +/* + * A test program to make sure that dirent works correctly. + * + * Copyright (C) 1998-2019 Toni Ronkko + * This file is part of dirent. Dirent may be freely distributed + * under the MIT license. For all details and documentation, see + * https://github.com/tronkko/dirent + */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#ifdef _MSC_VER +# include <direct.h> +# define chdir(x) _chdir(x) +#else +# include <unistd.h> +#endif +#include <sys/stat.h> +#include <dirent.h> +#include <errno.h> + +#undef NDEBUG +#include <assert.h> + +static void test_macros(void); +static void test_retrieval(void); +static void test_nonexistent(void); +static void test_isfile(void); +static void test_zero(void); +static void test_rewind(void); +static void test_chdir(void); +static void test_filename(void); +static void test_readdir(void); +static void test_wreaddir(void); + +int +main(int argc, char *argv[]) +{ + (void) argc; + (void) argv; + + /* Execute tests */ + test_macros(); + test_retrieval(); + test_nonexistent(); + test_isfile(); + test_zero(); + test_rewind(); + test_chdir(); + test_filename(); + test_readdir(); + test_wreaddir(); + + printf("OK\n"); + return EXIT_SUCCESS; +} + +/* Test file type macros */ +static void +test_macros(void) +{ + assert(DTTOIF(DT_REG) == S_IFREG); + assert(DTTOIF(DT_DIR) == S_IFDIR); + assert(DTTOIF(DT_FIFO) == S_IFIFO); + assert(DTTOIF(DT_SOCK) == S_IFSOCK); + assert(DTTOIF(DT_CHR) == S_IFCHR); + assert(DTTOIF(DT_BLK) == S_IFBLK); + + assert(IFTODT(S_IFREG) == DT_REG); + assert(IFTODT(S_IFDIR) == DT_DIR); + assert(IFTODT(S_IFIFO) == DT_FIFO); + assert(IFTODT(S_IFSOCK) == DT_SOCK); + assert(IFTODT(S_IFCHR) == DT_CHR); + assert(IFTODT(S_IFBLK) == DT_BLK); +} + +/* Test basic directory retrieval */ +static void +test_retrieval(void) +{ + /* Open directory */ + DIR *dir = opendir("tests/1"); + if (dir == NULL) { + fprintf(stderr, "Directory tests/1 not found\n"); + abort(); + } + + /* Read entries */ + struct dirent *ent; + int found = 0; + while ((ent = readdir(dir)) != NULL) { + /* Check each file */ + if (strcmp(ent->d_name, ".") == 0) { + /* Directory itself */ +#ifdef _DIRENT_HAVE_D_TYPE + assert(ent->d_type == DT_DIR); +#endif +#ifdef _DIRENT_HAVE_D_NAMLEN + assert(ent->d_namlen == 1); +#endif +#ifdef _D_EXACT_NAMLEN + assert(_D_EXACT_NAMLEN(ent) == 1); +#endif +#ifdef _D_ALLOC_NAMLEN + assert(_D_ALLOC_NAMLEN(ent) > 1); +#endif + found += 1; + } else if (strcmp(ent->d_name, "..") == 0) { + /* Parent directory */ +#ifdef _DIRENT_HAVE_D_TYPE + assert(ent->d_type == DT_DIR); +#endif +#ifdef _DIRENT_HAVE_D_NAMLEN + assert(ent->d_namlen == 2); +#endif +#ifdef _D_EXACT_NAMLEN + assert(_D_EXACT_NAMLEN(ent) == 2); +#endif +#ifdef _D_ALLOC_NAMLEN + assert(_D_ALLOC_NAMLEN(ent) > 2); +#endif + found += 2; + } else if (strcmp(ent->d_name, "file") == 0) { + /* Regular file */ +#ifdef _DIRENT_HAVE_D_TYPE + assert(ent->d_type == DT_REG); +#endif +#ifdef _DIRENT_HAVE_D_NAMLEN + assert(ent->d_namlen == 4); +#endif +#ifdef _D_EXACT_NAMLEN + assert(_D_EXACT_NAMLEN(ent) == 4); +#endif +#ifdef _D_ALLOC_NAMLEN + assert(_D_ALLOC_NAMLEN(ent) > 4); +#endif + found += 4; + } else if (strcmp(ent->d_name, "dir") == 0) { + /* Just a directory */ +#ifdef _DIRENT_HAVE_D_TYPE + assert(ent->d_type == DT_DIR); +#endif +#ifdef _DIRENT_HAVE_D_NAMLEN + assert(ent->d_namlen == 3); +#endif +#ifdef _D_EXACT_NAMLEN + assert(_D_EXACT_NAMLEN(ent) == 3); +#endif +#ifdef _D_ALLOC_NAMLEN + assert(_D_ALLOC_NAMLEN(ent) > 3); +#endif + found += 8; + } else { + /* Other file */ + fprintf(stderr, "Unexpected file %s\n", ent->d_name); + abort(); + } + } + + /* Make sure that all files were found */ + assert(found == 0xf); + + closedir(dir); +} + +/* Function opendir() fails if directory doesn't exist */ +static void +test_nonexistent(void) +{ + DIR *dir = opendir("tests/invalid"); + assert(dir == NULL); + assert(errno == ENOENT); +} + +/* Function opendir() fails if pathname is really a file */ +static void +test_isfile(void) +{ + DIR *dir = opendir("tests/1/file"); + assert(dir == NULL); + assert(errno == ENOTDIR); +} + +/* Function opendir() fails if pathname is a zero-length string */ +static void +test_zero(void) +{ + DIR *dir = opendir(""); + assert(dir == NULL); + assert(errno == ENOENT); +} + +/* Test rewind of directory stream */ +static void +test_rewind(void) +{ + /* Open directory */ + DIR *dir = opendir("tests/1"); + assert(dir != NULL); + + /* Read entries */ + int found = 0; + struct dirent *ent; + while ((ent = readdir(dir)) != NULL) { + /* Check each file */ + if (strcmp(ent->d_name, ".") == 0) { + /* Directory itself */ + found += 1; + } else if (strcmp(ent->d_name, "..") == 0) { + /* Parent directory */ + found += 2; + } else if (strcmp(ent->d_name, "file") == 0) { + /* Regular file */ + found += 4; + } else if (strcmp(ent->d_name, "dir") == 0) { + /* Just a directory */ + found += 8; + } else { + /* Other file */ + fprintf(stderr, "Unexpected file %s\n", ent->d_name); + abort(); + } + } + + /* Make sure that all files were found */ + assert(found == 0xf); + + /* Rewind stream and read entries again */ + rewinddir(dir); + found = 0; + + /* Read entries */ + while ((ent = readdir(dir)) != NULL) { + /* Check each file */ + if (strcmp(ent->d_name, ".") == 0) { + /* Directory itself */ + found += 1; + } else if (strcmp(ent->d_name, "..") == 0) { + /* Parent directory */ + found += 2; + } else if (strcmp(ent->d_name, "file") == 0) { + /* Regular file */ + found += 4; + } else if (strcmp(ent->d_name, "dir") == 0) { + /* Just a directory */ + found += 8; + } else { + /* Other file */ + fprintf(stderr, "Unexpected file %s\n", ent->d_name); + abort(); + } + } + + /* Make sure that all files were found */ + assert(found == 0xf); + + closedir(dir); +} + +/* Test rewind with intervening change of working directory */ +static void +test_chdir(void) +{ + /* Open directory */ + DIR *dir = opendir("tests/1"); + assert(dir != NULL); + + /* Read entries */ + struct dirent *ent; + int found = 0; + while ((ent = readdir(dir)) != NULL) { + /* Check each file */ + if (strcmp(ent->d_name, ".") == 0) { + /* Directory itself */ + found += 1; + } else if (strcmp(ent->d_name, "..") == 0) { + /* Parent directory */ + found += 2; + } else if (strcmp(ent->d_name, "file") == 0) { + /* Regular file */ + found += 4; + } else if (strcmp(ent->d_name, "dir") == 0) { + /* Just a directory */ + found += 8; + } else { + /* Other file */ + fprintf(stderr, "Unexpected file %s\n", ent->d_name); + abort(); + } + + } + + /* Make sure that all files were found */ + assert(found == 0xf); + + /* Change working directory */ + int errorcode = chdir("tests"); + assert(errorcode == 0); + + /* Rewind stream and read entries again */ + rewinddir(dir); + found = 0; + + /* Read entries */ + while ((ent = readdir(dir)) != NULL) { + /* Check each file */ + if (strcmp(ent->d_name, ".") == 0) { + /* Directory itself */ + found += 1; + } else if (strcmp(ent->d_name, "..") == 0) { + /* Parent directory */ + found += 2; + } else if (strcmp(ent->d_name, "file") == 0) { + /* Regular file */ + found += 4; + } else if (strcmp(ent->d_name, "dir") == 0) { + /* Just a directory */ + found += 8; + } else { + /* Other file */ + fprintf(stderr, "Unexpected file %s\n", ent->d_name); + abort(); + } + } + + /* Make sure that all files were found */ + assert(found == 0xf); + + /* Restore working directory */ + errorcode = chdir(".."); + assert(errorcode == 0); + + closedir(dir); +} + +/* Test long file name */ +static void +test_filename(void) +{ + /* Open directory */ + DIR *dir = opendir("tests/2"); + if (dir == NULL) { + fprintf(stderr, "Directory tests/2 not found\n"); + abort(); + } + + /* Read entries */ + struct dirent *ent; + int found = 0; + while ((ent = readdir(dir)) != NULL) { + /* Check each file */ + if (strcmp(ent->d_name, ".") == 0) { + /* Directory itself */ + found += 1; + } else if (strcmp(ent->d_name, "..") == 0) { + /* Parent directory */ + found += 2; + } else if (strcmp(ent->d_name, "file.txt") == 0) { + /* Regular 8+3 filename */ +#ifdef _DIRENT_HAVE_D_TYPE + assert(ent->d_type == DT_REG); +#endif +#ifdef _DIRENT_HAVE_D_NAMLEN + assert(ent->d_namlen == 8); +#endif +#ifdef _D_EXACT_NAMLEN + assert(_D_EXACT_NAMLEN(ent) == 8); +#endif +#ifdef _D_ALLOC_NAMLEN + assert(_D_ALLOC_NAMLEN(ent) > 8); +#endif + found += 4; + } else if (strcmp(ent->d_name, "Testfile-1.2.3.dat") == 0) { + /* Long file name with multiple dots */ +#ifdef _DIRENT_HAVE_D_TYPE + assert(ent->d_type == DT_REG); +#endif +#ifdef _DIRENT_HAVE_D_NAMLEN + assert(ent->d_namlen == 18); +#endif +#ifdef _D_EXACT_NAMLEN + assert(_D_EXACT_NAMLEN(ent) == 18); +#endif +#ifdef _D_ALLOC_NAMLEN + assert(_D_ALLOC_NAMLEN(ent) > 18); +#endif + found += 8; + } else { + /* Other file */ + fprintf(stderr, "Unexpected file %s\n", ent->d_name); + abort(); + } + } + + /* Make sure that all files were found */ + assert(found == 0xf); + + closedir(dir); +} + +/* Test basic directory retrieval with readdir_r */ +static void +test_readdir(void) +{ + /* Open directory */ + DIR *dir = opendir("tests/1"); + if (dir == NULL) { + fprintf(stderr, "Directory tests/1 not found\n"); + abort(); + } + + /* Read entries to table */ + struct dirent ent[10]; + struct dirent *entry; + size_t i = 0; + size_t n = 0; + while (readdir_r(dir, &ent[n], &entry) == /*OK*/0 && entry != 0) { + n++; + assert(n <= 4); + } + + /* Make sure that we got all the files from directory */ + assert(n == 4); + + /* Check entries in memory */ + int found = 0; + for (i = 0; i < 4; i++) { + entry = &ent[i]; + + /* Check each file */ + if (strcmp(entry->d_name, ".") == 0) { + /* Directory itself */ +#ifdef _DIRENT_HAVE_D_TYPE + assert(entry->d_type == DT_DIR); +#endif +#ifdef _DIRENT_HAVE_D_NAMLEN + assert(entry->d_namlen == 1); +#endif +#ifdef _D_EXACT_NAMLEN + assert(_D_EXACT_NAMLEN(entry) == 1); +#endif +#ifdef _D_ALLOC_NAMLEN + assert(_D_ALLOC_NAMLEN(entry) > 1); +#endif + found += 1; + } else if (strcmp(entry->d_name, "..") == 0) { + /* Parent directory */ +#ifdef _DIRENT_HAVE_D_TYPE + assert(entry->d_type == DT_DIR); +#endif +#ifdef _DIRENT_HAVE_D_NAMLEN + assert(entry->d_namlen == 2); +#endif +#ifdef _D_EXACT_NAMLEN + assert(_D_EXACT_NAMLEN(entry) == 2); +#endif +#ifdef _D_ALLOC_NAMLEN + assert(_D_ALLOC_NAMLEN(entry) > 2); +#endif + found += 2; + } else if (strcmp(entry->d_name, "file") == 0) { + /* Regular file */ +#ifdef _DIRENT_HAVE_D_TYPE + assert(entry->d_type == DT_REG); +#endif +#ifdef _DIRENT_HAVE_D_NAMLEN + assert(entry->d_namlen == 4); +#endif +#ifdef _D_EXACT_NAMLEN + assert(_D_EXACT_NAMLEN(entry) == 4); +#endif +#ifdef _D_ALLOC_NAMLEN + assert(_D_ALLOC_NAMLEN(entry) > 4); +#endif + found += 4; + } else if (strcmp(entry->d_name, "dir") == 0) { + /* Just a directory */ +#ifdef _DIRENT_HAVE_D_TYPE + assert(entry->d_type == DT_DIR); +#endif +#ifdef _DIRENT_HAVE_D_NAMLEN + assert(entry->d_namlen == 3); +#endif +#ifdef _D_EXACT_NAMLEN + assert(_D_EXACT_NAMLEN(entry) == 3); +#endif +#ifdef _D_ALLOC_NAMLEN + assert(_D_ALLOC_NAMLEN(entry) > 3); +#endif + found += 8; + } else { + /* Other file */ + fprintf(stderr, "Unexpected file %s\n", entry->d_name); + abort(); + } + + } + + /* Make sure that all files were found */ + assert(found == 0xf); + + closedir(dir); +} + +/* Basic directory retrieval with _wreaddir_r */ +static void +test_wreaddir(void) +{ +#ifdef WIN32 + /* Open directory */ + _WDIR *dir = _wopendir(L"tests/1"); + if (dir == NULL) { + fprintf(stderr, "Directory tests/1 not found\n"); + abort(); + } + + /* Read entries to table */ + struct _wdirent ent[10]; + struct _wdirent *entry; + size_t i = 0; + size_t n = 0; + while (_wreaddir_r(dir, &ent[n], &entry) == /*OK*/0 && entry != 0) { + n++; + assert(n <= 4); + } + + /* Make sure that we got all the files from directory */ + assert(n == 4); + + /* Check entries in memory */ + int found = 0; + for (i = 0; i < 4; i++) { + entry = &ent[i]; + + /* Check each file */ + if (wcscmp(entry->d_name, L".") == 0) { + /* Directory itself */ +#ifdef _DIRENT_HAVE_D_TYPE + assert(entry->d_type == DT_DIR); +#endif +#ifdef _DIRENT_HAVE_D_NAMLEN + assert(entry->d_namlen == 1); +#endif +#ifdef _D_EXACT_NAMLEN + assert(_D_EXACT_NAMLEN(entry) == 1); +#endif +#ifdef _D_ALLOC_NAMLEN + assert(_D_ALLOC_NAMLEN(entry) > 1); +#endif + found += 1; + } else if (wcscmp(entry->d_name, L"..") == 0) { + /* Parent directory */ +#ifdef _DIRENT_HAVE_D_TYPE + assert(entry->d_type == DT_DIR); +#endif +#ifdef _DIRENT_HAVE_D_NAMLEN + assert(entry->d_namlen == 2); +#endif +#ifdef _D_EXACT_NAMLEN + assert(_D_EXACT_NAMLEN(entry) == 2); +#endif +#ifdef _D_ALLOC_NAMLEN + assert(_D_ALLOC_NAMLEN(entry) > 2); +#endif + found += 2; + } else if (wcscmp(entry->d_name, L"file") == 0) { + /* Regular file */ +#ifdef _DIRENT_HAVE_D_TYPE + assert(entry->d_type == DT_REG); +#endif +#ifdef _DIRENT_HAVE_D_NAMLEN + assert(entry->d_namlen == 4); +#endif +#ifdef _D_EXACT_NAMLEN + assert(_D_EXACT_NAMLEN(entry) == 4); +#endif +#ifdef _D_ALLOC_NAMLEN + assert(_D_ALLOC_NAMLEN(entry) > 4); +#endif + found += 4; + } else if (wcscmp(entry->d_name, L"dir") == 0) { + /* Just a directory */ +#ifdef _DIRENT_HAVE_D_TYPE + assert(entry->d_type == DT_DIR); +#endif +#ifdef _DIRENT_HAVE_D_NAMLEN + assert(entry->d_namlen == 3); +#endif +#ifdef _D_EXACT_NAMLEN + assert(_D_EXACT_NAMLEN(entry) == 3); +#endif +#ifdef _D_ALLOC_NAMLEN + assert(_D_ALLOC_NAMLEN(entry) > 3); +#endif + found += 8; + } else { + /* Other file */ + fprintf(stderr, "Unexpected file\n"); + abort(); + } + } + + /* Make sure that all files were found */ + assert(found == 0xf); + + _wclosedir(dir); +#endif +} diff --git a/dirent/tests/t-scandir.c b/dirent/tests/t-scandir.c new file mode 100644 index 0000000..c463f9b --- /dev/null +++ b/dirent/tests/t-scandir.c @@ -0,0 +1,276 @@ +/* + * Make sure that scandir function works OK. + * + * Copyright (C) 1998-2019 Toni Ronkko + * This file is part of dirent. Dirent may be freely distributed + * under the MIT license. For all details and documentation, see + * https://github.com/tronkko/dirent + */ + +/* Silence warning about fopen being insecure (MS Visual Studio) */ +#define _CRT_SECURE_NO_WARNINGS + +/* Include prototype for versionsort (Linux) */ +#define _GNU_SOURCE + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <dirent.h> +#include <errno.h> +#include <time.h> +#include <limits.h> + +#undef NDEBUG +#include <assert.h> + +/* Filter and sort functions */ +static int only_readme(const struct dirent *entry); +static int no_directories(const struct dirent *entry); +static int reverse_alpha(const struct dirent **a, const struct dirent **b); + +int +main(int argc, char *argv[]) +{ + struct dirent **files; + int i; + int n; + + (void) argc; + (void) argv; + + /* Initialize random number generator */ + srand((unsigned) time(NULL)); + + /* Basic scan with simple filter function */ + { + /* Read directory entries */ + n = scandir("tests/3", &files, only_readme, alphasort); + assert(n == 1); + + /* Make sure that the filter works */ + assert(strcmp(files[0]->d_name, "README.txt") == 0); + + /* Release file names */ + for (i = 0; i < n; i++) { + free(files[i]); + } + free(files); + } + + /* Basic scan with default sorting function */ + { + /* Read directory entries in alphabetic order */ + n = scandir("tests/3", &files, NULL, alphasort); + assert(n == 13); + + /* Make sure that we got all the names in the proper order */ + assert(strcmp(files[0]->d_name, ".") == 0); + assert(strcmp(files[1]->d_name, "..") == 0); + assert(strcmp(files[2]->d_name, "3zero.dat") == 0); + assert(strcmp(files[3]->d_name, "666.dat") == 0); + assert(strcmp(files[4]->d_name, "Qwerty-my-aunt.dat") == 0); + assert(strcmp(files[5]->d_name, "README.txt") == 0); + assert(strcmp(files[6]->d_name, "aaa.dat") == 0); + assert(strcmp(files[7]->d_name, "dirent.dat") == 0); + assert(strcmp(files[8]->d_name, "empty.dat") == 0); + assert(strcmp(files[9]->d_name, "sane-1.12.0.dat") == 0); + assert(strcmp(files[10]->d_name, "sane-1.2.30.dat") == 0); + assert(strcmp(files[11]->d_name, "sane-1.2.4.dat") == 0); + assert(strcmp(files[12]->d_name, "zebra.dat") == 0); + + /* Release file names */ + for (i = 0; i < n; i++) { + free(files[i]); + } + free(files); + } + + /* Custom filter AND sort function */ + { + /* Read directory entries in alphabetic order */ + n = scandir("tests/3", &files, no_directories, reverse_alpha); + assert(n == 11); + + /* Make sure that we got file names in the reverse order */ + assert(strcmp(files[0]->d_name, "zebra.dat") == 0); + assert(strcmp(files[1]->d_name, "sane-1.2.4.dat") == 0); + assert(strcmp(files[2]->d_name, "sane-1.2.30.dat") == 0); + assert(strcmp(files[3]->d_name, "sane-1.12.0.dat") == 0); + assert(strcmp(files[4]->d_name, "empty.dat") == 0); + assert(strcmp(files[5]->d_name, "dirent.dat") == 0); + assert(strcmp(files[6]->d_name, "aaa.dat") == 0); + assert(strcmp(files[7]->d_name, "README.txt") == 0); + assert(strcmp(files[8]->d_name, "Qwerty-my-aunt.dat") == 0); + assert(strcmp(files[9]->d_name, "666.dat") == 0); + assert(strcmp(files[10]->d_name, "3zero.dat") == 0); + + /* Release file names */ + for (i = 0; i < n; i++) { + free(files[i]); + } + free(files); + } + + /* Trying to read from non-existent directory leads to an error */ + { + files = NULL; + n = scandir("tests/invalid", &files, NULL, alphasort); + assert(n == -1); + assert(files == NULL); + assert(errno == ENOENT); + } + + /* Trying to open file as a directory produces ENOTDIR error */ + { + files = NULL; + n = scandir("tests/3/666.dat", &files, NULL, alphasort); + assert(n == -1); + assert(files == NULL); + assert(errno == ENOTDIR); + } + + /* Sort files using versionsort() */ + { + files = NULL; + n = scandir("tests/3", &files, no_directories, versionsort); + assert(n == 11); + + /* + * Make sure that we got all the file names in the proper order: + * 1.2.4 < 1.2.30 < 1.12.0 + */ + assert(strcmp(files[0]->d_name, "3zero.dat") == 0); + assert(strcmp(files[1]->d_name, "666.dat") == 0); + assert(strcmp(files[2]->d_name, "Qwerty-my-aunt.dat") == 0); + assert(strcmp(files[3]->d_name, "README.txt") == 0); + assert(strcmp(files[4]->d_name, "aaa.dat") == 0); + assert(strcmp(files[5]->d_name, "dirent.dat") == 0); + assert(strcmp(files[6]->d_name, "empty.dat") == 0); + assert(strcmp(files[7]->d_name, "sane-1.2.4.dat") == 0); + assert(strcmp(files[8]->d_name, "sane-1.2.30.dat") == 0); + assert(strcmp(files[9]->d_name, "sane-1.12.0.dat") == 0); + assert(strcmp(files[10]->d_name, "zebra.dat") == 0); + + /* Release file names */ + for (i = 0; i < n; i++) { + free(files[i]); + } + free(files); + } + + /* Scan large directory */ + { + char dirname[PATH_MAX+1]; + int i; + int ok; + + /* Copy name of temporary directory to variable dirname */ +#ifdef WIN32 + i = GetTempPathA(PATH_MAX, dirname); + assert(i > 0); +#else + strcpy(dirname, "/tmp/"); + i = strlen(dirname); +#endif + + /* Append random characters to dirname */ + for (int j = 0; j < 10; j++) { + char c; + + /* Generate random character */ + c = "abcdefghijklmnopqrstuvwxyz"[rand() % 26]; + + /* Append character to dirname */ + assert(i < PATH_MAX); + dirname[i++] = c; + } + + /* Terminate directory name */ + assert(i < PATH_MAX); + dirname[i] = '\0'; + + /* Create directory */ +#ifdef WIN32 + ok = CreateDirectoryA(dirname, NULL); + assert(ok); +#else + ok = mkdir(dirname, 0700); + assert(ok == /*success*/0); +#endif + + /* Create one thousand files */ + assert(i + 5 < PATH_MAX); + for (int j = 0; j < 1000; j++) { + FILE *fp; + + /* Construct file name */ + dirname[i] = '/'; + dirname[i+1] = 'z'; + dirname[i+2] = '0' + ((j / 100) % 10); + dirname[i+3] = '0' + ((j / 10) % 10); + dirname[i+4] = '0' + (j % 10); + dirname[i+5] = '\0'; + + /* Create file */ + fp = fopen(dirname, "w"); + assert(fp != NULL); + fclose(fp); + + } + + /* Cut out the file name part */ + dirname[i] = '\0'; + + /* Scan directory */ + n = scandir(dirname, &files, no_directories, alphasort); + assert(n == 1000); + + /* Make sure that all 1000 files are read back */ + for (int j = 0; j < n; j++) { + char match[100]; + + /* Construct file name */ + match[0] = 'z'; + match[1] = '0' + ((j / 100) % 10); + match[2] = '0' + ((j / 10) % 10); + match[3] = '0' + (j % 10); + match[4] = '\0'; + + /* Make sure that file name matches that on the disk */ + assert(strcmp(files[j]->d_name, match) == 0); + + } + + /* Release file names */ + for (int j = 0; j < n; j++) { + free(files[j]); + } + free(files); + } + + printf("OK\n"); + return EXIT_SUCCESS; +} + +/* Only pass README.txt file */ +static int +only_readme(const struct dirent *entry) +{ + return strcmp(entry->d_name, "README.txt") == 0; +} + +/* Filter out directories */ +static int +no_directories(const struct dirent *entry) +{ + return entry->d_type != DT_DIR; +} + +/* Sort in reverse direction */ +static int +reverse_alpha(const struct dirent **a, const struct dirent **b) +{ + return strcoll((*b)->d_name, (*a)->d_name); +} diff --git a/dirent/tests/t-strverscmp.c b/dirent/tests/t-strverscmp.c new file mode 100644 index 0000000..fcb044f --- /dev/null +++ b/dirent/tests/t-strverscmp.c @@ -0,0 +1,206 @@ +/* + * Test program to make sure that strverscmp works correctly + * + * Copyright (C) 1998-2019 Toni Ronkko + * This file is part of dirent. Dirent may be freely distributed + * under the MIT license. For all details and documentation, see + * https://github.com/tronkko/dirent + */ + +/* Include prototype for strverscmp */ +#define _GNU_SOURCE + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <assert.h> +#include <dirent.h> +#include <ctype.h> + +int +main( + int argc, char *argv[]) +{ + (void) argc; + (void) argv; + + /* Strings without digits are compared as in strcmp() */ + assert(strverscmp("", "") == 0); + assert(strverscmp("abc", "abc") == 0); + assert(strverscmp("a", "b") < 0); + assert(strverscmp("b", "a") > 0); + + /* Shorter string is smaller, other things being equal */ + assert(strverscmp("a", "aa") < 0); + assert(strverscmp("aa", "a") > 0); + assert(strverscmp("abcdef", "abcdefg") < 0); + assert(strverscmp("abcdefg", "abcdef") > 0); + + /* Integers with equal length are compared as in strcmp() */ + assert(strverscmp("0", "0") == 0); + assert(strverscmp("000", "000") == 0); + assert(strverscmp("1", "2") < 0); + assert(strverscmp("2", "1") > 0); + assert(strverscmp("001", "100") < 0); + assert(strverscmp("100", "001") > 0); + assert(strverscmp("2020-07-01", "2020-07-02") < 0); + assert(strverscmp("2020-07-02", "2020-07-01") > 0); + assert(strverscmp("jan999", "jan999") == 0); + + /* Integers of different length are compared as numbers */ + assert(strverscmp("jan9", "jan10") < 0); + assert(strverscmp("jan10", "jan9") > 0); + assert(strverscmp("999", "1000") < 0); + assert(strverscmp("1000", "999") > 0); + assert(strverscmp("t12-1000", "t12-9999") < 0); + assert(strverscmp("t12-9999", "t12-1000") > 0); + assert(strverscmp("1000", "10001") < 0); + assert(strverscmp("10001", "1000") > 0); + assert(strverscmp("1000!", "10001") < 0); + assert(strverscmp("10001", "1000!") > 0); + assert(strverscmp("1000Z", "10001") < 0); + assert(strverscmp("10001", "1000Z") > 0); + + /* If numbers starts with zero, then longer number is smaller */ + assert(strverscmp("00", "0") < 0); + assert(strverscmp("0", "00") > 0); + assert(strverscmp("a000", "a00") < 0); + assert(strverscmp("a00", "a000") > 0); + assert(strverscmp("0000", "000") < 0); + assert(strverscmp("000", "0000") > 0); + assert(strverscmp("0000", "000!") < 0); + assert(strverscmp("000!", "0000") > 0); + assert(strverscmp("0000", "000Z") < 0); + assert(strverscmp("000Z", "0000") > 0); + assert(strverscmp("0000", "000Z") < 0); + assert(strverscmp("000Z", "0000") > 0); + assert(strverscmp("1.01", "1.0") < 0); + assert(strverscmp("1.0", "1.01") > 0); + assert(strverscmp("1.01", "1.0!") < 0); + assert(strverscmp("1.0!", "1.01") > 0); + assert(strverscmp("1.01", "1.0~") < 0); + assert(strverscmp("1.0~", "1.01") > 0); + + /* Number having more leading zeros is considered smaller */ + assert(strverscmp("item-0001", "item-001") < 0); + assert(strverscmp("item-001", "item-0001") > 0); + assert(strverscmp("item-001", "item-01") < 0); + assert(strverscmp("item-01", "item-001") > 0); + assert(strverscmp(".0001000", ".001") < 0); + assert(strverscmp(".001", ".0001000") > 0); + assert(strverscmp(".0001000", ".01") < 0); + assert(strverscmp(".01", ".0001000") > 0); + assert(strverscmp(".0001000", ".1") < 0); + assert(strverscmp(".1", ".0001000") > 0); + assert(strverscmp("1.0002", "1.0010000") < 0); + assert(strverscmp("1.0010000", "1.0002") > 0); + + /* Number starting with zero is smaller than any number */ + assert(strverscmp("item-009", "item-1") < 0); + assert(strverscmp("item-1", "item-009") > 0); + assert(strverscmp("item-099", "item-2") < 0); + assert(strverscmp("item-2", "item-099") > 0); + + /* Number vs alphabetical comparison */ + assert(strverscmp("1.001", "1.00!") < 0); + assert(strverscmp("1.00!", "1.001") > 0); + assert(strverscmp("1.001", "1.00x") < 0); + assert(strverscmp("1.00x", "1.001") > 0); + assert(strverscmp("1", "x") < 0); + assert(strverscmp("x", "1") > 0); + assert(strverscmp("1", "!") > 0); + assert(strverscmp("!", "1") < 0); + + /* Handling the end of string */ + assert(strverscmp("01", "011") < 0); + assert(strverscmp("011", "01") > 0); + assert(strverscmp("0100", "01000") < 0); + assert(strverscmp("01000", "0100") > 0); + assert(strverscmp("1", "1!") < 0); + assert(strverscmp("1!", "1") > 0); + assert(strverscmp("1", "1z") < 0); + assert(strverscmp("1z", "1") > 0); + + /* Ordering 000 < 00 < 01 < 010 < 09 < 0 < 1 < 9 < 10 */ + assert(strverscmp("000", "00") < 0); + assert(strverscmp("000", "01") < 0); + assert(strverscmp("000", "010") < 0); + assert(strverscmp("000", "09") < 0); + assert(strverscmp("000", "0") < 0); + assert(strverscmp("000", "1") < 0); + assert(strverscmp("000", "9") < 0); + assert(strverscmp("000", "10") < 0); + + assert(strverscmp("00", "01") < 0); + assert(strverscmp("00", "010") < 0); + assert(strverscmp("00", "09") < 0); + assert(strverscmp("00", "0") < 0); + assert(strverscmp("00", "1") < 0); + assert(strverscmp("00", "9") < 0); + assert(strverscmp("00", "10") < 0); + + assert(strverscmp("01", "010") < 0); + assert(strverscmp("01", "09") < 0); + assert(strverscmp("01", "0") < 0); + assert(strverscmp("01", "1") < 0); + assert(strverscmp("01", "9") < 0); + assert(strverscmp("01", "10") < 0); + + assert(strverscmp("010", "09") < 0); + assert(strverscmp("010", "0") < 0); + assert(strverscmp("010", "1") < 0); + assert(strverscmp("010", "9") < 0); + assert(strverscmp("010", "10") < 0); + + assert(strverscmp("09", "0") < 0); + assert(strverscmp("09", "1") < 0); + assert(strverscmp("09", "9") < 0); + assert(strverscmp("09", "10") < 0); + + assert(strverscmp("0", "1") < 0); + assert(strverscmp("0", "9") < 0); + assert(strverscmp("0", "10") < 0); + + assert(strverscmp("1", "9") < 0); + assert(strverscmp("1", "10") < 0); + + assert(strverscmp("9", "10") < 0); + + /* Compare speed */ + { +#define LENGTH 100 +#define REPEAT 1000000 + char a[LENGTH+1]; + char b[LENGTH+1]; + size_t i; + size_t j; + char letters[] = "01234567890123456789abdefghjkpqrtwxyz-/."; + size_t n = strlen(letters); + + /* Repeat test */ + for(i = 0; i < REPEAT; i++) { + int diff1; + int diff2; + + /* Generate two random strings of LENGTH characters */ + for(j = 0; j < LENGTH; j++) { + a[j] = letters[rand() % n]; + b[j] = letters[rand() % n]; + } + a[j] = '\0'; + b[j] = '\0'; + + /* Compare strings in both directions */ + diff1 = strverscmp(a, b); + diff2 = strverscmp(b, a); + + /* Must give identical result in both directions */ + assert((diff1 < 0 && diff2 > 0) + || (diff1 == 0 && diff2 == 0) + || (diff1 > 0 && diff2 < 0)); + } + } + + printf("OK\n"); + return EXIT_SUCCESS; +} diff --git a/dirent/tests/t-telldir.c b/dirent/tests/t-telldir.c new file mode 100644 index 0000000..e2a1763 --- /dev/null +++ b/dirent/tests/t-telldir.c @@ -0,0 +1,165 @@ +/* + * A test program to make sure that dirent works correctly. + * + * Copyright (C) 1998-2019 Toni Ronkko + * This file is part of dirent. Dirent may be freely distributed + * under the MIT license. For all details and documentation, see + * https://github.com/tronkko/dirent + */ + +/* Silence warning about strcmp being insecure (MS Visual Studio) */ +#define _CRT_SECURE_NO_WARNINGS + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <dirent.h> +#include <errno.h> + +#undef NDEBUG +#include <assert.h> + +static void test_telldir(void); + +int +main(int argc, char *argv[]) +{ + (void) argc; + (void) argv; + + test_telldir(); + + return EXIT_SUCCESS; +} + +static void +test_telldir(void) +{ + DIR *dir = opendir("tests/4"); + if (dir == NULL) { + fprintf(stderr, "Directory tests/4 not found\n"); + abort(); + } + + /* Get position of first file */ + long pos1 = telldir(dir); + printf("pos1: %lx\n", (long) pos1); + assert(pos1 >= 0); + + /* Read first file */ + struct dirent *ent = readdir(dir); + assert(ent != NULL); + struct dirent ent1 = *ent; + printf("ent1: %s %lx\n", ent->d_name, (long) ent->d_off); + + /* Seek back to the first position */ + seekdir(dir, pos1); + + /* Re-read the first entry */ + ent = readdir(dir); + assert(ent != NULL); + assert(strcmp(ent->d_name, ent1.d_name) == 0); + + /* Get position to second file */ + long pos2 = telldir(dir); + printf("pos2: %lx\n", (long) pos2); + assert(pos2 >= 0); + + /* Read second file */ + ent = readdir(dir); + assert(ent != NULL); + struct dirent ent2 = *ent; + printf("ent2: %s %lx\n", ent->d_name, (long) ent->d_off); + + /* Get position to third file */ + long pos3 = telldir(dir); + printf("pos3: %lx\n", (long) pos3); + assert(pos3 >= 0); + + /* Read third file */ + ent = readdir(dir); + assert(ent != NULL); + struct dirent ent3 = *ent; + printf("ent3: %s %lx\n", ent->d_name, (long) ent->d_off); + + /* Get position to fourth file */ + long pos4 = telldir(dir); + printf("pos4: %lx\n", (long) pos4); + assert(pos4 >= 0); + + /* Read fourth file */ + ent = readdir(dir); + assert(ent != NULL); + struct dirent ent4 = *ent; + printf("ent4: %s %lx\n", ent->d_name, (long) ent->d_off); + + /* Get position to fifth file */ + long pos5 = telldir(dir); + printf("pos5: %lx\n", (long) pos5); + assert(pos5 >= 0); + + /* Read fifth file */ + ent = readdir(dir); + assert(ent != NULL); + struct dirent ent5 = *ent; + printf("ent5: %s %lx\n", ent->d_name, (long) ent->d_off); + + /* Read position at the end of directory stream */ + long posx = telldir(dir); + assert(posx >= 0); + printf("posx: %lx\n", (long) posx); + + /* End of directory stream has been reached */ + ent = readdir(dir); + assert(ent == NULL); + + /* Seek back to position just before the end of stream */ + seekdir(dir, posx); + + /* Function telldir returns the same position when asked again */ + assert(telldir(dir) == posx); + assert(telldir(dir) == posx); + assert(telldir(dir) == posx); + + /* Read end of stream again */ + ent = readdir(dir); + assert(ent == NULL); + + /* Seek back to fifth file and read it again */ + seekdir(dir, pos5); + assert(telldir(dir) == pos5); + ent = readdir(dir); + assert(ent != NULL); + assert(strcmp(ent->d_name, ent5.d_name) == 0); + + /* Seek back to second file and read it again */ + seekdir(dir, pos2); + assert(telldir(dir) == pos2); + ent = readdir(dir); + assert(ent != NULL); + assert(strcmp(ent->d_name, ent2.d_name) == 0); + + /* Continue reading from the third file without a seek in between */ + assert(telldir(dir) == pos3); + ent = readdir(dir); + assert(ent != NULL); + assert(strcmp(ent->d_name, ent3.d_name) == 0); + + /* Read fourth position again */ + assert(telldir(dir) == pos4); + ent = readdir(dir); + assert(ent != NULL); + assert(strcmp(ent->d_name, ent4.d_name) == 0); + + /* Read fifth position again */ + assert(telldir(dir) == pos5); + ent = readdir(dir); + assert(ent != NULL); + assert(strcmp(ent->d_name, ent5.d_name) == 0); + + /* Read end of stream again */ + assert(telldir(dir) == posx); + ent = readdir(dir); + assert(ent == NULL); +} + diff --git a/dirent/tests/t-unicode.c b/dirent/tests/t-unicode.c new file mode 100644 index 0000000..8239120 --- /dev/null +++ b/dirent/tests/t-unicode.c @@ -0,0 +1,381 @@ +/* + * Test program to try unicode file names. + * + * Copyright (C) 1998-2019 Toni Ronkko + * This file is part of dirent. Dirent may be freely distributed + * under the MIT license. For all details and documentation, see + * https://github.com/tronkko/dirent + */ + +/* Silence warning about fopen being insecure */ +#define _CRT_SECURE_NO_WARNINGS + +#include <dirent.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <wchar.h> +#include <time.h> +#include <locale.h> + +#undef NDEBUG +#include <assert.h> + +int +main(int argc, char *argv[]) +{ +#ifdef WIN32 + wchar_t wpath[MAX_PATH+1]; + char path[MAX_PATH+1]; + DWORD i, j, k, x; + BOOL ok; + HANDLE fh; + _WDIR *wdir; + struct _wdirent *wentry; + DIR *dir; + struct dirent *entry; + char buffer[100]; + FILE *fp; + int counter = 0; + + /* Initialize random number generator */ + srand(((int) time(NULL)) * 257 + ((int) GetCurrentProcessId())); + + /* Set current locale */ + if (argc > 1) { + printf("Locale %s\n", argv[1]); + setlocale(LC_ALL, argv[1]); + } else { + setlocale(LC_ALL, ""); + } + + /****** CREATE FILE WITH UNICODE FILE NAME ******/ + + /* Get path to temporary directory (wide-character and ascii) */ + i = GetTempPathW(MAX_PATH, wpath); + assert(i > 0); + j = GetTempPathA(MAX_PATH, path); + assert(j > 0); + + /* Append random directory name */ + for (k = 0; k < 10; k++) { + /* Generate random character */ + char c = "abcdefghijklmnopqrstuvwxyz"[rand() % 26]; + + /* Append character to paths */ + assert(i < MAX_PATH && j < MAX_PATH); + wpath[i++] = c; + path[j++] = c; + } + + /* Terminate paths */ + assert(i < MAX_PATH && j < MAX_PATH); + wpath[i] = '\0'; + path[j] = '\0'; + + /* Remember the end of directory name */ + k = i; + + /* Create directory using unicode */ + ok = CreateDirectoryW(wpath, NULL); + if (!ok) { + DWORD e = GetLastError(); + wprintf(L"Cannot create directory %ls (code %u)\n", wpath, e); + abort(); + } + + /* Overwrite zero terminator with path separator */ + assert(i < MAX_PATH && j < MAX_PATH); + wpath[i++] = '\\'; + + /* Append a few unicode characters */ + assert(i < MAX_PATH); + wpath[i++] = 0x6d4b; + assert(i < MAX_PATH); + wpath[i++] = 0x8bd5; + + /* Terminate string */ + assert(i < MAX_PATH); + wpath[i] = '\0'; + + /* Create file with unicode */ + fh = CreateFileW( + wpath, + /* Access */ GENERIC_READ | GENERIC_WRITE, + /* Share mode */ 0, + /* Security attributes */ NULL, + /* Creation disposition */ CREATE_NEW, + /* Attributes */ FILE_ATTRIBUTE_NORMAL, + /* Template files */ NULL + ); + assert(fh != INVALID_HANDLE_VALUE); + + /* Write some data to file */ + ok = WriteFile( + /* File handle */ fh, + /* Pointer to data */ "hep\n", + /* Number of bytes to write */ 4, + /* Number of bytes written */ NULL, + /* Overlapped */ NULL + ); + assert(ok); + + /* Close file */ + ok = CloseHandle(fh); + assert(ok); + + /****** MAKE SURE THAT UNICODE FILE CAN BE READ BY _WREADDIR ******/ + + /* Zero terminate wide-character path and open directory stream */ + wpath[k] = '\0'; + wdir = _wopendir(wpath); + if (wdir == NULL) { + wprintf(L"Cannot open directory %ls\n", wpath); + abort(); + } + + /* Read through entries */ + counter = 0; + while ((wentry = _wreaddir(wdir)) != NULL) { + /* Skip pseudo directories */ + if (wcscmp(wentry->d_name, L".") == 0) { + continue; + } + if (wcscmp(wentry->d_name, L"..") == 0) { + continue; + } + + /* Found a file */ + counter++; + assert(wentry->d_type == DT_REG); + + /* Append file name to path */ + i = k; + assert(i < MAX_PATH); + wpath[i++] = '\\'; + x = 0; + while (wentry->d_name[x] != '\0') { + assert(i < MAX_PATH); + wpath[i++] = wentry->d_name[x++]; + } + assert(i < MAX_PATH); + wpath[i] = '\0'; + + /* Open file for read */ + fh = CreateFileW( + wpath, + /* Access */ GENERIC_READ, + /* Share mode */ 0, + /* Security attributes */ NULL, + /* Creation disposition */ OPEN_EXISTING, + /* Attributes */ FILE_ATTRIBUTE_NORMAL, + /* Template files */ NULL + ); + assert(fh != INVALID_HANDLE_VALUE); + + /* Read data from file */ + ok = ReadFile( + /* File handle */ fh, + /* Output buffer */ buffer, + /* Max number of bytes to read */ sizeof(buffer) - 1, + /* Number of bytes actually read */ &x, + /* Overlapped */ NULL + ); + assert(ok); + + /* Make sure that we got the file contents right */ + assert(x == 4); + assert(buffer[0] == 'h'); + assert(buffer[1] == 'e'); + assert(buffer[2] == 'p'); + assert(buffer[3] == '\n'); + + /* Close file */ + ok = CloseHandle(fh); + assert(ok); + } + assert(counter == 1); + + /* Close directory */ + _wclosedir(wdir); + + /****** MAKE SURE THAT UNICODE FILE NAME CAN BE READ BY READDIR *****/ + + /* Zero terminate ascii path and open directory stream */ + k = j; + path[k] = '\0'; + dir = opendir(path); + if (dir == NULL) { + fprintf(stderr, "Cannot open directory %s\n", path); + abort(); + } + + /* Read through entries */ + counter = 0; + while ((entry = readdir(dir)) != NULL) { + /* Skip pseudo directories */ + if (strcmp(entry->d_name, ".") == 0) { + continue; + } + if (strcmp(entry->d_name, "..") == 0) { + continue; + } + + /* Found a file */ + counter++; + assert(entry->d_type == DT_REG); + + /* Append file name to path */ + j = k; + assert(j < MAX_PATH); + path[j++] = '\\'; + x = 0; + while (entry->d_name[x] != '\0') { + assert(j < MAX_PATH); + path[j++] = entry->d_name[x++]; + } + assert(j < MAX_PATH); + path[j] = '\0'; + + /* Open file for read */ + fp = fopen(path, "r"); + if (!fp) { + fprintf(stderr, "Cannot open file %s\n", path); + abort(); + } + + /* Read data from file */ + if (fgets(buffer, sizeof(buffer), fp) == NULL) { + fprintf(stderr, "Cannot read file %s\n", path); + abort(); + } + + /* Make sure that we got the file contents right */ + assert(buffer[0] == 'h'); + assert(buffer[1] == 'e'); + assert(buffer[2] == 'p'); + assert(buffer[3] == '\n'); + assert(buffer[4] == '\0'); + + /* Close file */ + fclose(fp); + } + assert(counter == 1); + + /* Close directory */ + closedir(dir); + + /****** CREATE FILE WITH UTF-8 ******/ + + /* Append UTF-8 file name (åäö.txt) to path */ + j = k; + path[j++] = '\\'; + path[j++] = 0xc3; + path[j++] = 0xa5; + path[j++] = 0xc3; + path[j++] = 0xa4; + path[j++] = 0xc3; + path[j++] = 0xb6; + path[j++] = 0x2e; + path[j++] = 0x74; + path[j++] = 0x78; + path[j++] = 0x74; + assert(j < MAX_PATH); + path[j] = '\0'; + + /* + * Create file. + * + * Be ware that the code below creates a different file depending on + * the current locale! For example, if the current locale is + * english_us.65001, then the file name will be "åäö.txt" (7 + * characters). However, if the current locale is english_us.1252, + * then the file name will be "ÃċÃĊö.txt" (10 characters). + */ + printf("Creating %s\n", path); + fp = fopen(path, "w"); + if (!fp) { + fprintf(stderr, "Cannot open file %s\n", path); + abort(); + } + fputs("hep\n", fp); + fclose(fp); + + /* Open directory again */ + path[k] = '\0'; + dir = opendir(path); + if (dir == NULL) { + fprintf(stderr, "Cannot open directory %s\n", path); + abort(); + } + + /* Read through entries */ + counter = 0; + while ((entry = readdir(dir)) != NULL) { + /* Skip pseudo directories */ + if (strcmp(entry->d_name, ".") == 0) { + continue; + } + if (strcmp(entry->d_name, "..") == 0) { + continue; + } + + /* Found a file */ + counter++; + assert(entry->d_type == DT_REG); + + /* Append file name to path */ + j = k; + assert(j < MAX_PATH); + path[j++] = '\\'; + x = 0; + while (entry->d_name[x] != '\0') { + assert(j < MAX_PATH); + path[j++] = entry->d_name[x++]; + } + assert(j < MAX_PATH); + path[j] = '\0'; + + /* Print file name for debugging */ + printf("Opening \"%s\" hex ", path + k + 1); + x = 0; + while (entry->d_name[x] != '\0') { + printf("0x%02x ", + (unsigned) (entry->d_name[x++] & 0xff)); + } + printf("\n"); + + /* Open file for read */ + fp = fopen(path, "r"); + if (!fp) { + fprintf(stderr, "Cannot open file %s\n", path); + abort(); + } + + /* Read data from file */ + if (fgets(buffer, sizeof(buffer), fp) == NULL) { + fprintf(stderr, "Cannot read file %s\n", path); + abort(); + } + + /* Make sure that we got the file contents right */ + assert(buffer[0] == 'h'); + assert(buffer[1] == 'e'); + assert(buffer[2] == 'p'); + assert(buffer[3] == '\n'); + assert(buffer[4] == '\0'); + + /* Close file */ + fclose(fp); + } + assert(counter == 2); + + /* Close directory */ + closedir(dir); +#else + /* Linux */ + (void) argc; + (void) argv; +#endif + return EXIT_SUCCESS; +} diff --git a/dirent/tests/t-utf8.c b/dirent/tests/t-utf8.c new file mode 100644 index 0000000..fdc31fa --- /dev/null +++ b/dirent/tests/t-utf8.c @@ -0,0 +1,238 @@ +/* + * Test program to try UTF-8 file names. + * + * Copyright (C) 1998-2019 Toni Ronkko + * This file is part of dirent. Dirent may be freely distributed + * under the MIT license. For all details and documentation, see + * https://github.com/tronkko/dirent + */ + +/* Silence warning about fopen being insecure */ +#define _CRT_SECURE_NO_WARNINGS + +#include <dirent.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <wchar.h> +#include <time.h> +#include <locale.h> + +#undef NDEBUG +#include <assert.h> + +int +main(int argc, char *argv[]) +{ +#ifdef WIN32 + /* + * Select UTF-8 locale. This will change the way how C runtime + * functions such as fopen() and mkdir() handle character strings. + * For more information, please see: + * https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/setlocale-wsetlocale?view=msvc-160#utf-8-support + */ + setlocale(LC_ALL, "LC_CTYPE=.utf8"); + + /* Initialize random number generator */ + srand(((int) time(NULL)) * 257 + ((int) GetCurrentProcessId())); + + /* Get path to temporary directory */ + wchar_t wpath[MAX_PATH+1]; + DWORD i = GetTempPathW(MAX_PATH, wpath); + assert(i > 0); + + /* Ensure that path name ends in directory separator */ + assert(wpath[i - 1] == '\\'); + + /* Append random prefix */ + DWORD k; + for (k = 0; k < 8; k++) { + /* Generate random character */ + char c = "abcdefghijklmnopqrstuvwxyz"[rand() % 26]; + + /* Append character to path */ + assert(i < MAX_PATH); + wpath[i++] = c; + } + + /* Append a wide character to the path name */ + wpath[i++] = 0x00c4; + + /* Terminate the path name */ + assert(i < MAX_PATH); + wpath[i] = '\0'; + + /* Create directory with unicode name */ + BOOL ok = CreateDirectoryW(wpath, NULL); + if (!ok) { + DWORD e = GetLastError(); + wprintf(L"Cannot create directory %ls (code %u)\n", wpath, e); + abort(); + } + + /* Overwrite zero terminator with path separator */ + assert(i < MAX_PATH); + wpath[i++] = '\\'; + + /* Append a few unicode characters */ + assert(i < MAX_PATH); + wpath[i++] = 0x00f6; + assert(i < MAX_PATH); + wpath[i++] = 0x00e4; + + /* Terminate string */ + assert(i < MAX_PATH); + wpath[i] = '\0'; + + /* Create file with unicode name */ + HANDLE fh = CreateFileW( + wpath, + /* Access */ GENERIC_READ | GENERIC_WRITE, + /* Share mode */ 0, + /* Security attributes */ NULL, + /* Creation disposition */ CREATE_NEW, + /* Attributes */ FILE_ATTRIBUTE_NORMAL, + /* Template files */ NULL + ); + assert(fh != INVALID_HANDLE_VALUE); + + /* Write some data to file */ + ok = WriteFile( + /* File handle */ fh, + /* Pointer to data */ "hep\n", + /* Number of bytes to write */ 4, + /* Number of bytes written */ NULL, + /* Overlapped */ NULL + ); + assert(ok); + + /* Close file */ + ok = CloseHandle(fh); + assert(ok); + + /* Convert file name to UTF-8 */ + char path[MAX_PATH+1]; + int n = WideCharToMultiByte( + /* Code page to use in conversion */ CP_UTF8, + /* Flags */ 0, + /* Pointer to unicode string */ wpath, + /* Length of unicode string in characters */ i, + /* Pointer to output buffer */ path, + /* Size of output buffer in bytes */ MAX_PATH, + /* Pointer to default character */ NULL, + /* Pointer to boolean variable */ NULL + ); + assert(n > 0); + + /* Zero-terminate path */ + path[(size_t) n] = '\0'; + + /* Make sure that fopen() can open the file with UTF-8 file name */ + FILE *fp = fopen(path, "r"); + if (!fp) { + fprintf(stderr, "Cannot open file %s\n", path); + abort(); + } + + /* Read data from file */ + char buffer[100]; + if (fgets(buffer, sizeof(buffer), fp) == NULL) { + fprintf(stderr, "Cannot read file %s\n", path); + abort(); + } + + /* Make sure that we got the file contents right */ + assert(buffer[0] == 'h'); + assert(buffer[1] == 'e'); + assert(buffer[2] == 'p'); + assert(buffer[3] == '\n'); + assert(buffer[4] == '\0'); + + /* Close file */ + fclose(fp); + + /* Truncate path name to the last directory separator */ + i = 0; + k = 0; + while (path[k] != '\0') { + if (path[k] == '\\' || path[k] == '/') { + i = k; + } + k++; + } + path[i] = '\0'; + + /* Ensure that opendir() can open the directory with UTF-8 name */ + DIR *dir = opendir(path); + if (dir == NULL) { + fprintf(stderr, "Cannot open directory %s\n", path); + abort(); + } + + /* Read entries */ + int counter = 0; + struct dirent *entry; + while ((entry = readdir(dir)) != NULL) { + /* Skip pseudo directories */ + if (strcmp(entry->d_name, ".") == 0) { + continue; + } + if (strcmp(entry->d_name, "..") == 0) { + continue; + } + + /* Found a file */ + counter++; + assert(entry->d_type == DT_REG); + + /* Append file name to path */ + k = i; + assert(i < MAX_PATH); + path[i++] = '\\'; + DWORD x = 0; + while (entry->d_name[x] != '\0') { + assert(i < MAX_PATH); + path[i++] = entry->d_name[x++]; + } + assert(i < MAX_PATH); + path[i] = '\0'; + + /* Reset buffer */ + for (x = 0; x < sizeof(buffer); x++) { + buffer[x] = '\0'; + } + + /* Open file for read */ + fp = fopen(path, "r"); + if (!fp) { + fprintf(stderr, "Cannot open file %s\n", path); + abort(); + } + + /* Read data from file */ + if (fgets(buffer, sizeof(buffer), fp) == NULL) { + fprintf(stderr, "Cannot read file %s\n", path); + abort(); + } + + /* Make sure that we got the file contents right */ + assert(buffer[0] == 'h'); + assert(buffer[1] == 'e'); + assert(buffer[2] == 'p'); + assert(buffer[3] == '\n'); + assert(buffer[4] == '\0'); + + /* Close file */ + fclose(fp); + } + assert(counter == 1); + + /* Close directory */ + closedir(dir); +#else + /* Linux */ + (void) argc; + (void) argv; +#endif + return EXIT_SUCCESS; +} |