From a22840fe77ef20f9c5488b9f2b8cf90663c537db Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Tue, 2 Aug 2016 11:25:16 +0200 Subject: Fix process::path_search() to look in parent program's directory --- butl/process.cxx | 110 +++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 71 insertions(+), 39 deletions(-) diff --git a/butl/process.cxx b/butl/process.cxx index 5c9a0f0..b78321c 100644 --- a/butl/process.cxx +++ b/butl/process.cxx @@ -12,7 +12,7 @@ # include // _open_osfhandle(), _get_osfhandle(), _close() # include // _O_TEXT -# include // getenv() +# include // _MAX_PATH, getenv() # include // stat # include // stat(), S_IS* @@ -249,6 +249,11 @@ namespace butl #else // _WIN32 + // Why do we search for the program ourselves when CreateProcess() can be + // made to do that for us? Well, that's a bit of a historic mystery. We + // could use it to disable search in the current working directory. Or we + // could handle batch files automatically. + // static path path_search (const path& f) { @@ -257,58 +262,85 @@ namespace butl // If there is a directory component in the file, then the PATH search // does not apply. // - if (!f.directory ().empty ()) + if (!f.simple ()) return f; - string paths; - - // If there is no PATH in the environment then the default search path is - // the current directory. - // - if (const char* s = getenv ("PATH")) + path r; + auto search = [&r, &f] (const char* d, size_t n) -> bool { - paths = s; + string s (move (r).string ()); // Reuse buffer. + + if (n != 0) + { + s.assign (d, n); + + if (!traits::is_separator (s.back ())) + s += traits::directory_separator; + } + + s += f.string (); + r = path (move (s)); // Move back into result. - // Also check the current directory. + // Check that the file exist without checking for permissions, etc. // - paths += traits::path_separator; - } - else - paths = traits::path_separator; + struct stat info; + if (stat (r.string ().c_str (), &info) == 0 && S_ISREG (info.st_mode)) + return true; + + // Also try the path with the .exe extension. + // + r += ".exe"; - struct stat info; + if (stat (r.string ().c_str (), &info) == 0 && S_ISREG (info.st_mode)) + return true; - for (size_t b (0), e (paths.find (traits::path_separator)); - b != string::npos;) + return false; + }; + + // The search order is documented in CreateProcess(). First we look in + // the directory of the parent executable. + // { - dir_path p (string (paths, b, e != string::npos ? e - b : e)); + char d[_MAX_PATH + 1]; + DWORD n (GetModuleFileName (NULL, d, _MAX_PATH + 1)); - // Empty path (i.e., a double colon or a colon at the beginning or end - // of PATH) means search in the current dirrectory. - // - if (p.empty ()) - p = dir_path ("."); + if (n == 0 || n == _MAX_PATH + 1) // Failed or truncated. + throw process_error (last_error_msg ()); - path dp (p / f); + const char* p (traits::rfind_separator (d, n)); + assert (p != nullptr); - // Just check that the file exist without checking for permissions, etc. - // - if (stat (dp.string ().c_str (), &info) == 0 && S_ISREG (info.st_mode)) - return dp; + if (search (d, p - d + 1)) // Include trailing slash. + return r; + } - // Also try the path with the .exe extension. - // - dp += ".exe"; + // Next look in the current working directory. Crazy, I know. + // + if (search ("", 0)) + return r; - if (stat (dp.string ().c_str (), &info) == 0 && S_ISREG (info.st_mode)) - return dp; + // Finally, search in PATH. + // + if (const char* s = getenv ("PATH")) + { + string ps (s); - if (e == string::npos) - b = e; - else + for (size_t b (0), e (ps.find (traits::path_separator)); + b != string::npos;) { - b = e + 1; - e = paths.find (traits::path_separator, b); + // Empty path (i.e., a double colon or a colon at the beginning or end + // of PATH) means search in the current dirrectory. + // + if (search (ps.c_str () + b, (e != string::npos ? e : ps.size ()) - b)) + return r; + + if (e == string::npos) + b = e; + else + { + b = e + 1; + e = ps.find (traits::path_separator, b); + } } } @@ -448,7 +480,7 @@ namespace butl // Do PATH search. // - if (file.directory ().empty ()) + if (file.simple ()) file = path_search (file); if (file.empty ()) -- cgit v1.1