aboutsummaryrefslogtreecommitdiff
path: root/libbutl/process.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'libbutl/process.cxx')
-rw-r--r--libbutl/process.cxx352
1 files changed, 204 insertions, 148 deletions
diff --git a/libbutl/process.cxx b/libbutl/process.cxx
index 6c736c1..e416807 100644
--- a/libbutl/process.cxx
+++ b/libbutl/process.cxx
@@ -1,9 +1,7 @@
// file : libbutl/process.cxx -*- C++ -*-
// license : MIT; see accompanying LICENSE file
-#ifndef __cpp_modules_ts
-#include <libbutl/process.mxx>
-#endif
+#include <libbutl/process.hxx>
#include <errno.h>
@@ -49,6 +47,14 @@
# elif defined(__NetBSD__) && __NetBSD__ >= 6
# define LIBBUTL_POSIX_SPAWN
//
+// On OpenBSD posix_spawn() appeared in 5.2 (see the man page for details).
+//
+# elif defined(__OpenBSD__)
+# include <sys/param.h> // OpenBSD (yyyymm)
+# if OpenBSD >= 201211 // 5.2 released on 1 Nov 2012.
+# define LIBBUTL_POSIX_SPAWN
+# endif
+//
// posix_spawn() appeared in Version 3 of the Single UNIX Specification that
// was implemented in MacOS 10.5 (see the man page for details).
//
@@ -87,29 +93,20 @@
# endif // _MSC_VER
#endif
-#include <cassert>
-
-#ifndef __cpp_lib_modules_ts
-#include <string>
-#include <vector>
-#include <chrono>
-#include <cstdint>
-#include <cstddef>
-#include <system_error>
-
#include <ios> // ios_base::failure
-#include <cstring> // strlen(), strchr(), strncmp()
+#include <memory> // unique_ptr
+#include <cstring> // strlen(), strchr(), strpbrk(), strncmp()
#include <utility> // move()
#include <ostream>
+#include <cassert>
#ifndef _WIN32
-#include <thread> // this_thread::sleep_for()
+# include <thread> // this_thread::sleep_for()
#else
-#include <map>
-#include <ratio> // milli
-#include <cstdlib> // __argv[]
-#include <algorithm> // find()
-#endif
+# include <map>
+# include <ratio> // milli
+# include <cstdlib> // __argv[]
+# include <algorithm> // find()
#endif
#include <libbutl/process-details.hxx>
@@ -119,32 +116,8 @@ namespace butl
shared_mutex process_spawn_mutex; // Out of module purview.
}
-#ifdef __cpp_modules_ts
-module butl.process;
-
-// Only imports additional to interface.
-#ifdef __clang__
-#ifdef __cpp_lib_modules_ts
-import std.core;
-import std.io;
-import std.threading; // Clang wants it in purview (see process-details.hxx).
-#endif
-import butl.path;
-import butl.fdstream;
-import butl.vector_view;
-import butl.small_vector;
-#endif
-
-#ifndef _WIN32
-import std.threading;
-#endif
-
-import butl.utility; // icasecmp()
-import butl.fdstream; // fdopen_null()
-#else
-#include <libbutl/utility.mxx>
-#include <libbutl/fdstream.mxx>
-#endif
+#include <libbutl/utility.hxx> // icasecmp()
+#include <libbutl/fdstream.hxx> // fdopen_null()
using namespace std;
@@ -217,7 +190,7 @@ namespace butl
}
void process::
- print (ostream& o, const char* const args[], size_t n)
+ print (ostream& o, const char* const* args, size_t n)
{
size_t m (0);
const char* const* p (args);
@@ -253,6 +226,35 @@ namespace butl
} while (*p != nullptr);
}
+#if defined(LIBBUTL_POSIX_SPAWN) || defined(_WIN32)
+ // Return true if the NULL-terminated variable list contains an (un)set of
+ // the specified variable. The NULL list argument denotes an empty list.
+ //
+ // Note that on Windows variable names are case-insensitive.
+ //
+ static inline bool
+ contains_envvar (const char* const* vs, const char* v, size_t n)
+ {
+ if (vs != nullptr)
+ {
+ // Note that we don't expect the number of variables to (un)set to be
+ // large, so the linear search is OK.
+ //
+ while (const char* v1 = *vs++)
+ {
+#ifdef _WIN32
+ if (icasecmp (v1, v, n) == 0 && (v1[n] == '=' || v1[n] == '\0'))
+#else
+ if (strncmp (v1, v, n) == 0 && (v1[n] == '=' || v1[n] == '\0'))
+#endif
+ return true;
+ }
+ }
+
+ return false;
+ }
+#endif
+
#ifndef _WIN32
static process_path
@@ -381,10 +383,10 @@ namespace butl
}
process::
- process (const process_path& pp, const char* args[],
+ process (const process_path& pp, const char* const* args,
pipe pin, pipe pout, pipe perr,
const char* cwd,
- const char* const* envvars)
+ const char* const* evars)
{
int in (pin.in);
int out (pout.out);
@@ -452,6 +454,8 @@ namespace butl
else if (err == -2)
in_efd.out = open_null ();
+ const char* const* tevars (thread_env ());
+
// The posix_spawn()-based implementation.
//
#ifdef LIBBUTL_POSIX_SPAWN
@@ -540,47 +544,45 @@ namespace butl
fail (r);
#endif
- // Set/unset environment variables if requested.
+ // Set/unset the child process environment variables if requested.
//
- small_vector<const char*, 8> new_env;
+ vector<const char*> new_env;
- if (envvars != nullptr)
+ if (tevars != nullptr || evars != nullptr)
{
- for (const char* const* env (environ); *env != nullptr; ++env)
+ // Copy the non-overridden process environment variables into the
+ // child's environment.
+ //
+ for (const char* const* ev (environ); *ev != nullptr; ++ev)
{
- // Lookup the existing variable among those that are requested to be
- // (un)set. If not present, than add it to the child process
- // environment.
- //
- // Note that on POSIX variable names are case-sensitive.
- //
- // Alse note that we don't expect the number of variables to (un)set
- // to be large, so the linear search is OK.
- //
- const char* cv (*env);
- const char* eq (strchr (cv, '='));
- size_t n (eq != nullptr ? eq - cv : strlen (cv));
-
- const char* const* ev (envvars);
- for (; *ev != nullptr; ++ev)
- {
- const char* v (*ev);
- if (strncmp (cv, v, n) == 0 && (v[n] == '=' || v[n] == '\0'))
- break;
- }
+ const char* v (*ev);
+ const char* e (strchr (v, '='));
+ size_t n (e != nullptr ? e - v : strlen (v));
- if (*ev == nullptr)
- new_env.push_back (cv);
+ if (!contains_envvar (tevars, v, n) &&
+ !contains_envvar (evars, v, n))
+ new_env.push_back (v);
}
- // Copy the environment variables that are requested to be set.
+ // Copy non-overridden variable assignments into the child's
+ // environment.
//
- for (const char* const* ev (envvars); *ev != nullptr; ++ev)
+ auto set_vars = [&new_env] (const char* const* vs,
+ const char* const* ovs = nullptr)
{
- const char* v (*ev);
- if (strchr (v, '=') != nullptr)
- new_env.push_back (v);
- }
+ if (vs != nullptr)
+ {
+ while (const char* v = *vs++)
+ {
+ const char* e (strchr (v, '='));
+ if (e != nullptr && !contains_envvar (ovs, v, e - v))
+ new_env.push_back (v);
+ }
+ }
+ };
+
+ set_vars (tevars, evars);
+ set_vars (evars);
new_env.push_back (nullptr);
}
@@ -598,9 +600,9 @@ namespace butl
&fa,
nullptr /* attrp */,
const_cast<char* const*> (&args[0]),
- envvars != nullptr
- ? const_cast<char* const*> (new_env.data ())
- : environ);
+ new_env.empty ()
+ ? environ
+ : const_cast<char* const*> (new_env.data ()));
if (r != 0)
fail (r);
} // Release the lock in parent.
@@ -641,6 +643,10 @@ namespace butl
{
// Child.
//
+ // NOTE: make sure not to call anything that may acquire a mutex that
+ // could be already acquired in another thread, most notably
+ // malloc(). @@ What about exceptions (all the fail() calls)?
+
// Duplicate the user-supplied (fd > -1) or the created pipe descriptor
// to the standard stream descriptor (read end for STDIN_FILENO, write
// end otherwise). Close the pipe afterwards.
@@ -688,27 +694,38 @@ namespace butl
if (cwd != nullptr && *cwd != '\0' && chdir (cwd) != 0)
fail (true /* child */);
- // Set/unset environment variables if requested.
+ // Set/unset environment variables.
//
- if (envvars != nullptr)
+ auto set_vars = [] (const char* const* vs)
{
- while (const char* ev = *envvars++)
+ if (vs != nullptr)
{
- const char* v (strchr (ev, '='));
-
- try
- {
- if (v != nullptr)
- setenv (string (ev, v - ev), v + 1);
- else
- unsetenv (ev);
- }
- catch (const system_error& e)
+ while (const char* v = *vs++)
{
- throw process_child_error (e.code ().value ());
+ const char* e (strchr (v, '='));
+
+ try
+ {
+ // @@ TODO: redo without allocation (PATH_MAX?) Maybe
+ // also using C API to avoid exceptions.
+ //
+ if (e != nullptr)
+ setenv (string (v, e - v), e + 1);
+ else
+ unsetenv (v);
+ }
+ catch (const system_error& e)
+ {
+ // @@ Should we assume this cannot throw?
+ //
+ throw process_child_error (e.code ().value ());
+ }
}
}
- }
+ };
+
+ set_vars (tevars);
+ set_vars (evars);
// Try to re-exec after the "text file busy" failure for 450ms.
//
@@ -741,6 +758,13 @@ namespace butl
{
if (handle != 0)
{
+ // First close any open pipe ends for good measure but ignore any
+ // errors.
+ //
+ out_fd.reset ();
+ in_ofd.reset ();
+ in_efd.reset ();
+
int es;
int r (waitpid (handle, &es, 0));
handle = 0; // We have tried.
@@ -822,6 +846,12 @@ namespace butl
return getpid ();
}
+ process::handle_type process::
+ current_handle ()
+ {
+ return getpid ();
+ }
+
// process_exit
//
process_exit::
@@ -1274,13 +1304,30 @@ namespace butl
};
const char* process::
- quote_argument (const char* a, string& s)
+ quote_argument (const char* a, string& s, bool bat)
{
- // On Windows we need to protect values with spaces using quotes.
- // Since there could be actual quotes in the value, we need to
- // escape them.
+ // On Windows we need to protect values with spaces using quotes. Since
+ // there could be actual quotes in the value, we need to escape them.
+ //
+ // For batch files we also protect equal (`=`), comma (`,`) and semicolon
+ // (`;`) since otherwise an argument containing any of these will be split
+ // into several as if they were spaces (that is, the parts will appear in
+ // %1 %2, etc., instead of all in %1). This of course could break some
+ // batch files that rely on this semantics (for example, to automatically
+ // handle --foo=bar as --foo bar) but overall seeing a single argument
+ // (albeit quoted) is closer to the behavior of real executables. So we do
+ // this by default and if it becomes a problem we can invent a flag
+ // (probably in process_env) to disable this quoting (and while at it we
+ // may add a flag to disable all quoting since the user may need to quote
+ // some arguments but not others).
+ //
+ // While `()` and `[]` are not special characters, some "subsystems"
+ // (e.g., Cygwin/MSYS2) try to interpret them in certain contexts (e.g.,
+ // relative paths). So we quote them as well (over-quoting seems to be
+ // harmless according to the "Parsing C Command-Line Arguments" MSDN
+ // article).
//
- bool q (*a == '\0' || strchr (a, ' ') != nullptr);
+ bool q (*a == '\0' || strpbrk (a, bat ? " =,;" : " ()[]") != nullptr);
if (!q && strchr (a, '"') == nullptr)
return a;
@@ -1291,8 +1338,8 @@ namespace butl
s += '"';
// Note that backslashes don't need escaping, unless they immediately
- // precede the double quote (see `Parsing C Command-Line Arguments` MSDN
- // article for more details). For example:
+ // precede the double quote (see "Parsing C Command-Line Arguments" MSDN
+ // article for details). For example:
//
// -DPATH="C:\\foo\\" -> -DPATH=\"C:\\foo\\\\\"
// -DPATH=C:\foo bar\ -> "-DPATH=C:\foo bar\\"
@@ -1331,10 +1378,10 @@ namespace butl
static map<string, bool> detect_msys_cache_;
process::
- process (const process_path& pp, const char* args[],
+ process (const process_path& pp, const char* const* args,
pipe pin, pipe pout, pipe perr,
const char* cwd,
- const char* const* envvars)
+ const char* const* evars)
{
int in (pin.in);
int out (pout.out);
@@ -1356,7 +1403,9 @@ namespace butl
//
vector<char> new_env;
- if (envvars != nullptr)
+ const char* const* tevars (thread_env ());
+
+ if (tevars != nullptr || evars != nullptr)
{
// The environment block contains the variables in the following format:
//
@@ -1365,7 +1414,7 @@ namespace butl
// Note the trailing NULL character that follows the last variable
// (null-terminated) string.
//
- unique_ptr<char, void (*)(char*)> cvars (
+ unique_ptr<char, void (*)(char*)> pevars (
GetEnvironmentStringsA (),
[] (char* p)
{
@@ -1376,50 +1425,45 @@ namespace butl
assert (false);
});
- if (cvars.get () == nullptr)
+ if (pevars.get () == nullptr)
fail ();
- const char* cv (cvars.get ());
-
- // Copy the current environment variables.
+ // Copy the non-overridden process environment variables into the
+ // child's environment.
//
- while (*cv != '\0')
+ for (const char* v (pevars.get ()); *v != '\0'; )
{
- // Lookup the existing variable among those that are requested to be
- // (un)set. If not present, than copy it to the new block.
- //
- // Note that on Windows variable names are case-insensitive.
- //
- // Alse note that we don't expect the number of variables to (un)set
- // to be large, so the linear search is OK.
- //
- size_t n (strlen (cv) + 1); // Includes NULL character.
-
- const char* eq (strchr (cv, '='));
- size_t nn (eq != nullptr ? eq - cv : n - 1);
- const char* const* ev (envvars);
+ size_t n (strlen (v) + 1); // Includes NULL character.
- for (; *ev != nullptr; ++ev)
- {
- const char* v (*ev);
- if (icasecmp (cv, v, nn) == 0 && (v[nn] == '=' || v[nn] == '\0'))
- break;
- }
+ const char* e (strchr (v, '='));
+ size_t nn (e != nullptr ? e - v : n - 1);
- if (*ev == nullptr)
- new_env.insert (new_env.end (), cv, cv + n);
+ if (!contains_envvar (tevars, v, nn) &&
+ !contains_envvar (evars, v, nn))
+ new_env.insert (new_env.end (), v, v + n);
- cv += n;
+ v += n;
}
- // Copy the environment variables that are requested to be set.
+ // Copy non-overridden variable assignments into the child's
+ // environment.
//
- for (const char* const* ev (envvars); *ev != nullptr; ++ev)
+ auto set_vars = [&new_env] (const char* const* vs,
+ const char* const* ovs = nullptr)
{
- const char* v (*ev);
- if (strchr (v, '=') != nullptr)
- new_env.insert (new_env.end (), v, v + strlen (v) + 1);
- }
+ if (vs != nullptr)
+ {
+ while (const char* v = *vs++)
+ {
+ const char* e (strchr (v, '='));
+ if (e != nullptr && !contains_envvar (ovs, v, e - v))
+ new_env.insert (new_env.end (), v, v + strlen (v) + 1);
+ }
+ }
+ };
+
+ set_vars (tevars, evars);
+ set_vars (evars);
new_env.push_back ('\0'); // Terminate the new environment block.
}
@@ -1516,12 +1560,12 @@ namespace butl
//
string cmd_line;
{
- auto append = [&cmd_line, buf = string ()] (const char* a) mutable
+ auto append = [&batch, &cmd_line, buf = string ()] (const char* a) mutable
{
if (!cmd_line.empty ())
cmd_line += ' ';
- cmd_line += quote_argument (a, buf);
+ cmd_line += quote_argument (a, buf, batch.has_value ());
};
if (batch)
@@ -1763,7 +1807,6 @@ namespace butl
using namespace chrono;
-
// Retry for about 1 hour.
//
system_clock::duration timeout (1h);
@@ -1776,7 +1819,7 @@ namespace butl
0, // Primary thread security attributes.
true, // Inherit handles.
0, // Creation flags.
- envvars != nullptr ? new_env.data () : nullptr,
+ new_env.empty () ? nullptr : new_env.data (),
cwd != nullptr && *cwd != '\0' ? cwd : nullptr,
&si,
&pi))
@@ -1849,7 +1892,7 @@ namespace butl
return PeekNamedPipe (h, &c, 1, &n, nullptr, nullptr) && n == 1;
};
- // Hidden by butl::duration that is introduced via fdstream.mxx.
+ // Hidden by butl::duration that is introduced via fdstream.hxx.
//
using milli_duration = chrono::duration<DWORD, milli>;
@@ -1930,6 +1973,10 @@ namespace butl
{
if (handle != 0)
{
+ out_fd.reset ();
+ in_ofd.reset ();
+ in_efd.reset ();
+
DWORD es;
DWORD e (NO_ERROR);
if (WaitForSingleObject (handle, INFINITE) != WAIT_OBJECT_0 ||
@@ -2037,6 +2084,15 @@ namespace butl
return GetCurrentProcessId ();
}
+ process::handle_type process::
+ current_handle ()
+ {
+ // Note that the returned handle is a pseudo handle (-1) that does not
+ // need to be closed.
+ //
+ return GetCurrentProcess ();
+ }
+
// process_exit
//
process_exit::