aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--butl/buildfile1
-rw-r--r--butl/pager.cxx2
-rw-r--r--butl/process156
-rw-r--r--butl/process-run.cxx32
-rw-r--r--butl/process-run.txx216
-rw-r--r--butl/process.cxx22
-rw-r--r--butl/process.ixx11
-rw-r--r--tests/process-run/buildfile7
-rw-r--r--tests/process-run/driver.cxx91
-rw-r--r--tests/process-run/testscript14
-rw-r--r--tests/process/driver.cxx2
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;