From 45c84502092bde08eaed448a3e6fc21c482fbdba Mon Sep 17 00:00:00 2001 From: q66 Date: Sat, 13 May 2017 18:35:58 +0200 Subject: [PATCH] envvar support in subprocess --- build.cc | 2 +- ostd/process.hh | 115 ++++++++++++++++++++++++++++++------------- src/posix/process.cc | 42 +++++++++++++--- src/win32/process.cc | 41 +++++++++++++-- 4 files changed, 155 insertions(+), 45 deletions(-) diff --git a/build.cc b/build.cc index 761cb67..bf66e08 100644 --- a/build.cc +++ b/build.cc @@ -106,7 +106,7 @@ static void print_help(ostd::string_range arg0) { } static void exec_command(strvec const &args) { - if (int ret = ostd::subprocess{ostd::iter(args)}.close(); ret) { + if (int ret = ostd::subprocess{nullptr, ostd::iter(args)}.close(); ret) { throw std::runtime_error{ostd::format( ostd::appender(), "command failed with code %d", ret ).get()}; diff --git a/ostd/process.hh b/ostd/process.hh index a163a27..d0f5d26 100644 --- a/ostd/process.hh +++ b/ostd/process.hh @@ -107,6 +107,10 @@ enum class subprocess_stream { * as well as the `exec` family of functions. */ struct OSTD_EXPORT subprocess { +private: + struct nat {}; + +public: /** @brief The standard input redirection mode. * * The value is one of ostd::subprocess_stream. Set this before opening @@ -192,19 +196,20 @@ struct OSTD_EXPORT subprocess { * after constructing the object depending on `use_path`. It may * throw the same exceptions as the respective two methods. */ - template + template subprocess( - string_range cmd, InputRange &&args, bool use_path = true, + string_range cmd, InputRange1 &&args, InputRange2 &&envs, + bool use_path = true, subprocess_stream in_use = subprocess_stream::DEFAULT, subprocess_stream out_use = subprocess_stream::DEFAULT, - subprocess_stream err_use = subprocess_stream::DEFAULT, - std::enable_if_t< - is_input_range, bool - > = true + subprocess_stream err_use = subprocess_stream::DEFAULT ): use_in(in_use), use_out(out_use), use_err(err_use) { - open_full(cmd, std::forward(args), use_path); + open_full( + cmd, std::forward(args), + std::forward(envs), use_path + ); } /** @brief Initializes the structure and opens a subprocess. @@ -213,19 +218,16 @@ struct OSTD_EXPORT subprocess { * after constructing the object depending on `use_path`. It may * throw the same exceptions as the respective two methods. */ - template + template subprocess( - InputRange &&args, bool use_path = true, + string_range cmd, InputRange1 &&args, bool use_path = true, subprocess_stream in_use = subprocess_stream::DEFAULT, subprocess_stream out_use = subprocess_stream::DEFAULT, - subprocess_stream err_use = subprocess_stream::DEFAULT, - std::enable_if_t< - is_input_range, bool - > = true + subprocess_stream err_use = subprocess_stream::DEFAULT ): use_in(in_use), use_out(out_use), use_err(err_use) { - open_full(nullptr, std::forward(args), use_path); + open_full(cmd, std::forward(args), nullptr, use_path); } /** @brief Moves the subprocess data. */ @@ -297,6 +299,12 @@ struct OSTD_EXPORT subprocess { * * If `path` is empty, the first element of `args` is used. * + * The `envs` argument represents a range of string-like types just like + * `args` and each string represents an environment variable of the + * subprocess, in format `name=value`. If you do not want to specify + * custom environment variables, use an overload without the `envs` + * argument - the child process will inherit its parent's environment. + * * If this fails for any reason, ostd::subprocess_error will be thrown. * Having another child process running is considered a failure, so * use close() or detach() before opening another. @@ -309,15 +317,18 @@ struct OSTD_EXPORT subprocess { * * @see open_command(), close() */ - template - void open_path(string_range path, InputRange &&args) { - open_full(path, std::forward(args), false); + template + void open_path(string_range path, InputRange1 &&args, InputRange2 &&envs) { + open_full( + path, std::forward(args), + std::forward(envs), false + ); } - /** @brief Like open_path() with an empty first argument. */ + /** @brief See the main open_path() documentation. */ template - void open_path(InputRange &&args) { - open_path(nullptr, std::forward(args)); + void open_path(string_range path, InputRange &&args) { + open_full(path, std::forward(args), nullptr, false); } /** @brief Opens a subprocess using the given arguments. @@ -331,6 +342,12 @@ struct OSTD_EXPORT subprocess { * * If `cmd` is empty, the first element of `args` is used. * + * The `envs` argument represents a range of string-like types just like + * `args` and each string represents an environment variable of the + * subprocess, in format `name=value`. If you do not want to specify + * custom environment variables, use an overload without the `envs` + * argument - the child process will inherit its parent's environment. + * * If this fails for any reason, ostd::subprocess_error will be thrown. * Having another child process running is considered a failure, so * use close() or detach() before opening another. @@ -343,15 +360,18 @@ struct OSTD_EXPORT subprocess { * * @see open_path(), close() */ - template - void open_command(string_range cmd, InputRange &&args) { - open_full(cmd, std::forward(args), true); + template + void open_command(string_range cmd, InputRange1 &&args, InputRange2 &&envs) { + open_full( + cmd, std::forward(args), + std::forward(envs), true + ); } - /** @brief Like open_command() with an empty first argument. */ + /** @brief See the main open_command() documentation. */ template - void open_command(InputRange &&args) { - open_command(nullptr, std::forward(args)); + void open_command(string_range cmd, InputRange &&args) { + open_full(cmd, std::forward(args), nullptr, true); } /** @brief Detaches the child process. @@ -380,31 +400,60 @@ struct OSTD_EXPORT subprocess { } private: - template - void open_full(string_range cmd, InputRange args, bool use_path) { + template + void open_full( + string_range cmd, InputRange1 args, InputRange2 env, bool use_path + ) { static_assert( - std::is_constructible_v>, + std::is_constructible_v< + string_range, range_reference_t + >, "The arguments must be strings" ); + if constexpr(!std::is_null_pointer_v) { + static_assert( + std::is_constructible_v< + string_range, range_reference_t + >, + "The environment variables must be strings" + ); + } if (p_current) { throw subprocess_error{"another child process already running"}; } - open_impl(use_path, cmd, [](string_range &arg, void *data) { - InputRange &argr = *static_cast(data); + auto argf = [](string_range &arg, void *data) { + InputRange1 &argr = *static_cast(data); if (argr.empty()) { return false; } arg = string_range{argr.front()}; argr.pop_front(); return true; - }, &args); + }; + bool (*envf)(string_range &, void *) = nullptr; + void *argp = &args, *envp = nullptr; + + if constexpr(!std::is_null_pointer_v) { + envf = [](string_range envv, void *data) { + InputRange2 &envr = *static_cast(data); + if (envr.empty()) { + return false; + } + envv = string_range{envr.front()}; + envr.pop_front(); + return true; + }; + envp = &env; + } + open_impl(use_path, cmd, argf, argp, envf, envp); } void open_impl( bool use_path, string_range cmd, - bool (*func)(string_range &, void *), void *data + bool (*func)(string_range &, void *), void *data, + bool (*efunc)(string_range &, void *), void *edatap ); void reset(); diff --git a/src/posix/process.cc b/src/posix/process.cc index 34655c0..b0b3c92 100644 --- a/src/posix/process.cc +++ b/src/posix/process.cc @@ -133,7 +133,8 @@ struct pipe { OSTD_EXPORT void subprocess::open_impl( bool use_path, string_range cmd, - bool (*func)(string_range &, void *), void *datap + bool (*func)(string_range &, void *), void *datap, + bool (*efunc)(string_range &, void *), void *edatap ) { if (use_in == subprocess_stream::STDOUT) { throw subprocess_error{"could not redirect stdin to stdout"}; @@ -143,15 +144,16 @@ OSTD_EXPORT void subprocess::open_impl( char *cmd = nullptr; std::vector vec; ~argpv() { - auto vs = vec.size(); - if (!vs) { + if (vec.empty()) { return; } if (cmd && (cmd != vec[0])) { delete[] cmd; } - for (std::size_t i = 0; i < (vs - 1); ++i) { - delete[] vec[i]; + for (char *p: vec) { + if (p) { + delete[] p; + } } } } argp; @@ -179,8 +181,24 @@ OSTD_EXPORT void subprocess::open_impl( argp.cmd = str; } - /* terminate */ + /* terminate args */ argp.vec.push_back(nullptr); + char **argpp = argp.vec.data(); + + /* environment */ + char **envp = nullptr; + if (edatap) { + auto vsz = argp.vec.size(); + for (string_range r; efunc(r, edatap);) { + auto sz = r.size(); + char *str = new char[sz + 1]; + string_range::traits_type::copy(str, r.data(), sz)[sz] = '\0'; + argp.vec.push_back(str); + } + /* terminate environment */ + argp.vec.push_back(nullptr); + envp = &argp.vec[vsz]; + } /* fd_errno used to detect if exec failed */ pipe fd_errno, fd_stdin, fd_stdout, fd_stderr; @@ -226,9 +244,17 @@ OSTD_EXPORT void subprocess::open_impl( } } if (use_path) { - execvp(argp.cmd, argp.vec.data()); + if (envp) { + execvpe(argp.cmd, argpp, envp); + } else { + execvp(argp.cmd, argpp); + } } else { - execv(argp.cmd, argp.vec.data()); + if (envp) { + execve(argp.cmd, argpp, envp); + } else { + execv(argp.cmd, argpp); + } } /* exec has returned, so error has occured */ fd_errno.write_errno(); diff --git a/src/win32/process.cc b/src/win32/process.cc index f46ac55..d5942d2 100644 --- a/src/win32/process.cc +++ b/src/win32/process.cc @@ -324,7 +324,8 @@ static std::unique_ptr concat_args( OSTD_EXPORT void subprocess::open_impl( bool use_path, string_range cmd, - bool (*func)(string_range &, void *), void *datap + bool (*func)(string_range &, void *), void *datap, + bool (*efunc)(string_range &, void *), void *edatap ) { if (use_in == subprocess_stream::STDOUT) { throw subprocess_error{"could not redirect stdin to stdout"}; @@ -418,6 +419,40 @@ OSTD_EXPORT void subprocess::open_impl( std::wstring cmdpath; auto cmdline = concat_args(cmd, func, datap, cmdpath); + std::wstring envstr; + if (edatap) { + for (string_range r; efunc(r, edatap);) { + std::unique_ptr wr{new wchar_t[r.size() + 1]}; + auto req = MultiByteToWideChar( + CP_UTF8, 0, r.data(), r.size(), wr.get(), wr.size() + 1 + ); + if (!req) { + throw subprocess_error{"unicode conversion failed"}; + } + wr.get()[req] = L'\0'; + /* include terminating zero */ + envstr.append(wr.get(), req + 1); + } + } else { + struct env_guard { + wchar_t *envp = nullptr; + env_guard() { + envp = GetEnvironmentStringsW(); + if (!envp) { + throw subprocess_error{"could not get environment block"}; + } + } + ~env_guard() { + FreeEnvironmentStrings(envp); + } + } envs; + wchar_t *endp = envs.envp; + while (*endp) { + endp += wcslen(endp) + 1; + } + envstr.append(envs.envp, std::size_t(endp - envs.envp)); + } + /* owned by CreateProcess, do not close explicitly */ pipe_in.p_r = nullptr; pipe_out.p_w = nullptr; @@ -432,8 +467,8 @@ OSTD_EXPORT void subprocess::open_impl( nullptr, /* process security attributes */ nullptr, /* primary thread security attributes */ true, /* inherit handles */ - CREATE_SUSPENDED, /* creation flags */ - nullptr, /* use parent env */ + CREATE_SUSPENDED | CREATE_UNICODE_ENVIRONMENT, /* creation flags */ + envstr.data(), /* use parent env */ nullptr, /* use parent cwd */ &si, &pi