[ros-diffs] [jmorlan] 40280: Make command parsing in DoCommand/Execute more compatible with Windows

jmorlan at svn.reactos.org jmorlan at svn.reactos.org
Sun Mar 29 07:13:36 CEST 2009


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=40280&r1=40279&r2=40280&view=diff
==============================================================================
--- 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=40280&r1=40279&r2=40280&view=diff
==============================================================================
--- 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=40280&r1=40279&r2=40280&view=diff
==============================================================================
--- 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?rev=40280&r1=40279&r2=40280&view=diff
==============================================================================
--- 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?rev=40280&r1=40279&r2=40280&view=diff
==============================================================================
--- 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:



More information about the Ros-diffs mailing list