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