Panda3D
win32ArgParser.cxx
1 // Filename: win32ArgParser.cxx
2 // Created by: drose (08Nov11)
3 //
4 ////////////////////////////////////////////////////////////////////
5 //
6 // PANDA 3D SOFTWARE
7 // Copyright (c) Carnegie Mellon University. All rights reserved.
8 //
9 // All use of this software is subject to the terms of the revised BSD
10 // license. You should have received a copy of this license along
11 // with this source code in a file named "LICENSE."
12 //
13 ////////////////////////////////////////////////////////////////////
14 
15 #include "win32ArgParser.h"
16 
17 #ifdef _WIN32
18 
19 #include "memoryBase.h"
20 #include "textEncoder.h"
21 #include "globPattern.h"
22 #include "filename.h"
23 #include "executionEnvironment.h"
24 
25 #include <windows.h>
26 #include <Tlhelp32.h>
27 
28 ////////////////////////////////////////////////////////////////////
29 // Function: Win32ArgParser::Constructor
30 // Access: Public
31 // Description:
32 ////////////////////////////////////////////////////////////////////
33 Win32ArgParser::
34 Win32ArgParser() :
35  _argv(NULL),
36  _argc(0)
37 {
38 }
39 
40 ////////////////////////////////////////////////////////////////////
41 // Function: Win32ArgParser::Destructor
42 // Access: Public
43 // Description:
44 ////////////////////////////////////////////////////////////////////
45 Win32ArgParser::
46 ~Win32ArgParser() {
47  clear();
48 }
49 
50 ////////////////////////////////////////////////////////////////////
51 // Function: Win32ArgParser::clear
52 // Access: Public
53 // Description: Resets the parser to empty command line and
54 // deallocates the internal argv array.
55 ////////////////////////////////////////////////////////////////////
56 void Win32ArgParser::
57 clear() {
58  assert(_argc == (int)_args.size());
59 
60  if (_argv != NULL) {
61  for (int i = 0; i < _argc; ++i) {
62  PANDA_FREE_ARRAY(_argv[i]);
63  }
64  PANDA_FREE_ARRAY(_argv);
65  _argv = NULL;
66  }
67 
68  _argc = 0;
69  _args.clear();
70 }
71 
72 ////////////////////////////////////////////////////////////////////
73 // Function: Win32ArgParser::set_command_line
74 // Access: Public
75 // Description: Sets the string that indicates the full Win32 command
76 // line, and starts parsing this into argc, argv.
77 ////////////////////////////////////////////////////////////////////
78 void Win32ArgParser::
79 set_command_line(const string &command_line) {
80  clear();
81 
82  const char *p = command_line.c_str();
83  while (*p != '\0') {
84  parse_unquoted_arg(p);
85 
86  // Skip whitespace.
87  while (*p != '\0' && isspace(*p)) {
88  ++p;
89  }
90  }
91 
92  assert(_argc == 0 && _argv == NULL);
93  _argc = (int)_args.size();
94  _argv = (char **)PANDA_MALLOC_ARRAY(_argc * sizeof(char *));
95  for (int i = 0; i < _argc; ++i) {
96  const string &arg = _args[i];
97  char *astr = (char *)PANDA_MALLOC_ARRAY(arg.size() + 1);
98  memcpy(astr, arg.data(), arg.size());
99  astr[arg.size()] = '\0';
100  _argv[i] = astr;
101  }
102 }
103 
104 ////////////////////////////////////////////////////////////////////
105 // Function: Win32ArgParser::set_command_line
106 // Access: Public
107 // Description: Sets the Unicode string that indicates the full Win32
108 // command line, and starts parsing this into argc,
109 // argv.
110 ////////////////////////////////////////////////////////////////////
111 void Win32ArgParser::
112 set_command_line(const wstring &command_line) {
113  TextEncoder encoder;
115  encoder.set_wtext(command_line);
116  set_command_line(encoder.get_text());
117 }
118 
119 ////////////////////////////////////////////////////////////////////
120 // Function: Win32ArgParser::set_system_command_line
121 // Access: Public
122 // Description: Tells the parser to call GetCommandLine() to query
123 // the system command line string, and parse it into
124 // argc, argv.
125 ////////////////////////////////////////////////////////////////////
126 void Win32ArgParser::
127 set_system_command_line() {
128  LPWSTR command_line = GetCommandLineW();
129  set_command_line(command_line);
130 }
131 
132 ////////////////////////////////////////////////////////////////////
133 // Function: Win32ArgParser::get_argv
134 // Access: Public
135 // Description: Returns the argv array as computed by
136 // set_command_line() or set_system_command_line().
137 // This array indexes directly into data allocated
138 // within the Win32ArgParser object; it will remain
139 // valid until set_command_line() or clear() is again
140 // called, or until the parser object destructs.
141 ////////////////////////////////////////////////////////////////////
142 char **Win32ArgParser::
143 get_argv() {
144  return _argv;
145 }
146 
147 ////////////////////////////////////////////////////////////////////
148 // Function: Win32ArgParser::get_argc
149 // Access: Public
150 // Description: Returns the number of elements in the argv array.
151 ////////////////////////////////////////////////////////////////////
152 int Win32ArgParser::
153 get_argc() {
154  return _argc;
155 }
156 
157 ////////////////////////////////////////////////////////////////////
158 // Function: Win32ArgParser::do_glob
159 // Access: Public, Static
160 // Description: Returns true if we should attempt to process (and
161 // apply glob matching) to the command line, or false if
162 // we should not (for instance, because it has already
163 // been done by the shell).
164 ////////////////////////////////////////////////////////////////////
165 bool Win32ArgParser::
166 do_glob() {
167  // First, we check for the PANDA_GLOB environment variable. If this
168  // is present, it overrides any other checks: "0" means not to do
169  // the glob, "1" means to do it.
170  string envvar = ExecutionEnvironment::get_environment_variable("PANDA_GLOB");
171  if (!envvar.empty()) {
172  istringstream strm(envvar);
173  int value;
174  strm >> value;
175  if (!strm.fail()) {
176  return (value != 0);
177  }
178  }
179 
180  // Nothing explicit, so the default is to perform globbing only if
181  // we were launched from the Windows default command shell, cmd.exe.
182  // Presumably if we were launched from something else, like Python,
183  // the caller won't expect globbing to be performed; and if we were
184  // launched from a Cygwin shell, it will already have been
185  // performed.
186 
187  // Unfortunately, it is surprisingly difficult to determine the
188  // parent process in Windows. We have to enumerate all of the
189  // processes to find it.
190 
191  HANDLE toolhelp = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
192 
193  PROCESSENTRY32 entry;
194  memset(&entry, 0, sizeof(entry));
195  entry.dwSize = sizeof(entry);
196 
197  DWORD current_id = GetCurrentProcessId();
198  DWORD parent_id = -1;
199 
200  if (Process32First(toolhelp, &entry)) {
201  do {
202  if (entry.th32ProcessID == current_id) {
203  parent_id = entry.th32ParentProcessID;
204  break;
205  }
206  } while (Process32Next(toolhelp, &entry));
207  }
208 
209  Filename parent_exe;
210  if (parent_id != -1) {
211  // Now we've got the parent process ID, go back through the list
212  // to get its process name.
213  if (Process32First(toolhelp, &entry)) {
214  do {
215  if (entry.th32ProcessID == parent_id) {
216  parent_exe = Filename::from_os_specific(entry.szExeFile);
217  break;
218  }
219  } while (Process32Next(toolhelp, &entry));
220  }
221  }
222 
223  CloseHandle(toolhelp);
224  string basename = parent_exe.get_basename();
225  if (basename == "cmd.exe") {
226  return true;
227  }
228 
229  return false;
230 }
231 
232 ////////////////////////////////////////////////////////////////////
233 // Function: Win32ArgParser::parse_quoted_arg
234 // Access: Private
235 // Description: Parses the quoted argument beginning at p and returns
236 // it. Advances p to the first character following the
237 // close quote.
238 ////////////////////////////////////////////////////////////////////
239 string Win32ArgParser::
240 parse_quoted_arg(const char *&p) {
241  char quote = *p;
242  ++p;
243  string result;
244 
245  while (*p != '\0' && *p != quote) {
246  // TODO: handle caret? What does it mean?
247 
248  if (*p == '\\') {
249  // A backslash is an escape character only when it precedes a
250  // quote mark, or a series of backslashes precede a quote mark.
251  int num_slashes = 1;
252  ++p;
253  while (*p == '\\') {
254  ++p;
255  ++num_slashes;
256  }
257  if (*p == quote) {
258  // A series of backslashes precede a quote mark. This means
259  // something special. First, each pair of backslashes means a
260  // single backslash.
261  for (int i = 0; i < num_slashes; i += 2) {
262  result += '\\';
263  }
264  // And if there's no odd backslashes left over, we've reached
265  // the closing quote and we're done.
266  if ((num_slashes & 1) == 0) {
267  ++p;
268  return result;
269  }
270 
271  // But if there's an odd backslash, it simply escapes the
272  // quote mark.
273  result += quote;
274  ++p;
275 
276  } else {
277  // A series of backslashes not followed by a quote mark is
278  // interpreted literally, not even counting them by twos, per
279  // Win32's weird rules.
280  for (int i = 0; i < num_slashes; ++i) {
281  result += '\\';
282  }
283  }
284 
285  } else {
286  // Neither a backslash nor a quote mark, so just interpret it
287  // literally.
288  result += *p;
289  ++p;
290  }
291  }
292 
293  if (*p == quote) {
294  ++p;
295  }
296 
297  return result;
298 }
299 
300 ////////////////////////////////////////////////////////////////////
301 // Function: Win32ArgParser::parse_unquoted_arg
302 // Access: Private
303 // Description: Parses the unquoted argument beginning at p and saves
304 // it in _char_args. Advances p to the first whitespace
305 // following the argument.
306 ////////////////////////////////////////////////////////////////////
307 void Win32ArgParser::
308 parse_unquoted_arg(const char *&p) {
309  string result;
310  bool contains_quotes = false;
311  while (*p != '\0' && !isspace(*p)) {
312  if (*p == '"') {
313  // Begin a quoted sequence.
314  contains_quotes = true;
315  result += parse_quoted_arg(p);
316  } else {
317  // A regular character.
318  result += *p;
319  ++p;
320  }
321  }
322 
323  Filename filename = Filename::from_os_specific(result);
324  GlobPattern glob(filename);
325  if (!contains_quotes && glob.has_glob_characters()) {
326  // If the arg contains one or more glob characters (and no
327  // quotation marks), we attempt to expand the files. This means
328  // we interpret it as a Windows-specific filename.
329  vector_string expand;
330  if (glob.match_files(expand) != 0) {
331  // The files matched. Add the expansions.
332  vector_string::const_iterator ei;
333  for (ei = expand.begin(); ei != expand.end(); ++ei) {
334  Filename filename(*ei);
335  save_arg(filename.to_os_specific());
336  }
337  } else {
338  // There wasn't a match. Just add the original, unexpanded
339  // string, like bash does.
340  save_arg(result);
341  }
342 
343  } else {
344  // No glob characters means we just store it directly. Also, an
345  // embedded quoted string, anywhere within the arg, means we can't
346  // expand the glob characters.
347  save_arg(result);
348  }
349 }
350 
351 ////////////////////////////////////////////////////////////////////
352 // Function: Win32ArgParser::save_arg
353 // Access: Private
354 // Description: Stores the indicated string as the next argument in
355 // _args.
356 ////////////////////////////////////////////////////////////////////
357 void Win32ArgParser::
358 save_arg(const string &arg) {
359  _args.push_back(arg);
360 }
361 
362 #endif // _WIN32
string get_basename() const
Returns the basename part of the filename.
Definition: filename.I:436
This class can be used to convert text between multiple representations, e.g.
Definition: textEncoder.h:37
static TextEncoder::Encoding get_filesystem_encoding()
Specifies the default encoding to be used for all subsequent Filenames objects.
Definition: filename.I:778
The name of a file, such as a texture file or an Egg file.
Definition: filename.h:44
static string get_environment_variable(const string &var)
Returns the definition of the indicated environment variable, or the empty string if the variable is ...
void set_encoding(Encoding encoding)
Specifies how the string set via set_text() is to be interpreted.
Definition: textEncoder.I:59
void set_wtext(const wstring &wtext)
Changes the text that is stored in the encoder.
Definition: textEncoder.I:516
string to_os_specific() const
Converts the filename from our generic Unix-like convention (forward slashes starting with the root a...
Definition: filename.cxx:1196
This class can be used to test for string matches against standard Unix-shell filename globbing conve...
Definition: globPattern.h:37
string get_text() const
Returns the current text, as encoded via the current encoding system.
Definition: textEncoder.I:167
static Filename from_os_specific(const string &os_specific, Type type=T_general)
This named constructor returns a Panda-style filename (that is, using forward slashes, and no drive letter) based on the supplied filename string that describes a filename in the local system conventions (for instance, on Windows, it may use backslashes or begin with a drive letter and a colon).
Definition: filename.cxx:332