diff --git a/build.cc b/build.cc index ab5cf80..a4dbb29 100644 --- a/build.cc +++ b/build.cc @@ -110,35 +110,22 @@ static void print_help(ostd::string_range arg0) { ); } -static void exec_command(std::string const &cmd, strvec const &args) { - auto argp = std::make_unique(args.size() + 2); - argp[0] = const_cast(cmd.data()); - for (std::size_t i = 0; i < args.size(); ++i) { - argp[i + 1] = const_cast(args[i].data()); - } - argp[args.size() + 1] = nullptr; - - auto pid = fork(); - if (pid == -1) { - throw std::runtime_error{"command failed"}; - } else if (!pid) { - if (execvp(argp[0], argp.get())) { - argp.reset(); - std::exit(1); - } - } else { - if (int retc = -1; (waitpid(pid, &retc, 0) == -1) || retc) { - auto app = ostd::appender(); - ostd::format(app, "command failed with code %d", retc); - throw std::runtime_error{app.get()}; - } +static void exec_command(strvec const &args) { + ostd::process_info pi; + pi.open(nullptr, ostd::iter(args)); + if (int ret; (ret = pi.close())) { + auto app = ostd::appender(); + ostd::format(app, "command failed with code %d", ret); + throw std::runtime_error{app.get()}; } } -static std::string get_command(std::string const &cmd, strvec const &args) { - std::string ret = cmd; +static std::string get_command(strvec const &args) { + std::string ret; for (auto &s: args) { - ret += ' '; + if (!ret.empty()) { + ret += ' '; + } ret += s; } return ret; @@ -292,17 +279,17 @@ int main(int argc, char **argv) { } }; - auto exec_v = [&io_msgs](std::string const &cmd, strvec const &args) { + auto exec_v = [&io_msgs](strvec const &args) { if (verbose) { - io_msgs.put(get_command(cmd, args)); + io_msgs.put(get_command(args)); } - exec_command(cmd, args); + exec_command(args); }; auto call_cxx = [&]( fs::path const &input, fs::path const &output, bool shared ) { - strvec args; + strvec args = { cxx }; add_args(args, cxxflags); auto ifs = input.string(); @@ -322,7 +309,7 @@ int main(int argc, char **argv) { args.push_back(outp.string()); args.push_back(ifs); - exec_v(cxx, args); + exec_v(args); return outp; }; @@ -333,7 +320,7 @@ int main(int argc, char **argv) { auto call_as = [&]( fs::path const &input, fs::path const &output, bool shared ) { - strvec args; + strvec args = { as }; add_args(args, asflags); auto ifs = input.string(); @@ -353,7 +340,7 @@ int main(int argc, char **argv) { args.push_back(outp.string()); args.push_back(ifs); - exec_v(as, args); + exec_v(args); return outp; }; @@ -363,7 +350,7 @@ int main(int argc, char **argv) { ) { echo_q("LD: %s", output); - strvec args; + strvec args = { cxx }; add_args(args, cxxflags); args.push_back("-o"); @@ -375,24 +362,26 @@ int main(int argc, char **argv) { add_args(args, ldflags); - exec_v(cxx, args); + exec_v(args); if (build_cfg == "release") { args.clear(); + args.push_back(strip); args.push_back(output.string()); - exec_v(strip, args); + exec_v(args); } }; auto call_ldlib = [&]( fs::path const &output, pathvec const &files, bool shared ) { - strvec args; if (shared) { - add_args(args, SHARED_CXXFLAGS); - add_args(args, SHARED_LDFLAGS); - call_ld(output, files, args); + strvec flags; + add_args(flags, SHARED_CXXFLAGS); + add_args(flags, SHARED_LDFLAGS); + call_ld(output, files, flags); } else { + strvec args = { ar }; echo_q("AR: %s", output); args.push_back("rcs"); @@ -401,7 +390,7 @@ int main(int argc, char **argv) { args.push_back(p.string()); } - exec_v(ar, args); + exec_v(args); } }; @@ -538,7 +527,7 @@ int main(int argc, char **argv) { } if (build_testsuite) { - exec_v("./test_runner", { TEST_DIR.string() }); + exec_v({ "./test_runner", TEST_DIR.string() }); } io_msgs.close(); diff --git a/ostd/process.hh b/ostd/process.hh index 8c37753..dd0a5f4 100644 --- a/ostd/process.hh +++ b/ostd/process.hh @@ -15,9 +15,16 @@ #define OSTD_PROCESS_HH #include +#include +#include +#include +#include +#include #include "ostd/platform.hh" #include "ostd/string.hh" +#include "ostd/range.hh" +#include "ostd/io.hh" namespace ostd { @@ -69,6 +76,88 @@ OutputRange &&split_args(OutputRange &&out, string_range str) { return std::forward(out); } +struct process_error: std::system_error { + using std::system_error::system_error; +}; + +enum class process_pipe { + DEFAULT = 0, + PIPE, + STDOUT +}; + +struct OSTD_EXPORT process_info { + process_pipe use_in = process_pipe::DEFAULT; + process_pipe use_out = process_pipe::DEFAULT; + process_pipe use_err = process_pipe::DEFAULT; + file_stream in = file_stream{}; + file_stream out = file_stream{}; + file_stream err = file_stream{}; + + process_info() {} + process_info( + process_pipe in_use, process_pipe out_use, process_pipe err_use + ): + use_in(in_use), use_out(out_use), use_err(err_use) + {} + + process_info(process_info &&i): + use_in(i.use_in), use_out(i.use_out), use_err(i.use_err), + in(std::move(i.in)), out(std::move(i.out)), err(std::move(i.err)), + pid(i.pid), errno_fd(i.errno_fd) + { + i.pid = -1; + i.errno_fd = -1; + } + + process_info &operator=(process_info &&i) { + swap(i); + return *this; + } + + void swap(process_info &i) { + using std::swap; + swap(use_in, i.use_in); + swap(use_out, i.use_out); + swap(use_err, i.use_err); + swap(in, i.in); + swap(out, i.out); + swap(err, i.err); + swap(pid, i.pid); + swap(errno_fd, i.errno_fd); + } + + ~process_info(); + + template + void open(string_range cmd, InputRange args, bool use_path = true) { + static_assert( + std::is_constructible_v>, + "The arguments must be strings" + ); + std::vector argv; + if (cmd.empty()) { + if (args.empty()) { + throw process_error{EINVAL, std::generic_category()}; + } + cmd = args[0]; + } + for (; !args.empty(); args.pop_front()) { + argv.emplace_back(args.front()); + } + open_impl(std::string{cmd}, argv, use_path); + } + + int close(); + +private: + void open_impl( + std::string const &cmd, std::vector const &args, + bool use_path + ); + int pid = -1, errno_fd = -1; +}; + /** @} */ } /* namespace ostd */ diff --git a/src/process.cc b/src/process.cc index e708258..a1c8ab0 100644 --- a/src/process.cc +++ b/src/process.cc @@ -4,6 +4,8 @@ */ #include +#include +#include #include #include #include @@ -15,6 +17,8 @@ # include #else # include +# include +# include # include #endif @@ -122,4 +126,152 @@ OSTD_EXPORT void split_args_impl( } } /* namespace detail */ + +static void stdstream_close(FILE *f) { + std::fclose(f); +} + +OSTD_EXPORT void process_info::open_impl( + std::string const &cmd, std::vector const &args, + bool use_path +) { + if (use_in == process_pipe::STDOUT) { + throw process_error{EINVAL, std::generic_category()}; + } + +#ifndef OSTD_PLATFORM_WIN32 + auto argp = std::make_unique(args.size() + 1); + for (std::size_t i = 0; i < args.size(); ++i) { + argp[i] = const_cast(args[i].data()); + } + argp[args.size()] = nullptr; + + int fd_errno[2]; /* used to detect if exec failed */ + int fd_stdin[2]; + int fd_stdout[2]; + int fd_stderr[2]; + + if ( + (pipe(fd_errno) < 0) || + ((use_in == process_pipe::PIPE) && (pipe(fd_stdin) < 0)) || + ((use_out == process_pipe::PIPE) && (pipe(fd_stdout) < 0)) || + ((use_err == process_pipe::PIPE) && (pipe(fd_stderr) < 0)) + ) { + throw process_error{errno, std::generic_category()}; + } + + auto cpid = fork(); + if (cpid == -1) { + throw process_error{errno, std::generic_category()}; + } else if (!cpid) { + /* child process */ + ::close(fd_errno[0]); + /* fcntl fails, write the errno to be read from parent */ + if (fcntl(fd_errno[1], F_SETFD, FD_CLOEXEC) < 0) { + write(fd_errno[1], int(errno), sizeof(int)); + std::exit(1); + } + /* prepare standard streams */ + if (use_in == process_pipe::PIPE) { + /* close writing end */ + ::close(fd_stdin[1]); + if (dup2(fd_stdin[0], STDIN_FILENO) < 0) { + write(fd_errno[1], int(errno), sizeof(int)); + std::exit(1); + } + } + if (use_out == process_pipe::PIPE) { + /* close reading end */ + ::close(fd_stdout[0]); + if (dup2(fd_stdout[1], STDOUT_FILENO) < 0) { + write(fd_errno[1], int(errno), sizeof(int)); + std::exit(1); + } + } + if (use_err == process_pipe::PIPE) { + /* close reading end */ + ::close(fd_stderr[0]); + if (dup2(fd_stderr[1], STDERR_FILENO) < 0) { + write(fd_errno[1], int(errno), sizeof(int)); + std::exit(1); + } + } else if (use_err == process_pipe::STDOUT) { + if (dup2(STDOUT_FILENO, STDERR_FILENO) < 0) { + write(fd_errno[1], int(errno), sizeof(int)); + std::exit(1); + } + } + int ret = 0; + if (use_path) { + ret = execvp(cmd.data(), argp.get()); + } else { + ret = execv(cmd.data(), argp.get()); + } + std::exit(1); + } else { + /* parent process */ + ::close(fd_errno[1]); + if (use_in == process_pipe::PIPE) { + /* close reading end */ + ::close(fd_stdin[0]); + in.open(fdopen(fd_stdin[1], "w"), stdstream_close); + } + if (use_out == process_pipe::PIPE) { + /* close writing end */ + ::close(fd_stdout[1]); + out.open(fdopen(fd_stdout[0], "r"), stdstream_close); + } + if (use_err == process_pipe::PIPE) { + /* close writing end */ + ::close(fd_stderr[1]); + err.open(fdopen(fd_stderr[0], "r"), stdstream_close); + } + pid = int(cpid); + errno_fd = fd_errno[1]; + } +#else + /* stub for now */ + return; +#endif +} + +OSTD_EXPORT int process_info::close() { + if (pid < 0) { + throw process_error{ECHILD, std::generic_category()}; + } +#ifndef OSTD_PLATFORM_WIN32 + int retc = 0; + if (pid_t wp; (wp = waitpid(pid, &retc, 0)) < 0) { + throw process_error{errno, std::generic_category()}; + } + if (retc) { + int eno; + auto r = read(errno_fd, &eno, sizeof(int)); + ::close(errno_fd); + errno_fd = -1; + if (r < 0) { + throw process_error{errno, std::generic_category()}; + } else if (r == sizeof(int)) { + throw process_error{eno, std::generic_category()}; + } + } + return retc; +#else + throw process_error{ECHILD, std::generic_category()}; +#endif +} + +OSTD_EXPORT process_info::~process_info() { + try { + close(); + } catch (process_error const &) {} +#ifndef OSTD_PLATFORM_WIN32 + if (errno_fd > 0) { + ::close(errno_fd); + } +#else + return; +#endif +} + } /* namespace ostd */