// Filename: win32ArgParser.cxx // Created by: drose (08Nov11) // //////////////////////////////////////////////////////////////////// // // 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." // //////////////////////////////////////////////////////////////////// #include "win32ArgParser.h" #ifdef _WIN32 #include "memoryBase.h" #include "textEncoder.h" #include "globPattern.h" #include "filename.h" #include "executionEnvironment.h" #include #include //////////////////////////////////////////////////////////////////// // Function: Win32ArgParser::Constructor // Access: Public // Description: //////////////////////////////////////////////////////////////////// Win32ArgParser:: Win32ArgParser() : _argv(NULL), _argc(0) { } //////////////////////////////////////////////////////////////////// // Function: Win32ArgParser::Destructor // Access: Public // Description: //////////////////////////////////////////////////////////////////// Win32ArgParser:: ~Win32ArgParser() { clear(); } //////////////////////////////////////////////////////////////////// // Function: Win32ArgParser::clear // Access: Public // Description: 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(); } //////////////////////////////////////////////////////////////////// // Function: Win32ArgParser::set_command_line // Access: Public // Description: 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; } } //////////////////////////////////////////////////////////////////// // Function: Win32ArgParser::set_command_line // Access: Public // Description: 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()); } //////////////////////////////////////////////////////////////////// // Function: Win32ArgParser::set_system_command_line // Access: Public // Description: 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); } //////////////////////////////////////////////////////////////////// // Function: Win32ArgParser::get_argv // Access: Public // Description: 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; } //////////////////////////////////////////////////////////////////// // Function: Win32ArgParser::get_argc // Access: Public // Description: Returns the number of elements in the argv array. //////////////////////////////////////////////////////////////////// int Win32ArgParser:: get_argc() { return _argc; } //////////////////////////////////////////////////////////////////// // Function: Win32ArgParser::do_glob // Access: Public, Static // Description: 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; } //////////////////////////////////////////////////////////////////// // Function: Win32ArgParser::parse_quoted_arg // Access: Private // Description: 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; } //////////////////////////////////////////////////////////////////// // Function: Win32ArgParser::parse_unquoted_arg // Access: Private // Description: 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); } } //////////////////////////////////////////////////////////////////// // Function: Win32ArgParser::save_arg // Access: Private // Description: Stores the indicated string as the next argument in // _args. //////////////////////////////////////////////////////////////////// void Win32ArgParser:: save_arg(const string &arg) { _args.push_back(arg); } #endif // _WIN32