aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2017-04-21 09:17:01 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2017-04-21 09:17:01 +0200
commit5a36f357e174d002722122d2408c57fb43da6e59 (patch)
tree4d31e5ac43a3013426d26f48e7a42850c2aa8aea
parent083834dcc5b76fe6f1318eb2b91c5caf08cd0c5f (diff)
Implement execution of Windows batch files
-rw-r--r--butl/process.cxx133
-rw-r--r--tests/process/buildfile2
-rw-r--r--tests/process/driver.cxx25
-rw-r--r--tests/process/testscript17
4 files changed, 135 insertions, 42 deletions
diff --git a/butl/process.cxx b/butl/process.cxx
index 1efa103..850ba9b 100644
--- a/butl/process.cxx
+++ b/butl/process.cxx
@@ -13,7 +13,7 @@
# include <butl/win32-utility>
# include <io.h> // _get_osfhandle(), _close()
-# include <stdlib.h> // _MAX_PATH, getenv()
+# include <stdlib.h> // _MAX_PATH
# include <sys/types.h> // stat
# include <sys/stat.h> // stat(), S_IS*
@@ -25,7 +25,7 @@
# define STDERR_FILENO 2
# endif // _MSC_VER
-# include <cstdlib> // __argv[]
+# include <cstdlib> // getenv(), __argv[]
# include <butl/small-vector>
#endif
@@ -574,16 +574,16 @@ namespace butl
size_t fn (strlen (f));
- // Unless there is already the .exe extension, then we will need to add
- // it. Note that running .bat files requires starting cmd.exe and passing
- // the batch file as an argument (see CreateProcess() for deails). So
- // if/when we decide to support those, it will have to be handled
- // differently.
+ // Unless there is already the .exe/.bat extension, then we will need to
+ // add it.
//
bool ext;
{
const char* e (traits::find_extension (f, fn));
- ext = (e == nullptr || casecmp (e, ".exe") != 0);
+ ext = (e == nullptr ||
+ (casecmp (e, ".exe") != 0 &&
+ casecmp (e, ".bat") != 0 &&
+ casecmp (e, ".cmd") != 0));
}
process_path r (f, path (), path ()); // Make sure it is not empty.
@@ -599,9 +599,26 @@ namespace butl
return _stat (f, &si) == 0 && S_ISREG (si.st_mode);
};
- auto search = [&ep, f, fn, ext, &exists] (const char* d,
- size_t dn,
- bool norm = false) -> bool
+ // Check with extensions: .exe, .cmd, and .bat.
+ //
+ auto exists_ext = [&exists] (string& s) -> bool
+ {
+ size_t i (s.size () + 1); // First extension letter.
+
+ s += ".exe";
+ if (exists (s.c_str ()))
+ return true;
+
+ s[i] = 'c'; s[i + 1] = 'm'; s[i + 2] = 'd';
+ if (exists (s.c_str ()))
+ return true;
+
+ s[i] = 'b'; s[i + 1] = 'a'; s[i + 2] = 't';
+ return exists (s.c_str ());
+ };
+
+ auto search = [&ep, f, fn, ext, &exists, &exists_ext] (
+ const char* d, size_t dn, bool norm = false) -> bool
{
string s (move (ep).string ()); // Reuse buffer.
@@ -619,12 +636,15 @@ namespace butl
if (norm)
ep.normalize ();
- // Add the .exe extension if necessary.
- //
- if (ext)
- ep += ".exe";
+ if (!ext)
+ return exists (ep.string ().c_str ());
- return exists (ep.string ().c_str ());
+ // Try with the extensions.
+ //
+ s = move (ep).string ();
+ bool e (exists_ext (s));
+ ep = path (move (s));
+ return e;
};
// If there is a directory component in the file, then the PATH search
@@ -636,13 +656,17 @@ namespace butl
{
if (traits::absolute (f, fn))
{
- if (ext)
+ bool e;
+ if (!ext)
+ e = exists (r.effect_string ());
+ else
{
- ep = path (f, fn);
- ep += ".exe";
+ string s (f, fn);
+ e = exists_ext (s);
+ ep = path (move (s));
}
- if (exists (r.effect_string ()))
+ if (e)
return r;
}
else
@@ -869,6 +893,24 @@ namespace butl
const process_path& pp, const char* args[],
int in, int out, int err)
{
+ // Figure out if this is a batch file since running them requires starting
+ // cmd.exe and passing the batch file as an argument (see CreateProcess()
+ // for deails).
+ //
+ const char* batch (nullptr);
+ {
+ const char* p (pp.effect_string ());
+ const char* e (path::traits::find_extension (p, strlen (p)));
+ if (e != nullptr && (casecmp (e, ".bat") == 0 ||
+ casecmp (e, ".cmd") == 0))
+ {
+ batch = getenv ("COMSPEC");
+
+ if (batch == nullptr)
+ batch = "C:\\Windows\\System32\\cmd.exe";
+ }
+ }
+
fdpipe out_fd;
fdpipe in_ofd;
fdpipe in_efd;
@@ -934,31 +976,44 @@ namespace butl
// Serialize the arguments to string.
//
string cmd_line;
-
- for (const char* const* p (args); *p != 0; ++p)
{
- if (p != args)
- cmd_line += ' ';
+ auto append = [&cmd_line] (const string& a)
+ {
+ if (!cmd_line.empty ())
+ cmd_line += ' ';
- // 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.
- //
- string a (*p);
- bool quote (a.empty () || a.find (' ') != string::npos);
+ // 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.
+ //
+ bool quote (a.empty () || a.find (' ') != string::npos);
+
+ if (quote)
+ cmd_line += '"';
- if (quote)
- cmd_line += '"';
+ for (size_t i (0); i < a.size (); ++i)
+ {
+ if (a[i] == '"')
+ cmd_line += "\\\"";
+ else
+ cmd_line += a[i];
+ }
- for (size_t i (0); i < a.size (); ++i)
+ if (quote)
+ cmd_line += '"';
+ };
+
+ if (batch != nullptr)
{
- if (a[i] == '"')
- cmd_line += "\\\"";
- else
- cmd_line += a[i];
+ append (batch);
+ append ("/c");
+ append (pp.effect_string ());
}
- if (quote)
- cmd_line += '"';
+ for (const char* const* p (args + (batch != nullptr ? 1 : 0));
+ *p != 0;
+ ++p)
+ append (*p);
}
// Prepare other process information.
@@ -1035,7 +1090,7 @@ namespace butl
fail ("invalid file descriptor");
if (!CreateProcess (
- pp.effect_string (),
+ batch != nullptr ? batch : pp.effect_string (),
const_cast<char*> (cmd_line.c_str ()),
0, // Process security attributes.
0, // Primary thread security attributes.
diff --git a/tests/process/buildfile b/tests/process/buildfile
index ae765c4..72cbec2 100644
--- a/tests/process/buildfile
+++ b/tests/process/buildfile
@@ -2,6 +2,6 @@
# copyright : Copyright (c) 2014-2017 Code Synthesis Ltd
# license : MIT; see accompanying LICENSE file
-exe{driver}: cxx{driver} ../../butl/lib{butl}
+exe{driver}: cxx{driver} ../../butl/lib{butl} test{testscript}
include ../../butl/
diff --git a/tests/process/driver.cxx b/tests/process/driver.cxx
index fc9ffe9..ae5015c 100644
--- a/tests/process/driver.cxx
+++ b/tests/process/driver.cxx
@@ -143,10 +143,11 @@ exec (const path& p,
}
catch (const process_error& e)
{
+ //cerr << args[0] << ": " << e << endl;
+
if (e.child)
exit (1);
- //cerr << args[0] << ": " << e << endl;
return false;
}
}
@@ -176,6 +177,7 @@ main (int argc, const char* argv[])
for (; i != argc; ++i)
{
string v (argv[i]);
+
if (v == "-c")
child = true;
else if (v == "-b")
@@ -254,6 +256,8 @@ main (int argc, const char* argv[])
return 0;
}
+ dir_path owd (dir_path::current_directory ());
+
// Test processes created as "already terminated".
//
{
@@ -309,7 +313,7 @@ main (int argc, const char* argv[])
assert (exec (dir_path (".") / fp.leaf ()));
- // Fail for unexistent file path.
+ // Fail for non-existent file path.
//
assert (!exec (dir_path (".") / path ("dr")));
@@ -343,4 +347,21 @@ main (int argc, const char* argv[])
pr.handle = reinterpret_cast<process::handle_type> (-1);
assert (!pr.wait (true) && !pr.wait (false));
#endif
+
+ // Test execution of Windows batch files. The test file is in the original
+ // working directory.
+ //
+#ifdef _WIN32
+ {
+ assert (exec (owd / "test.bat"));
+ assert (exec (owd / "test"));
+
+ paths = owd.string () + path::traits::path_separator + paths;
+ assert (_putenv (("PATH=" + paths).c_str ()) == 0);
+
+ assert (exec (path ("test.bat")));
+ assert (exec (path ("test")));
+ assert (!exec (path ("testX.bat")));
+ }
+#endif
}
diff --git a/tests/process/testscript b/tests/process/testscript
new file mode 100644
index 0000000..c734a05
--- /dev/null
+++ b/tests/process/testscript
@@ -0,0 +1,17 @@
+# file : tests/process/testscript
+# copyright : Copyright (c) 2014-2017 Code Synthesis Ltd
+# license : MIT; see accompanying LICENSE file
+
+if ($cxx.target.class == 'windows')
+ cat <<EOI >=test.bat
+ @echo off
+ setlocal
+ goto end
+ :error
+ endlocal
+ exit /b 1
+ :end
+ endlocal
+ EOI
+end;
+$*