Make command parsing in DoCommand/Execute more compatible with Windows

svn path=/trunk/; revision=40280
This commit is contained in:
Jeffrey Morlan 2009-03-29 05:13:35 +00:00
parent 47bc28fa47
commit ad25b56ea8
5 changed files with 120 additions and 249 deletions

View file

@ -37,7 +37,9 @@
INT cmd_call (LPTSTR param) 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)); TRACE ("cmd_call: (\'%s\')\n", debugstr_aw(param));
if (!_tcsncmp (param, _T("/?"), 2)) if (!_tcsncmp (param, _T("/?"), 2))
@ -50,26 +52,31 @@ INT cmd_call (LPTSTR param)
if (!SubstituteVars(param, line, _T('%'))) if (!SubstituteVars(param, line, _T('%')))
return nErrorLevel = 1; return nErrorLevel = 1;
param = line; /* Find start and end of first word */
while (_istspace(*param)) first = line;
param++; while (_istspace(*first))
if (*param == _T(':') && (bc)) 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 */ /* CALL :label - call a subroutine of the current batch file */
TCHAR *first = param; while (*param == _T(' '))
while (*param && !_istspace(*param))
param++; param++;
if (*param)
{
/* Separate label and arguments */
*param++ = _T('\0');
while (_istspace(*param))
param++;
}
return !Batch(bc->BatchFilePath, first, param, NULL); return !Batch(bc->BatchFilePath, first, param, NULL);
} }
return !DoCommand(param, NULL); return !DoCommand(first, param, NULL);
} }
/* EOF */ /* EOF */

View file

