forked from OctaForge/libostd
envvar support in subprocess
This commit is contained in:
parent
2fc506d1bb
commit
45c8450209
2
build.cc
2
build.cc
|
@ -106,7 +106,7 @@ static void print_help(ostd::string_range arg0) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static void exec_command(strvec const &args) {
|
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(
|
throw std::runtime_error{ostd::format(
|
||||||
ostd::appender<std::string>(), "command failed with code %d", ret
|
ostd::appender<std::string>(), "command failed with code %d", ret
|
||||||
).get()};
|
).get()};
|
||||||
|
|
115
ostd/process.hh
115
ostd/process.hh
|
@ -107,6 +107,10 @@ enum class subprocess_stream {
|
||||||
* as well as the `exec` family of functions.
|
* as well as the `exec` family of functions.
|
||||||
*/
|
*/
|
||||||
struct OSTD_EXPORT subprocess {
|
struct OSTD_EXPORT subprocess {
|
||||||
|
private:
|
||||||
|
struct nat {};
|
||||||
|
|
||||||
|
public:
|
||||||
/** @brief The standard input redirection mode.
|
/** @brief The standard input redirection mode.
|
||||||
*
|
*
|
||||||
* The value is one of ostd::subprocess_stream. Set this before opening
|
* 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
|
* after constructing the object depending on `use_path`. It may
|
||||||
* throw the same exceptions as the respective two methods.
|
* throw the same exceptions as the respective two methods.
|
||||||
*/
|
*/
|
||||||
template<typename InputRange>
|
template<typename InputRange1, typename InputRange2>
|
||||||
subprocess(
|
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 in_use = subprocess_stream::DEFAULT,
|
||||||
subprocess_stream out_use = subprocess_stream::DEFAULT,
|
subprocess_stream out_use = subprocess_stream::DEFAULT,
|
||||||
subprocess_stream err_use = subprocess_stream::DEFAULT,
|
subprocess_stream err_use = subprocess_stream::DEFAULT
|
||||||
std::enable_if_t<
|
|
||||||
is_input_range<InputRange>, bool
|
|
||||||
> = true
|
|
||||||
):
|
):
|
||||||
use_in(in_use), use_out(out_use), use_err(err_use)
|
use_in(in_use), use_out(out_use), use_err(err_use)
|
||||||
{
|
{
|
||||||
open_full(cmd, std::forward<InputRange>(args), use_path);
|
open_full(
|
||||||
|
cmd, std::forward<InputRange1>(args),
|
||||||
|
std::forward<InputRange2>(envs), use_path
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @brief Initializes the structure and opens a subprocess.
|
/** @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
|
* after constructing the object depending on `use_path`. It may
|
||||||
* throw the same exceptions as the respective two methods.
|
* throw the same exceptions as the respective two methods.
|
||||||
*/
|
*/
|
||||||
template<typename InputRange>
|
template<typename InputRange1>
|
||||||
subprocess(
|
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 in_use = subprocess_stream::DEFAULT,
|
||||||
subprocess_stream out_use = subprocess_stream::DEFAULT,
|
subprocess_stream out_use = subprocess_stream::DEFAULT,
|
||||||
subprocess_stream err_use = subprocess_stream::DEFAULT,
|
subprocess_stream err_use = subprocess_stream::DEFAULT
|
||||||
std::enable_if_t<
|
|
||||||
is_input_range<InputRange>, bool
|
|
||||||
> = true
|
|
||||||
):
|
):
|
||||||
use_in(in_use), use_out(out_use), use_err(err_use)
|
use_in(in_use), use_out(out_use), use_err(err_use)
|
||||||
{
|
{
|
||||||
open_full(nullptr, std::forward<InputRange>(args), use_path);
|
open_full(cmd, std::forward<InputRange1>(args), nullptr, use_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @brief Moves the subprocess data. */
|
/** @brief Moves the subprocess data. */
|
||||||
|
@ -297,6 +299,12 @@ struct OSTD_EXPORT subprocess {
|
||||||
*
|
*
|
||||||
* If `path` is empty, the first element of `args` is used.
|
* 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.
|
* If this fails for any reason, ostd::subprocess_error will be thrown.
|
||||||
* Having another child process running is considered a failure, so
|
* Having another child process running is considered a failure, so
|
||||||
* use close() or detach() before opening another.
|
* use close() or detach() before opening another.
|
||||||
|
@ -309,15 +317,18 @@ struct OSTD_EXPORT subprocess {
|
||||||
*
|
*
|
||||||
* @see open_command(), close()
|
* @see open_command(), close()
|
||||||
*/
|
*/
|
||||||
template<typename InputRange>
|
template<typename InputRange1, typename InputRange2>
|
||||||
void open_path(string_range path, InputRange &&args) {
|
void open_path(string_range path, InputRange1 &&args, InputRange2 &&envs) {
|
||||||
open_full(path, std::forward<InputRange>(args), false);
|
open_full(
|
||||||
|
path, std::forward<InputRange1>(args),
|
||||||
|
std::forward<InputRange2>(envs), false
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @brief Like open_path() with an empty first argument. */
|
/** @brief See the main open_path() documentation. */
|
||||||
template<typename InputRange>
|
template<typename InputRange>
|
||||||
void open_path(InputRange &&args) {
|
void open_path(string_range path, InputRange &&args) {
|
||||||
open_path(nullptr, std::forward<InputRange>(args));
|
open_full(path, std::forward<InputRange>(args), nullptr, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @brief Opens a subprocess using the given arguments.
|
/** @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.
|
* 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.
|
* If this fails for any reason, ostd::subprocess_error will be thrown.
|
||||||
* Having another child process running is considered a failure, so
|
* Having another child process running is considered a failure, so
|
||||||
* use close() or detach() before opening another.
|
* use close() or detach() before opening another.
|
||||||
|
@ -343,15 +360,18 @@ struct OSTD_EXPORT subprocess {
|
||||||
*
|
*
|
||||||
* @see open_path(), close()
|
* @see open_path(), close()
|
||||||
*/
|
*/
|
||||||
template<typename InputRange>
|
template<typename InputRange1, typename InputRange2>
|
||||||
void open_command(string_range cmd, InputRange &&args) {
|
void open_command(string_range cmd, InputRange1 &&args, InputRange2 &&envs) {
|
||||||
open_full(cmd, std::forward<InputRange>(args), true);
|
open_full(
|
||||||
|
cmd, std::forward<InputRange1>(args),
|
||||||
|
std::forward<InputRange2>(envs), true
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @brief Like open_command() with an empty first argument. */
|
/** @brief See the main open_command() documentation. */
|
||||||
template<typename InputRange>
|
template<typename InputRange>
|
||||||
void open_command(InputRange &&args) {
|
void open_command(string_range cmd, InputRange &&args) {
|
||||||
open_command(nullptr, std::forward<InputRange>(args));
|
open_full(cmd, std::forward<InputRange>(args), nullptr, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @brief Detaches the child process.
|
/** @brief Detaches the child process.
|
||||||
|
@ -380,31 +400,60 @@ struct OSTD_EXPORT subprocess {
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
template<typename InputRange>
|
template<typename InputRange1, typename InputRange2>
|
||||||
void open_full(string_range cmd, InputRange args, bool use_path) {
|
void open_full(
|
||||||
|
string_range cmd, InputRange1 args, InputRange2 env, bool use_path
|
||||||
|
) {
|
||||||
static_assert(
|
static_assert(
|
||||||
std::is_constructible_v<string_range, range_reference_t<InputRange>>,
|
std::is_constructible_v<
|
||||||
|
string_range, range_reference_t<InputRange1>
|
||||||
|
>,
|
||||||
"The arguments must be strings"
|
"The arguments must be strings"
|
||||||
);
|
);
|
||||||
|
if constexpr(!std::is_null_pointer_v<InputRange2>) {
|
||||||
|
static_assert(
|
||||||
|
std::is_constructible_v<
|
||||||
|
string_range, range_reference_t<InputRange2>
|
||||||
|
>,
|
||||||
|
"The environment variables must be strings"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (p_current) {
|
if (p_current) {
|
||||||
throw subprocess_error{"another child process already running"};
|
throw subprocess_error{"another child process already running"};
|
||||||
}
|
}
|
||||||
|
|
||||||
open_impl(use_path, cmd, [](string_range &arg, void *data) {
|
auto argf = [](string_range &arg, void *data) {
|
||||||
InputRange &argr = *static_cast<InputRange *>(data);
|
InputRange1 &argr = *static_cast<InputRange1 *>(data);
|
||||||
if (argr.empty()) {
|
if (argr.empty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
arg = string_range{argr.front()};
|
arg = string_range{argr.front()};
|
||||||
argr.pop_front();
|
argr.pop_front();
|
||||||
return true;
|
return true;
|
||||||
}, &args);
|
};
|
||||||
|
bool (*envf)(string_range &, void *) = nullptr;
|
||||||
|
void *argp = &args, *envp = nullptr;
|
||||||
|
|
||||||
|
if constexpr(!std::is_null_pointer_v<InputRange2>) {
|
||||||
|
envf = [](string_range envv, void *data) {
|
||||||
|
InputRange2 &envr = *static_cast<InputRange2 *>(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(
|
void open_impl(
|
||||||
bool use_path, string_range cmd,
|
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();
|
void reset();
|
||||||
|
|
|
@ -133,7 +133,8 @@ struct pipe {
|
||||||
|
|
||||||
OSTD_EXPORT void subprocess::open_impl(
|
OSTD_EXPORT void subprocess::open_impl(
|
||||||
bool use_path, string_range cmd,
|
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) {
|
if (use_in == subprocess_stream::STDOUT) {
|
||||||
throw subprocess_error{"could not redirect stdin to stdout"};
|
throw subprocess_error{"could not redirect stdin to stdout"};
|
||||||
|
@ -143,15 +144,16 @@ OSTD_EXPORT void subprocess::open_impl(
|
||||||
char *cmd = nullptr;
|
char *cmd = nullptr;
|
||||||
std::vector<char *> vec;
|
std::vector<char *> vec;
|
||||||
~argpv() {
|
~argpv() {
|
||||||
auto vs = vec.size();
|
if (vec.empty()) {
|
||||||
if (!vs) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (cmd && (cmd != vec[0])) {
|
if (cmd && (cmd != vec[0])) {
|
||||||
delete[] cmd;
|
delete[] cmd;
|
||||||
}
|
}
|
||||||
for (std::size_t i = 0; i < (vs - 1); ++i) {
|
for (char *p: vec) {
|
||||||
delete[] vec[i];
|
if (p) {
|
||||||
|
delete[] p;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} argp;
|
} argp;
|
||||||
|
@ -179,8 +181,24 @@ OSTD_EXPORT void subprocess::open_impl(
|
||||||
argp.cmd = str;
|
argp.cmd = str;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* terminate */
|
/* terminate args */
|
||||||
argp.vec.push_back(nullptr);
|
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 */
|
/* fd_errno used to detect if exec failed */
|
||||||
pipe fd_errno, fd_stdin, fd_stdout, fd_stderr;
|
pipe fd_errno, fd_stdin, fd_stdout, fd_stderr;
|
||||||
|
@ -226,9 +244,17 @@ OSTD_EXPORT void subprocess::open_impl(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (use_path) {
|
if (use_path) {
|
||||||
execvp(argp.cmd, argp.vec.data());
|
if (envp) {
|
||||||
|
execvpe(argp.cmd, argpp, envp);
|
||||||
} else {
|
} else {
|
||||||
execv(argp.cmd, argp.vec.data());
|
execvp(argp.cmd, argpp);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (envp) {
|
||||||
|
execve(argp.cmd, argpp, envp);
|
||||||
|
} else {
|
||||||
|
execv(argp.cmd, argpp);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
/* exec has returned, so error has occured */
|
/* exec has returned, so error has occured */
|
||||||
fd_errno.write_errno();
|
fd_errno.write_errno();
|
||||||
|
|
|
@ -324,7 +324,8 @@ static std::unique_ptr<wchar_t[]> concat_args(
|
||||||
|
|
||||||
OSTD_EXPORT void subprocess::open_impl(
|
OSTD_EXPORT void subprocess::open_impl(
|
||||||
bool use_path, string_range cmd,
|
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) {
|
if (use_in == subprocess_stream::STDOUT) {
|
||||||
throw subprocess_error{"could not redirect stdin to stdout"};
|
throw subprocess_error{"could not redirect stdin to stdout"};
|
||||||
|
@ -418,6 +419,40 @@ OSTD_EXPORT void subprocess::open_impl(
|
||||||
std::wstring cmdpath;
|
std::wstring cmdpath;
|
||||||
auto cmdline = concat_args(cmd, func, datap, cmdpath);
|
auto cmdline = concat_args(cmd, func, datap, cmdpath);
|
||||||
|
|
||||||
|
std::wstring envstr;
|
||||||
|
if (edatap) {
|
||||||
|
for (string_range r; efunc(r, edatap);) {
|
||||||
|
std::unique_ptr<wchar_t[]> 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 */
|
/* owned by CreateProcess, do not close explicitly */
|
||||||
pipe_in.p_r = nullptr;
|
pipe_in.p_r = nullptr;
|
||||||
pipe_out.p_w = nullptr;
|
pipe_out.p_w = nullptr;
|
||||||
|
@ -432,8 +467,8 @@ OSTD_EXPORT void subprocess::open_impl(
|
||||||
nullptr, /* process security attributes */
|
nullptr, /* process security attributes */
|
||||||
nullptr, /* primary thread security attributes */
|
nullptr, /* primary thread security attributes */
|
||||||
true, /* inherit handles */
|
true, /* inherit handles */
|
||||||
CREATE_SUSPENDED, /* creation flags */
|
CREATE_SUSPENDED | CREATE_UNICODE_ENVIRONMENT, /* creation flags */
|
||||||
nullptr, /* use parent env */
|
envstr.data(), /* use parent env */
|
||||||
nullptr, /* use parent cwd */
|
nullptr, /* use parent cwd */
|
||||||
&si,
|
&si,
|
||||||
&pi
|
&pi
|
||||||
|
|
Loading…
Reference in a new issue