From 550b5257aba507bcce98f6832b8905769a14955d Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Mon, 10 Apr 2017 13:19:19 +0200 Subject: Add process_run()/process_start() higher-level API on top of class process --- butl/buildfile | 1 + butl/pager.cxx | 2 +- butl/process | 156 +++++++++++++++++++++++++++++++++---- butl/process-run.cxx | 32 ++++++++ butl/process-run.txx | 216 +++++++++++++++++++++++++++++++++++++++++++++++++++ butl/process.cxx | 22 +++--- butl/process.ixx | 11 +++ 7 files changed, 415 insertions(+), 25 deletions(-) create mode 100644 butl/process-run.cxx create mode 100644 butl/process-run.txx (limited to 'butl') diff --git a/butl/buildfile b/butl/buildfile index b54a887..6cef7f2 100644 --- a/butl/buildfile +++ b/butl/buildfile @@ -22,6 +22,7 @@ lib{butl}: \ {hxx txx }{ prefix-map } \ {hxx ixx cxx}{ process } \ {hxx }{ process-details } \ + { txx cxx}{ process-run } \ {hxx cxx}{ sha256 } \ {hxx }{ small-vector } \ {hxx txx }{ string-table } \ diff --git a/butl/pager.cxx b/butl/pager.cxx index fe59e5b..003c65c 100644 --- a/butl/pager.cxx +++ b/butl/pager.cxx @@ -146,7 +146,7 @@ namespace butl } catch (const process_error& e) { - if (e.child ()) + if (e.child) { cerr << args[0] << ": unable to execute: " << e << endl; exit (1); diff --git a/butl/process b/butl/process index 31cb681..6f22718 100644 --- a/butl/process +++ b/butl/process @@ -17,31 +17,28 @@ #include #include #include -#include // auto_fd +#include // auto_fd, fdpipe namespace butl { struct process_error: std::system_error { - bool - child () const {return child_;} - - public: + const bool child; -#ifndef _WIN32 - process_error (int e, bool child) - : system_error (e, std::generic_category ()), child_ (child) {} -#else process_error (int e, bool child = false) - : system_error (e, std::generic_category ()), child_ (child) {} + : system_error (e, std::generic_category ()), child (child) {} +#ifdef _WIN32 process_error (const std::string& d, int fallback_errno_code = 0) : system_error (fallback_errno_code, std::system_category (), d), - child_ (false) {} + child (false) {} #endif + }; - private: - bool child_; + struct process_child_error: process_error + { + explicit + process_child_error (int e): process_error (e, true) {} }; // A process executable has three paths: initial, recall, and effective. @@ -142,6 +139,8 @@ namespace butl code_type code () const; + explicit operator bool () const {return normal () && code () == 0;} + // Abnormal termination information. // #ifndef _WIN32 @@ -205,7 +204,7 @@ namespace butl // // Throw process_error if anything goes wrong. Note that some of the // exceptions (e.g., if exec() failed) can be thrown in the child - // version of us. + // version of us (as process_child_error). // // Note that the versions without the the process_path argument may // temporarily change args[0] (see path_search() for details). @@ -254,9 +253,12 @@ namespace butl wait (bool ignore_errors = false); // Return true if the process has already terminated in which case - // the argument is set to the result of wait(). + // optionally set the argument to the result of wait(). // bool + try_wait (); + + bool try_wait (bool&); // Note that the destructor will wait for the process but will ignore @@ -363,8 +365,132 @@ namespace butl auto_fd in_ofd; // Read from it to receive from stdout. auto_fd in_efd; // Read from it to receive from stderr. }; + + // Higher-level process running interface that aims to make executing a + // process for the common cases as simple as calling a functions. Normally + // it is further simplified by project-specific wrapper functions that + // handle the process_error exception as well as abnormal and/or non-zero + // exit status. + // + // The I/O/E arguments determine the child's stdin/stdout/stderr. They can + // be of type int, auto_fd (and, in the future, perhaps also fd_pipe, + // string, buffer, etc). For example, the following call will make stdin + // read from /dev/null, stdout redirect to stderr, and inherit the parent's + // stderr. + // + // process_run (..., fdnull (), 2, 2, ...) + // + // The P argument is the program path. It can be anything that can be passed + // to process::path_search() (const char*, std::string, path) or the + // process_path itself. + // + // The A arguments can be anything convertible to const char* via the + // overloaded process_arg_as() (see below). Out of the box you can use const + // char*, std::string, path/dir_path, and numeric types. + // + // + // + template + process_exit + process_run (I&& in, + O&& out, + E&& err, + const dir_path& cwd, + const P&, + A&&... args); + + // The version with the command callback that can be used for printing the + // command line or similar. It should be callable with the following + // signature: + // + // void (const char*[], std::size_t) + // + template + process_exit + process_run (const C&, + I&& in, + O&& out, + E&& err, + const dir_path& cwd, + const P&, + A&&... args); + + // Versions that start the process without waiting. + // + template + process + process_start (I&& in, + O&& out, + E&& err, + const dir_path& cwd, + const P&, + A&&... args); + + template + process + process_start (const C&, + I&& in, + O&& out, + E&& err, + const dir_path& cwd, + const P&, + A&&... args); + + // Conversion of types to their C string representations. Can be overloaded + // (including via ADL) for custom types. The default implementation calls + // to_string() which covers all the numeric values via std::to_string () and + // also any type that defines to_string() (via ADL). + // + template + inline const char* + process_arg_as (const T& x, std::string& storage) + { + using namespace std; + return (storage = to_string (x)).c_str (); + } + + inline const char* + process_arg_as (const std::string& s, std::string&) {return s.c_str ();} + + template + inline const char* + process_arg_as (const basic_path& p, std::string&) + { + return p.string ().c_str (); + } + + inline const char* + process_arg_as (const char* s, std::string&) {return s;} + + template + inline const char* + process_arg_as (char (&s)[N], std::string&) {return s;} + + template + inline const char* + process_arg_as (const char (&s)[N], std::string&) {return s;} } #include +#include + #endif // BUTL_PROCESS diff --git a/butl/process-run.cxx b/butl/process-run.cxx new file mode 100644 index 0000000..c3b2856 --- /dev/null +++ b/butl/process-run.cxx @@ -0,0 +1,32 @@ +// file : butl/process-run.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include + +#include // exit() +#include // cerr + +using namespace std; + +namespace butl +{ + process + process_start (const dir_path& cwd, + const process_path& pp, + const char* cmd[], + int in, + int out, + int err) + { + try + { + return process (cwd.string ().c_str (), pp, cmd, in, out, err); + } + catch (const process_child_error& e) + { + cerr << "unable to execute " << cmd[0] << ": " << e << endl; + exit (1); + } + } +} diff --git a/butl/process-run.txx b/butl/process-run.txx new file mode 100644 index 0000000..013be4c --- /dev/null +++ b/butl/process-run.txx @@ -0,0 +1,216 @@ +// file : butl/process-run.txx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include +#include // move(), forward(), index_sequence + +namespace butl +{ + inline int process_stdin (int v) {assert (v >= 0); return v;} + inline int process_stdout (int v) {assert (v >= 0); return v;} + inline int process_stderr (int v) {assert (v >= 0); return v;} + + inline int + process_stdin (const auto_fd& v) {assert (v.get () >= 0); return v.get ();} + + inline int + process_stdout (const auto_fd& v) {assert (v.get () >= 0); return v.get ();} + + inline int + process_stderr (const auto_fd& v) {assert (v.get () >= 0); return v.get ();} + + process + process_start (const dir_path& cwd, + const process_path& pp, + const char* cmd[], + int in, + int out, + int err); + + template + process + process_start (std::index_sequence, + const C& cmdc, + I&& in, + O&& out, + E&& err, + const dir_path& cwd, + const process_path& pp, + A&&... args) + { + // Map stdin/stdout/stderr arguments to their integer values, as expected + // by the process constructor. + // + int in_i (process_stdin (std::forward (in))); + int out_i (process_stdout (std::forward (out))); + int err_i (process_stderr (std::forward (err))); + + // Construct the command line array. + // + const std::size_t args_size (sizeof... (args)); + + std::string storage[args_size]; + const char* cmd[args_size + 2] = { + pp.recall_string (), + process_arg_as (args, storage[index])..., + nullptr}; + + // The last argument can be NULL (used to handle zero A... pack). + // + cmdc (cmd, args_size + 2); + + // @@ Do we need to make sure certain fd's are closed before calling + // wait()? Is this only the case with pipes? Needs thinking. + + return process_start (cwd, pp, cmd, in_i, out_i, err_i); + } + + template + inline process + process_start (const C& cmdc, + I&& in, + O&& out, + E&& err, + const dir_path& cwd, + const process_path& pp, + A&&... args) + { + return process_start (std::index_sequence_for (), + cmdc, + std::forward (in), + std::forward (out), + std::forward (err), + cwd, + pp, + std::forward (args)...); + } + + template + inline process + process_start (I&& in, + O&& out, + E&& err, + const dir_path& cwd, + const P& p, + A&&... args) + { + return process_start ([] (const char* [], std::size_t) {}, + std::forward (in), + std::forward (out), + std::forward (err), + cwd, + process::path_search (p, true), + std::forward (args)...); + } + + template + inline process + process_start (const C& cmdc, + I&& in, + O&& out, + E&& err, + const dir_path& cwd, + const P& p, + A&&... args) + { + return process_start (cmdc, + std::forward (in), + std::forward (out), + std::forward (err), + cwd, + process::path_search (p, true), + std::forward (args)...); + } + + template + inline process_exit + process_run (const C& cmdc, + I&& in, + O&& out, + E&& err, + const dir_path& cwd, + const process_path& pp, + A&&... args) + { + process pr ( + process_start (cmdc, + std::forward (in), + std::forward (out), + std::forward (err), + cwd, + pp, + std::forward (args)...)); + + pr.wait (); + return *pr.exit; + } + + template + inline process_exit + process_run (I&& in, + O&& out, + E&& err, + const dir_path& cwd, + const P& p, + A&&... args) + { + return process_run ([] (const char* [], std::size_t) {}, + std::forward (in), + std::forward (out), + std::forward (err), + cwd, + process::path_search (p, true), + std::forward (args)...); + } + + template + inline process_exit + process_run (const C& cmdc, + I&& in, + O&& out, + E&& err, + const dir_path& cwd, + const P& p, + A&&... args) + { + return process_run (cmdc, + std::forward (in), + std::forward (out), + std::forward (err), + cwd, + process::path_search (p, true), + std::forward (args)...); + } +} diff --git a/butl/process.cxx b/butl/process.cxx index 189d1ee..74892e6 100644 --- a/butl/process.cxx +++ b/butl/process.cxx @@ -64,7 +64,7 @@ namespace butl process_path r (try_path_search (f, init, fb)); if (r.empty ()) - throw process_error (ENOENT, false); + throw process_error (ENOENT); return r; } @@ -259,7 +259,13 @@ namespace butl fdpipe in_ofd; fdpipe in_efd; - auto fail = [] (bool child) {throw process_error (errno, child);}; + auto fail = [] (bool child) + { + if (child) + throw process_child_error (errno); + else + throw process_error (errno); + }; auto open_pipe = [] () -> fdpipe { @@ -276,7 +282,7 @@ namespace butl // other hand the only possible values are EMFILE and ENFILE. Lets use // EMFILE as the more probable. This is a temporary code after all. // - throw process_error (EMFILE, false); + throw process_error (EMFILE); } }; @@ -391,7 +397,7 @@ namespace butl // information available" semantics. // if (!ie) - throw process_error (errno, false); + throw process_error (errno); } else exit = process_exit (es, process_exit::as_status); @@ -401,7 +407,7 @@ namespace butl } bool process:: - try_wait (bool& s) + try_wait () { if (handle != 0) { @@ -414,12 +420,11 @@ namespace butl handle = 0; // We have tried. if (r == -1) - throw process_error (errno, false); + throw process_error (errno); exit = process_exit (es, process_exit::as_status); } - s = exit && exit->normal () && exit->code () == 0; return true; } @@ -1089,7 +1094,7 @@ namespace butl } bool process:: - try_wait (bool& s) + try_wait () { if (handle != 0) { @@ -1112,7 +1117,6 @@ namespace butl exit->status = es; } - s = exit && exit->normal () && exit->code () == 0; return true; } diff --git a/butl/process.ixx b/butl/process.ixx index 8935adb..c452b0f 100644 --- a/butl/process.ixx +++ b/butl/process.ixx @@ -165,4 +165,15 @@ namespace butl return *this; } + + inline bool process:: + try_wait (bool& s) + { + bool r (try_wait ()); + + if (r) + s = exit && exit->normal () && exit->code () == 0; + + return r; + } } -- cgit v1.1