@ -213,15 +213,6 @@ ConvertULargeInteger(ULONGLONG num, LPTSTR des, INT len, BOOL bPutSeperator)
return n; return n;
} }
/*
* 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? * Is a process a console process?
*/ */
@ -313,7 +304,7 @@ HANDLE RunFile(DWORD flags, LPTSTR filename, LPTSTR params,
/* /*
* This command (in first) was not found in the command table * 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 * First - first word on command line
* Rest - rest of command line * Rest - rest of command line
*/ */
@ -321,103 +312,41 @@ HANDLE RunFile(DWORD flags, LPTSTR filename, LPTSTR params,
static BOOL static BOOL
Execute (LPTSTR Full, LPTSTR First, LPTSTR Rest, PARSED_COMMAND *Cmd) Execute (LPTSTR Full, LPTSTR First, LPTSTR Rest, PARSED_COMMAND *Cmd)
{ {
TCHAR *szFullName=NULL; TCHAR szFullName[MAX_PATH];
TCHAR *first = NULL; TCHAR *first, *rest, *dot;
TCHAR *rest = NULL;
TCHAR *full = NULL;
TCHAR *dot = NULL;
TCHAR szWindowTitle[MAX_PATH]; TCHAR szWindowTitle[MAX_PATH];
DWORD dwExitCode = 0; DWORD dwExitCode = 0;
TCHAR *FirstEnd;
TRACE ("Execute: \'%s\' \'%s\'\n", debugstr_aw(first), debugstr_aw(rest)); 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;
}
/* Though it was already parsed once, we have a different set of rules /* Though it was already parsed once, we have a different set of rules
for parsing before we pass to CreateProccess */ for parsing before we pass to CreateProccess */
if(!_tcschr(Full,_T('\"'))) if (First[0] == _T('/') || (First[0] && First[1] == _T(':')))
{ {
_tcscpy(first,First); /* Use the entire first word as the program name (no change) */
_tcscpy(rest,Rest); FirstEnd = First + _tcslen(First);
_tcscpy(full,Full);
} }
else 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; BOOL bInside = FALSE;
rest[0] = _T('\0'); for (FirstEnd = First; *FirstEnd; FirstEnd++)
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)) if (!bInside && (_istspace(*FirstEnd) || _tcschr(_T(",;=/"), *FirstEnd)))
bInside = !bInside;
if(!_tcsncmp(&first[i], _T(" "), 1) && !bInside)
{
_tcscpy(rest,&first[i]);
first[i] = _T('\0');
break; break;
bInside ^= *FirstEnd == _T('"');
}
} }
} /* Copy the new first/rest into the buffer */
i = 0; first = Full;
/* remove any slashes */ rest = &Full[FirstEnd - First + 1];
while(i < _tcslen(first)) _tcscpy(rest, FirstEnd);
{ _tcscat(rest, Rest);
if(first[i] == _T('\"')) *FirstEnd = _T('\0');
memmove(&first[i],&first[i + 1], _tcslen(&first[i]) * sizeof(TCHAR)); _tcscpy(first, First);
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);
}
/* check for a drive change */ /* check for a drive change */
if ((_istalpha (first[0])) && (!_tcscmp (first + 1, _T(":")))) if ((_istalpha (first[0])) && (!_tcscmp (first + 1, _T(":"))))
@ -435,27 +364,16 @@ Execute (LPTSTR Full, LPTSTR First, LPTSTR Rest, PARSED_COMMAND *Cmd)
} }
if (!working) ConErrResPuts (STRING_FREE_ERROR1); if (!working) ConErrResPuts (STRING_FREE_ERROR1);
cmd_free (first);
cmd_free (rest);
cmd_free (full);
cmd_free (szFullName);
nErrorLevel = 1;
return working; return working;
} }
/* get the PATH environment variable and parse it */ /* get the PATH environment variable and parse it */
/* search the PATH environment variable for the binary */ /* search the PATH environment variable for the binary */
if (!SearchForExecutable (first, szFullName)) StripQuotes(First);
if (!SearchForExecutable(First, szFullName))
{ {
error_bad_command(first); error_bad_command(first);
cmd_free (first);
cmd_free (rest);
cmd_free (full);
cmd_free (szFullName);
nErrorLevel = 1;
return FALSE; return FALSE;
} }
GetConsoleTitle (szWindowTitle, MAX_PATH); GetConsoleTitle (szWindowTitle, MAX_PATH);
@ -464,6 +382,8 @@ Execute (LPTSTR Full, LPTSTR First, LPTSTR Rest, PARSED_COMMAND *Cmd)
dot = _tcsrchr (szFullName, _T('.')); dot = _tcsrchr (szFullName, _T('.'));
if (dot && (!_tcsicmp (dot, _T(".bat")) || !_tcsicmp (dot, _T(".cmd")))) 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)); TRACE ("[BATCH: %s %s]\n", debugstr_aw(szFullName), debugstr_aw(rest));
Batch (szFullName, first, rest, Cmd); Batch (szFullName, first, rest, Cmd);
} }
@ -473,8 +393,11 @@ Execute (LPTSTR Full, LPTSTR First, LPTSTR Rest, PARSED_COMMAND *Cmd)
PROCESS_INFORMATION prci; PROCESS_INFORMATION prci;
STARTUPINFO stui; STARTUPINFO stui;
TRACE ("[EXEC: %s %s]\n", debugstr_aw(full), debugstr_aw(rest)); /* build command line for CreateProcess(): first + " " + rest */
/* build command line for CreateProcess() */ if (*rest)
rest[-1] = _T(' ');
TRACE ("[EXEC: %s]\n", debugstr_aw(Full));
/* fill startup info */ /* fill startup info */
memset (&stui, 0, sizeof (STARTUPINFO)); memset (&stui, 0, sizeof (STARTUPINFO));
@ -487,7 +410,7 @@ Execute (LPTSTR Full, LPTSTR First, LPTSTR Rest, PARSED_COMMAND *Cmd)
ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT | ENABLE_ECHO_INPUT ); ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT | ENABLE_ECHO_INPUT );
if (CreateProcess (szFullName, if (CreateProcess (szFullName,
full, Full,
NULL, NULL,
NULL, NULL,
TRUE, TRUE,
@ -534,7 +457,7 @@ Execute (LPTSTR Full, LPTSTR First, LPTSTR Rest, PARSED_COMMAND *Cmd)
} }
else else
{ {
TRACE ("[ShellExecute failed!: %s]\n", debugstr_aw(full)); TRACE ("[ShellExecute failed!: %s]\n", debugstr_aw(Full));
error_bad_command (first); error_bad_command (first);
nErrorLevel = 1; nErrorLevel = 1;
} }
@ -550,10 +473,6 @@ Execute (LPTSTR Full, LPTSTR First, LPTSTR Rest, PARSED_COMMAND *Cmd)
OutputCodePage = GetConsoleOutputCP(); OutputCodePage = GetConsoleOutputCP();
SetConsoleTitle (szWindowTitle); SetConsoleTitle (szWindowTitle);
cmd_free(first);
cmd_free(rest);
cmd_free(full);
cmd_free (szFullName);
return nErrorLevel == 0; return nErrorLevel == 0;
} }
@ -563,120 +482,60 @@ Execute (LPTSTR Full, LPTSTR First, LPTSTR Rest, PARSED_COMMAND *Cmd)
* command is one of them. If it is, call the command. If not, call * command is one of them. If it is, call the command. If not, call
* execute to run it as an external program. * 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 BOOL
DoCommand (LPTSTR line, PARSED_COMMAND *Cmd) DoCommand(LPTSTR first, LPTSTR rest, PARSED_COMMAND *Cmd)
{ {
TCHAR *com = NULL; /* the first word in the command */ TCHAR com[_tcslen(first) + _tcslen(rest) + 2]; /* full command line */
TCHAR *cp = NULL; TCHAR *cp;
LPTSTR cstart; LPTSTR param; /* pointer to command's parameters */
LPTSTR rest; /* pointer to the rest of the command line */
INT cl; INT cl;
LPCOMMAND cmdptr; LPCOMMAND cmdptr;
BOOL ret = TRUE; BOOL nointernal = FALSE;
TRACE ("DoCommand: (\'%s\')\n", debugstr_aw(line)); TRACE ("DoCommand: (\'%s\' \'%s\')\n", debugstr_aw(first), debugstr_aw(rest));
com = cmd_alloc( (_tcslen(line) +512)*sizeof(TCHAR) ); /* If present in the first word, these characters end the name of an
if (com == NULL) * internal command and become the beginning of its parameters. */
cp = first + _tcscspn(first, _T("\t +,/;=[]"));
for (cl = 0; cl < (cp - first); cl++)
{ {
error_out_of_memory(); /* These characters do it too, but if one of them is present,
return FALSE; * 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" */
cp = com; if (_tcschr(_T(".:\\"), first[cl]))
/* Skip over initial white space */
while (_istspace (*line))
line++;
rest = line;
cstart = rest;
/* Anything to do ? */
if (*rest)
{ {
if (*rest == _T('"')) TCHAR tmp = *cp;
{
/* 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'); *cp = _T('\0');
nointernal = IsExistingFile(first);
/* Do not limit commands to MAX_PATH */ *cp = tmp;
/* break;
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 */ /* Scan internal command table */
for (cmdptr = cmds;; cmdptr++) for (cmdptr = cmds; !nointernal && cmdptr->name; cmdptr++)
{ {
/* If end of table execute ext cmd */ if (!_tcsnicmp(first, cmdptr->name, cl) && cmdptr->name[cl] == _T('\0'))
if (cmdptr->name == NULL)
{ {
ret = Execute (line, com, rest, Cmd); _tcscpy(com, first);
break; _tcscat(com, rest);
} param = &com[cl];
if (!_tcscmp (com, cmdptr->name)) /* Skip over whitespace to rest of line, exclude 'echo' command */
{ if (_tcsicmp(cmdptr->name, _T("echo")) != 0)
cmdptr->func (rest); while (_istspace(*param))
break; param++;
} return !cmdptr->func(param);
/* 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 Execute(com, first, rest, Cmd);
return ret;
} }
@ -825,7 +684,7 @@ BOOL
ExecuteCommand(PARSED_COMMAND *Cmd) ExecuteCommand(PARSED_COMMAND *Cmd)
{ {
PARSED_COMMAND *Sub; PARSED_COMMAND *Sub;
LPTSTR ExpandedLine; LPTSTR First, Rest;
BOOL Success = TRUE; BOOL Success = TRUE;
if (!PerformRedirection(Cmd->Redirections)) if (!PerformRedirection(Cmd->Redirections))
@ -834,14 +693,18 @@ ExecuteCommand(PARSED_COMMAND *Cmd)
switch (Cmd->Type) switch (Cmd->Type)
{ {
case C_COMMAND: case C_COMMAND:
ExpandedLine = DoDelayedExpansion(Cmd->Command.CommandLine);
if (!ExpandedLine)
{
Success = FALSE; Success = FALSE;
break; 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);
} }
Success = DoCommand(ExpandedLine, Cmd);
cmd_free(ExpandedLine);
break; break;
case C_QUIET: case C_QUIET:
case C_BLOCK: case C_BLOCK:

View file

@ -112,7 +112,7 @@ VOID RemoveBreakHandler (VOID);
BOOL SubstituteVars(TCHAR *Src, TCHAR *Dest, TCHAR Delim); BOOL SubstituteVars(TCHAR *Src, TCHAR *Dest, TCHAR Delim);
BOOL SubstituteForVars(TCHAR *Src, TCHAR *Dest); BOOL SubstituteForVars(TCHAR *Src, TCHAR *Dest);
LPTSTR DoDelayedExpansion(LPTSTR Line); 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); BOOL ReadLine(TCHAR *commandline, BOOL bMore);
int cmd_main (int argc, const TCHAR *argv[]); int cmd_main (int argc, const TCHAR *argv[]);
@ -375,8 +375,8 @@ typedef struct _PARSED_COMMAND
{ {
struct struct
{ {
TCHAR *Tail; TCHAR *Rest;
TCHAR CommandLine[]; TCHAR First[];
} Command; } Command;
struct struct
{ {

View file

@ -704,11 +704,7 @@ INT CommandShowCommandsDetail (LPTSTR param)
/* If a param was send, display help of correspondent command */ /* If a param was send, display help of correspondent command */
if (_tcslen(param)) if (_tcslen(param))
{ {
LPTSTR NewCommand = cmd_alloc((_tcslen(param)+4)*sizeof(TCHAR)); DoCommand(param, _T("/?"), NULL);
_tcscpy(NewCommand, param);
_tcscat(NewCommand, _T(" /?"));
DoCommand(NewCommand, NULL);
cmd_free(NewCommand);
} }
/* Else, display detailed commands list */ /* Else, display detailed commands list */
else else

View file

@ -576,7 +576,7 @@ static PARSED_COMMAND *ParseCommandPart(void)
Type = ParseToken(_T('('), STANDARD_SEPS); Type = ParseToken(_T('('), STANDARD_SEPS);
if (Type == TOK_NORMAL) if (Type == TOK_NORMAL)
{ {
Pos = _stpcpy(ParsedLine, CurrentToken); Pos = _stpcpy(ParsedLine, CurrentToken) + 1;
break; break;
} }
else if (Type == TOK_REDIRECTION) else if (Type == TOK_REDIRECTION)
@ -645,14 +645,15 @@ static PARSED_COMMAND *ParseCommandPart(void)
break; break;
} }
} }
*Pos++ = _T('\0');
Cmd = cmd_alloc(FIELD_OFFSET(PARSED_COMMAND, Command.CommandLine[Pos + 1 - ParsedLine])); Cmd = cmd_alloc(FIELD_OFFSET(PARSED_COMMAND, Command.First[Pos - ParsedLine]));
Cmd->Type = C_COMMAND; Cmd->Type = C_COMMAND;
Cmd->Next = NULL; Cmd->Next = NULL;
Cmd->Subcommands = NULL; Cmd->Subcommands = NULL;
Cmd->Redirections = RedirList; Cmd->Redirections = RedirList;
_tcscpy(Cmd->Command.CommandLine, ParsedLine); memcpy(Cmd->Command.First, ParsedLine, (Pos - ParsedLine) * sizeof(TCHAR));
Cmd->Command.Tail = Cmd->Command.CommandLine + TailOffset; Cmd->Command.Rest = Cmd->Command.First + TailOffset;
return Cmd; return Cmd;
} }
@ -747,7 +748,9 @@ EchoCommand(PARSED_COMMAND *Cmd)
switch (Cmd->Type) switch (Cmd->Type)
{ {
case C_COMMAND: 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); ConOutPrintf(_T("%s"), Buf);
break; break;
case C_QUIET: case C_QUIET:
@ -852,10 +855,12 @@ Unparse(PARSED_COMMAND *Cmd, TCHAR *Out, TCHAR *OutEnd)
switch (Cmd->Type) switch (Cmd->Type)
{ {
case C_COMMAND: case C_COMMAND:
if (!SubstituteForVars(Cmd->Command.CommandLine, Buf)) return NULL;
/* This is fragile since there could be special characters, but /* This is fragile since there could be special characters, but
* Windows doesn't bother escaping them, so for compatibility * Windows doesn't bother escaping them, so for compatibility
* we probably shouldn't do it either */ * 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) STRING(Buf)
break; break;
case C_QUIET: case C_QUIET: