diff --git a/BUILD.bazel b/BUILD.bazel index db18e8c..cafd9c2 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -1,18 +1,26 @@ load( "@gz//bazel/skylark:build_defs.bzl", "GZ_FEATURES", + "GZ_ROOT", "GZ_VISIBILITY", "gz_configure_header", "gz_export_header", "gz_include_header", ) +load("@rules_license//rules:license.bzl", "license") package( + default_applicable_licenses = [GZ_ROOT + "utils:license"], default_visibility = GZ_VISIBILITY, features = GZ_FEATURES, ) -licenses(["notice"]) # Apache-2.0 +license( + name = "license", + package_name = "gz-utils", +) + +licenses(["notice"]) exports_files(["LICENSE"]) @@ -39,8 +47,8 @@ gz_include_header( name = "utilshh_genrule", out = "include/gz/utils.hh", hdrs = public_headers_no_gen + [ - "include/gz/utils/config.hh", "include/gz/utils/Export.hh", + "include/gz/utils/config.hh", ], ) @@ -54,6 +62,7 @@ cc_library( name = "utils", srcs = ["src/Environment.cc"], hdrs = public_headers, + copts = ["-fexceptions"], includes = ["include"], ) @@ -72,6 +81,7 @@ cc_library( cc_test( name = "ImplPtr_TEST", + size = "small", srcs = ["test/integration/implptr/ImplPtr_TEST.cc"], deps = [ ":implptr_test_classes", @@ -93,6 +103,7 @@ cc_test( cc_test( name = "NeverDestroyed_TEST", srcs = ["src/NeverDestroyed_TEST.cc"], + copts = ["-fexceptions"], deps = [ ":utils", "@gtest", diff --git a/Changelog.md b/Changelog.md index 7d86038..38445d2 100644 --- a/Changelog.md +++ b/Changelog.md @@ -4,6 +4,26 @@ ## Gazebo Utils 2.x +## Gazebo Utils 2.2.0 (2023-11-08) + +1. Make the single argument constructor inherit the env + * [Pull request #113](https://github.com/gazebosim/gz-utils/pull/113) + +1. Add new functions for manipulating the environment + * [Pull request #114](https://github.com/gazebosim/gz-utils/pull/114) + +1. Add license checking support to bazel + * [Pull request #108](https://github.com/gazebosim/gz-utils/pull/108) + +1. Include what you use + * [Pull request #107](https://github.com/gazebosim/gz-utils/pull/107) + +1. Ensure all licenses are reflected in the LICENSE file + * [Pull request #106](https://github.com/gazebosim/gz-utils/pull/106) + +1. Not sure why this was here, but it is unused + * [Pull request #105](https://github.com/gazebosim/gz-utils/pull/105) + ## Gazebo Utils 2.1.0 (2023-09-26) 1. Documentation fixes diff --git a/LICENSE b/LICENSE index 4909afd..c80932f 100644 --- a/LICENSE +++ b/LICENSE @@ -175,4 +175,61 @@ END OF TERMS AND CONDITIONS - +------------------ + +Files: cli/include/vendored-cli/* + +CLI11 1.8 Copyright (c) 2017-2019 University of Cincinnati, developed by Henry +Schreiner under NSF AWARD 1414736. All rights reserved. + +Redistribution and use in source and binary forms of CLI11, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +------------------ + +Files: include/gz/utils/detail/subprocess.h + + This is free and unencumbered software released into the public domain. + + Anyone is free to copy, modify, publish, use, compile, sell, or + distribute this software, either in source code form or as a compiled + binary, for any purpose, commercial or non-commercial, and by any + means. + + In jurisdictions that recognize copyright laws, the author or authors + of this software dedicate any and all copyright interest in the + software to the public domain. We make this dedication for the benefit + of the public at large and to the detriment of our heirs and + successors. We intend this dedication to be an overt act of + relinquishment in perpetuity of all present and future rights to this + software under copyright law. + + 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 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. + + For more information, please refer to diff --git a/cli/BUILD.bazel b/cli/BUILD.bazel index 096f09e..b97bc2d 100644 --- a/cli/BUILD.bazel +++ b/cli/BUILD.bazel @@ -1,7 +1,18 @@ load( "@gz//bazel/skylark:build_defs.bzl", + "GZ_ROOT", "GZ_VISIBILITY", ) +load("@rules_license//rules:license.bzl", "license") + +package( + default_applicable_licenses = [GZ_ROOT + "utils/cli:license"], +) + +license( + name = "license", + package_name = "gz-utils-cli", +) public_headers = [ "include/gz/utils/cli/GzFormatter.hpp", @@ -12,10 +23,14 @@ public_headers = [ cc_library( name = "cli", hdrs = public_headers, + copts = ["-fexceptions"], includes = [ "include", "include/external-cli", ], visibility = GZ_VISIBILITY, - deps = ["@cli11"], + deps = [ + GZ_ROOT + "utils:utils", + "@cli11" + ], ) diff --git a/cli/include/gz/utils/cli/GzFormatter.hpp b/cli/include/gz/utils/cli/GzFormatter.hpp index 55d8b71..efd3644 100644 --- a/cli/include/gz/utils/cli/GzFormatter.hpp +++ b/cli/include/gz/utils/cli/GzFormatter.hpp @@ -19,6 +19,7 @@ #define GZ_UTILS_CLI_GZ_FORMATTER_HPP_ #include +#include #include #include #include @@ -26,6 +27,7 @@ #include "gz/utils/cli/App.hpp" #include "gz/utils/cli/FormatterFwd.hpp" +#include "gz/utils/cli/StringTools.hpp" #include "gz/utils/Export.hh" ////////////////////////////////////////////////// diff --git a/include/gz/utils/Environment.hh b/include/gz/utils/Environment.hh index 8ac06a4..3302f88 100644 --- a/include/gz/utils/Environment.hh +++ b/include/gz/utils/Environment.hh @@ -22,6 +22,8 @@ #include #include +#include +#include namespace gz { @@ -66,7 +68,66 @@ bool GZ_UTILS_VISIBLE setenv( /// \return True if the variable was unset or false otherwise. bool GZ_UTILS_VISIBLE unsetenv(const std::string &_name); -} +/// \brief Unset all environment variables +/// +/// Note: This function is not thread-safe and should not be called +/// concurrently with `env` or `setenv` +/// +/// \return True if the environment was unset or false otherwise. +bool GZ_UTILS_VISIBLE clearenv(); + +/// \brief Type alias for a collection of environment variables +using EnvironmentMap = std::unordered_map; + +/// \brief Type alias for a collection of environment variables +/// Each entry is of the form KEY=VAL +using EnvironmentStrings = std::vector; + +/// \brief Convert a vector of environment variables to a map +/// +/// \param[in] _envStrings Vector collection of environment variables +/// \return Mapped collection of environment variables. +EnvironmentMap GZ_UTILS_VISIBLE envStringsToMap( + const EnvironmentStrings &_envStrings); + +/// \brief Convert a map of environment variables to a vector +/// +/// \param[in] _envMap Collection of mapped environment variables. +/// \return Vector collection of environment variables. +EnvironmentStrings GZ_UTILS_VISIBLE envMapToStrings( + const EnvironmentMap &_envMap); + +/// \brief Retrieve all current environment variables +/// +/// Note: This function is not thread-safe and should not be called +/// concurrently with `setenv` or `unsetenv` +/// +/// \return A collection of current environment variables +EnvironmentMap GZ_UTILS_VISIBLE env(); + +/// \brief Set the environment variable '_name'. +/// +/// Note: On Windows setting an empty string (_value=="") +/// is the equivalent of unsetting the variable. +// +/// Note: This function is not thread-safe and should not be called +/// concurrently with `env` or `unsetenv` +/// +/// \param[in] _vars Collection of environment variables to set +/// \return True if all variables were set or false otherwise. +bool GZ_UTILS_VISIBLE setenv(const EnvironmentMap &_vars); + +/// \brief Print the entire current environment to a string +/// +/// This prints each variable in the form KEY=VALUE\n +/// +/// Note: This function is not thread-safe and should not be called +/// concurrently with `setenv` or `unsetenv` +/// +/// \return A string containing all environment variables +/// NOLINTNEXTLINE - This is incorrectly parsed as a global variable +std::string GZ_UTILS_VISIBLE printenv(); +} // namespace GZ_UTILS_VERSION_NAMESPACE } // namespace utils } // namespace gz diff --git a/include/gz/utils/Subprocess.hh b/include/gz/utils/Subprocess.hh index 787daf5..f7f9fab 100644 --- a/include/gz/utils/Subprocess.hh +++ b/include/gz/utils/Subprocess.hh @@ -19,8 +19,12 @@ #define GZ_UTILS__SUBPROCESS_HH_ #include "detail/subprocess.h" +#include "gz/utils/Environment.hh" + +#include #include #include +#include #include @@ -32,46 +36,95 @@ namespace gz namespace utils { +/// \brief Create a RAII-type object that encapsulates a subprocess. class Subprocess { + /// \brief Constructor + /// + /// This variant will spawn a subprocess that inherits the environment + /// from the calling process. + /// + /// \param[in] _commandLine set of arguments starting with an executable + /// used to spawn the subprocess + public: explicit Subprocess(const std::vector &_commandLine): + commandLine(_commandLine), + environment({}), + inheritEnvironment(true) + { + this->Create(); + } + + /// \brief Constructor + /// + /// This variant will spawn a subprocess that uses the user-specified + /// environment + /// + /// \param[in] _commandLine set of arguments starting with an executable + /// used to spawn the subprocess + /// \param[in] _environment environment variables to set in the spawned + /// subprocess public: Subprocess(const std::vector &_commandLine, - const std::vector &_environment = {}): + gz::utils::EnvironmentMap _environment): commandLine(_commandLine), - environment(_environment) + environment(std::move(_environment)), + inheritEnvironment(false) { this->Create(); } + /// \brief Constructor + /// + /// This variant will spawn a subprocess that uses the user-specified + /// environment + /// + /// \param[in] _commandLine set of arguments starting with an executable + /// used to spawn the subprocess + /// \param[in] _environment environment variables to set in the spawned + /// subprocess + public: Subprocess(const std::vector &_commandLine, + const gz::utils::EnvironmentStrings &_environment): + Subprocess(_commandLine, gz::utils::envStringsToMap(_environment)) + { + } + + private: void Create() { - if (this->process) + if (this->process != nullptr) return; this->process = new subprocess_s; + auto environmentStr = gz::utils::envMapToStrings(this->environment); + std::vector environmentCstr; std::vector commandLineCstr; + for (const auto &val : this->commandLine) { commandLineCstr.push_back(val.c_str()); } commandLineCstr.push_back(nullptr); - std::vector environmentCstr; - for (const auto &val : this->environment) + if (!this->inheritEnvironment) { - environmentCstr.push_back(val.c_str()); + for (const auto &val : environmentStr) + { + environmentCstr.push_back(val.c_str()); + } + environmentCstr.push_back(nullptr); } - environmentCstr.push_back(nullptr); int ret = -1; - if (this->environment.size()) + if (!this->inheritEnvironment) { ret = subprocess_create_ex(commandLineCstr.data(), 0, environmentCstr.data(), this->process); } else { - ret = subprocess_create(commandLineCstr.data(), 0, this->process); + ret = subprocess_create(commandLineCstr.data(), + subprocess_option_inherit_environment, + this->process); } if (0 != ret) @@ -84,21 +137,21 @@ class Subprocess public: ~Subprocess() { - if (this->process) + if (this->process != nullptr) subprocess_destroy(this->process); delete this->process; } public: std::string Stdout() { - std::string result{""}; - if (this->process) + std::string result; + if (this->process != nullptr) { - auto p_stdout = subprocess_stdout(this->process); + auto *p_stdout = subprocess_stdout(this->process); char buffer[128]; while (!feof(p_stdout)) { - if (fgets(buffer, 128, p_stdout) != NULL) + if (fgets(buffer, 128, p_stdout) != nullptr) result += buffer; } } @@ -107,14 +160,14 @@ class Subprocess public: std::string Stderr() { - std::string result{""}; - if (this->process) + std::string result; + if (this->process != nullptr) { - auto p_stdout = subprocess_stderr(this->process); + auto *p_stdout = subprocess_stderr(this->process); char buffer[128]; while (!feof(p_stdout)) { - if (fgets(buffer, 128, p_stdout) != NULL) + if (fgets(buffer, 128, p_stdout) != nullptr) result += buffer; } } @@ -124,7 +177,7 @@ class Subprocess public: bool Alive() { - if (this->process) + if (this->process != nullptr) return subprocess_alive(this->process); else return false; @@ -132,7 +185,7 @@ class Subprocess public: bool Terminate() { - if (this->process) + if (this->process != nullptr) return subprocess_terminate(this->process) != 0; else return false; @@ -141,7 +194,7 @@ class Subprocess public: int Join() { int return_code = -1; - if (this->process) + if (this->process != nullptr) { auto ret = subprocess_join(this->process, &return_code); if (ret != 0) @@ -155,7 +208,9 @@ class Subprocess protected: std::vector commandLine; - protected: std::vector environment; + protected: EnvironmentMap environment; + + protected: bool inheritEnvironment; protected: subprocess_s * process {nullptr}; }; diff --git a/src/Environment.cc b/src/Environment.cc index 73c996c..d477414 100644 --- a/src/Environment.cc +++ b/src/Environment.cc @@ -17,9 +17,19 @@ #include +#include #include -#include +#include +#include +#ifdef _WIN32 +#include +#include +#endif + +#ifndef _WIN32 +extern char ** environ; +#endif namespace gz { @@ -99,6 +109,102 @@ bool unsetenv(const std::string &_name) #endif return true; } + +///////////////////////////////////////////////// +bool clearenv() +{ + bool success = true; +#if __linux__ + if (0 != ::clearenv()) + { + success = false; + } +#else + // Windows and macOS don't have clearenv + // so iterate and clear one-by-one + for (const auto &[key, value] : env()) + { + success &= unsetenv(key); + } +#endif + return success; +} + +///////////////////////////////////////////////// +EnvironmentMap env() +{ + EnvironmentMap ret; + + char **currentEnv = nullptr; +#ifdef _WIN32 + currentEnv = *__p__environ(); +#else + currentEnv = environ; +#endif + // In the case that clearenv() was just called + // currentEnv will be nullptr + if (currentEnv == nullptr) + return {}; + + std::vector envStrings; + for (; *currentEnv; ++currentEnv) + { + envStrings.emplace_back(*currentEnv); + } + return envStringsToMap(envStrings); +} + +///////////////////////////////////////////////// +bool setenv(const EnvironmentMap &_vars) +{ + bool success = true; + for (const auto &[key, value] : _vars) + { + success &= setenv(key, value); + } + return success; +} + +///////////////////////////////////////////////// +EnvironmentMap envStringsToMap(const EnvironmentStrings &_envStrings) +{ + EnvironmentMap ret; + for (const auto &pair : _envStrings) + { + auto eqPos = pair.find('='); + if (eqPos != std::string::npos) + { + ret.emplace(pair.substr(0, eqPos), pair.substr(eqPos + 1)); + } + } + return ret; } + +///////////////////////////////////////////////// +EnvironmentStrings envMapToStrings(const EnvironmentMap &_envMap) +{ + EnvironmentStrings ret; + auto sorted = std::vector>( + _envMap.begin(), _envMap.end()); + std::sort(sorted.begin(), sorted.end()); + for (auto [key, value] : sorted) + { + ret.push_back(key + "=" + value); + } + return ret; } + +///////////////////////////////////////////////// +std::string printenv() +{ + std::string ret; + for (const auto &entry : envMapToStrings(env())) + { + ret.append(entry); + ret.append("\n"); + } + return ret; } +} // namespace GZ_UTILS_VERSION_NAMESPACE +} // namespace utils +} // namespace gz diff --git a/src/Environment_TEST.cc b/src/Environment_TEST.cc index 7968577..496dddb 100644 --- a/src/Environment_TEST.cc +++ b/src/Environment_TEST.cc @@ -19,11 +19,15 @@ #include +#include + using namespace gz; ///////////////////////////////////////////////// TEST(Environment, emptyENV) { + gz::utils::clearenv(); + std::string var; EXPECT_FALSE(utils::env("!!SHOULD_NOT_EXIST!!", var)); EXPECT_TRUE(var.empty()); @@ -32,6 +36,8 @@ TEST(Environment, emptyENV) ///////////////////////////////////////////////// TEST(Environment, envSet) { + gz::utils::clearenv(); + const auto key = "GZ_ENV_SET"; ASSERT_TRUE(utils::setenv(key, "VALUE")); @@ -65,6 +71,8 @@ TEST(Environment, envSet) ///////////////////////////////////////////////// TEST(Environment, envUnset) { + gz::utils::clearenv(); + const auto key = "GZ_ENV_UNSET"; ASSERT_TRUE(utils::unsetenv(key)); @@ -92,8 +100,10 @@ TEST(Environment, envUnset) } ///////////////////////////////////////////////// -TEST(Util_TEST, envSetEmpty) +TEST(Environment, envSetEmpty) { + gz::utils::clearenv(); + const auto key = "GZ_ENV_SET_EMPTY"; ASSERT_TRUE(utils::setenv(key, "")); @@ -131,3 +141,73 @@ TEST(Util_TEST, envSetEmpty) } ASSERT_TRUE(utils::unsetenv(key)); } + +///////////////////////////////////////////////// +TEST(Environment, envGetCollection) +{ + gz::utils::clearenv(); + auto currentEnv = gz::utils::env(); + EXPECT_EQ(currentEnv.size(), 0); + + ASSERT_TRUE(gz::utils::setenv("GZ_FOO_KEY", "GZ_FOO_VAL")); + ASSERT_TRUE(gz::utils::setenv("GZ_BAR_KEY", "GZ_BAR_VAL")); + ASSERT_TRUE(gz::utils::setenv("GZ_BAZ_KEY", "GZ_BAZ_VAL")); + + currentEnv = gz::utils::env(); + EXPECT_EQ(currentEnv.size(), 3); + + EXPECT_EQ(currentEnv["GZ_FOO_KEY"], "GZ_FOO_VAL"); + EXPECT_EQ(currentEnv["GZ_BAR_KEY"], "GZ_BAR_VAL"); + EXPECT_EQ(currentEnv["GZ_BAZ_KEY"], "GZ_BAZ_VAL"); +} + +///////////////////////////////////////////////// +TEST(Environment, printenv) +{ + gz::utils::clearenv(); + EXPECT_EQ(gz::utils::printenv(), ""); + + ASSERT_TRUE(gz::utils::setenv("GZ_FOO_KEY", "GZ_FOO_VAL")); + ASSERT_TRUE(gz::utils::setenv("GZ_BAR_KEY", "GZ_BAR_VAL")); + ASSERT_TRUE(gz::utils::setenv("GZ_BAZ_KEY", "GZ_BAZ_VAL")); + + // Always returned in sorted order + EXPECT_EQ(gz::utils::printenv(), + "GZ_BAR_KEY=GZ_BAR_VAL\nGZ_BAZ_KEY=GZ_BAZ_VAL\nGZ_FOO_KEY=GZ_FOO_VAL\n"); +} + +///////////////////////////////////////////////// +TEST(Environment, envStringsToMap) +{ + gz::utils::EnvironmentStrings strings; + strings.emplace_back("GZ_FOO_KEY=GZ_FOO_VAL"); + strings.emplace_back("GZ_BAR_KEY=GZ_BAR_VAL"); + strings.emplace_back("GZ_BAZ_KEY=GZ_BAZ_VAL"); + strings.emplace_back("BAD_KEY"); + + { + auto envMap = gz::utils::envStringsToMap(strings); + EXPECT_EQ(3u, envMap.size()); + EXPECT_EQ("GZ_FOO_VAL", envMap["GZ_FOO_KEY"]); + EXPECT_EQ("GZ_BAR_VAL", envMap["GZ_BAR_KEY"]); + EXPECT_EQ("GZ_BAZ_VAL", envMap["GZ_BAZ_KEY"]); + } +} + +///////////////////////////////////////////////// +TEST(Environment, envMapToStrings) +{ + gz::utils::EnvironmentMap envMap; + envMap.insert({{"GZ_FOO_KEY"}, {"GZ_FOO_VAL"}}); + envMap.insert({{"GZ_BAR_KEY"}, {"GZ_BAR_VAL"}}); + envMap.insert({{"GZ_BAZ_KEY"}, {"GZ_BAZ_VAL"}}); + + { + auto envStrings = gz::utils::envMapToStrings(envMap); + + EXPECT_EQ(3u, envStrings.size()); + EXPECT_EQ("GZ_BAR_KEY=GZ_BAR_VAL", envStrings[0]); + EXPECT_EQ("GZ_BAZ_KEY=GZ_BAZ_VAL", envStrings[1]); + EXPECT_EQ("GZ_FOO_KEY=GZ_FOO_VAL", envStrings[2]); + } +} diff --git a/src/NeverDestroyed_TEST.cc b/src/NeverDestroyed_TEST.cc index 30d2b73..1b670b8 100644 --- a/src/NeverDestroyed_TEST.cc +++ b/src/NeverDestroyed_TEST.cc @@ -19,7 +19,12 @@ #include +#include +#include +#include #include +#include +#include #include using namespace gz; diff --git a/test/integration/implptr/ImplPtr_TEST.cc b/test/integration/implptr/ImplPtr_TEST.cc index 3154421..eedbdb9 100644 --- a/test/integration/implptr/ImplPtr_TEST.cc +++ b/test/integration/implptr/ImplPtr_TEST.cc @@ -17,9 +17,10 @@ #include -#include #include "implptr_test_classes.hh" +#include + using namespace gz::implptr_test_classes; ///////////////////////////////////////////////// diff --git a/test/integration/implptr/implptr_test_classes.cc b/test/integration/implptr/implptr_test_classes.cc index 1d13a78..0603bb2 100644 --- a/test/integration/implptr/implptr_test_classes.cc +++ b/test/integration/implptr/implptr_test_classes.cc @@ -17,6 +17,8 @@ #include "implptr_test_classes.hh" +#include + #include #include @@ -153,4 +155,3 @@ void CopyableObjectAlt::SetString(const std::string &_value) { (*dataPtr).svalue = _value; } - diff --git a/test/integration/implptr/implptr_test_classes.hh b/test/integration/implptr/implptr_test_classes.hh index 5b5f2a0..b9e5b3b 100644 --- a/test/integration/implptr/implptr_test_classes.hh +++ b/test/integration/implptr/implptr_test_classes.hh @@ -42,7 +42,7 @@ namespace gz public: int GetInt() const; /// \brief Set the int value held by the pimpl - public: void SetInt(const int _value); + public: void SetInt(int _value); /// \brief Get the string value held by the pimpl public: const std::string &GetString() const; @@ -72,7 +72,7 @@ namespace gz public: int GetInt() const; /// \brief Set the int value held by the pimpl - public: void SetInt(const int _value); + public: void SetInt(int _value); /// \brief Get the string value held by the pimpl public: const std::string &GetString() const; @@ -102,7 +102,7 @@ namespace gz public: int GetInt() const; /// \brief Set the int value held by the pimpl - public: void SetInt(const int _value); + public: void SetInt(int _value); /// \brief Get the string value held by the pimpl public: const std::string &GetString() const; diff --git a/test/integration/subprocess/subprocess_main.cc b/test/integration/subprocess/subprocess_main.cc index 436172e..d3afa05 100644 --- a/test/integration/subprocess/subprocess_main.cc +++ b/test/integration/subprocess/subprocess_main.cc @@ -24,8 +24,8 @@ class OutputSink { - public: OutputSink(const std::string &_dest): - dest(_dest) + public: explicit OutputSink(std::string _dest): + dest(std::move(_dest)) { } @@ -34,13 +34,11 @@ class OutputSink if (dest == "cout" || dest == "both") { std::cout << val << std::endl; - } else if (dest == "cerr" || dest == "both") { std::cerr << val << std::endl; } - return; } private: std::string dest; @@ -60,6 +58,10 @@ int main(int argc, char **argv) int iter_ms = 0; app.add_option("--iteration-ms", iter_ms, "length of one iteration"); + bool environment = false; + app.add_flag("--environment", environment, + "print the environment variables"); + CLI11_PARSE(app, argc, argv); auto sink = OutputSink(output); @@ -69,10 +71,9 @@ int main(int argc, char **argv) std::this_thread::sleep_for(std::chrono::milliseconds(iter_ms)); } - std::string env_var; - if(gz::utils::env("ENV_VAR", env_var)) + if (environment) { - sink.Write("ENV_VAR=" + env_var); + sink.Write(gz::utils::printenv()); } return 0; } diff --git a/test/integration/subprocess_TEST.cc b/test/integration/subprocess_TEST.cc index 32c6ed4..47c8976 100644 --- a/test/integration/subprocess_TEST.cc +++ b/test/integration/subprocess_TEST.cc @@ -21,6 +21,7 @@ #include #include +#include "gz/utils/Environment.hh" using Subprocess = gz::utils::Subprocess; @@ -33,6 +34,28 @@ TEST(Subprocess, CreateInvalid) EXPECT_FALSE(proc.Alive()); } +///////////////////////////////////////////////// +TEST(Subprocess, CreateInvalidSpaces) +{ + // Test if a user passes a string with spaces, rather than + // a vector of strings + std::string command(kExecutablePath); + command.append(" --help"); + auto proc = Subprocess({command}); + + // Block until the executable is done + auto ret = proc.Join(); + EXPECT_EQ(-1, ret); + + EXPECT_FALSE(proc.Alive()); + + auto cout = proc.Stdout(); + auto cerr = proc.Stdout(); + + EXPECT_TRUE(cout.empty()); + EXPECT_TRUE(cerr.empty()); +} + ///////////////////////////////////////////////// TEST(Subprocess, CreateValid) { @@ -94,38 +117,88 @@ TEST(Subprocess, Cerr) ///////////////////////////////////////////////// TEST(Subprocess, Environment) { + ASSERT_TRUE(gz::utils::clearenv()); + ASSERT_TRUE(gz::utils::setenv("GZ_FOO_KEY", "GZ_FOO_VAL")); + ASSERT_TRUE(gz::utils::setenv("GZ_BAR_KEY", "GZ_BAR_VAL")); + ASSERT_TRUE(gz::utils::setenv("GZ_BAZ_KEY", "GZ_BAZ_VAL")); + + { + // Default behavior is to inherit the environment + auto proc = Subprocess({kExecutablePath, "--output=cout", "--environment"}); + // Block until the executable is done + auto ret = proc.Join(); + EXPECT_EQ(0u, ret); + + auto cout = proc.Stdout(); + EXPECT_NE(std::string::npos, cout.find("GZ_FOO_KEY=GZ_FOO_VAL")); + EXPECT_NE(std::string::npos, cout.find("GZ_BAR_KEY=GZ_BAR_VAL")); + EXPECT_NE(std::string::npos, cout.find("GZ_BAZ_KEY=GZ_BAZ_VAL")); + } + { - auto proc = Subprocess({kExecutablePath, "--output=cout"}, - {"ENV_VAR=foobar"}); + // Passing an empty map as the second arg clears the environment + auto proc = Subprocess( + {kExecutablePath, "--output=cout", "--environment"}, + gz::utils::EnvironmentMap()); // Block until the executable is done auto ret = proc.Join(); EXPECT_EQ(0u, ret); auto cout = proc.Stdout(); - EXPECT_NE(std::string::npos, cout.find("ENV_VAR=foobar")); + EXPECT_EQ(std::string::npos, cout.find("GZ_FOO_KEY=GZ_FOO_VAL")); + EXPECT_EQ(std::string::npos, cout.find("GZ_BAR_KEY=GZ_BAR_VAL")); + EXPECT_EQ(std::string::npos, cout.find("GZ_BAZ_KEY=GZ_BAZ_VAL")); } { - auto proc = Subprocess({kExecutablePath, "--output=cerr"}, - {"ENV_VAR=foobar2"}); + // Passing an empty vector as the second arg clears the environment + auto proc = Subprocess( + {kExecutablePath, "--output=cout", "--environment"}, + gz::utils::EnvironmentStrings()); // Block until the executable is done auto ret = proc.Join(); EXPECT_EQ(0u, ret); - auto cerr = proc.Stderr(); - EXPECT_NE(std::string::npos, cerr.find("ENV_VAR=foobar2")); + auto cout = proc.Stdout(); + EXPECT_EQ(std::string::npos, cout.find("GZ_FOO_KEY=GZ_FOO_VAL")); + EXPECT_EQ(std::string::npos, cout.find("GZ_BAR_KEY=GZ_BAR_VAL")); + EXPECT_EQ(std::string::npos, cout.find("GZ_BAZ_KEY=GZ_BAZ_VAL")); } { - auto proc = Subprocess({kExecutablePath}, - {"ENV_VAR=foobar3"}); + // Passing a map sets those variables, clearing the rest + auto proc = Subprocess( + {kExecutablePath, "--output=cout", "--environment"}, + gz::utils::EnvironmentMap{ + {"QUX_KEY", "QUX_VAL"} + }); // Block until the executable is done auto ret = proc.Join(); EXPECT_EQ(0u, ret); auto cout = proc.Stdout(); - auto cerr = proc.Stderr(); - EXPECT_EQ(std::string::npos, cerr.find("ENV_VAR=foobar3")); - EXPECT_EQ(std::string::npos, cout.find("ENV_VAR=foobar3")); + EXPECT_EQ(std::string::npos, cout.find("GZ_FOO_KEY=GZ_FOO_VAL")); + EXPECT_EQ(std::string::npos, cout.find("GZ_BAR_KEY=GZ_BAR_VAL")); + EXPECT_EQ(std::string::npos, cout.find("GZ_BAZ_KEY=GZ_BAZ_VAL")); + EXPECT_NE(std::string::npos, cout.find("QUX_KEY=QUX_VAL")); } + + { + // Passing a map sets those variables, clearing the rest + auto proc = Subprocess( + {kExecutablePath, "--output=cout", "--environment"}, + gz::utils::EnvironmentStrings{ + {"QUX_KEY=QUX_VAL"} + }); + // Block until the executable is done + auto ret = proc.Join(); + EXPECT_EQ(0u, ret); + + auto cout = proc.Stdout(); + EXPECT_EQ(std::string::npos, cout.find("GZ_FOO_KEY=GZ_FOO_VAL")); + EXPECT_EQ(std::string::npos, cout.find("GZ_BAR_KEY=GZ_BAR_VAL")); + EXPECT_EQ(std::string::npos, cout.find("GZ_BAZ_KEY=GZ_BAZ_VAL")); + EXPECT_NE(std::string::npos, cout.find("QUX_KEY=QUX_VAL")); + } + } diff --git a/test/integration/win_dirent.h b/test/integration/win_dirent.h deleted file mode 100644 index 8ee5508..0000000 --- a/test/integration/win_dirent.h +++ /dev/null @@ -1,945 +0,0 @@ -/* - * Dirent interface for Microsoft Visual Studio - * Version 1.21 - * - * 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 - */ -#ifndef DIRENT_H -#define DIRENT_H - -/* - * 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 - -/* 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 d_namlen without zero terminator */ -#define _D_EXACT_NAMLEN(p) ((p)->d_namlen) - -/* Return number of bytes needed to store d_namlen */ -#define _D_ALLOC_NAMLEN(p) (PATH_MAX) - - -#ifdef __cplusplus -extern "C" { -#endif - - -/* Wide-character version */ -struct _wdirent { - /* Always zero */ - long d_ino; - - /* Structure size */ - unsigned short d_reclen; - - /* Length of name without \0 */ - size_t d_namlen; - - /* File type */ - int d_type; - - /* File name */ - // cppcheck-suppress * - wchar_t d_name[PATH_MAX]; -}; -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; - - /* Win32 search handle */ - HANDLE handle; - - /* Initial directory name */ - wchar_t *patt; -}; -typedef struct _WDIR _WDIR; - -static _WDIR *_wopendir (const wchar_t *dirname); -static struct _wdirent *_wreaddir (_WDIR *dirp); -static int _wclosedir (_WDIR *dirp); -static void _wrewinddir (_WDIR* dirp); - - -/* For compatibility with Symbian */ -#define wdirent _wdirent -#define WDIR _WDIR -#define wopendir _wopendir -#define wreaddir _wreaddir -#define wclosedir _wclosedir -#define wrewinddir _wrewinddir - - -/* Multi-byte character versions */ -struct dirent { - /* Always zero */ - long d_ino; - - /* Structure size */ - unsigned short d_reclen; - - /* Length of name without \0 */ - size_t d_namlen; - - /* File type */ - int d_type; - - /* File name */ - // cppcheck-suppress * - char d_name[PATH_MAX]; -}; -typedef struct dirent dirent; - -struct DIR { - struct dirent ent; - struct _WDIR *wdirp; -}; -typedef struct DIR DIR; - -static DIR *opendir (const char *dirname); -static struct dirent *readdir (DIR *dirp); -static int closedir (DIR *dirp); -static void rewinddir (DIR* dirp); - - -/* Internal utility functions */ -static WIN32_FIND_DATAW *dirent_first (_WDIR *dirp); -static WIN32_FIND_DATAW *dirent_next (_WDIR *dirp); - -static int dirent_mbstowcs_s( - size_t *pReturnValue, - wchar_t *wcstr, - size_t sizeInWords, - const char *mbstr, - size_t count); - -static int dirent_wcstombs_s( - size_t *pReturnValue, - char *mbstr, - size_t sizeInBytes, - const wchar_t *wcstr, - size_t count); - -static void dirent_set_errno (int error); - -/* - * 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) -{ - _WDIR *dirp = NULL; - int error; - - /* Must have directory name */ - if (dirname == NULL || dirname[0] == '\0') { - dirent_set_errno (ENOENT); - return NULL; - } - - /* Allocate new _WDIR structure */ - dirp = (_WDIR*) malloc (sizeof (struct _WDIR)); - if (dirp != NULL) { - DWORD n; - - /* Reset _WDIR structure */ - dirp->handle = INVALID_HANDLE_VALUE; - dirp->patt = NULL; - dirp->cached = 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 its an absolute path. - */ -# if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP) - n = wcslen(dirname); -# else - n = GetFullPathNameW (dirname, 0, NULL, NULL); -# endif - - /* Allocate room for absolute directory name and search pattern */ - dirp->patt = (wchar_t*) malloc (sizeof (wchar_t) * n + 16); - if (dirp->patt) { - - /* - * 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 its an absolute path. - */ -# if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP) - wcsncpy_s(dirp->patt, n+1, dirname, n); -# else - n = GetFullPathNameW (dirname, n, dirp->patt, NULL); -# endif - if (n > 0) { - wchar_t *p; - - /* Append search pattern \* to the directory name */ - p = dirp->patt + n; - if (dirp->patt < p) { - 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)) { - /* Directory stream opened successfully */ - error = 0; - } else { - /* Cannot retrieve first entry */ - error = 1; - dirent_set_errno (ENOENT); - } - - } else { - /* Cannot retrieve full path name */ - dirent_set_errno (ENOENT); - error = 1; - } - - } else { - /* Cannot allocate memory for search pattern */ - error = 1; - } - - } else { - /* Cannot allocate _WDIR structure */ - error = 1; - } - - /* Clean up in case of error */ - if (error && dirp) { - _wclosedir (dirp); - dirp = NULL; - } - - return dirp; -} - -/* - * Read next directory entry. The directory entry is returned in dirent - * structure in the d_name field. Individual directory entries returned by - * this function include regular files, sub-directories, pseudo-directories - * "." and ".." as well as volume labels, hidden files and system files. - */ -static struct _wdirent* -_wreaddir( - _WDIR *dirp) -{ - WIN32_FIND_DATAW *datap; - struct _wdirent *entp; - - /* Read next directory entry */ - datap = dirent_next (dirp); - if (datap) { - size_t n; - DWORD attr; - - /* Pointer to directory entry to return */ - entp = &dirp->ent; - - /* - * 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. - */ - n = 0; - // cppcheck-suppress * - while (n + 1 < PATH_MAX && datap->cFileName[n] != 0) { - entp->d_name[n] = datap->cFileName[n]; - n++; - } - dirp->ent.d_name[n] = 0; - - /* Length of file name excluding zero terminator */ - entp->d_namlen = n; - - /* File type */ - attr = datap->dwFileAttributes; - // cppcheck-suppress * - if ((attr & FILE_ATTRIBUTE_DEVICE) != 0) { - // cppcheck-suppress * - entp->d_type = DT_CHR; - } else if ((attr & FILE_ATTRIBUTE_DIRECTORY) != 0) { - // cppcheck-suppress * - entp->d_type = DT_DIR; - } else { - // cppcheck-suppress * - entp->d_type = DT_REG; - } - - /* Reset dummy fields */ - entp->d_ino = 0; - entp->d_reclen = sizeof (struct _wdirent); - - } else { - - /* Last directory entry read */ - entp = NULL; - - } - - return entp; -} - -/* - * 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) -{ - int ok; - if (dirp) { - - /* Release search handle */ - if (dirp->handle != INVALID_HANDLE_VALUE) { - FindClose (dirp->handle); - dirp->handle = INVALID_HANDLE_VALUE; - } - - /* Release search pattern */ - if (dirp->patt) { - free (dirp->patt); - dirp->patt = NULL; - } - - /* Release directory structure */ - free (dirp); - ok = /*success*/0; - - } else { - /* Invalid directory stream */ - dirent_set_errno (EBADF); - ok = /*failure*/-1; - } - return ok; -} - -/* - * Rewind directory stream such that _wreaddir() returns the very first - * file name again. - */ -static void -_wrewinddir( - _WDIR* dirp) -{ - if (dirp) { - /* Release existing search handle */ - if (dirp->handle != INVALID_HANDLE_VALUE) { - FindClose (dirp->handle); - } - - /* Open new search handle */ - dirent_first (dirp); - } -} - -/* Get first directory entry (internal) */ -static WIN32_FIND_DATAW* -dirent_first( - _WDIR *dirp) -{ - WIN32_FIND_DATAW *datap; - - /* Open directory and retrieve the first entry */ - dirp->handle = FindFirstFileExW( - dirp->patt, FindExInfoStandard, &dirp->data, - FindExSearchNameMatch, NULL, 0); - if (dirp->handle != INVALID_HANDLE_VALUE) { - - /* a directory entry is now waiting in memory */ - datap = &dirp->data; - dirp->cached = 1; - - } else { - - /* Failed to re-open directory: no directory entry in memory */ - dirp->cached = 0; - datap = NULL; - - } - return datap; -} - -/* Get next directory entry (internal) */ -static WIN32_FIND_DATAW* -dirent_next( - _WDIR *dirp) -{ - WIN32_FIND_DATAW *p; - - /* Get next directory entry */ - if (dirp->cached != 0) { - - /* A valid directory entry already in memory */ - p = &dirp->data; - dirp->cached = 0; - - } else if (dirp->handle != INVALID_HANDLE_VALUE) { - - /* Get the next directory entry from stream */ - if (FindNextFileW (dirp->handle, &dirp->data) != FALSE) { - /* Got a file */ - p = &dirp->data; - } else { - /* The very last entry has been processed or an error occured */ - FindClose (dirp->handle); - dirp->handle = INVALID_HANDLE_VALUE; - p = NULL; - } - - } else { - - /* End of directory stream reached */ - p = NULL; - - } - - return p; -} - -/* - * Open directory stream using plain old C-string. - */ -static DIR* -opendir( - const char *dirname) -{ - struct DIR *dirp; - int error; - - /* Must have directory name */ - if (dirname == NULL || dirname[0] == '\0') { - dirent_set_errno (ENOENT); - return NULL; - } - - /* Allocate memory for DIR structure */ - dirp = (DIR*) malloc (sizeof (struct DIR)); - if (dirp) { - // cppcheck-suppress * - wchar_t wname[PATH_MAX]; - size_t n; - - /* Convert directory name to wide-character string */ - // cppcheck-suppress * - error = dirent_mbstowcs_s (&n, wname, PATH_MAX, dirname, PATH_MAX); - if (!error) { - - /* Open directory stream using wide-character name */ - dirp->wdirp = _wopendir (wname); - if (dirp->wdirp) { - /* Directory stream opened */ - error = 0; - } else { - /* Failed to open directory stream */ - error = 1; - } - - } else { - /* - * Cannot convert file name to wide-character string. This - * occurs if the string contains invalid multi-byte sequences or - * the output buffer is too small to contain the resulting - * string. - */ - error = 1; - } - - } else { - /* Cannot allocate DIR structure */ - error = 1; - } - - /* Clean up in case of error */ - if (error && dirp) { - free (dirp); - dirp = NULL; - } - - return dirp; -} - -/* - * Read next directory entry. - * - * When working with text consoles, please note that file names returned by - * readdir() are represented in the default ANSI code page while any output to - * console is typically formatted on another code page. Thus, non-ASCII - * characters in file names will not usually display correctly on console. The - * problem can be fixed in two ways: (1) change the character set of console - * to 1252 using chcp utility and use Lucida Console font, or (2) use - * _cprintf function when writing to console. The _cprinf() will re-encode - * ANSI strings to the console code page so many non-ASCII characters will - * display correcly. - */ -static struct dirent* -readdir( - DIR *dirp) -{ - WIN32_FIND_DATAW *datap; - struct dirent *entp; - - /* Read next directory entry */ - datap = dirent_next (dirp->wdirp); - if (datap) { - size_t n; - int error; - - /* Attempt to convert file name to multi-byte string */ - error = dirent_wcstombs_s( - // cppcheck-suppress * - &n, dirp->ent.d_name, PATH_MAX, datap->cFileName, PATH_MAX); - - /* - * If the file name cannot be represented by a multi-byte string, - * then attempt to use old 8+3 file name. This allows traditional - * Unix-code to access some file names despite of unicode - * characters, 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 = dirent_wcstombs_s( - // cppcheck-suppress * - &n, dirp->ent.d_name, PATH_MAX, - // cppcheck-suppress * - datap->cAlternateFileName, PATH_MAX); - } - - if (!error) { - DWORD attr; - - /* Initialize directory entry for return */ - entp = &dirp->ent; - - /* Length of file name excluding zero terminator */ - entp->d_namlen = n - 1; - - /* File attributes */ - attr = datap->dwFileAttributes; - // cppcheck-suppress * - if ((attr & FILE_ATTRIBUTE_DEVICE) != 0) { - // cppcheck-suppress * - entp->d_type = DT_CHR; - } else if ((attr & FILE_ATTRIBUTE_DIRECTORY) != 0) { - // cppcheck-suppress * - entp->d_type = DT_DIR; - } else { - // cppcheck-suppress * - entp->d_type = DT_REG; - } - - /* Reset dummy fields */ - entp->d_ino = 0; - entp->d_reclen = sizeof (struct dirent); - - } else { - /* - * Cannot convert file name to multi-byte string so construct - * an errornous directory entry and return that. Note that - * we cannot return NULL as that would stop the processing - * of directory entries completely. - */ - entp = &dirp->ent; - entp->d_name[0] = '?'; - entp->d_name[1] = '\0'; - entp->d_namlen = 1; - entp->d_type = DT_UNKNOWN; - entp->d_ino = 0; - entp->d_reclen = 0; - } - - } else { - /* No more directory entries */ - entp = NULL; - } - - return entp; -} - -/* - * Close directory stream. - */ -static int -closedir( - DIR *dirp) -{ - int ok; - if (dirp) { - - /* Close wide-character directory stream */ - ok = _wclosedir (dirp->wdirp); - dirp->wdirp = NULL; - - /* Release multi-byte character version */ - free (dirp); - - } else { - - /* Invalid directory stream */ - dirent_set_errno (EBADF); - ok = /*failure*/-1; - - } - return ok; -} - -/* - * Rewind directory stream to beginning. - */ -static void -rewinddir( - DIR* dirp) -{ - /* Rewind wide-character string directory stream */ - _wrewinddir (dirp->wdirp); -} - -/* Convert multi-byte string to wide character string */ -static int -dirent_mbstowcs_s( - size_t *pReturnValue, - wchar_t *wcstr, - size_t sizeInWords, - const char *mbstr, - size_t count) -{ - int error; - -#if defined(_MSC_VER) && _MSC_VER >= 1400 - - /* Microsoft Visual Studio 2005 or later */ - error = mbstowcs_s (pReturnValue, wcstr, sizeInWords, mbstr, count); - -#else - - /* Older Visual Studio or non-Microsoft compiler */ - size_t n; - - /* Convert to wide-character string (or count characters) */ - n = mbstowcs (wcstr, mbstr, sizeInWords); - if (!wcstr || n < count) { - - /* Zero-terminate output buffer */ - if (wcstr && sizeInWords) { - if (n >= sizeInWords) { - n = sizeInWords - 1; - } - wcstr[n] = 0; - } - - /* Length of resuting multi-byte string WITH zero terminator */ - if (pReturnValue) { - *pReturnValue = n + 1; - } - - /* Success */ - error = 0; - - } else { - - /* Could not convert string */ - error = 1; - - } - -#endif - - return error; -} - -/* Convert wide-character string to multi-byte string */ -static int -dirent_wcstombs_s( - size_t *pReturnValue, - char *mbstr, - size_t sizeInBytes, /* max size of mbstr */ - const wchar_t *wcstr, - size_t count) -{ - int error; - -#if defined(_MSC_VER) && _MSC_VER >= 1400 - - /* Microsoft Visual Studio 2005 or later */ - error = wcstombs_s (pReturnValue, mbstr, sizeInBytes, wcstr, count); - -#else - - /* Older Visual Studio or non-Microsoft compiler */ - size_t n; - - /* Convert to multi-byte string (or count the number of bytes needed) */ - n = wcstombs (mbstr, wcstr, sizeInBytes); - if (!mbstr || n < count) { - - /* 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 */ - error = 0; - - } else { - - /* Cannot convert string */ - error = 1; - - } - -#endif - - return error; -} - -/* Set errno variable */ -static void -dirent_set_errno( - int error) -{ -#if defined(_MSC_VER) && _MSC_VER >= 1400 - - /* Microsoft Visual Studio 2005 and later */ - _set_errno (error); - -#else - - /* Non-Microsoft compiler or older Microsoft compiler */ - errno = error; - -#endif -} - - -#ifdef __cplusplus -} -#endif -#endif /*DIRENT_H*/ -