Author: jmorlan
Date: Sun Mar 29 09:13:35 2009
New Revision: 40280
URL:
http://svn.reactos.org/svn/reactos?rev=40280&view=rev
Log:
Make command parsing in DoCommand/Execute more compatible with Windows
Modified:
trunk/reactos/base/shell/cmd/call.c
trunk/reactos/base/shell/cmd/cmd.c
trunk/reactos/base/shell/cmd/cmd.h
trunk/reactos/base/shell/cmd/internal.c
trunk/reactos/base/shell/cmd/parser.c
Modified: trunk/reactos/base/shell/cmd/call.c
URL:
http://svn.reactos.org/svn/reactos/trunk/reactos/base/shell/cmd/call.c?rev=…
==============================================================================
--- trunk/reactos/base/shell/cmd/call.c [iso-8859-1] (original)
+++ trunk/reactos/base/shell/cmd/call.c [iso-8859-1] Sun Mar 29 09:13:35 2009
@@ -37,7 +37,9 @@
INT cmd_call (LPTSTR param)
{
- TCHAR line[CMDLINE_LENGTH];
+ TCHAR line[CMDLINE_LENGTH + 1];
+ TCHAR *first;
+ BOOL bInQuote = FALSE;
TRACE ("cmd_call: (\'%s\')\n", debugstr_aw(param));
if (!_tcsncmp (param, _T("/?"), 2))
@@ -50,26 +52,31 @@
if (!SubstituteVars(param, line, _T('%')))
return nErrorLevel = 1;
- param = line;
- while (_istspace(*param))
- param++;
- if (*param == _T(':') && (bc))
+ /* Find start and end of first word */
+ first = line;
+ while (_istspace(*first))
+ first++;
+
+ for (param = first; *param; param++)
+ {
+ if (!bInQuote && (_istspace(*param) || _tcschr(_T(",;="), *param)))
+ break;
+ bInQuote ^= (*param == _T('"'));
+ }
+
+ /* Separate first word from rest of line */
+ memmove(param + 1, param, (_tcslen(param) + 1) * sizeof(TCHAR));
+ *param++ = _T('\0');
+
+ if (*first == _T(':') && (bc))
{
/* CALL :label - call a subroutine of the current batch file */
- TCHAR *first = param;
- while (*param && !_istspace(*param))
+ while (*param == _T(' '))
param++;
- if (*param)
- {
- /* Separate label and arguments */
- *param++ = _T('\0');
- while (_istspace(*param))
- param++;
- }
return !Batch(bc->BatchFilePath, first, param, NULL);
}
- return !DoCommand(param, NULL);
+ return !DoCommand(first, param, NULL);
}
/* EOF */
Modified: trunk/reactos/base/shell/cmd/cmd.c
URL:
http://svn.reactos.org/svn/reactos/trunk/reactos/base/shell/cmd/cmd.c?rev=4…
==============================================================================
--- trunk/reactos/base/shell/cmd/cmd.c [iso-8859-1] (original)
+++ trunk/reactos/base/shell/cmd/cmd.c [iso-8859-1] Sun Mar 29 09:13:35 2009
@@ -214,15 +214,6 @@
}
/*
- * is character a delimeter when used on first word?
- *
- */
-static BOOL IsDelimiter (TCHAR c)
-{
- return (c == _T('/') || c == _T('=') || c == _T('\0') ||
_istspace (c));
-}
-
-/*
* Is a process a console process?
*/
static BOOL IsConsoleProcess(HANDLE Process)
@@ -313,7 +304,7 @@
/*
* This command (in first) was not found in the command table
*
- * Full - whole command line
+ * Full - buffer to hold whole command line
* First - first word on command line
* Rest - rest of command line
*/
@@ -321,103 +312,41 @@
static BOOL
Execute (LPTSTR Full, LPTSTR First, LPTSTR Rest, PARSED_COMMAND *Cmd)
{
- TCHAR *szFullName=NULL;
- TCHAR *first = NULL;
- TCHAR *rest = NULL;
- TCHAR *full = NULL;
- TCHAR *dot = NULL;
+ TCHAR szFullName[MAX_PATH];
+ TCHAR *first, *rest, *dot;
TCHAR szWindowTitle[MAX_PATH];
DWORD dwExitCode = 0;
-
- TRACE ("Execute: \'%s\' \'%s\'\n", debugstr_aw(first),
debugstr_aw(rest));
-
- /* we need biger buffer that First, Rest, Full are already
- need rewrite some code to use cmd_realloc when it need instead
- of add 512bytes extra */
-
- first = cmd_alloc ( (_tcslen(Full) + 512) * sizeof(TCHAR));
- if (first == NULL)
- {
- error_out_of_memory();
- nErrorLevel = 1;
- return FALSE;
- }
-
- rest = cmd_alloc ( (_tcslen(Full) + 512) * sizeof(TCHAR));
- if (rest == NULL)
- {
- cmd_free (first);
- error_out_of_memory();
- nErrorLevel = 1;
- return FALSE;
- }
-
- full = cmd_alloc ( (_tcslen(Full) + 512) * sizeof(TCHAR));
- if (full == NULL)
- {
- cmd_free (first);
- cmd_free (rest);
- error_out_of_memory();
- nErrorLevel = 1;
- return FALSE;
- }
-
- szFullName = cmd_alloc ( (_tcslen(Full) + 512) * sizeof(TCHAR));
- if (full == NULL)
- {
- cmd_free (first);
- cmd_free (rest);
- cmd_free (full);
- error_out_of_memory();
- nErrorLevel = 1;
- return FALSE;
- }
-
+ TCHAR *FirstEnd;
+
+ TRACE ("Execute: \'%s\' \'%s\'\n", debugstr_aw(First),
debugstr_aw(Rest));
/* Though it was already parsed once, we have a different set of rules
for parsing before we pass to CreateProccess */
- if(!_tcschr(Full,_T('\"')))
- {
- _tcscpy(first,First);
- _tcscpy(rest,Rest);
- _tcscpy(full,Full);
+ if (First[0] == _T('/') || (First[0] && First[1] == _T(':')))
+ {
+ /* Use the entire first word as the program name (no change) */
+ FirstEnd = First + _tcslen(First);
}
else
{
- UINT i = 0;
+ /* If present in the first word, spaces and ,;=/ end the program
+ * name and become the beginning of its parameters. */
BOOL bInside = FALSE;
- rest[0] = _T('\0');
- full[0] = _T('\0');
- first[0] = _T('\0');
- _tcscpy(first,Full);
- /* find the end of the command and start of the args */
- for(i = 0; i < _tcslen(first); i++)
- {
- if(!_tcsncmp(&first[i], _T("\""), 1))
- bInside = !bInside;
- if(!_tcsncmp(&first[i], _T(" "), 1) && !bInside)
- {
- _tcscpy(rest,&first[i]);
- first[i] = _T('\0');
+ for (FirstEnd = First; *FirstEnd; FirstEnd++)
+ {
+ if (!bInside && (_istspace(*FirstEnd) || _tcschr(_T(",;=/"),
*FirstEnd)))
break;
- }
-
- }
- i = 0;
- /* remove any slashes */
- while(i < _tcslen(first))
- {
- if(first[i] == _T('\"'))
- memmove(&first[i],&first[i + 1], _tcslen(&first[i]) * sizeof(TCHAR));
- else
- i++;
- }
- /* Drop quotes around it just in case there is a space */
- _tcscpy(full,_T("\""));
- _tcscat(full,first);
- _tcscat(full,_T("\" "));
- _tcscat(full,rest);
- }
+ bInside ^= *FirstEnd == _T('"');
+ }
+ }
+
+ /* Copy the new first/rest into the buffer */
+ first = Full;
+ rest = &Full[FirstEnd - First + 1];
+ _tcscpy(rest, FirstEnd);
+ _tcscat(rest, Rest);
+ *FirstEnd = _T('\0');
+ _tcscpy(first, First);
/* check for a drive change */
if ((_istalpha (first[0])) && (!_tcscmp (first + 1, _T(":"))))
@@ -435,27 +364,16 @@
}
if (!working) ConErrResPuts (STRING_FREE_ERROR1);
-
- cmd_free (first);
- cmd_free (rest);
- cmd_free (full);
- cmd_free (szFullName);
- nErrorLevel = 1;
return working;
}
/* get the PATH environment variable and parse it */
/* search the PATH environment variable for the binary */
- if (!SearchForExecutable (first, szFullName))
- {
- error_bad_command (first);
- cmd_free (first);
- cmd_free (rest);
- cmd_free (full);
- cmd_free (szFullName);
- nErrorLevel = 1;
- return FALSE;
-
+ StripQuotes(First);
+ if (!SearchForExecutable(First, szFullName))
+ {
+ error_bad_command(first);
+ return FALSE;
}
GetConsoleTitle (szWindowTitle, MAX_PATH);
@@ -464,6 +382,8 @@
dot = _tcsrchr (szFullName, _T('.'));
if (dot && (!_tcsicmp (dot, _T(".bat")) || !_tcsicmp (dot,
_T(".cmd"))))
{
+ while (*rest == _T(' '))
+ rest++;
TRACE ("[BATCH: %s %s]\n", debugstr_aw(szFullName), debugstr_aw(rest));
Batch (szFullName, first, rest, Cmd);
}
@@ -473,8 +393,11 @@
PROCESS_INFORMATION prci;
STARTUPINFO stui;
- TRACE ("[EXEC: %s %s]\n", debugstr_aw(full), debugstr_aw(rest));
- /* build command line for CreateProcess() */
+ /* build command line for CreateProcess(): first + " " + rest */
+ if (*rest)
+ rest[-1] = _T(' ');
+
+ TRACE ("[EXEC: %s]\n", debugstr_aw(Full));
/* fill startup info */
memset (&stui, 0, sizeof (STARTUPINFO));
@@ -487,7 +410,7 @@
ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT | ENABLE_ECHO_INPUT );
if (CreateProcess (szFullName,
- full,
+ Full,
NULL,
NULL,
TRUE,
@@ -534,7 +457,7 @@
}
else
{
- TRACE ("[ShellExecute failed!: %s]\n", debugstr_aw(full));
+ TRACE ("[ShellExecute failed!: %s]\n", debugstr_aw(Full));
error_bad_command (first);
nErrorLevel = 1;
}
@@ -550,10 +473,6 @@
OutputCodePage = GetConsoleOutputCP();
SetConsoleTitle (szWindowTitle);
- cmd_free(first);
- cmd_free(rest);
- cmd_free(full);
- cmd_free (szFullName);
return nErrorLevel == 0;
}
@@ -563,120 +482,60 @@
* command is one of them. If it is, call the command. If not, call
* execute to run it as an external program.
*
- * line - the command line of the program to run
- *
+ * first - first word on command line
+ * rest - rest of command line
*/
BOOL
-DoCommand (LPTSTR line, PARSED_COMMAND *Cmd)
-{
- TCHAR *com = NULL; /* the first word in the command */
- TCHAR *cp = NULL;
- LPTSTR cstart;
- LPTSTR rest; /* pointer to the rest of the command line */
+DoCommand(LPTSTR first, LPTSTR rest, PARSED_COMMAND *Cmd)
+{
+ TCHAR com[_tcslen(first) + _tcslen(rest) + 2]; /* full command line */
+ TCHAR *cp;
+ LPTSTR param; /* pointer to command's parameters */
INT cl;
LPCOMMAND cmdptr;
- BOOL ret = TRUE;
-
- TRACE ("DoCommand: (\'%s\')\n", debugstr_aw(line));
-
- com = cmd_alloc( (_tcslen(line) +512)*sizeof(TCHAR) );
- if (com == NULL)
- {
- error_out_of_memory();
- return FALSE;
- }
-
- cp = com;
- /* Skip over initial white space */
- while (_istspace (*line))
- line++;
- rest = line;
-
- cstart = rest;
-
- /* Anything to do ? */
- if (*rest)
- {
- if (*rest == _T('"'))
- {
- /* treat quoted words specially */
-
- rest++;
-
- while(*rest != _T('\0') && *rest != _T('"'))
- *cp++ = _totlower (*rest++);
- if (*rest == _T('"'))
- rest++;
- }
- else
- {
- while (!IsDelimiter (*rest))
- *cp++ = _totlower (*rest++);
- }
-
-
- /* Terminate first word */
- *cp = _T('\0');
-
- /* Do not limit commands to MAX_PATH */
- /*
- if(_tcslen(com) > MAX_PATH)
- {
- error_bad_command();
- cmd_free(com);
- return;
- }
- */
-
- /* Skip over whitespace to rest of line, exclude 'echo' command */
- if (_tcsicmp (com, _T("echo")))
- {
- while (_istspace (*rest))
- rest++;
- }
-
- /* Scan internal command table */
- for (cmdptr = cmds;; cmdptr++)
- {
- /* If end of table execute ext cmd */
- if (cmdptr->name == NULL)
- {
- ret = Execute (line, com, rest, Cmd);
- break;
- }
-
- if (!_tcscmp (com, cmdptr->name))
- {
- cmdptr->func (rest);
- break;
- }
-
- /* The following code handles the case of commands like CD which
- * are recognised even when the command name and parameter are
- * not space separated.
- *
- * e.g dir..
- * cd\freda
- */
-
- /* Get length of command name */
- cl = _tcslen (cmdptr->name);
-
- if ((cmdptr->flags & CMD_SPECIAL) &&
- (!_tcsncmp (cmdptr->name, com, cl)) &&
- (_tcschr (_T("\\.-"), *(com + cl))))
- {
- /* OK its one of the specials...*/
-
- /* Call with new rest */
- cmdptr->func (cstart + cl);
- break;
- }
- }
- }
- cmd_free(com);
- return ret;
+ BOOL nointernal = FALSE;
+
+ TRACE ("DoCommand: (\'%s\' \'%s\')\n", debugstr_aw(first),
debugstr_aw(rest));
+
+ /* If present in the first word, these characters end the name of an
+ * internal command and become the beginning of its parameters. */
+ cp = first + _tcscspn(first, _T("\t +,/;=[]"));
+
+ for (cl = 0; cl < (cp - first); cl++)
+ {
+ /* These characters do it too, but if one of them is present,
+ * then we check to see if the word is a file name and skip
+ * checking for internal commands if so.
+ * This allows running programs with names like "echo.exe" */
+ if (_tcschr(_T(".:\\"), first[cl]))
+ {
+ TCHAR tmp = *cp;
+ *cp = _T('\0');
+ nointernal = IsExistingFile(first);
+ *cp = tmp;
+ break;
+ }
+ }
+
+ /* Scan internal command table */
+ for (cmdptr = cmds; !nointernal && cmdptr->name; cmdptr++)
+ {
+ if (!_tcsnicmp(first, cmdptr->name, cl) && cmdptr->name[cl] ==
_T('\0'))
+ {
+ _tcscpy(com, first);
+ _tcscat(com, rest);
+ param = &com[cl];
+
+ /* Skip over whitespace to rest of line, exclude 'echo' command */
+ if (_tcsicmp(cmdptr->name, _T("echo")) != 0)
+ while (_istspace(*param))
+ param++;
+ return !cmdptr->func(param);
+ }
+ }
+
+ return Execute(com, first, rest, Cmd);
}
@@ -825,7 +684,7 @@
ExecuteCommand(PARSED_COMMAND *Cmd)
{
PARSED_COMMAND *Sub;
- LPTSTR ExpandedLine;
+ LPTSTR First, Rest;
BOOL Success = TRUE;
if (!PerformRedirection(Cmd->Redirections))
@@ -834,14 +693,18 @@
switch (Cmd->Type)
{
case C_COMMAND:
- ExpandedLine = DoDelayedExpansion(Cmd->Command.CommandLine);
- if (!ExpandedLine)
- {
- Success = FALSE;
- break;
- }
- Success = DoCommand(ExpandedLine, Cmd);
- cmd_free(ExpandedLine);
+ Success = FALSE;
+ First = DoDelayedExpansion(Cmd->Command.First);
+ if (First)
+ {
+ Rest = DoDelayedExpansion(Cmd->Command.Rest);
+ if (Rest)
+ {
+ Success = DoCommand(First, Rest, Cmd);
+ cmd_free(Rest);
+ }
+ cmd_free(First);
+ }
break;
case C_QUIET:
case C_BLOCK:
Modified: trunk/reactos/base/shell/cmd/cmd.h
URL:
http://svn.reactos.org/svn/reactos/trunk/reactos/base/shell/cmd/cmd.h?rev=4…
==============================================================================
--- trunk/reactos/base/shell/cmd/cmd.h [iso-8859-1] (original)
+++ trunk/reactos/base/shell/cmd/cmd.h [iso-8859-1] Sun Mar 29 09:13:35 2009
@@ -112,7 +112,7 @@
BOOL SubstituteVars(TCHAR *Src, TCHAR *Dest, TCHAR Delim);
BOOL SubstituteForVars(TCHAR *Src, TCHAR *Dest);
LPTSTR DoDelayedExpansion(LPTSTR Line);
-BOOL DoCommand (LPTSTR line, struct _PARSED_COMMAND *Cmd);
+BOOL DoCommand(LPTSTR first, LPTSTR rest, struct _PARSED_COMMAND *Cmd);
BOOL ReadLine(TCHAR *commandline, BOOL bMore);
int cmd_main (int argc, const TCHAR *argv[]);
@@ -375,8 +375,8 @@
{
struct
{
- TCHAR *Tail;
- TCHAR CommandLine[];
+ TCHAR *Rest;
+ TCHAR First[];
} Command;
struct
{
Modified: trunk/reactos/base/shell/cmd/internal.c
URL:
http://svn.reactos.org/svn/reactos/trunk/reactos/base/shell/cmd/internal.c?…
==============================================================================
--- trunk/reactos/base/shell/cmd/internal.c [iso-8859-1] (original)
+++ trunk/reactos/base/shell/cmd/internal.c [iso-8859-1] Sun Mar 29 09:13:35 2009
@@ -704,11 +704,7 @@
/* If a param was send, display help of correspondent command */
if (_tcslen(param))
{
- LPTSTR NewCommand = cmd_alloc((_tcslen(param)+4)*sizeof(TCHAR));
- _tcscpy(NewCommand, param);
- _tcscat(NewCommand, _T(" /?"));
- DoCommand(NewCommand, NULL);
- cmd_free(NewCommand);
+ DoCommand(param, _T("/?"), NULL);
}
/* Else, display detailed commands list */
else
Modified: trunk/reactos/base/shell/cmd/parser.c
URL:
http://svn.reactos.org/svn/reactos/trunk/reactos/base/shell/cmd/parser.c?re…
==============================================================================
--- trunk/reactos/base/shell/cmd/parser.c [iso-8859-1] (original)
+++ trunk/reactos/base/shell/cmd/parser.c [iso-8859-1] Sun Mar 29 09:13:35 2009
@@ -576,7 +576,7 @@
Type = ParseToken(_T('('), STANDARD_SEPS);
if (Type == TOK_NORMAL)
{
- Pos = _stpcpy(ParsedLine, CurrentToken);
+ Pos = _stpcpy(ParsedLine, CurrentToken) + 1;
break;
}
else if (Type == TOK_REDIRECTION)
@@ -645,14 +645,15 @@
break;
}
}
-
- Cmd = cmd_alloc(FIELD_OFFSET(PARSED_COMMAND, Command.CommandLine[Pos + 1 -
ParsedLine]));
+ *Pos++ = _T('\0');
+
+ Cmd = cmd_alloc(FIELD_OFFSET(PARSED_COMMAND, Command.First[Pos - ParsedLine]));
Cmd->Type = C_COMMAND;
Cmd->Next = NULL;
Cmd->Subcommands = NULL;
Cmd->Redirections = RedirList;
- _tcscpy(Cmd->Command.CommandLine, ParsedLine);
- Cmd->Command.Tail = Cmd->Command.CommandLine + TailOffset;
+ memcpy(Cmd->Command.First, ParsedLine, (Pos - ParsedLine) * sizeof(TCHAR));
+ Cmd->Command.Rest = Cmd->Command.First + TailOffset;
return Cmd;
}
@@ -747,7 +748,9 @@
switch (Cmd->Type)
{
case C_COMMAND:
- if (SubstituteForVars(Cmd->Command.CommandLine, Buf))
+ if (SubstituteForVars(Cmd->Command.First, Buf))
+ ConOutPrintf(_T("%s"), Buf);
+ if (SubstituteForVars(Cmd->Command.Rest, Buf))
ConOutPrintf(_T("%s"), Buf);
break;
case C_QUIET:
@@ -852,10 +855,12 @@
switch (Cmd->Type)
{
case C_COMMAND:
- if (!SubstituteForVars(Cmd->Command.CommandLine, Buf)) return NULL;
/* This is fragile since there could be special characters, but
* Windows doesn't bother escaping them, so for compatibility
* we probably shouldn't do it either */
+ if (!SubstituteForVars(Cmd->Command.First, Buf)) return NULL;
+ STRING(Buf)
+ if (!SubstituteForVars(Cmd->Command.Rest, Buf)) return NULL;
STRING(Buf)
break;
case C_QUIET: