326 lines
8.1 KiB
C++
326 lines
8.1 KiB
C++
/**
|
|
* PANDA 3D SOFTWARE
|
|
* Copyright (c) Carnegie Mellon University. All rights reserved.
|
|
*
|
|
* All use of this software is subject to the terms of the revised BSD
|
|
* license. You should have received a copy of this license along
|
|
* with this source code in a file named "LICENSE."
|
|
*
|
|
* @file win32ArgParser.cxx
|
|
* @author drose
|
|
* @date 2011-11-08
|
|
*/
|
|
|
|
#include "win32ArgParser.h"
|
|
|
|
#ifdef _WIN32
|
|
|
|
#include "memoryBase.h"
|
|
#include "textEncoder.h"
|
|
#include "globPattern.h"
|
|
#include "filename.h"
|
|
#include "executionEnvironment.h"
|
|
|
|
#include <windows.h>
|
|
#include <Tlhelp32.h>
|
|
|
|
/**
|
|
*
|
|
*/
|
|
Win32ArgParser::
|
|
Win32ArgParser() :
|
|
_argv(NULL),
|
|
_argc(0)
|
|
{
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
Win32ArgParser::
|
|
~Win32ArgParser() {
|
|
clear();
|
|
}
|
|
|
|
/**
|
|
* Resets the parser to empty command line and deallocates the internal argv
|
|
* array.
|
|
*/
|
|
void Win32ArgParser::
|
|
clear() {
|
|
assert(_argc == (int)_args.size());
|
|
|
|
if (_argv != NULL) {
|
|
for (int i = 0; i < _argc; ++i) {
|
|
PANDA_FREE_ARRAY(_argv[i]);
|
|
}
|
|
PANDA_FREE_ARRAY(_argv);
|
|
_argv = NULL;
|
|
}
|
|
|
|
_argc = 0;
|
|
_args.clear();
|
|
}
|
|
|
|
/**
|
|
* Sets the string that indicates the full Win32 command line, and starts
|
|
* parsing this into argc, argv.
|
|
*/
|
|
void Win32ArgParser::
|
|
set_command_line(const string &command_line) {
|
|
clear();
|
|
|
|
const char *p = command_line.c_str();
|
|
while (*p != '\0') {
|
|
parse_unquoted_arg(p);
|
|
|
|
// Skip whitespace.
|
|
while (*p != '\0' && isspace(*p)) {
|
|
++p;
|
|
}
|
|
}
|
|
|
|
assert(_argc == 0 && _argv == NULL);
|
|
_argc = (int)_args.size();
|
|
_argv = (char **)PANDA_MALLOC_ARRAY(_argc * sizeof(char *));
|
|
for (int i = 0; i < _argc; ++i) {
|
|
const string &arg = _args[i];
|
|
char *astr = (char *)PANDA_MALLOC_ARRAY(arg.size() + 1);
|
|
memcpy(astr, arg.data(), arg.size());
|
|
astr[arg.size()] = '\0';
|
|
_argv[i] = astr;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets the Unicode string that indicates the full Win32 command line, and
|
|
* starts parsing this into argc, argv.
|
|
*/
|
|
void Win32ArgParser::
|
|
set_command_line(const wstring &command_line) {
|
|
TextEncoder encoder;
|
|
encoder.set_encoding(Filename::get_filesystem_encoding());
|
|
encoder.set_wtext(command_line);
|
|
set_command_line(encoder.get_text());
|
|
}
|
|
|
|
/**
|
|
* Tells the parser to call GetCommandLine() to query the system command line
|
|
* string, and parse it into argc, argv.
|
|
*/
|
|
void Win32ArgParser::
|
|
set_system_command_line() {
|
|
LPWSTR command_line = GetCommandLineW();
|
|
set_command_line(command_line);
|
|
}
|
|
|
|
/**
|
|
* Returns the argv array as computed by set_command_line() or
|
|
* set_system_command_line(). This array indexes directly into data allocated
|
|
* within the Win32ArgParser object; it will remain valid until
|
|
* set_command_line() or clear() is again called, or until the parser object
|
|
* destructs.
|
|
*/
|
|
char **Win32ArgParser::
|
|
get_argv() {
|
|
return _argv;
|
|
}
|
|
|
|
/**
|
|
* Returns the number of elements in the argv array.
|
|
*/
|
|
int Win32ArgParser::
|
|
get_argc() {
|
|
return _argc;
|
|
}
|
|
|
|
/**
|
|
* Returns true if we should attempt to process (and apply glob matching) to
|
|
* the command line, or false if we should not (for instance, because it has
|
|
* already been done by the shell).
|
|
*/
|
|
bool Win32ArgParser::
|
|
do_glob() {
|
|
// First, we check for the PANDA_GLOB environment variable. If this is
|
|
// present, it overrides any other checks: "0" means not to do the glob, "1"
|
|
// means to do it.
|
|
string envvar = ExecutionEnvironment::get_environment_variable("PANDA_GLOB");
|
|
if (!envvar.empty()) {
|
|
istringstream strm(envvar);
|
|
int value;
|
|
strm >> value;
|
|
if (!strm.fail()) {
|
|
return (value != 0);
|
|
}
|
|
}
|
|
|
|
// Nothing explicit, so the default is to perform globbing only if we were
|
|
// launched from the Windows default command shell, cmd.exe. Presumably if
|
|
// we were launched from something else, like Python, the caller won't
|
|
// expect globbing to be performed; and if we were launched from a Cygwin
|
|
// shell, it will already have been performed.
|
|
|
|
// Unfortunately, it is surprisingly difficult to determine the parent
|
|
// process in Windows. We have to enumerate all of the processes to find
|
|
// it.
|
|
|
|
HANDLE toolhelp = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
|
|
|
|
PROCESSENTRY32 entry;
|
|
memset(&entry, 0, sizeof(entry));
|
|
entry.dwSize = sizeof(entry);
|
|
|
|
DWORD current_id = GetCurrentProcessId();
|
|
DWORD parent_id = -1;
|
|
|
|
if (Process32First(toolhelp, &entry)) {
|
|
do {
|
|
if (entry.th32ProcessID == current_id) {
|
|
parent_id = entry.th32ParentProcessID;
|
|
break;
|
|
}
|
|
} while (Process32Next(toolhelp, &entry));
|
|
}
|
|
|
|
Filename parent_exe;
|
|
if (parent_id != -1) {
|
|
// Now we've got the parent process ID, go back through the list to get
|
|
// its process name.
|
|
if (Process32First(toolhelp, &entry)) {
|
|
do {
|
|
if (entry.th32ProcessID == parent_id) {
|
|
parent_exe = Filename::from_os_specific(entry.szExeFile);
|
|
break;
|
|
}
|
|
} while (Process32Next(toolhelp, &entry));
|
|
}
|
|
}
|
|
|
|
CloseHandle(toolhelp);
|
|
string basename = parent_exe.get_basename();
|
|
if (basename == "cmd.exe") {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Parses the quoted argument beginning at p and returns it. Advances p to
|
|
* the first character following the close quote.
|
|
*/
|
|
string Win32ArgParser::
|
|
parse_quoted_arg(const char *&p) {
|
|
char quote = *p;
|
|
++p;
|
|
string result;
|
|
|
|
while (*p != '\0' && *p != quote) {
|
|
// TODO: handle caret? What does it mean?
|
|
|
|
if (*p == '\\') {
|
|
// A backslash is an escape character only when it precedes a quote
|
|
// mark, or a series of backslashes precede a quote mark.
|
|
int num_slashes = 1;
|
|
++p;
|
|
while (*p == '\\') {
|
|
++p;
|
|
++num_slashes;
|
|
}
|
|
if (*p == quote) {
|
|
// A series of backslashes precede a quote mark. This means something
|
|
// special. First, each pair of backslashes means a single backslash.
|
|
for (int i = 0; i < num_slashes; i += 2) {
|
|
result += '\\';
|
|
}
|
|
// And if there's no odd backslashes left over, we've reached the
|
|
// closing quote and we're done.
|
|
if ((num_slashes & 1) == 0) {
|
|
++p;
|
|
return result;
|
|
}
|
|
|
|
// But if there's an odd backslash, it simply escapes the quote mark.
|
|
result += quote;
|
|
++p;
|
|
|
|
} else {
|
|
// A series of backslashes not followed by a quote mark is interpreted
|
|
// literally, not even counting them by twos, per Win32's weird rules.
|
|
for (int i = 0; i < num_slashes; ++i) {
|
|
result += '\\';
|
|
}
|
|
}
|
|
|
|
} else {
|
|
// Neither a backslash nor a quote mark, so just interpret it literally.
|
|
result += *p;
|
|
++p;
|
|
}
|
|
}
|
|
|
|
if (*p == quote) {
|
|
++p;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Parses the unquoted argument beginning at p and saves it in _char_args.
|
|
* Advances p to the first whitespace following the argument.
|
|
*/
|
|
void Win32ArgParser::
|
|
parse_unquoted_arg(const char *&p) {
|
|
string result;
|
|
bool contains_quotes = false;
|
|
while (*p != '\0' && !isspace(*p)) {
|
|
if (*p == '"') {
|
|
// Begin a quoted sequence.
|
|
contains_quotes = true;
|
|
result += parse_quoted_arg(p);
|
|
} else {
|
|
// A regular character.
|
|
result += *p;
|
|
++p;
|
|
}
|
|
}
|
|
|
|
Filename filename = Filename::from_os_specific(result);
|
|
GlobPattern glob(filename);
|
|
if (!contains_quotes && glob.has_glob_characters()) {
|
|
// If the arg contains one or more glob characters (and no quotation
|
|
// marks), we attempt to expand the files. This means we interpret it as
|
|
// a Windows-specific filename.
|
|
vector_string expand;
|
|
if (glob.match_files(expand) != 0) {
|
|
// The files matched. Add the expansions.
|
|
vector_string::const_iterator ei;
|
|
for (ei = expand.begin(); ei != expand.end(); ++ei) {
|
|
Filename filename(*ei);
|
|
save_arg(filename.to_os_specific());
|
|
}
|
|
} else {
|
|
// There wasn't a match. Just add the original, unexpanded string, like
|
|
// bash does.
|
|
save_arg(result);
|
|
}
|
|
|
|
} else {
|
|
// No glob characters means we just store it directly. Also, an embedded
|
|
// quoted string, anywhere within the arg, means we can't expand the glob
|
|
// characters.
|
|
save_arg(result);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Stores the indicated string as the next argument in _args.
|
|
*/
|
|
void Win32ArgParser::
|
|
save_arg(const string &arg) {
|
|
_args.push_back(arg);
|
|
}
|
|
|
|
#endif // _WIN32
|