From fff257c059d538e757ee5e2083ffc7c46981ee31 Mon Sep 17 00:00:00 2001 From: Jeffrey Morlan Date: Fri, 6 Mar 2009 18:05:45 +0000 Subject: [PATCH] Implement SETLOCAL and ENDLOCAL commands. Make delayed expansion optional (disabled by default, enabled by CMD /V switch or with SETLOCAL) svn path=/trunk/; revision=39892 --- reactos/base/shell/cmd/batch.c | 12 +++- reactos/base/shell/cmd/batch.h | 1 + reactos/base/shell/cmd/cmd.c | 7 +- reactos/base/shell/cmd/cmd.h | 1 + reactos/base/shell/cmd/setlocal.c | 106 +++++++++++++++++++++++++++--- 5 files changed, 117 insertions(+), 10 deletions(-) diff --git a/reactos/base/shell/cmd/batch.c b/reactos/base/shell/cmd/batch.c index 1f7c5fbe245..75a11af4f43 100644 --- a/reactos/base/shell/cmd/batch.c +++ b/reactos/base/shell/cmd/batch.c @@ -189,6 +189,9 @@ VOID ExitBatch (LPTSTR msg) /* Preserve echo state across batch calls */ bEcho = bc->bEcho; + while (bc->setlocal) + cmd_endlocal(_T("")); + bc = bc->prev; } @@ -239,16 +242,23 @@ BOOL Batch (LPTSTR fullname, LPTSTR firstword, LPTSTR param, PARSED_COMMAND *Cmd } else { + struct _SETLOCAL *setlocal = NULL; /* If a batch file runs another batch file as part of a compound command * (e.g. "x.bat & somethingelse") then the first file gets terminated. */ - if (Cmd != NULL) + if (bc && Cmd != NULL) + { + /* Get its SETLOCAL stack so it can be migrated to the new context */ + setlocal = bc->setlocal; + bc->setlocal = NULL; ExitBatch(NULL); + } /* Create a new context. This function will not * return until this context has been exited */ new.prev = bc; bc = &new; bc->RedirList = NULL; + bc->setlocal = setlocal; } GetFullPathName(fullname, sizeof(bc->BatchFilePath) / sizeof(TCHAR), bc->BatchFilePath, NULL); diff --git a/reactos/base/shell/cmd/batch.h b/reactos/base/shell/cmd/batch.h index 7f4d503a084..6c1f016fee5 100644 --- a/reactos/base/shell/cmd/batch.h +++ b/reactos/base/shell/cmd/batch.h @@ -18,6 +18,7 @@ typedef struct tagBATCHCONTEXT BOOL bEcho; /* Preserve echo flag across batch calls */ REDIRECTION *RedirList; PARSED_COMMAND *current; + struct _SETLOCAL *setlocal; } BATCH_CONTEXT, *LPBATCH_CONTEXT; typedef struct tagFORCONTEXT diff --git a/reactos/base/shell/cmd/cmd.c b/reactos/base/shell/cmd/cmd.c index 79b26110859..d59d858c43e 100644 --- a/reactos/base/shell/cmd/cmd.c +++ b/reactos/base/shell/cmd/cmd.c @@ -157,6 +157,7 @@ BOOL bCtrlBreak = FALSE; /* Ctrl-Break or Ctrl-C hit */ BOOL bIgnoreEcho = FALSE; /* Set this to TRUE to prevent a newline, when executing a command */ INT nErrorLevel = 0; /* Errorlevel of last launched external program */ BOOL bChildProcessRunning = FALSE; +BOOL bDelayedExpansion = FALSE; DWORD dwChildProcessId = 0; OSVERSIONINFO osvi; HANDLE hIn; @@ -1295,7 +1296,7 @@ DoDelayedExpansion(LPTSTR Line) if (!SubstituteForVars(Line, Buf1)) return NULL; - if (!_tcschr(Buf1, _T('!'))) + if (!bDelayedExpansion || !_tcschr(Buf1, _T('!'))) return cmd_dup(Buf1); /* FIXME: Delayed substitutions actually aren't quite the same as @@ -1663,6 +1664,10 @@ Initialize() SetScreenColor (wColor, TRUE); } #endif + else if (_totlower(ptr[1]) == _T('v')) + { + bDelayedExpansion = _tcsnicmp(&ptr[2], _T(":off"), 4); + } } } diff --git a/reactos/base/shell/cmd/cmd.h b/reactos/base/shell/cmd/cmd.h index c50cf3dca1b..28be4fd4894 100644 --- a/reactos/base/shell/cmd/cmd.h +++ b/reactos/base/shell/cmd/cmd.h @@ -59,6 +59,7 @@ extern WORD wDefColor; extern BOOL bCtrlBreak; extern BOOL bIgnoreEcho; extern BOOL bExit; +extern BOOL bDelayedExpansion; extern INT nErrorLevel; extern SHORT maxx; extern SHORT maxy; diff --git a/reactos/base/shell/cmd/setlocal.c b/reactos/base/shell/cmd/setlocal.c index 4bf5022f47c..67b8a675796 100644 --- a/reactos/base/shell/cmd/setlocal.c +++ b/reactos/base/shell/cmd/setlocal.c @@ -1,5 +1,5 @@ /* - * GOTO.C - goto internal batch command. + * SETLOCAL.C - setlocal and endlocal internal batch commands. * * History: * @@ -9,19 +9,109 @@ #include +typedef struct _SETLOCAL { + struct _SETLOCAL *Prev; + BOOL DelayedExpansion; + LPTSTR Environment; +} SETLOCAL; -/* unimplemented */ - -/* our current default is delayedexpansion */ - -INT cmd_setlocal (LPTSTR param) +/* Create a copy of the current environment */ +static LPTSTR DuplicateEnvironment() { - return 0; + LPTSTR Environ = GetEnvironmentStrings(); + LPTSTR End, EnvironCopy; + if (!Environ) + return NULL; + for (End = Environ; *End; End += _tcslen(End) + 1) + ; + EnvironCopy = cmd_alloc((End + 1 - Environ) * sizeof(TCHAR)); + if (EnvironCopy) + memcpy(EnvironCopy, Environ, (End + 1 - Environ) * sizeof(TCHAR)); + FreeEnvironmentStrings(Environ); + return EnvironCopy; +} + +INT cmd_setlocal(LPTSTR param) +{ + SETLOCAL *Saved; + + /* SETLOCAL only works inside a batch file */ + if (!bc) + return 0; + + Saved = cmd_alloc(sizeof(SETLOCAL)); + if (!Saved) + { + error_out_of_memory(); + return 1; + } + Saved->Prev = bc->setlocal; + Saved->DelayedExpansion = bDelayedExpansion; + Saved->Environment = DuplicateEnvironment(); + if (!Saved->Environment) + { + error_out_of_memory(); + cmd_free(Saved); + return 1; + } + bc->setlocal = Saved; + + nErrorLevel = 0; + + if (*param == _T('\0')) + /* nothing */; + else if (!_tcsicmp(param, _T("enabledelayedexpansion"))) + bDelayedExpansion = TRUE; + else if (!_tcsicmp(param, _T("disabledelayedexpansion"))) + bDelayedExpansion = FALSE; + else + error_invalid_parameter_format(param); + + return nErrorLevel; } /* endlocal doesn't take any params */ -INT cmd_endlocal (LPTSTR param) +INT cmd_endlocal(LPTSTR param) { + LPTSTR Environ, Name, Value; + SETLOCAL *Saved; + + /* Pop a SETLOCAL struct off of this batch file's stack */ + if (!bc || !(Saved = bc->setlocal)) + return 0; + bc->setlocal = Saved->Prev; + + bDelayedExpansion = Saved->DelayedExpansion; + + /* First, clear out the environment. Since making any changes to the + * environment invalidates pointers obtained from GetEnvironmentStrings(), + * we must make a copy of it and get the variable names from that */ + Environ = DuplicateEnvironment(); + if (Environ) + { + for (Name = Environ; *Name; Name += _tcslen(Name) + 1) + { + if (!(Value = _tcschr(Name + 1, _T('=')))) + continue; + *Value++ = _T('\0'); + SetEnvironmentVariable(Name, NULL); + Name = Value; + } + cmd_free(Environ); + } + + /* Now, restore variables from the copy saved by cmd_setlocal */ + for (Name = Saved->Environment; *Name; Name += _tcslen(Name) + 1) + { + if (!(Value = _tcschr(Name + 1, _T('=')))) + continue; + *Value++ = _T('\0'); + SetEnvironmentVariable(Name, Value); + Name = Value; + } + + cmd_free(Saved->Environment); + cmd_free(Saved); return 0; }