aboutsummaryrefslogtreecommitdiff
path: root/butl/process
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2016-08-21 12:36:35 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2016-08-21 12:36:35 +0200
commite930d5c9cb4176c6055bde2b4ff196f4b5f92f69 (patch)
tree948cd3832e609248d02c8abaafe16385f93a44a7 /butl/process
parentb6616430560102415bb82062b845a23c830bac0d (diff)
Redo process path search to better accommodate Windows-specific semantics
Diffstat (limited to 'butl/process')
-rw-r--r--butl/process101
1 files changed, 95 insertions, 6 deletions
diff --git a/butl/process b/butl/process
index ed9f798..75f3c66 100644
--- a/butl/process
+++ b/butl/process
@@ -13,6 +13,7 @@
#include <cstdint> // uint32_t
#include <system_error>
+#include <butl/path>
#include <butl/export>
namespace butl
@@ -28,14 +29,68 @@ namespace butl
process_error (int e, bool child)
: system_error (e, std::system_category ()), child_ (child) {}
#else
- process_error (const std::string& d)
- : system_error (ECHILD, std::system_category (), d), child_ (false) {}
+ process_error (int e)
+ : system_error (e, std::system_category ()), child_ (false) {}
+
+ process_error (const std::string& d, int e = ECHILD)
+ : system_error (e, std::system_category (), d), child_ (false) {}
#endif
private:
bool child_;
};
+ // A process executable has three paths: initial, recall, and effective.
+ // Initial is the original "command" that you specify in argv[0] and on
+ // POSIX that's what ends up in the child's argv[0]. But not on Windows. On
+ // Windows the command is first searched for in the parent executable's
+ // directory and if found then that's what should end up in child's argv[0].
+ // So this is the recall path. It is called recall because this is what the
+ // caller of the parent process will be able to execute if you printed the
+ // command line. Finally, effective is the actual path to the executable
+ // that will include the directory part if found in PATH, the .exe extension
+ // if one is missing, etc.
+ //
+ // As an example, let's say we run foo\foo.exe that itself spawns bar which
+ // is found as foo\bar.exe. The paths will then be:
+ //
+ // initial: bar
+ // recall: foo\bar
+ // effective: foo\bar.exe
+ //
+ // In most cases, at least on POSIX, all three paths will be the same. As an
+ // optimization, if the recall path is empty, then it means it is the same
+ // as initial. Similarly, if the effective path is empty then, it is the
+ // same as recall (and if that is empty, as initial).
+ //
+ // Note that the call to path_search() below adjust args[0] to point to the
+ // recall path which brings up lifetime issues. To address this this class
+ // also implements an RAII-based auto-restore of args[0] to its initial
+ // value.
+ //
+ class process_path
+ {
+ public:
+ const char* initial = nullptr;
+ path recall;
+ path effect;
+
+ // Moveable-only type.
+ //
+ process_path (process_path&&);
+ process_path& operator= (process_path&&);
+
+ process_path (const process_path&) = delete;
+ process_path& operator= (const process_path&) = delete;
+
+ process_path () = default;
+ process_path (const char* i, const char** a0): initial (i), args0_ (a0) {}
+ ~process_path () {if (args0_ != nullptr) *args0_ = initial;}
+
+ private:
+ const char** args0_ = nullptr;
+ };
+
class LIBBUTL_EXPORT process
{
public:
@@ -70,7 +125,13 @@ namespace butl
// exceptions (e.g., if exec() failed) can be thrown in the child
// version of us.
//
- process (char const* const args[], int in = 0, int out = 1, int err = 2);
+ // Note that the versions without the the process_path argument may
+ // temporarily change args[0] (see path_search() for details).
+ //
+ process (const char* args[], int in = 0, int out = 1, int err = 2);
+
+ process (const process_path&, const char* args[],
+ int in = 0, int out = 1, int err = 2);
// The "piping" constructor, for example:
//
@@ -80,14 +141,26 @@ namespace butl
// rhs.wait (); // Wait for last first.
// lhs.wait ();
//
- process (char const* const args[], process& in, int out = 1, int err = 2);
+ process (const char* args[], process& in, int out = 1, int err = 2);
+
+ process (const process_path&, const char* args[],
+ process& in, int out = 1, int err = 2);
// Versions of the above constructors that allow us to change the
// current working directory of the child process. NULL and empty
// cwd arguments are ignored.
//
- process (const char* cwd, char const* const[], int = 0, int = 1, int = 2);
- process (const char* cwd, char const* const[], process&, int = 1, int = 2);
+ process (const char* cwd, const char* [], int = 0, int = 1, int = 2);
+
+ process (const char* cwd,
+ const process_path&, const char* [],
+ int = 0, int = 1, int = 2);
+
+ process (const char* cwd, const char* [], process&, int = 1, int = 2);
+
+ process (const char* cwd,
+ const process_path&, const char* [],
+ process&, int = 1, int = 2);
// Wait for the process to terminate. Return true if the process
// terminated normally and with the zero exit status. Unless ignore_error
@@ -122,6 +195,22 @@ namespace butl
//
process ();
+ // Resolve process' paths based on the initial path in args0. If recall
+ // differs from initial, adjust args0 to point to the recall path. If
+ // resolution fails, throw process_error. Normally, you will use this
+ // function like this:
+ //
+ // const char* args[] = {"foo", ..., nullptr};
+ //
+ // process_path pp (process::path_search (args[0]))
+ //
+ // ... // E.g., print args[0].
+ //
+ // process p (pp, args);
+ //
+ process_path
+ path_search (const char*& args0);
+
public:
#ifndef _WIN32
using handle_type = pid_t;