windows/SystemCommands.cpp
Go to the documentation of this file.
1 /*----------------------------------------------------------------------------*/
2 /* */
3 /* Copyright (c) 1995, 2004 IBM Corporation. All rights reserved. */
4 /* Copyright (c) 2005-2009 Rexx Language Association. All rights reserved. */
5 /* */
6 /* This program and the accompanying materials are made available under */
7 /* the terms of the Common Public License v1.0 which accompanies this */
8 /* distribution. A copy is also available at the following address: */
9 /* http://www.oorexx.org/license.html */
10 /* */
11 /* Redistribution and use in source and binary forms, with or */
12 /* without modification, are permitted provided that the following */
13 /* conditions are met: */
14 /* */
15 /* Redistributions of source code must retain the above copyright */
16 /* notice, this list of conditions and the following disclaimer. */
17 /* Redistributions in binary form must reproduce the above copyright */
18 /* notice, this list of conditions and the following disclaimer in */
19 /* the documentation and/or other materials provided with the distribution. */
20 /* */
21 /* Neither the name of Rexx Language Association nor the names */
22 /* of its contributors may be used to endorse or promote products */
23 /* derived from this software without specific prior written permission. */
24 /* */
25 /* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS */
26 /* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT */
27 /* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS */
28 /* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT */
29 /* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, */
30 /* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED */
31 /* TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, */
32 /* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY */
33 /* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING */
34 /* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS */
35 /* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */
36 /* */
37 /*----------------------------------------------------------------------------*/
38 /******************************************************************************/
39 /* wincmd.c - C methods for handling calls to system exits and subcommand */
40 /* handlers. */
41 /* */
42 /* C methods: */
43 /* SysCommand - Method to invoke a subcommand handler */
44 /* */
45 /* Internal routines: */
46 /* sys_command - Run a command through system command processor. */
47 /******************************************************************************/
48 #include <string.h> /* Get strcpy, strcat, etc. */
49 
50 #include <process.h>
51 #include <stdlib.h>
52 
53 #include "RexxCore.h" /* global REXX declarations */
54 #include "StringClass.hpp"
55 #include "RexxActivity.hpp"
56 #include "ActivityManager.hpp"
57 #include "ProtectedObject.hpp"
58 #include "RexxInternalApis.h" /* Get private REXXAPI API's */
59 #include "SystemInterpreter.hpp"
60 #include "InterpreterInstance.hpp"
62 
63 #define CMDBUFSIZE32S 260 /* Max size of executable cmd */
64 #define CMDBUFSIZENT 8092 /* Max size of executable cmd */
65 #define CMDDEFNAME32S "COMMAND.COM" /* Default Win 95 cmd handler */
66 #define CMDDEFNAMENT "CMD.EXE" /* Default Win NT cmd handler */
67 #define UNKNOWN_COMMAND 1 /* unknown command return code */
68 #include "direct.h"
69 
70 #define SYSENV "CMD" /* Default windows cmd environment*/
71 #define COMSPEC "COMSPEC" /* cmd handler env name */
72 
73 #define SHOWWINDOWFLAGS SW_HIDE // determines visibility of cmd
74  // window SHOW, HIDE etc...
75  // function prototypes
76 /**
77  * Retrieve the globally default initial address.
78  *
79  * @return The string name of the default address.
80  */
82 {
83  return OREF_INITIALADDRESS;
84 }
85 
86 
87 /* Handle "SET XX=YYY" command in same process */
88 bool sys_process_set(RexxExitContext *context, const char *command, const char * cmd, RexxObjectPtr &rc)
89 {
90  rc = NULLOBJECT;
91  const char * eqsign;
92  const char * st;
93  char name[256];
94  char value[4096];
95  eqsign = strchr(cmd, '=');
96  if (!eqsign)
97  {
98  return false;
99  }
100 
101  st = &cmd[4];
102  while ((*st) && (*st == ' '))
103  {
104  st++;
105  }
106  if (st == eqsign)
107  {
108  return false;
109  }
110  strncpy(name, st, eqsign-st);
111  name[eqsign-st]='\0';
112 
113  if (ExpandEnvironmentStrings(eqsign+1, value, 4095) && SetEnvironmentVariable(name,value))
114  {
115  rc = context->False(); // just return a zero
116  }
117  else
118  {
119  context->RaiseCondition("ERROR", context->String(command), NULLOBJECT, context->WholeNumberToObject(GetLastError()));
120  }
121  return true;
122 }
123 
124 
125 /* Returns a copy of s without quotes */
126 char *unquote(const char *s)
127 {
128  char *unquoted = (char*)malloc(sizeof(char) * strlen(s) + 1);
129 
130  if (unquoted != NULL)
131  {
132  char *p = unquoted;
133  while ( (*p = *s++) != 0 )
134  {
135  if (*p != '"')
136  {
137  p++;
138  }
139  }
140  *p = '\0';
141  }
142  return unquoted;
143 }
144 
145 
146 /* Handle "CD XXX" command in same process */
147 bool sys_process_cd(RexxExitContext *context, const char *command, const char * cmd, RexxObjectPtr &res)
148 {
149  const char * st;
150  int rc;
151  res = NULLOBJECT;
152 
153  st = &cmd[3];
154  while ((*st) && (*st == ' '))
155  {
156  st++;
157  }
158  if (!*st)
159  {
160  return false;
161  }
162 
163  if ((strlen(st) == 2) && (st[1] == ':'))
164  {
165  rc = _chdrive(toupper( *st ) - 'A' + 1);
166  }
167  else
168  {
169  char *unquoted = unquote(st);
170  if (unquoted == NULL)
171  {
172  return false;
173  }
174  rc = _chdir(unquoted);
175  free(unquoted);
176  }
177  if (rc != 0)
178  {
179  context->RaiseCondition("ERROR", context->String(command), NULLOBJECT, context->WholeNumberToObject(GetLastError()));
180  }
181  else
182  {
183  res = context->False();
184  }
185 
186  return true;
187 }
188 
189 
190 /*-----------------------------------------------------------------------------
191  | Name: sysCommandNT |
192  | |
193  | Arguments: cmd - Command to be executed |
194  | |
195  | Returned: rc - Return Code |
196  | Note: if CreateProcess fails return GetLastError code |
197  | else return rc from executed command |
198  | |
199  | Notes: Handles processing of a system command on a Windows NT system |
200  | |
201  ----------------------------------------------------------------------------*/
202 bool sysCommandNT(RexxExitContext *context, const char *command, const char *cmdstring_ptr, bool direct, RexxObjectPtr &result)
203 {
204  DWORD rc;
205  STARTUPINFO siStartInfo; // process startup info
206  PROCESS_INFORMATION piProcInfo; // returned process info
207  char ctitle[256];
208  DWORD creationFlags;
209  bool titleChanged;
210 
211  ZeroMemory(&siStartInfo, sizeof(siStartInfo));
212  ZeroMemory(&piProcInfo, sizeof(piProcInfo));
213  /****************************************************************************/
214  /* Invoke the system command handler to execute the command */
215  /****************************************************************************/
216  siStartInfo.cb = sizeof(siStartInfo);
217 
218  siStartInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
219  siStartInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
220  siStartInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE);
221  titleChanged = GetConsoleTitle(ctitle, 255) != 0;
222  siStartInfo.lpTitle = (LPSTR)cmdstring_ptr;
223  creationFlags = GetPriorityClass(GetCurrentProcess()) | CREATE_NEW_PROCESS_GROUP;
224  if (!siStartInfo.hStdInput && !siStartInfo.hStdOutput && !titleChanged) /* is REXXHIDE running without console */
225  {
226  if (!direct)
227  {
228  siStartInfo.wShowWindow = SHOWWINDOWFLAGS;
229  siStartInfo.dwFlags |= STARTF_USESHOWWINDOW;
230  }
232  {
233  creationFlags |= CREATE_NEW_CONSOLE; /* new console if CMD ord COMMAND was specified */
234  }
235  }
236  else /* just use standard handles if we are running in a console */
237  {
238  if (direct)
239  {
240  siStartInfo.dwFlags = STARTF_USESTDHANDLES; /* no SW_HIDE for direct commands */
241  }
242  else
243  {
244  siStartInfo.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
245  }
246  siStartInfo.wShowWindow = SHOWWINDOWFLAGS;
247  }
248 
249  if (CreateProcess(NULL, // address of module name
250  (LPSTR)cmdstring_ptr,// address of command line
251  NULL, // address of process security attrs
252  NULL, // address of thread security attrs
253  true, // new process inherits handles?
254  // creation flags
255  creationFlags,
256  NULL, // address of new environment block
257  NULL, // address of current directory name
258  &siStartInfo, // address of STARTUPINFO
259  &piProcInfo)) // address of PROCESS_INFORMATION
260  {
261  // CreateProcess succeeded, now wait for the process to end.
262  if (titleChanged)
263  {
264  SetConsoleTitle(siStartInfo.lpTitle);
265  }
266  SystemInterpreter::exceptionHostProcess = piProcInfo.hProcess;
267  SystemInterpreter::exceptionHostProcessId = piProcInfo.dwProcessId;
268 
269  if (WAIT_FAILED != WaitForSingleObject ( piProcInfo.hProcess, INFINITE ) )
270  {
271  // Completed ok, get termination rc
272  GetExitCodeProcess(piProcInfo.hProcess, &rc);
273  }
274  else
275  {
276  rc = GetLastError(); // Bad termination, get error code
277  context->RaiseCondition("FAILURE", context->String(command), NULLOBJECT, context->WholeNumberToObject(rc));
278  result = NULLOBJECT;
279  return true;
280  }
281 
282  // The new process must be detached so it will be discarded
283  // automatically after execution. The thread must be closed first
284  if (titleChanged)
285  {
286  SetConsoleTitle(ctitle);
287  }
288  CloseHandle(piProcInfo.hThread);
289  CloseHandle(piProcInfo.hProcess);
290  }
291  else
292  {
293  // return this as a failure for now ... the caller might try this again
294  // later
295  return false;
296  }
297 
300 
301  if (rc != 0)
302  {
303  context->RaiseCondition("ERROR", context->String(command), NULLOBJECT, context->Int32ToObject(rc));
304  result = NULLOBJECT;
305  return true;
306  }
307  // this is a zero return
308  result = context->False();
309  return true;
310 }
311 
312 
313 
314 /******************************************************************************/
315 /* Name: sys_command */
316 /* */
317 /* Arguments: cmd - Command to be executed */
318 /* */
319 /* Returned: rc - Return Code */
320 /* Note: if non-zero rc from DosExecPgm return DosExecPgm rc */
321 /* else if non-zero termination code from system return code */
322 /* else return rc from executed command */
323 /* */
324 /* Notes: Handles processing of a system command. Finds location of */
325 /* system command handler using the COMSPEC environment variable */
326 /* and invokes the system specific routine which invokes the */
327 /* command handler with the command to be executed */
328 /* */
329 /******************************************************************************/
331 {
332  // address the command information
333  const char *cmd = context->StringData(command);
334  const char *cl_opt = " /c "; // The "/c" opt for system commandd handler
335  const char *interncmd;
336 
337  // Remove the "quiet sign" if present
338  if (cmd[0] == '@')
339  {
340  interncmd = cmd + 1;
341  }
342  else
343  {
344  interncmd = cmd;
345  }
346 
347  /* Check for redirection symbols, ignore them when enclosed in double
348  * quotes. If there are more than 2 double qoutes, cmd.exe strips off the
349  * first and last. To preserve what the user sent to us, we count the
350  * double quotes, and, if more than two, add a double quote to the front and
351  * end of the string.
352  */
353  size_t quoteCount = 0;
354  bool noDirectInvoc = false;
355  bool inQuotes = false;
356  size_t i;
357 
358  for (i = 0; i < strlen(interncmd); i++)
359  {
360  if (interncmd[i] == '"')
361  {
362  inQuotes = !inQuotes;
363  quoteCount++;
364  }
365  else
366  {
367  /* if we're in the unquoted part and the current character is one of */
368  /* the redirection characters or the & for multiple commands then we */
369  /* will no longer try to invoke the command directly */
370  if (!inQuotes && (strchr("<>|&", interncmd[i]) != NULL))
371  {
372  noDirectInvoc = true;
373  if ( quoteCount > 2 )
374  {
375  break;
376  }
377  }
378  }
379  }
380 
381  i = 0; // reset to zero for next usage
382 
383  // scan for the first non-whitespace character
384  size_t j = 0;
385  while (interncmd[j] == ' ')
386  {
387  j++;
388  }
389 
390  RexxObjectPtr result = NULLOBJECT;
391 
392  if (!noDirectInvoc)
393  {
394  char tmp[8];
395  strncpy(tmp, &interncmd[j], 4);
396  tmp[4] = '\0';
397 
398  if (!stricmp("set ",tmp))
399  {
400  if (sys_process_set(context, cmd, &interncmd[j], result))
401  {
402  return result;
403  }
404  }
405  else
406  {
407  strncpy(tmp, &interncmd[j], 3);
408  tmp[3] = '\0';
409  if (!stricmp("cd ",tmp))
410  {
411  if (sys_process_cd(context, cmd, &interncmd[j], result))
412  {
413  return result;
414  }
415  }
416  else
417  { // Check if the command is to change drive
418  if ((tmp[1] == ':') && ((tmp[2] == ' ') || (!tmp[2])))
419  {
420  int code = _chdrive(toupper( tmp[0] ) - 'A' + 1);
421  if (code != 0)
422  {
423  context->RaiseCondition("ERROR", command, NULLOBJECT, context->WholeNumberToObject(code));
424  return NULLOBJECT;
425  }
426  else
427  {
428  // 0 result.
429  return context->False();
430  }
431  }
432  else
433  {
434  // Check if a START command is specified, if so do not
435  // invoke the command directly.
436  strncpy(tmp, &interncmd[j], 6);
437  tmp[6] = '\0';
438  noDirectInvoc = stricmp("start ",tmp) == 0;
439  }
440  }
441  }
442  }
443 
444  const char *sys_cmd_handler; // Pointer to system cmd handler
445 
446  // Determine the system command interpreter. This could be the full path
447  // name if COMSPEC is set, or a default name if it isn't. We no longer
448  // suport Windows 95, so the default will be cmd.exe. But, it is still
449  // possible for people to set COMSPEC to command.com, so we could end up
450  // with command.com
451  if ( (sys_cmd_handler = getenv(COMSPEC)) == NULL )
452  {
453  sys_cmd_handler = CMDDEFNAMENT;
454  }
455 
456  // Determine the maximum possible buffer size needed to pass the final
457  // command to sysCommandNT().
458  size_t maxBufferSize = strlen(sys_cmd_handler) + 1
459  + strlen(cl_opt)
460  + strlen(&interncmd[j])
461  + 2 // Two possible extra quotes
462  + 1; // Terminating null
463 
464  char cmdstring[CMDBUFSIZENT]; // Default static buffer.
465  char *cmdstring_ptr = cmdstring; // Will point to static buffer.
466 
467  if ( maxBufferSize > CMDBUFSIZENT )
468  {
469  // Allocate dynamic memory and set cmdstring_ptr to point to it.
470  cmdstring_ptr = (char *)LocalAlloc(LPTR, maxBufferSize);
471  if ( cmdstring_ptr == NULL )
472  {
473  context->RaiseException1(Rexx_Error_System_resources_user_defined,
474  context->String("Failed to allocate memory"));
475  return NULLOBJECT;
476  }
477  }
478  else
479  {
480  // We want maxBufferSize to relect the actual size of the buffer we are
481  // using so that we can test the return from SearchPath()
482  maxBufferSize = CMDBUFSIZENT;
483  }
484 
487 
488  // Check whether or not the command to invoke is cmd.exe or command.com
489  _strupr(strcpy(cmdstring_ptr, &interncmd[j]));
490  bool searchFile = strstr(cmdstring_ptr, "CMD") != NULL;
491 
492  if (searchFile)
493  {
494  if (cmdstring_ptr[0] == '\"')
495  {
496  cmdstring_ptr++;
497  while (cmdstring_ptr[i] && (cmdstring_ptr[i] != '\"'))
498  {
499  i++;
500  }
501  cmdstring_ptr[i]='\0';
502  }
503  else if (cmdstring_ptr[0] == '\'')
504  {
505  cmdstring_ptr++;
506  while (cmdstring_ptr[i] && (cmdstring_ptr[i] != '\''))
507  {
508  i++;
509  }
510  cmdstring_ptr[i]='\0';
511  }
512  else
513  {
514  while (cmdstring_ptr[i] && (cmdstring_ptr[i] != ' '))
515  {
516  i++;
517  }
518  cmdstring_ptr[i]='\0';
519  }
520 
521  LPSTR filepart;
522  uint32_t count = SearchPath(NULL, cmdstring_ptr, ".EXE", (uint32_t)(maxBufferSize - 1), cmdstring, &filepart);
523  bool fileFound = count != 0 && count <= maxBufferSize;
524 
525  // Set pointer back again to cmd buffer (might have been increased)
526  cmdstring_ptr = cmdstring;
527 
528  if (fileFound && !stricmp(sys_cmd_handler, cmdstring_ptr))
529  {
532  }
533  }
534 
535  // First check whether we can run the command directly as a program. (There
536  // can be no file redirection when not using cmd.exe or command.com)
537  if (SystemInterpreter::explicitConsole || !noDirectInvoc)
538  {
539  // Invoke this directly. If we fail, we fall through and try again.
540  if (sysCommandNT(context, cmd, &interncmd[j], true, result))
541  {
542  if ( cmdstring_ptr != cmdstring )
543  {
544  // Not pointing to static buffer so we need to free it.
545  LocalFree(cmdstring_ptr);
546  }
547  return result;
548  }
549  }
550 
551  // We couldn't invoke the command directly, or we tried and failed. So,
552  // pass the command to cmd.exe or command.com.
553 
554 
555  // Start the command buffer with the system cmd handler
556  strcpy(cmdstring_ptr,sys_cmd_handler);
557 
558  // Check whether or not the user specified the /k option. If so do not use
559  // the /c option. The /k option can only be specified as the first
560  // argument, and if used, keeps the command handler process open after the
561  // command has finished. Normally the /c option would be usee to close the
562  // command handler process when the command is finished..
563  if (!( (strlen(interncmd) > j+1) && (interncmd[j] == '/')
564  && ((interncmd[j+1] == 'k') || (interncmd[j+1] == 'K'))
565  && ((interncmd[j+2] == ' ') || (interncmd[j+2] == '\0')) ))
566  {
567  strcat(cmdstring_ptr,cl_opt);
568  }
569  else
570  {
572  strcat(cmdstring_ptr," ");
573  }
574 
575  // Add cmd to be executed, possibly quoting it to preserve embedded quotes.
576  if ( quoteCount> 2 )
577  {
578  strcat(cmdstring_ptr,"\"");
579  strcat(cmdstring_ptr,interncmd);
580  strcat(cmdstring_ptr,"\"");
581  }
582  else
583  {
584  strcat(cmdstring_ptr,interncmd);
585  }
586 
587  // Invoke the command
588  if (!sysCommandNT(context, cmd, cmdstring_ptr, false, result))
589  {
590  // Failed, get error code and return
591  context->RaiseCondition("FAILURE", context->String(cmd), NULLOBJECT, context->WholeNumberToObject(GetLastError()));
592 
593  if ( cmdstring_ptr != cmdstring )
594  {
595  // Not pointing to static buffer so we need to free it.
596  LocalFree(cmdstring_ptr);
597  }
598  return NULLOBJECT;
599  }
600 
601  if ( cmdstring_ptr != cmdstring )
602  {
603  // Not pointing to static buffer so we need to free it.
604  LocalFree(cmdstring_ptr);
605  }
607  return result;
608 }
609 
610 
611 /**
612  * Register the standard system command handlers.
613  *
614  * @param instance The created instance.
615  */
617 {
618  // Windows only has the single command environment, we also register this
619  // under "" for the default handler
623 }
void addCommandHandler(const char *name, const char *registeredName)
void registerCommandHandlers(InterpreterInstance *i)
static RexxString * getDefaultAddressName()
#define Rexx_Error_System_resources_user_defined
Definition: oorexxerrors.h:68
struct _RexxStringObject * RexxStringObject
Definition: rexx.h:128
struct _RexxObjectPtr * RexxObjectPtr
Definition: rexx.h:127
#define NULLOBJECT
Definition: rexx.h:147
#define RexxEntry
Definition: rexx.h:235
void * REXXPFN
int SearchPath(int SearchFlag, const char *path, const char *filename, char *buf, size_t buf_size)
bool sys_process_set(RexxExitContext *context, const char *command, const char *cmd, RexxObjectPtr &rc)
bool sysCommandNT(RexxExitContext *context, const char *command, const char *cmdstring_ptr, bool direct, RexxObjectPtr &result)
#define COMSPEC
#define CMDBUFSIZENT
#define SHOWWINDOWFLAGS
char * unquote(const char *s)
bool sys_process_cd(RexxExitContext *context, const char *command, const char *cmd, RexxObjectPtr &res)
#define CMDDEFNAMENT
RexxObjectPtr RexxEntry systemCommandHandler(RexxExitContext *context, RexxStringObject address, RexxStringObject command)
unsigned int uint32_t