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) {
|
||||
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<std::string>(), "command failed with code %d", ret
|
||||
).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.
|
||||
*/
|
||||
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<typename InputRange>
|
||||
template<typename InputRange1, typename InputRange2>
|
||||
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<InputRange>, 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<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.
|
||||
|
@ -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<typename InputRange>
|
||||
template<typename InputRange1>
|
||||
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<InputRange>, 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<InputRange>(args), use_path);
|
||||
open_full(cmd, std::forward<InputRange1>(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<typename InputRange>
|
||||
void open_path(string_range path, InputRange &&args) {
|
||||
open_full(path, std::forward<InputRange>(args), false);
|
||||
template<typename InputRange1, typename InputRange2>
|
||||
void open_path(string_range path, InputRange1 &&args, InputRange2 &&envs) {
|
||||
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>
|
||||
void open_path(InputRange &&args) {
|
||||
open_path(nullptr, std::forward<InputRange>(args));
|
||||
void open_path(string_range path, InputRange &&args) {
|
||||
open_full(path, std::forward<InputRange>(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<typename InputRange>
|
||||
void open_command(string_range cmd, InputRange &&args) {
|
||||
open_full(cmd, std::forward<InputRange>(args), true);
|
||||
template<typename InputRange1, typename InputRange2>
|
||||
void open_command(string_range cmd, InputRange1 &&args, InputRange2 &&envs) {
|
||||
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>
|
||||
void open_command(InputRange &&args) {
|
||||
open_command(nullptr, std::forward<InputRange>(args));
|
||||
void open_command(string_range cmd, InputRange &&args) {
|
||||
open_full(cmd, std::forward<InputRange>(args), nullptr, true);
|
||||
}
|
||||
|
||||
/** @brief Detaches the child process.
|
||||
|
@ -380,31 +400,60 @@ struct OSTD_EXPORT subprocess {
|
|||
}
|
||||
|
||||
private:
|
||||
template<typename InputRange>
|
||||
void open_full(string_range cmd, InputRange args, bool use_path) {
|
||||
template<typename InputRange1, typename InputRange2>
|
||||
void open_full(
|
||||
string_range cmd, InputRange1 args, InputRange2 env, bool use_path
|
||||
) {
|
||||
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"
|
||||
);
|
||||
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) {
|
||||
throw subprocess_error{"another child process already running"};
|
||||
}
|
||||
|
||||
open_impl(use_path, cmd, [](string_range &arg, void *data) {
|
||||
InputRange &argr = *static_cast<InputRange *>(data);
|
||||
auto argf = [](string_range &arg, void *data) {
|
||||
InputRange1 &argr = *static_cast<InputRange1 *>(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<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(
|
||||
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();
|
||||
|
|
|
@ -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<char *> 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();
|
||||
|
|
|
@ -324,7 +324,8 @@ static std::unique_ptr<wchar_t[]> 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<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 */
|
||||
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
|
||||
|
|
Loading…
Reference in a new issue