diff options
-rw-r--r-- | butl/buildfile | 1 | ||||
-rw-r--r-- | butl/pager.cxx | 2 | ||||
-rw-r--r-- | butl/process | 156 | ||||
-rw-r--r-- | butl/process-run.cxx | 32 | ||||
-rw-r--r-- | butl/process-run.txx | 216 | ||||
-rw-r--r-- | butl/process.cxx | 22 | ||||
-rw-r--r-- | butl/process.ixx | 11 | ||||
-rw-r--r-- | tests/process-run/buildfile | 7 | ||||
-rw-r--r-- | tests/process-run/driver.cxx | 91 | ||||
-rw-r--r-- | tests/process-run/testscript | 14 | ||||
-rw-r--r-- | tests/process/driver.cxx | 2 |
11 files changed, 528 insertions, 26 deletions
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 <butl/path> #include <butl/export> #include <butl/optional> -#include <butl/fdstream> // auto_fd +#include <butl/fdstream> // 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 <typename I, + typename O, + typename E, + typename P, + typename... A> + 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 <typename C, + typename I, + typename O, + typename E, + typename P, + typename... A> + 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 <typename I, + typename O, + typename E, + typename P, + typename... A> + process + process_start (I&& in, + O&& out, + E&& err, + const dir_path& cwd, + const P&, + A&&... args); + + template <typename C, + typename I, + typename O, + typename E, + typename P, + typename... A> + 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 <typename T> + 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 <typename K> + inline const char* + process_arg_as (const basic_path<char, K>& p, std::string&) + { + return p.string ().c_str (); + } + + inline const char* + process_arg_as (const char* s, std::string&) {return s;} + + template <std::size_t N> + inline const char* + process_arg_as (char (&s)[N], std::string&) {return s;} + + template <std::size_t N> + inline const char* + process_arg_as (const char (&s)[N], std::string&) {return s;} } #include <butl/process.ixx> +#include <butl/process-run.txx> + #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 <butl/process> + +#include <cstdlib> // exit() +#include <iostream> // 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 <cassert> +#include <utility> // 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 <typename C, + typename I, + typename O, + typename E, + typename... A, + typename std::size_t... index> + process + process_start (std::index_sequence<index...>, + 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<I> (in))); + int out_i (process_stdout (std::forward<O> (out))); + int err_i (process_stderr (std::forward<E> (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 <typename C, + typename I, + typename O, + typename E, + typename... A> + 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<A...> (), + cmdc, + std::forward<I> (in), + std::forward<O> (out), + std::forward<E> (err), + cwd, + pp, + std::forward<A> (args)...); + } + + template <typename I, + typename O, + typename E, + typename P, + typename... A> + 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<I> (in), + std::forward<O> (out), + std::forward<E> (err), + cwd, + process::path_search (p, true), + std::forward<A> (args)...); + } + + template <typename C, + typename I, + typename O, + typename E, + typename P, + typename... A> + 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<I> (in), + std::forward<O> (out), + std::forward<E> (err), + cwd, + process::path_search (p, true), + std::forward<A> (args)...); + } + + template <typename C, + typename I, + typename O, + typename E, + typename... A> + 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<I> (in), + std::forward<O> (out), + std::forward<E> (err), + cwd, + pp, + std::forward<A> (args)...)); + + pr.wait (); + return *pr.exit; + } + + template <typename I, + typename O, + typename E, + typename P, + typename... A> + 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<I> (in), + std::forward<O> (out), + std::forward<E> (err), + cwd, + process::path_search (p, true), + std::forward<A> (args)...); + } + + template <typename C, + typename I, + typename O, + typename E, + typename P, + typename... A> + 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<I> (in), + std::forward<O> (out), + std::forward<E> (err), + cwd, + process::path_search (p, true), + std::forward<A> (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; + } } diff --git a/tests/process-run/buildfile b/tests/process-run/buildfile new file mode 100644 index 0000000..5c55af9 --- /dev/null +++ b/tests/process-run/buildfile @@ -0,0 +1,7 @@ +# file : tests/process-run/buildfile +# copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +# license : MIT; see accompanying LICENSE file + +exe{driver}: cxx{driver} ../../butl/lib{butl} test{testscript} + +include ../../butl/ diff --git a/tests/process-run/driver.cxx b/tests/process-run/driver.cxx new file mode 100644 index 0000000..15d5f61 --- /dev/null +++ b/tests/process-run/driver.cxx @@ -0,0 +1,91 @@ +// file : tests/process-run/driver.cxx -*- C++ -*- +// copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +// license : MIT; see accompanying LICENSE file + +#include <iostream> + +#include <butl/path> +#include <butl/process> +#include <butl/fdstream> + +using namespace std; +using namespace butl; + +template <typename I, + typename O, + typename E, + typename P, + typename... A> +process_exit +run (I&& in, + O&& out, + E&& err, + const P& p, + A&&... args) +{ + return process_run (forward<I> (in), + forward<O> (out), + forward<E> (err), + dir_path (), + p, + forward<A> (args)...); +} + +int +main (int argc, const char* argv[]) +{ + if (argc < 2) // No argument test. + return 0; + + string a (argv[1]); + + if (a == "-c") + { + // -i read from stdin + // -o write argument to stdout + // -e write argument to stderr + // -x exit with argument + // + for (int i (2); i != argc; ++i) + { + a = argv[i]; + + if (a == "-i") cin >> a; + else if (a == "-o") cout << argv[++i] << endl; + else if (a == "-e") cerr << argv[++i] << endl; + else if (a == "-x") return atoi (argv[++i]); + } + + return 0; + } + else + assert (a == "-p"); + + const string p (argv[0]); + + assert (run (0, 1, 2, p)); + assert (run (0, 1, 2, p, "-c")); + + process_run ([] (const char* c[], size_t n) + { + process::print (cout, c, n); + cout << endl; + }, + 0, 1, 2, + dir_path (), + p, + "-c"); + + // Stream conversion and redirection. + // + assert (run (fdnull (), 1, 2, p, "-c", "-i")); + assert (run (fdnull (), 2, 2, p, "-c", "-o", "abc")); + assert (run (fdnull (), 1, 1, p, "-c", "-e", "abc")); + + // Argument conversion. + // + assert (run (0, 1, 2, p, "-c", "-o", "abc")); + assert (run (0, 1, 2, p, "-c", "-o", string ("abc"))); + assert (run (0, 1, 2, p, "-c", "-o", path ("abc"))); + assert (run (0, 1, 2, p, "-c", "-o", 123)); +} diff --git a/tests/process-run/testscript b/tests/process-run/testscript new file mode 100644 index 0000000..49abac4 --- /dev/null +++ b/tests/process-run/testscript @@ -0,0 +1,14 @@ +# file : tests/process-run/testscript +# copyright : Copyright (c) 2014-2017 Code Synthesis Ltd +# license : MIT; see accompanying LICENSE file + +$* -p >>"EOO" 2>>EOE +$0 -c +abc +abc +abc +abc +123 +EOO +abc +EOE diff --git a/tests/process/driver.cxx b/tests/process/driver.cxx index 1fb685c..fc9ffe9 100644 --- a/tests/process/driver.cxx +++ b/tests/process/driver.cxx @@ -143,7 +143,7 @@ exec (const path& p, } catch (const process_error& e) { - if (e.child ()) + if (e.child) exit (1); //cerr << args[0] << ": " << e << endl; |