From 0f92f7352d1964a9859868439e8ded2c4de2111e Mon Sep 17 00:00:00 2001 From: Rory Bradford Date: Sat, 19 Nov 2022 11:11:26 +0000 Subject: Full native win32 support This will now compile with MSVC using the make.bat batch file. It does however bring in some additional third party dependencies: ports of dirent and getopt (something I'd ideally like to work on in the future). Signed-off-by: Rory Bradford --- .gitignore | 6 +- dirent/.gitignore | 27 + dirent/CMakeLists.txt | 64 ++ dirent/CONTRIBUTING.md | 41 ++ dirent/ChangeLog | 129 ++++ dirent/LICENSE | 21 + dirent/README.md | 176 +++++ dirent/distclean.cmake | 62 ++ dirent/examples/cat.c | 127 ++++ dirent/examples/dir.c | 234 +++++++ dirent/examples/du.c | 199 ++++++ dirent/examples/find.c | 184 ++++++ dirent/examples/locate.c | 262 ++++++++ dirent/examples/ls.c | 148 +++++ dirent/examples/scandir.c | 152 +++++ dirent/examples/updatedb.c | 206 ++++++ dirent/include/dirent.h | 1212 +++++++++++++++++++++++++++++++++++ dirent/tests/1/dir/readme.txt | 3 + dirent/tests/1/file | 0 dirent/tests/2/Testfile-1.2.3.dat | 0 dirent/tests/2/file.txt | 1 + dirent/tests/3/3zero.dat | 0 dirent/tests/3/666.dat | 0 dirent/tests/3/Qwerty-my-aunt.dat | 0 dirent/tests/3/README.txt | 2 + dirent/tests/3/aaa.dat | 0 dirent/tests/3/dirent.dat | 0 dirent/tests/3/empty.dat | 0 dirent/tests/3/sane-1.12.0.dat | 0 dirent/tests/3/sane-1.2.30.dat | 0 dirent/tests/3/sane-1.2.4.dat | 0 dirent/tests/3/zebra.dat | 0 dirent/tests/4/asidofilusphosphorus | 0 dirent/tests/4/bubblebob | 0 dirent/tests/4/celsiusfortissimo | 0 dirent/tests/t-compile.c | 46 ++ dirent/tests/t-cplusplus.cpp | 155 +++++ dirent/tests/t-dirent.c | 608 ++++++++++++++++++ dirent/tests/t-scandir.c | 276 ++++++++ dirent/tests/t-strverscmp.c | 206 ++++++ dirent/tests/t-telldir.c | 165 +++++ dirent/tests/t-unicode.c | 381 +++++++++++ dirent/tests/t-utf8.c | 238 +++++++ getopt/.gitignore | 4 + getopt/LICENSE | 165 +++++ getopt/README.md | 9 + getopt/appveyor.yml | 5 + getopt/article.md | 224 +++++++ getopt/getopt.c | 973 ++++++++++++++++++++++++++++ getopt/getopt.h | 136 ++++ getopt/getopt.sln | 31 + getopt/getopt.vcxproj | 193 ++++++ ignore.c | 5 +- ignore.h | 4 + make.bat | 8 + rf.c | 44 +- rf.h | 1 - 57 files changed, 7107 insertions(+), 26 deletions(-) create mode 100644 dirent/.gitignore create mode 100644 dirent/CMakeLists.txt create mode 100644 dirent/CONTRIBUTING.md create mode 100644 dirent/ChangeLog create mode 100644 dirent/LICENSE create mode 100644 dirent/README.md create mode 100644 dirent/distclean.cmake create mode 100644 dirent/examples/cat.c create mode 100644 dirent/examples/dir.c create mode 100644 dirent/examples/du.c create mode 100644 dirent/examples/find.c create mode 100644 dirent/examples/locate.c create mode 100644 dirent/examples/ls.c create mode 100644 dirent/examples/scandir.c create mode 100644 dirent/examples/updatedb.c create mode 100644 dirent/include/dirent.h create mode 100644 dirent/tests/1/dir/readme.txt create mode 100644 dirent/tests/1/file create mode 100644 dirent/tests/2/Testfile-1.2.3.dat create mode 100644 dirent/tests/2/file.txt create mode 100644 dirent/tests/3/3zero.dat create mode 100644 dirent/tests/3/666.dat create mode 100644 dirent/tests/3/Qwerty-my-aunt.dat create mode 100644 dirent/tests/3/README.txt create mode 100644 dirent/tests/3/aaa.dat create mode 100644 dirent/tests/3/dirent.dat create mode 100644 dirent/tests/3/empty.dat create mode 100644 dirent/tests/3/sane-1.12.0.dat create mode 100644 dirent/tests/3/sane-1.2.30.dat create mode 100644 dirent/tests/3/sane-1.2.4.dat create mode 100644 dirent/tests/3/zebra.dat create mode 100644 dirent/tests/4/asidofilusphosphorus create mode 100644 dirent/tests/4/bubblebob create mode 100644 dirent/tests/4/celsiusfortissimo create mode 100644 dirent/tests/t-compile.c create mode 100644 dirent/tests/t-cplusplus.cpp create mode 100644 dirent/tests/t-dirent.c create mode 100644 dirent/tests/t-scandir.c create mode 100644 dirent/tests/t-strverscmp.c create mode 100644 dirent/tests/t-telldir.c create mode 100644 dirent/tests/t-unicode.c create mode 100644 dirent/tests/t-utf8.c create mode 100644 getopt/.gitignore create mode 100644 getopt/LICENSE create mode 100644 getopt/README.md create mode 100644 getopt/appveyor.yml create mode 100644 getopt/article.md create mode 100644 getopt/getopt.c create mode 100644 getopt/getopt.h create mode 100644 getopt/getopt.sln create mode 100644 getopt/getopt.vcxproj create mode 100644 make.bat delete mode 100644 rf.h diff --git a/.gitignore b/.gitignore index 9651efe..2029cf6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ /rf *.o -*.1 -*.5 \ No newline at end of file +*.exe +*.obj +*.lib +*.exp \ No newline at end of file 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 $) + 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 +#include +#include +#include +#include +#include + +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 . + * 2021-07-20 13:42 .. + * 2020-06-21 15:00 402 desktop.ini + * 2020-06-21 15:00 Omat kuvatiedostot + * 2020-06-21 15:00 Omat musiikkitiedostot + * 2020-06-21 15:00 Omat videotiedostot + * 2018-12-21 18:34 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 +#include +#include +#include +#include +#include +#include +#include + +#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 = ""; + } else if (S_ISREG(stbuf.st_mode)) { + /* Regular file */ + type = ""; + } else if (S_ISLNK(stbuf.st_mode)) { + /* Link */ + type = ""; + } else { + /* Named pipe, socket, character device or else */ + type = ""; + } + + /* 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 +#include +#include +#include +#include +#include +#include + +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 +#include +#include +#include +#include +#include + +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 +#include +#include +#include +#ifdef WIN32 +# include +# include +#endif +#include + +/* 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 +#include +#include +#include +#include +#include + +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 +#include +#include +#include +#include +#include + +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 +#include +#include +#include +#ifdef WIN32 +# include +# include +#endif +#include + +/* 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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 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 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 diff --git a/dirent/tests/3/666.dat b/dirent/tests/3/666.dat new file mode 100644 index 0000000..e69de29 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 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 diff --git a/dirent/tests/3/dirent.dat b/dirent/tests/3/dirent.dat new file mode 100644 index 0000000..e69de29 diff --git a/dirent/tests/3/empty.dat b/dirent/tests/3/empty.dat new file mode 100644 index 0000000..e69de29 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 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 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 diff --git a/dirent/tests/3/zebra.dat b/dirent/tests/3/zebra.dat new file mode 100644 index 0000000..e69de29 diff --git a/dirent/tests/4/asidofilusphosphorus b/dirent/tests/4/asidofilusphosphorus new file mode 100644 index 0000000..e69de29 diff --git a/dirent/tests/4/bubblebob b/dirent/tests/4/bubblebob new file mode 100644 index 0000000..e69de29 diff --git a/dirent/tests/4/celsiusfortissimo b/dirent/tests/4/celsiusfortissimo new file mode 100644 index 0000000..e69de29 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 +#ifdef WIN32 +# include +# include +#endif +#include +#include +#include + +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 +#include +#include +#include +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 +#include +#include +#ifdef _MSC_VER +# include +# define chdir(x) _chdir(x) +#else +# include +#endif +#include +#include +#include + +#undef NDEBUG +#include + +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 +#include +#include +#include +#include +#include +#include +#include + +#undef NDEBUG +#include + +/* 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 +#include +#include +#include +#include +#include + +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 +#include +#include +#include +#include + +#undef NDEBUG +#include + +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 +#include +#include +#include +#include +#include +#include + +#undef NDEBUG +#include + +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 +#include +#include +#include +#include +#include +#include + +#undef NDEBUG +#include + +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; +} diff --git a/getopt/.gitignore b/getopt/.gitignore new file mode 100644 index 0000000..975acee --- /dev/null +++ b/getopt/.gitignore @@ -0,0 +1,4 @@ +Win32/ +x64/ +.vs/ +*.user \ No newline at end of file diff --git a/getopt/LICENSE b/getopt/LICENSE new file mode 100644 index 0000000..153d416 --- /dev/null +++ b/getopt/LICENSE @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. \ No newline at end of file diff --git a/getopt/README.md b/getopt/README.md new file mode 100644 index 0000000..d294ae4 --- /dev/null +++ b/getopt/README.md @@ -0,0 +1,9 @@ +getopt port for Visual C++ +========================== + +[![Build status](https://ci.appveyor.com/api/projects/status/reb7dt6x7jdn1700/branch/master?svg=true)](https://ci.appveyor.com/project/qmfrederik/getopt/branch/master) + +This repository contains a port of getopt which can be used with Visual C++. It is intended to be used with vcpkg. + +The Visual C++ port was originally done by Ludvik Jerabek, and described in the [Full getopt Port for Unicode and Multibyte Microsoft Visual C, C++, or MFC Projects](https://www.codeproject.com/Articles/157001/Full-getopt-Port-for-Unicode-and-Multibyte-Microso/) +article. This repository contains a copy of that code and the article. \ No newline at end of file diff --git a/getopt/appveyor.yml b/getopt/appveyor.yml new file mode 100644 index 0000000..868a0b0 --- /dev/null +++ b/getopt/appveyor.yml @@ -0,0 +1,5 @@ +build_script: + - msbuild getopt.sln /p:Configuration=Debug /p:Platform=Win32 /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" + - msbuild getopt.sln /p:Configuration=Debug /p:Platform=x64 /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" + - msbuild getopt.sln /p:Configuration=Release /p:Platform=Win32 /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" + - msbuild getopt.sln /p:Configuration=Release /p:Platform=x64 /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" diff --git a/getopt/article.md b/getopt/article.md new file mode 100644 index 0000000..68c2c0d --- /dev/null +++ b/getopt/article.md @@ -0,0 +1,224 @@ +Full getopt Port for Unicode and Multibyte Microsoft Visual C, C++, or MFC Projects +=================================================================================== + +Ludvik Jerabek, 15 Oct 2012 + +Originally published at https://www.codeproject.com/Articles/157001/Full-getopt-Port-for-Unicode-and-Multibyte-Microso/ + + +Introduction +------------ + +This software was written after hours of searching for a robust Microsoft C and C++ implementation of getopt, +which led to devoid results. + +This software is a modification of the Free Software Foundation, Inc. getopt library for parsing command line arguments +and its purpose is to provide a Microsoft Visual C friendly derivative. The source code provides functionality for both +Unicode and Multibyte builds and supports `getopt`, `getopt_long`, and `getopt_long_only`. Additionally the code supports the +`POSIXLY_CORRECT` environment flag. The library uses the `_UNICODE` preprocessor directive when defining the `getopt`, `getopt_long`, +and `getopt_long_only` functions. These functions are mapped back to `getopt_a`\`getopt_w`, `getopt_long_a`\`getopt_long_w`, and +`getopt_long_only_a`\`getopt_long_only_w` respectively. This improvement was made to allow a single DLL to be used in both +multibyte and Unicode projects. + +The original GNU code used several header and implementation files containing numerous preprocessor directives specific +to Linux environments which have been removed. After removing unneeded dependencies, it was condensed into a single +header and implementation file which can be compiled into a DLL, LIB, or directly included into any Visual C, C++, or MFC +project. The getopt library can be used by proprietary software, however; certain measures need to be taken to ensure +proprietary code adheres to the Lesser GNU Public License for non-derivative works. Please refer to the licensing section +of this article for more details on how this software is licensed. + +For the sake of brevity, this article doesn't discuss how to use the `getopt` functions. Anyone new to using the `getopt` +functions should refer to the GNU tutorial for using getopt. + +Licensing +--------- + +Since `getopt` is licensed under LGPL, it is free to use in proprietary software under some restrictions. +When using this library as part of a proprietary software solution, it is important that the library is used as +a dynamically linked library (DLL) and is not statically linked or directly compiled into proprietary source code. +Static linkage requires that your software be released GPL. Therefore, by keeping the library separately referenced +via Dynamic Link Library (DLL), allows the DLL to be modified and updated without changing the proprietary software +which utilizes the library; under this condition proprietary software is said to "use" the library. Thus, it is not +considered to be a derivative work and can be distributed freely under any license. + +Preprocessor Definitions +------------------------ + +Compiling `getopt` as a Dynamic Link Library (DLL) requires the preprocessor definition of `EXPORTS_GETOPT`. +The definition of `EXPORTS_GETOPT` sets the internal preprocessor definition `_GETOPT_API` to the value `__declspec(dllexport)`. +Compiling getopt as a Static Library (LIB) or directly including the source and header file within a project requires the +preprocessor definition of `STATIC_GETOPT`. The definition of `STATIC_GETOPT` clears the value of the internal preprocessor definition +of `_GETOPT_API`. Compiling software to use _getopt.dll_ requires that no library specific preprocessor definitions be used. +When no library specific preprocessor definitions are used the value assigned to the internal preprocessor definition `_GETOPT_API` is `__declspec(dllimport)`. + +The code segment below demonstrates the logic outlined above: + +```c +#if defined(EXPORTS_GETOPT) && defined(STATIC_GETOPT) + #error "The preprocessor definitions of EXPORTS_GETOPT + and STATIC_GETOPT can only be used individually" +#elif defined(STATIC_GETOPT) +#pragma message("Warning static builds of getopt violate the Lesser GNU Public License") + #define _GETOPT_API +#elif defined(EXPORTS_GETOPT) + #pragma message("Exporting getopt library") + #define _GETOPT_API __declspec(dllexport) +#else + #pragma message("Importing getopt library") + #define _GETOPT_API __declspec(dllimport) +#endif +``` + +The following code segment located in _getopt.h_ is responsible for mapping the correct version of the `getopt`, `getopt_long`, +and `getopt_long_only` functions. The `getopt` functions appended with `_a` to denote ANSI characters using the char type and +Unicode functions are appended with `_w` to denote wide characters using the `wchar_t` type. + +```c +#ifdef _UNICODE + #define getopt getopt_w + #define getopt_long getopt_long_w + #define getopt_long_only getopt_long_only_w + #define option option_w + #define optarg optarg_w +#else + #define getopt getopt_a + #define getopt_long getopt_long_a + #define getopt_long_only getopt_long_only_a + #define option option_a + #define optarg optarg_a +#endif +``` + +Using the Code +--------------- + +The code is used identical to GNU `getopt`. + +```c +#include +#include +#include "tchar.h" +#include "getopt.h" + +int _tmain(int argc, TCHAR** argv) +{ + static int verbose_flag; + int c; + + while (1) + { + static struct option long_options[] = + { + {_T("verbose"), ARG_NONE, &verbose_flag, 1}, + {_T("brief"), ARG_NONE, &verbose_flag, 0}, + {_T("add"), ARG_NONE, 0, _T('a')}, + {_T("append"), ARG_NONE, 0, _T('b')}, + {_T("delete"), ARG_REQ, 0, _T('d')}, + {_T("create"), ARG_REQ, 0, _T('c')}, + {_T("file"), ARG_REQ, 0 , _T('f')}, + { ARG_NULL , ARG_NULL , ARG_NULL , ARG_NULL } + }; + + int option_index = 0; + c = getopt_long(argc, argv, _T("abc:d:f:"), long_options, &option_index); + + // Check for end of operation or error + if (c == -1) + break; + + // Handle options + switch (c) + { + case 0: + /* If this option set a flag, do nothing else now. */ + if (long_options[option_index].flag != 0) + break; + _tprintf (_T("option %s"), long_options[option_index].name); + if (optarg) + _tprintf (_T(" with arg %s"), optarg); + _tprintf (_T("\n")); + break; + + case _T('a'): + _tprintf(_T("option -a\n")); + break; + + case _T('b'): + _tprintf(_T("option -b\n")); + break; + + case _T('c'): + _tprintf (_T("option -c with value `%s'\n"), optarg); + break; + + case _T('d'): + _tprintf (_T("option -d with value `%s'\n"), optarg); + break; + + case _T('f'): + _tprintf (_T("option -f with value `%s'\n"), optarg); + break; + + case '?': + /* getopt_long already printed an error message. */ + break; + + default: + abort(); + } + } + + if (verbose_flag) + _tprintf (_T("verbose flag is set\n")); + + + if (optind < argc) + { + _tprintf (_T("non-option ARGV-elements: ")); + while (optind < argc) _tprintf (_T("%s "), argv[optind++]); + _tprintf (_T("\n")); + } + return 0; +} +``` + +Using this Code with C++ Precompiled Headers +-------------------------------------------- + +When using this code statically within a C++ project with precompiled headers, it is necessary to rename `getopt.c` +to `getopt.cpp` in order to circumvent the following compiler error: + +``` +"C1853 - Precompiled header file is from a previous version of the compiler, +or the precompiled header is C++ and you are using it from C (or vice versa)." +``` + +Additionally precompiled header file must be added as the first include of the `getopt.c` or `getopt.cpp` file. +For example, if you are using _stdafx.h_ as the precompiled header, the following would be expected: + +``` +// File comments removed +#include "stdafx.h" +#define _CRT_SECURE_NO_WARNINGS +#include +#include +#include "getopt.h" +``` + +History +------- + +- 02/03/2011 - Initial release +- 02/20/2011 - Fixed L4 compiler warnings +- 07/05/2011 - Added no_argument, required_argument, optional_argument def +- 08/05/2011 - Fixed non-argument runtime bug which caused runtime exception +- 08/09/2011 - Added code to export functions for DLL and LIB +- 02/15/2012 - Fixed _GETOPT_THROW definition missing in implementation file +- 08/03/2012 - Created separate functions for char and wchar_t characters so single DLL can do both Unicode and ANSI +- 10/15/2012 - Modified to match latest GNU features +- 06/19/2015 - Fixed maximum option limitation caused by option_a (255) and option_w (65535) structure val variable + +License +------- + +This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3) \ No newline at end of file diff --git a/getopt/getopt.c b/getopt/getopt.c new file mode 100644 index 0000000..fc7b1a0 --- /dev/null +++ b/getopt/getopt.c @@ -0,0 +1,973 @@ +/* Getopt for Microsoft C +This code is a modification of the Free Software Foundation, Inc. +Getopt library for parsing command line argument the purpose was +to provide a Microsoft Visual C friendly derivative. This code +provides functionality for both Unicode and Multibyte builds. + +Date: 02/03/2011 - Ludvik Jerabek - Initial Release +Version: 1.0 +Comment: Supports getopt, getopt_long, and getopt_long_only +and POSIXLY_CORRECT environment flag +License: LGPL + +Revisions: + +02/03/2011 - Ludvik Jerabek - Initial Release +02/20/2011 - Ludvik Jerabek - Fixed compiler warnings at Level 4 +07/05/2011 - Ludvik Jerabek - Added no_argument, required_argument, optional_argument defs +08/03/2011 - Ludvik Jerabek - Fixed non-argument runtime bug which caused runtime exception +08/09/2011 - Ludvik Jerabek - Added code to export functions for DLL and LIB +02/15/2012 - Ludvik Jerabek - Fixed _GETOPT_THROW definition missing in implementation file +08/01/2012 - Ludvik Jerabek - Created separate functions for char and wchar_t characters so single dll can do both unicode and ansi +10/15/2012 - Ludvik Jerabek - Modified to match latest GNU features +06/19/2015 - Ludvik Jerabek - Fixed maximum option limitation caused by option_a (255) and option_w (65535) structure val variable + +**DISCLAIMER** +THIS MATERIAL IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +EITHER EXPRESS OR IMPLIED, INCLUDING, BUT Not LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE, OR NON-INFRINGEMENT. SOME JURISDICTIONS DO NOT ALLOW THE +EXCLUSION OF IMPLIED WARRANTIES, SO THE ABOVE EXCLUSION MAY NOT +APPLY TO YOU. IN NO EVENT WILL I BE LIABLE TO ANY PARTY FOR ANY +DIRECT, INDIRECT, SPECIAL OR OTHER CONSEQUENTIAL DAMAGES FOR ANY +USE OF THIS MATERIAL INCLUDING, WITHOUT LIMITATION, ANY LOST +PROFITS, BUSINESS INTERRUPTION, LOSS OF PROGRAMS OR OTHER DATA ON +YOUR INFORMATION HANDLING SYSTEM OR OTHERWISE, EVEN If WE ARE +EXPRESSLY ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +*/ +#define _CRT_SECURE_NO_WARNINGS +#include +#include +#include +#include "getopt.h" + +#ifdef __cplusplus + #define _GETOPT_THROW throw() +#else + #define _GETOPT_THROW +#endif + +int optind = 1; +int opterr = 1; +int optopt = '?'; +enum ENUM_ORDERING { REQUIRE_ORDER, PERMUTE, RETURN_IN_ORDER }; + +// +// +// Ansi structures and functions follow +// +// + +static struct _getopt_data_a +{ + int optind; + int opterr; + int optopt; + char *optarg; + int __initialized; + char *__nextchar; + enum ENUM_ORDERING __ordering; + int __posixly_correct; + int __first_nonopt; + int __last_nonopt; +} getopt_data_a; +char *optarg_a; + +static void exchange_a(char **argv, struct _getopt_data_a *d) +{ + int bottom = d->__first_nonopt; + int middle = d->__last_nonopt; + int top = d->optind; + char *tem; + while (top > middle && middle > bottom) + { + if (top - middle > middle - bottom) + { + int len = middle - bottom; + register int i; + for (i = 0; i < len; i++) + { + tem = argv[bottom + i]; + argv[bottom + i] = argv[top - (middle - bottom) + i]; + argv[top - (middle - bottom) + i] = tem; + } + top -= len; + } + else + { + int len = top - middle; + register int i; + for (i = 0; i < len; i++) + { + tem = argv[bottom + i]; + argv[bottom + i] = argv[middle + i]; + argv[middle + i] = tem; + } + bottom += len; + } + } + d->__first_nonopt += (d->optind - d->__last_nonopt); + d->__last_nonopt = d->optind; +} +static const char *_getopt_initialize_a (const char *optstring, struct _getopt_data_a *d, int posixly_correct) +{ + d->__first_nonopt = d->__last_nonopt = d->optind; + d->__nextchar = NULL; + d->__posixly_correct = posixly_correct | !!getenv("POSIXLY_CORRECT"); + if (optstring[0] == '-') + { + d->__ordering = RETURN_IN_ORDER; + ++optstring; + } + else if (optstring[0] == '+') + { + d->__ordering = REQUIRE_ORDER; + ++optstring; + } + else if (d->__posixly_correct) + d->__ordering = REQUIRE_ORDER; + else + d->__ordering = PERMUTE; + return optstring; +} +int _getopt_internal_r_a (int argc, char *const *argv, const char *optstring, const struct option_a *longopts, int *longind, int long_only, struct _getopt_data_a *d, int posixly_correct) +{ + int print_errors = d->opterr; + if (argc < 1) + return -1; + d->optarg = NULL; + if (d->optind == 0 || !d->__initialized) + { + if (d->optind == 0) + d->optind = 1; + optstring = _getopt_initialize_a (optstring, d, posixly_correct); + d->__initialized = 1; + } + else if (optstring[0] == '-' || optstring[0] == '+') + optstring++; + if (optstring[0] == ':') + print_errors = 0; + if (d->__nextchar == NULL || *d->__nextchar == '\0') + { + if (d->__last_nonopt > d->optind) + d->__last_nonopt = d->optind; + if (d->__first_nonopt > d->optind) + d->__first_nonopt = d->optind; + if (d->__ordering == PERMUTE) + { + if (d->__first_nonopt != d->__last_nonopt && d->__last_nonopt != d->optind) + exchange_a ((char **) argv, d); + else if (d->__last_nonopt != d->optind) + d->__first_nonopt = d->optind; + while (d->optind < argc && (argv[d->optind][0] != '-' || argv[d->optind][1] == '\0')) + d->optind++; + d->__last_nonopt = d->optind; + } + if (d->optind != argc && !strcmp(argv[d->optind], "--")) + { + d->optind++; + if (d->__first_nonopt != d->__last_nonopt && d->__last_nonopt != d->optind) + exchange_a((char **) argv, d); + else if (d->__first_nonopt == d->__last_nonopt) + d->__first_nonopt = d->optind; + d->__last_nonopt = argc; + d->optind = argc; + } + if (d->optind == argc) + { + if (d->__first_nonopt != d->__last_nonopt) + d->optind = d->__first_nonopt; + return -1; + } + if ((argv[d->optind][0] != '-' || argv[d->optind][1] == '\0')) + { + if (d->__ordering == REQUIRE_ORDER) + return -1; + d->optarg = argv[d->optind++]; + return 1; + } + d->__nextchar = (argv[d->optind] + 1 + (longopts != NULL && argv[d->optind][1] == '-')); + } + if (longopts != NULL && (argv[d->optind][1] == '-' || (long_only && (argv[d->optind][2] || !strchr(optstring, argv[d->optind][1]))))) + { + char *nameend; + unsigned int namelen; + const struct option_a *p; + const struct option_a *pfound = NULL; + struct option_list + { + const struct option_a *p; + struct option_list *next; + } *ambig_list = NULL; + int exact = 0; + int indfound = -1; + int option_index; + for (nameend = d->__nextchar; *nameend && *nameend != '='; nameend++); + namelen = (unsigned int)(nameend - d->__nextchar); + for (p = longopts, option_index = 0; p->name; p++, option_index++) + if (!strncmp(p->name, d->__nextchar, namelen)) + { + if (namelen == (unsigned int)strlen(p->name)) + { + pfound = p; + indfound = option_index; + exact = 1; + break; + } + else if (pfound == NULL) + { + pfound = p; + indfound = option_index; + } + else if (long_only || pfound->has_arg != p->has_arg || pfound->flag != p->flag || pfound->val != p->val) + { + struct option_list *newp = (struct option_list*)alloca(sizeof(*newp)); + newp->p = p; + newp->next = ambig_list; + ambig_list = newp; + } + } + if (ambig_list != NULL && !exact) + { + if (print_errors) + { + struct option_list first; + first.p = pfound; + first.next = ambig_list; + ambig_list = &first; + fprintf (stderr, "%s: option '%s' is ambiguous; possibilities:", argv[0], argv[d->optind]); + do + { + fprintf (stderr, " '--%s'", ambig_list->p->name); + ambig_list = ambig_list->next; + } + while (ambig_list != NULL); + fputc ('\n', stderr); + } + d->__nextchar += strlen(d->__nextchar); + d->optind++; + d->optopt = 0; + return '?'; + } + if (pfound != NULL) + { + option_index = indfound; + d->optind++; + if (*nameend) + { + if (pfound->has_arg) + d->optarg = nameend + 1; + else + { + if (print_errors) + { + if (argv[d->optind - 1][1] == '-') + { + fprintf(stderr, "%s: option '--%s' doesn't allow an argument\n",argv[0], pfound->name); + } + else + { + fprintf(stderr, "%s: option '%c%s' doesn't allow an argument\n",argv[0], argv[d->optind - 1][0],pfound->name); + } + } + d->__nextchar += strlen(d->__nextchar); + d->optopt = pfound->val; + return '?'; + } + } + else if (pfound->has_arg == 1) + { + if (d->optind < argc) + d->optarg = argv[d->optind++]; + else + { + if (print_errors) + { + fprintf(stderr,"%s: option '--%s' requires an argument\n",argv[0], pfound->name); + } + d->__nextchar += strlen(d->__nextchar); + d->optopt = pfound->val; + return optstring[0] == ':' ? ':' : '?'; + } + } + d->__nextchar += strlen(d->__nextchar); + if (longind != NULL) + *longind = option_index; + if (pfound->flag) + { + *(pfound->flag) = pfound->val; + return 0; + } + return pfound->val; + } + if (!long_only || argv[d->optind][1] == '-' || strchr(optstring, *d->__nextchar) == NULL) + { + if (print_errors) + { + if (argv[d->optind][1] == '-') + { + fprintf(stderr, "%s: unrecognized option '--%s'\n",argv[0], d->__nextchar); + } + else + { + fprintf(stderr, "%s: unrecognized option '%c%s'\n",argv[0], argv[d->optind][0], d->__nextchar); + } + } + d->__nextchar = (char *)""; + d->optind++; + d->optopt = 0; + return '?'; + } + } + { + char c = *d->__nextchar++; + char *temp = (char*)strchr(optstring, c); + if (*d->__nextchar == '\0') + ++d->optind; + if (temp == NULL || c == ':' || c == ';') + { + if (print_errors) + { + fprintf(stderr, "%s: invalid option -- '%c'\n", argv[0], c); + } + d->optopt = c; + return '?'; + } + if (temp[0] == 'W' && temp[1] == ';') + { + char *nameend; + const struct option_a *p; + const struct option_a *pfound = NULL; + int exact = 0; + int ambig = 0; + int indfound = 0; + int option_index; + if (longopts == NULL) + goto no_longs; + if (*d->__nextchar != '\0') + { + d->optarg = d->__nextchar; + d->optind++; + } + else if (d->optind == argc) + { + if (print_errors) + { + fprintf(stderr,"%s: option requires an argument -- '%c'\n",argv[0], c); + } + d->optopt = c; + if (optstring[0] == ':') + c = ':'; + else + c = '?'; + return c; + } + else + d->optarg = argv[d->optind++]; + for (d->__nextchar = nameend = d->optarg; *nameend && *nameend != '='; nameend++); + for (p = longopts, option_index = 0; p->name; p++, option_index++) + if (!strncmp(p->name, d->__nextchar, nameend - d->__nextchar)) + { + if ((unsigned int) (nameend - d->__nextchar) == strlen(p->name)) + { + pfound = p; + indfound = option_index; + exact = 1; + break; + } + else if (pfound == NULL) + { + pfound = p; + indfound = option_index; + } + else if (long_only || pfound->has_arg != p->has_arg || pfound->flag != p->flag || pfound->val != p->val) + ambig = 1; + } + if (ambig && !exact) + { + if (print_errors) + { + fprintf(stderr, "%s: option '-W %s' is ambiguous\n",argv[0], d->optarg); + } + d->__nextchar += strlen(d->__nextchar); + d->optind++; + return '?'; + } + if (pfound != NULL) + { + option_index = indfound; + if (*nameend) + { + if (pfound->has_arg) + d->optarg = nameend + 1; + else + { + if (print_errors) + { + fprintf(stderr, "%s: option '-W %s' doesn't allow an argument\n",argv[0], pfound->name); + } + d->__nextchar += strlen(d->__nextchar); + return '?'; + } + } + else if (pfound->has_arg == 1) + { + if (d->optind < argc) + d->optarg = argv[d->optind++]; + else + { + if (print_errors) + { + fprintf(stderr, "%s: option '-W %s' requires an argument\n",argv[0], pfound->name); + } + d->__nextchar += strlen(d->__nextchar); + return optstring[0] == ':' ? ':' : '?'; + } + } + else + d->optarg = NULL; + d->__nextchar += strlen(d->__nextchar); + if (longind != NULL) + *longind = option_index; + if (pfound->flag) + { + *(pfound->flag) = pfound->val; + return 0; + } + return pfound->val; + } +no_longs: + d->__nextchar = NULL; + return 'W'; + } + if (temp[1] == ':') + { + if (temp[2] == ':') + { + if (*d->__nextchar != '\0') + { + d->optarg = d->__nextchar; + d->optind++; + } + else + d->optarg = NULL; + d->__nextchar = NULL; + } + else + { + if (*d->__nextchar != '\0') + { + d->optarg = d->__nextchar; + d->optind++; + } + else if (d->optind == argc) + { + if (print_errors) + { + fprintf(stderr,"%s: option requires an argument -- '%c'\n",argv[0], c); + } + d->optopt = c; + if (optstring[0] == ':') + c = ':'; + else + c = '?'; + } + else + d->optarg = argv[d->optind++]; + d->__nextchar = NULL; + } + } + return c; + } +} +int _getopt_internal_a (int argc, char *const *argv, const char *optstring, const struct option_a *longopts, int *longind, int long_only, int posixly_correct) +{ + int result; + getopt_data_a.optind = optind; + getopt_data_a.opterr = opterr; + result = _getopt_internal_r_a (argc, argv, optstring, longopts,longind, long_only, &getopt_data_a,posixly_correct); + optind = getopt_data_a.optind; + optarg_a = getopt_data_a.optarg; + optopt = getopt_data_a.optopt; + return result; +} +int getopt_a (int argc, char *const *argv, const char *optstring) _GETOPT_THROW +{ + return _getopt_internal_a (argc, argv, optstring, (const struct option_a *) 0, (int *) 0, 0, 0); +} +int getopt_long_a (int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index) _GETOPT_THROW +{ + return _getopt_internal_a (argc, argv, options, long_options, opt_index, 0, 0); +} +int getopt_long_only_a (int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index) _GETOPT_THROW +{ + return _getopt_internal_a (argc, argv, options, long_options, opt_index, 1, 0); +} +int _getopt_long_r_a (int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index, struct _getopt_data_a *d) +{ + return _getopt_internal_r_a (argc, argv, options, long_options, opt_index,0, d, 0); +} +int _getopt_long_only_r_a (int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index, struct _getopt_data_a *d) +{ + return _getopt_internal_r_a (argc, argv, options, long_options, opt_index, 1, d, 0); +} + +// +// +// Unicode Structures and Functions +// +// + +static struct _getopt_data_w +{ + int optind; + int opterr; + int optopt; + wchar_t *optarg; + int __initialized; + wchar_t *__nextchar; + enum ENUM_ORDERING __ordering; + int __posixly_correct; + int __first_nonopt; + int __last_nonopt; +} getopt_data_w; +wchar_t *optarg_w; + +static void exchange_w(wchar_t **argv, struct _getopt_data_w *d) +{ + int bottom = d->__first_nonopt; + int middle = d->__last_nonopt; + int top = d->optind; + wchar_t *tem; + while (top > middle && middle > bottom) + { + if (top - middle > middle - bottom) + { + int len = middle - bottom; + register int i; + for (i = 0; i < len; i++) + { + tem = argv[bottom + i]; + argv[bottom + i] = argv[top - (middle - bottom) + i]; + argv[top - (middle - bottom) + i] = tem; + } + top -= len; + } + else + { + int len = top - middle; + register int i; + for (i = 0; i < len; i++) + { + tem = argv[bottom + i]; + argv[bottom + i] = argv[middle + i]; + argv[middle + i] = tem; + } + bottom += len; + } + } + d->__first_nonopt += (d->optind - d->__last_nonopt); + d->__last_nonopt = d->optind; +} +static const wchar_t *_getopt_initialize_w (const wchar_t *optstring, struct _getopt_data_w *d, int posixly_correct) +{ + d->__first_nonopt = d->__last_nonopt = d->optind; + d->__nextchar = NULL; + d->__posixly_correct = posixly_correct | !!_wgetenv(L"POSIXLY_CORRECT"); + if (optstring[0] == L'-') + { + d->__ordering = RETURN_IN_ORDER; + ++optstring; + } + else if (optstring[0] == L'+') + { + d->__ordering = REQUIRE_ORDER; + ++optstring; + } + else if (d->__posixly_correct) + d->__ordering = REQUIRE_ORDER; + else + d->__ordering = PERMUTE; + return optstring; +} +int _getopt_internal_r_w (int argc, wchar_t *const *argv, const wchar_t *optstring, const struct option_w *longopts, int *longind, int long_only, struct _getopt_data_w *d, int posixly_correct) +{ + int print_errors = d->opterr; + if (argc < 1) + return -1; + d->optarg = NULL; + if (d->optind == 0 || !d->__initialized) + { + if (d->optind == 0) + d->optind = 1; + optstring = _getopt_initialize_w (optstring, d, posixly_correct); + d->__initialized = 1; + } + else if (optstring[0] == L'-' || optstring[0] == L'+') + optstring++; + if (optstring[0] == L':') + print_errors = 0; + if (d->__nextchar == NULL || *d->__nextchar == L'\0') + { + if (d->__last_nonopt > d->optind) + d->__last_nonopt = d->optind; + if (d->__first_nonopt > d->optind) + d->__first_nonopt = d->optind; + if (d->__ordering == PERMUTE) + { + if (d->__first_nonopt != d->__last_nonopt && d->__last_nonopt != d->optind) + exchange_w((wchar_t **) argv, d); + else if (d->__last_nonopt != d->optind) + d->__first_nonopt = d->optind; + while (d->optind < argc && (argv[d->optind][0] != L'-' || argv[d->optind][1] == L'\0')) + d->optind++; + d->__last_nonopt = d->optind; + } + if (d->optind != argc && !wcscmp(argv[d->optind], L"--")) + { + d->optind++; + if (d->__first_nonopt != d->__last_nonopt && d->__last_nonopt != d->optind) + exchange_w((wchar_t **) argv, d); + else if (d->__first_nonopt == d->__last_nonopt) + d->__first_nonopt = d->optind; + d->__last_nonopt = argc; + d->optind = argc; + } + if (d->optind == argc) + { + if (d->__first_nonopt != d->__last_nonopt) + d->optind = d->__first_nonopt; + return -1; + } + if ((argv[d->optind][0] != L'-' || argv[d->optind][1] == L'\0')) + { + if (d->__ordering == REQUIRE_ORDER) + return -1; + d->optarg = argv[d->optind++]; + return 1; + } + d->__nextchar = (argv[d->optind] + 1 + (longopts != NULL && argv[d->optind][1] == L'-')); + } + if (longopts != NULL && (argv[d->optind][1] == L'-' || (long_only && (argv[d->optind][2] || !wcschr(optstring, argv[d->optind][1]))))) + { + wchar_t *nameend; + unsigned int namelen; + const struct option_w *p; + const struct option_w *pfound = NULL; + struct option_list + { + const struct option_w *p; + struct option_list *next; + } *ambig_list = NULL; + int exact = 0; + int indfound = -1; + int option_index; + for (nameend = d->__nextchar; *nameend && *nameend != L'='; nameend++); + namelen = (unsigned int)(nameend - d->__nextchar); + for (p = longopts, option_index = 0; p->name; p++, option_index++) + if (!wcsncmp(p->name, d->__nextchar, namelen)) + { + if (namelen == (unsigned int)wcslen(p->name)) + { + pfound = p; + indfound = option_index; + exact = 1; + break; + } + else if (pfound == NULL) + { + pfound = p; + indfound = option_index; + } + else if (long_only || pfound->has_arg != p->has_arg || pfound->flag != p->flag || pfound->val != p->val) + { + struct option_list *newp = (struct option_list*)alloca(sizeof(*newp)); + newp->p = p; + newp->next = ambig_list; + ambig_list = newp; + } + } + if (ambig_list != NULL && !exact) + { + if (print_errors) + { + struct option_list first; + first.p = pfound; + first.next = ambig_list; + ambig_list = &first; + fwprintf(stderr, L"%s: option '%s' is ambiguous; possibilities:", argv[0], argv[d->optind]); + do + { + fwprintf (stderr, L" '--%s'", ambig_list->p->name); + ambig_list = ambig_list->next; + } + while (ambig_list != NULL); + fputwc (L'\n', stderr); + } + d->__nextchar += wcslen(d->__nextchar); + d->optind++; + d->optopt = 0; + return L'?'; + } + if (pfound != NULL) + { + option_index = indfound; + d->optind++; + if (*nameend) + { + if (pfound->has_arg) + d->optarg = nameend + 1; + else + { + if (print_errors) + { + if (argv[d->optind - 1][1] == L'-') + { + fwprintf(stderr, L"%s: option '--%s' doesn't allow an argument\n",argv[0], pfound->name); + } + else + { + fwprintf(stderr, L"%s: option '%c%s' doesn't allow an argument\n",argv[0], argv[d->optind - 1][0],pfound->name); + } + } + d->__nextchar += wcslen(d->__nextchar); + d->optopt = pfound->val; + return L'?'; + } + } + else if (pfound->has_arg == 1) + { + if (d->optind < argc) + d->optarg = argv[d->optind++]; + else + { + if (print_errors) + { + fwprintf(stderr,L"%s: option '--%s' requires an argument\n",argv[0], pfound->name); + } + d->__nextchar += wcslen(d->__nextchar); + d->optopt = pfound->val; + return optstring[0] == L':' ? L':' : L'?'; + } + } + d->__nextchar += wcslen(d->__nextchar); + if (longind != NULL) + *longind = option_index; + if (pfound->flag) + { + *(pfound->flag) = pfound->val; + return 0; + } + return pfound->val; + } + if (!long_only || argv[d->optind][1] == L'-' || wcschr(optstring, *d->__nextchar) == NULL) + { + if (print_errors) + { + if (argv[d->optind][1] == L'-') + { + fwprintf(stderr, L"%s: unrecognized option '--%s'\n",argv[0], d->__nextchar); + } + else + { + fwprintf(stderr, L"%s: unrecognized option '%c%s'\n",argv[0], argv[d->optind][0], d->__nextchar); + } + } + d->__nextchar = (wchar_t *)L""; + d->optind++; + d->optopt = 0; + return L'?'; + } + } + { + wchar_t c = *d->__nextchar++; + wchar_t *temp = (wchar_t*)wcschr(optstring, c); + if (*d->__nextchar == L'\0') + ++d->optind; + if (temp == NULL || c == L':' || c == L';') + { + if (print_errors) + { + fwprintf(stderr, L"%s: invalid option -- '%c'\n", argv[0], c); + } + d->optopt = c; + return L'?'; + } + if (temp[0] == L'W' && temp[1] == L';') + { + wchar_t *nameend; + const struct option_w *p; + const struct option_w *pfound = NULL; + int exact = 0; + int ambig = 0; + int indfound = 0; + int option_index; + if (longopts == NULL) + goto no_longs; + if (*d->__nextchar != L'\0') + { + d->optarg = d->__nextchar; + d->optind++; + } + else if (d->optind == argc) + { + if (print_errors) + { + fwprintf(stderr,L"%s: option requires an argument -- '%c'\n",argv[0], c); + } + d->optopt = c; + if (optstring[0] == L':') + c = L':'; + else + c = L'?'; + return c; + } + else + d->optarg = argv[d->optind++]; + for (d->__nextchar = nameend = d->optarg; *nameend && *nameend != L'='; nameend++); + for (p = longopts, option_index = 0; p->name; p++, option_index++) + if (!wcsncmp(p->name, d->__nextchar, nameend - d->__nextchar)) + { + if ((unsigned int) (nameend - d->__nextchar) == wcslen(p->name)) + { + pfound = p; + indfound = option_index; + exact = 1; + break; + } + else if (pfound == NULL) + { + pfound = p; + indfound = option_index; + } + else if (long_only || pfound->has_arg != p->has_arg || pfound->flag != p->flag || pfound->val != p->val) + ambig = 1; + } + if (ambig && !exact) + { + if (print_errors) + { + fwprintf(stderr, L"%s: option '-W %s' is ambiguous\n",argv[0], d->optarg); + } + d->__nextchar += wcslen(d->__nextchar); + d->optind++; + return L'?'; + } + if (pfound != NULL) + { + option_index = indfound; + if (*nameend) + { + if (pfound->has_arg) + d->optarg = nameend + 1; + else + { + if (print_errors) + { + fwprintf(stderr, L"%s: option '-W %s' doesn't allow an argument\n",argv[0], pfound->name); + } + d->__nextchar += wcslen(d->__nextchar); + return L'?'; + } + } + else if (pfound->has_arg == 1) + { + if (d->optind < argc) + d->optarg = argv[d->optind++]; + else + { + if (print_errors) + { + fwprintf(stderr, L"%s: option '-W %s' requires an argument\n",argv[0], pfound->name); + } + d->__nextchar += wcslen(d->__nextchar); + return optstring[0] == L':' ? L':' : L'?'; + } + } + else + d->optarg = NULL; + d->__nextchar += wcslen(d->__nextchar); + if (longind != NULL) + *longind = option_index; + if (pfound->flag) + { + *(pfound->flag) = pfound->val; + return 0; + } + return pfound->val; + } +no_longs: + d->__nextchar = NULL; + return L'W'; + } + if (temp[1] == L':') + { + if (temp[2] == L':') + { + if (*d->__nextchar != L'\0') + { + d->optarg = d->__nextchar; + d->optind++; + } + else + d->optarg = NULL; + d->__nextchar = NULL; + } + else + { + if (*d->__nextchar != L'\0') + { + d->optarg = d->__nextchar; + d->optind++; + } + else if (d->optind == argc) + { + if (print_errors) + { + fwprintf(stderr,L"%s: option requires an argument -- '%c'\n",argv[0], c); + } + d->optopt = c; + if (optstring[0] == L':') + c = L':'; + else + c = L'?'; + } + else + d->optarg = argv[d->optind++]; + d->__nextchar = NULL; + } + } + return c; + } +} +int _getopt_internal_w (int argc, wchar_t *const *argv, const wchar_t *optstring, const struct option_w *longopts, int *longind, int long_only, int posixly_correct) +{ + int result; + getopt_data_w.optind = optind; + getopt_data_w.opterr = opterr; + result = _getopt_internal_r_w (argc, argv, optstring, longopts,longind, long_only, &getopt_data_w,posixly_correct); + optind = getopt_data_w.optind; + optarg_w = getopt_data_w.optarg; + optopt = getopt_data_w.optopt; + return result; +} +int getopt_w (int argc, wchar_t *const *argv, const wchar_t *optstring) _GETOPT_THROW +{ + return _getopt_internal_w (argc, argv, optstring, (const struct option_w *) 0, (int *) 0, 0, 0); +} +int getopt_long_w (int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index) _GETOPT_THROW +{ + return _getopt_internal_w (argc, argv, options, long_options, opt_index, 0, 0); +} +int getopt_long_only_w (int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index) _GETOPT_THROW +{ + return _getopt_internal_w (argc, argv, options, long_options, opt_index, 1, 0); +} +int _getopt_long_r_w (int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index, struct _getopt_data_w *d) +{ + return _getopt_internal_r_w (argc, argv, options, long_options, opt_index,0, d, 0); +} +int _getopt_long_only_r_w (int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index, struct _getopt_data_w *d) +{ + return _getopt_internal_r_w (argc, argv, options, long_options, opt_index, 1, d, 0); +} \ No newline at end of file diff --git a/getopt/getopt.h b/getopt/getopt.h new file mode 100644 index 0000000..5ed4a46 --- /dev/null +++ b/getopt/getopt.h @@ -0,0 +1,136 @@ +/* Getopt for Microsoft C +This code is a modification of the Free Software Foundation, Inc. +Getopt library for parsing command line argument the purpose was +to provide a Microsoft Visual C friendly derivative. This code +provides functionality for both Unicode and Multibyte builds. + +Date: 02/03/2011 - Ludvik Jerabek - Initial Release +Version: 1.0 +Comment: Supports getopt, getopt_long, and getopt_long_only +and POSIXLY_CORRECT environment flag +License: LGPL + +Revisions: + +02/03/2011 - Ludvik Jerabek - Initial Release +02/20/2011 - Ludvik Jerabek - Fixed compiler warnings at Level 4 +07/05/2011 - Ludvik Jerabek - Added no_argument, required_argument, optional_argument defs +08/03/2011 - Ludvik Jerabek - Fixed non-argument runtime bug which caused runtime exception +08/09/2011 - Ludvik Jerabek - Added code to export functions for DLL and LIB +02/15/2012 - Ludvik Jerabek - Fixed _GETOPT_THROW definition missing in implementation file +08/01/2012 - Ludvik Jerabek - Created separate functions for char and wchar_t characters so single dll can do both unicode and ansi +10/15/2012 - Ludvik Jerabek - Modified to match latest GNU features +06/19/2015 - Ludvik Jerabek - Fixed maximum option limitation caused by option_a (255) and option_w (65535) structure val variable + +**DISCLAIMER** +THIS MATERIAL IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, +EITHER EXPRESS OR IMPLIED, INCLUDING, BUT Not LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE, OR NON-INFRINGEMENT. SOME JURISDICTIONS DO NOT ALLOW THE +EXCLUSION OF IMPLIED WARRANTIES, SO THE ABOVE EXCLUSION MAY NOT +APPLY TO YOU. IN NO EVENT WILL I BE LIABLE TO ANY PARTY FOR ANY +DIRECT, INDIRECT, SPECIAL OR OTHER CONSEQUENTIAL DAMAGES FOR ANY +USE OF THIS MATERIAL INCLUDING, WITHOUT LIMITATION, ANY LOST +PROFITS, BUSINESS INTERRUPTION, LOSS OF PROGRAMS OR OTHER DATA ON +YOUR INFORMATION HANDLING SYSTEM OR OTHERWISE, EVEN If WE ARE +EXPRESSLY ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +*/ +#ifndef __GETOPT_H_ + #define __GETOPT_H_ + + #ifdef _GETOPT_API + #undef _GETOPT_API + #endif + + #if defined(EXPORTS_GETOPT) && defined(STATIC_GETOPT) + #error "The preprocessor definitions of EXPORTS_GETOPT and STATIC_GETOPT can only be used individually" + #elif defined(STATIC_GETOPT) + #pragma message("Warning static builds of getopt violate the Lesser GNU Public License") + #define _GETOPT_API + #elif defined(EXPORTS_GETOPT) + #pragma message("Exporting getopt library") + #define _GETOPT_API __declspec(dllexport) + #else + #pragma message("Importing getopt library") + #define _GETOPT_API __declspec(dllimport) + #endif + + // Change behavior for C\C++ + #ifdef __cplusplus + #define _BEGIN_EXTERN_C extern "C" { + #define _END_EXTERN_C } + #define _GETOPT_THROW throw() + #else + #define _BEGIN_EXTERN_C + #define _END_EXTERN_C + #define _GETOPT_THROW + #endif + + // Standard GNU options + #define null_argument 0 /*Argument Null*/ + #define no_argument 0 /*Argument Switch Only*/ + #define required_argument 1 /*Argument Required*/ + #define optional_argument 2 /*Argument Optional*/ + + // Shorter Options + #define ARG_NULL 0 /*Argument Null*/ + #define ARG_NONE 0 /*Argument Switch Only*/ + #define ARG_REQ 1 /*Argument Required*/ + #define ARG_OPT 2 /*Argument Optional*/ + + #include + #include + +_BEGIN_EXTERN_C + + extern _GETOPT_API int optind; + extern _GETOPT_API int opterr; + extern _GETOPT_API int optopt; + + // Ansi + struct option_a + { + const char* name; + int has_arg; + int *flag; + int val; + }; + extern _GETOPT_API char *optarg_a; + extern _GETOPT_API int getopt_a(int argc, char *const *argv, const char *optstring) _GETOPT_THROW; + extern _GETOPT_API int getopt_long_a(int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index) _GETOPT_THROW; + extern _GETOPT_API int getopt_long_only_a(int argc, char *const *argv, const char *options, const struct option_a *long_options, int *opt_index) _GETOPT_THROW; + + // Unicode + struct option_w + { + const wchar_t* name; + int has_arg; + int *flag; + int val; + }; + extern _GETOPT_API wchar_t *optarg_w; + extern _GETOPT_API int getopt_w(int argc, wchar_t *const *argv, const wchar_t *optstring) _GETOPT_THROW; + extern _GETOPT_API int getopt_long_w(int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index) _GETOPT_THROW; + extern _GETOPT_API int getopt_long_only_w(int argc, wchar_t *const *argv, const wchar_t *options, const struct option_w *long_options, int *opt_index) _GETOPT_THROW; + +_END_EXTERN_C + + #undef _BEGIN_EXTERN_C + #undef _END_EXTERN_C + #undef _GETOPT_THROW + #undef _GETOPT_API + + #ifdef _UNICODE + #define getopt getopt_w + #define getopt_long getopt_long_w + #define getopt_long_only getopt_long_only_w + #define option option_w + #define optarg optarg_w + #else + #define getopt getopt_a + #define getopt_long getopt_long_a + #define getopt_long_only getopt_long_only_a + #define option option_a + #define optarg optarg_a + #endif +#endif // __GETOPT_H_ diff --git a/getopt/getopt.sln b/getopt/getopt.sln new file mode 100644 index 0000000..45fb7b0 --- /dev/null +++ b/getopt/getopt.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27705.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "getopt", "getopt.vcxproj", "{4BBAEAA1-9AD1-42D2-B6DC-7F73C4B3D238}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {4BBAEAA1-9AD1-42D2-B6DC-7F73C4B3D238}.Debug|Win32.ActiveCfg = Debug|Win32 + {4BBAEAA1-9AD1-42D2-B6DC-7F73C4B3D238}.Debug|Win32.Build.0 = Debug|Win32 + {4BBAEAA1-9AD1-42D2-B6DC-7F73C4B3D238}.Debug|x64.ActiveCfg = Debug|x64 + {4BBAEAA1-9AD1-42D2-B6DC-7F73C4B3D238}.Debug|x64.Build.0 = Debug|x64 + {4BBAEAA1-9AD1-42D2-B6DC-7F73C4B3D238}.Release|Win32.ActiveCfg = Release|Win32 + {4BBAEAA1-9AD1-42D2-B6DC-7F73C4B3D238}.Release|Win32.Build.0 = Release|Win32 + {4BBAEAA1-9AD1-42D2-B6DC-7F73C4B3D238}.Release|x64.ActiveCfg = Release|x64 + {4BBAEAA1-9AD1-42D2-B6DC-7F73C4B3D238}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {4E6DE38C-035F-4DBE-9385-C35AC4C787ED} + EndGlobalSection +EndGlobal diff --git a/getopt/getopt.vcxproj b/getopt/getopt.vcxproj new file mode 100644 index 0000000..f2cf127 --- /dev/null +++ b/getopt/getopt.vcxproj @@ -0,0 +1,193 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {4BBAEAA1-9AD1-42D2-B6DC-7F73C4B3D238} + GetOptTest + Win32Proj + + + + DynamicLibrary + Unicode + v140 + + + DynamicLibrary + Unicode + v140 + + + DynamicLibrary + Unicode + true + v140 + + + DynamicLibrary + Unicode + true + v140 + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + $(SolutionDir)$(Platform)\$(Configuration)\ + $(SolutionDir)$(Platform)\$(Configuration)\ + false + false + true + true + getopt + + + + + + + + MaxSpeed + true + WIN32;NDEBUG;_CONSOLE;EXPORTS_GETOPT;%(PreprocessorDefinitions) + MultiThreadedDLL + true + + + Level3 + ProgramDatabase + + + $(OutDir)\getopt.dll + true + Console + true + true + MachineX86 + + + + + + + + + + + + + MaxSpeed + true + WIN32;NDEBUG;_CONSOLE;EXPORTS_GETOPT;%(PreprocessorDefinitions) + MultiThreadedDLL + true + + + Level3 + ProgramDatabase + + + $(OutDir)\getopt.dll + true + Console + true + true + + + + + + + + + + + + + Disabled + WIN32;_DEBUG;_CONSOLE;EXPORTS_GETOPT;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebugDLL + + + Level4 + EditAndContinue + + + $(OutDir)\getopt.dll + true + Console + MachineX86 + + + + + + + + + + + + + Disabled + WIN32;_DEBUG;_CONSOLE;EXPORTS_GETOPT;%(PreprocessorDefinitions) + EnableFastChecks + MultiThreadedDebugDLL + + + Level4 + ProgramDatabase + + + $(OutDir)\getopt.dll + true + Console + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ignore.c b/ignore.c index 8bff2ce..9640d0b 100644 --- a/ignore.c +++ b/ignore.c @@ -7,7 +7,6 @@ #include #include "ignore.h" -#include "rf.h" static int total_size; @@ -19,13 +18,13 @@ struct ignores *init_ignores(char *path) { return NULL; } - char line[MAX_PATH_LENGTH]; + char line[MAXPATHLEN]; total_size = IGNORE_SIZE; struct ignores *ignores = (struct ignores *)calloc(sizeof(struct ignores), 1); ignores->list = (char **)calloc(sizeof(char *), IGNORE_SIZE); ignores->size = 0; - while (fgets(line, MAX_PATH_LENGTH, ignore) != NULL) { + while (fgets(line, MAXPATHLEN, ignore) != NULL) { /** isspace doesn't necessarily hold true for `\n` for anything * other than the 'C' locale on some platforms, so we have to do * an additional check for this diff --git a/ignore.h b/ignore.h index 1bc3add..3fcc445 100644 --- a/ignore.h +++ b/ignore.h @@ -3,6 +3,10 @@ #define IGNORE_SIZE 100 +#ifdef _WIN32 +#define MAXPATHLEN 1024 +#endif + struct ignores { char **list; int size; diff --git a/make.bat b/make.bat new file mode 100644 index 0000000..151e0df --- /dev/null +++ b/make.bat @@ -0,0 +1,8 @@ +@echo off + +if "%1%" == "clean" ( + del *.obj *.exe *.lib *.exp *.pdb + exit /b +) + +cl /nologo /W3 rf.c ignore.c include\common\strl.c getopt\getopt.c /link /out:rf.exe \ No newline at end of file diff --git a/rf.c b/rf.c index 95dc432..c31feb7 100644 --- a/rf.c +++ b/rf.c @@ -4,21 +4,31 @@ #define _XOPEN_SOURCE 700 #include -#include + #include -#include #include #include #include -#include #include #include -#include #ifdef _WIN32 +#include "dirent/include/dirent.h" +#include "getopt/getopt.h" +#include #include +#pragma comment(lib, "shlwapi") +#define RFIGNORE_PATTERN "%s\\%s" +#define HOME_ENV_VAR "USERPROFILE" +#define MAXPATHLEN 1024 #else +#include #include +#include +#include +#include +#define RFIGNORE_PATTERN "%s/%s" +#define HOME_ENV_VAR "HOME" #endif #include "ignore.h" @@ -120,8 +130,8 @@ static int recurse_find(char **patterns, int *pattern_count, const char *dirname struct stat entry_stat; if (stat(full_path, &entry_stat)) { + fprintf(stderr, "%s: ", full_path); perror("stat"); - exit(EXIT_FAILURE); continue; } @@ -194,16 +204,16 @@ int main(int argc, char **argv) { char cwd[MAXPATHLEN]; int unmatched_error = 0; +#ifdef _WIN32 + if (_getcwd(cwd, MAXPATHLEN) == NULL) { +#else if (getcwd(cwd, MAXPATHLEN) == NULL) { +#endif perror("getcwd"); exit(EXIT_FAILURE); } -#ifdef _WIN32 - char *home = getenv("USERPROFILE"); -#else - char *home = getenv("HOME"); -#endif + char *home = getenv(HOME_ENV_VAR); while ((ch = getopt(argc, argv, "d:l:svw")) > -1) { switch (ch) { @@ -243,17 +253,11 @@ int main(int argc, char **argv) { } } - char global_ignore_path[(strlen(home) + strlen(".rfignore") + 1)]; - char local_ignore_path[strlen(cwd) + strlen(".rfignore") + 1]; - -#ifdef _WIN32 - const char *pattern = "%s\\%s"; -#else - const char *pattern = "%s/%s"; -#endif + char global_ignore_path[MAXPATHLEN]; + char local_ignore_path[MAXPATHLEN]; - snprintf(global_ignore_path, (strlen(pattern) + strlen(home) + strlen(RFIGNORE)), pattern, home, RFIGNORE); - snprintf(local_ignore_path, (strlen(pattern) + strlen(cwd) + strlen(RFIGNORE)), pattern, cwd, RFIGNORE); + snprintf(global_ignore_path, MAXPATHLEN, RFIGNORE_PATTERN, home, RFIGNORE); + snprintf(local_ignore_path, MAXPATHLEN, RFIGNORE_PATTERN, cwd, RFIGNORE); global_ignores = init_ignores(global_ignore_path); local_ignores = init_ignores(local_ignore_path); diff --git a/rf.h b/rf.h deleted file mode 100644 index 4695fa1..0000000 --- a/rf.h +++ /dev/null @@ -1 +0,0 @@ -#define MAX_PATH_LENGTH 1024 -- cgit 1.4.1-2-gfad0