2006-02-16 23:23:37 +00:00
|
|
|
/*
|
|
|
|
* CMD.C - command-line interface.
|
|
|
|
*
|
|
|
|
*
|
|
|
|
* History:
|
|
|
|
*
|
|
|
|
* 17 Jun 1994 (Tim Norman)
|
|
|
|
* started.
|
|
|
|
*
|
|
|
|
* 08 Aug 1995 (Matt Rains)
|
|
|
|
* I have cleaned up the source code. changes now bring this source
|
|
|
|
* into guidelines for recommended programming practice.
|
|
|
|
*
|
|
|
|
* A added the the standard FreeDOS GNU licence test to the
|
|
|
|
* initialize() function.
|
|
|
|
*
|
|
|
|
* Started to replace puts() with printf(). this will help
|
|
|
|
* standardize output. please follow my lead.
|
|
|
|
*
|
|
|
|
* I have added some constants to help making changes easier.
|
|
|
|
*
|
|
|
|
* 15 Dec 1995 (Tim Norman)
|
|
|
|
* major rewrite of the code to make it more efficient and add
|
|
|
|
* redirection support (finally!)
|
|
|
|
*
|
|
|
|
* 06 Jan 1996 (Tim Norman)
|
|
|
|
* finished adding redirection support! Changed to use our own
|
|
|
|
* exec code (MUCH thanks to Svante Frey!!)
|
|
|
|
*
|
|
|
|
* 29 Jan 1996 (Tim Norman)
|
|
|
|
* added support for CHDIR, RMDIR, MKDIR, and ERASE, as per
|
|
|
|
* suggestion of Steffan Kaiser
|
|
|
|
*
|
|
|
|
* changed "file not found" error message to "bad command or
|
|
|
|
* filename" thanks to Dustin Norman for noticing that confusing
|
|
|
|
* message!
|
|
|
|
*
|
|
|
|
* changed the format to call internal commands (again) so that if
|
|
|
|
* they want to split their commands, they can do it themselves
|
|
|
|
* (none of the internal functions so far need that much power, anyway)
|
|
|
|
*
|
|
|
|
* 27 Aug 1996 (Tim Norman)
|
|
|
|
* added in support for Oliver Mueller's ALIAS command
|
|
|
|
*
|
|
|
|
* 14 Jun 1997 (Steffan Kaiser)
|
|
|
|
* added ctrl-break handling and error level
|
|
|
|
*
|
|
|
|
* 16 Jun 1998 (Rob Lake)
|
|
|
|
* Runs command.com if /P is specified in command line. Command.com
|
|
|
|
* also stays permanent. If /C is in the command line, starts the
|
|
|
|
* program next in the line.
|
|
|
|
*
|
|
|
|
* 21 Jun 1998 (Rob Lake)
|
|
|
|
* Fixed up /C so that arguments for the program
|
|
|
|
*
|
|
|
|
* 08-Jul-1998 (John P. Price)
|
|
|
|
* Now sets COMSPEC environment variable
|
|
|
|
* misc clean up and optimization
|
|
|
|
* added date and time commands
|
|
|
|
* changed to using spawnl instead of exec. exec does not copy the
|
|
|
|
* environment to the child process!
|
|
|
|
*
|
|
|
|
* 14 Jul 1998 (Hans B Pufal)
|
|
|
|
* Reorganised source to be more efficient and to more closely
|
|
|
|
* follow MS-DOS conventions. (eg %..% environment variable
|
|
|
|
* replacement works form command line as well as batch file.
|
|
|
|
*
|
|
|
|
* New organisation also properly support nested batch files.
|
|
|
|
*
|
|
|
|
* New command table structure is half way towards providing a
|
|
|
|
* system in which COMMAND will find out what internal commands
|
|
|
|
* are loaded
|
|
|
|
*
|
|
|
|
* 24 Jul 1998 (Hans B Pufal) [HBP_003]
|
|
|
|
* Fixed return value when called with /C option
|
|
|
|
*
|
|
|
|
* 27 Jul 1998 John P. Price
|
|
|
|
* added config.h include
|
|
|
|
*
|
|
|
|
* 28 Jul 1998 John P. Price
|
|
|
|
* added showcmds function to show commands and options available
|
|
|
|
*
|
|
|
|
* 07-Aug-1998 (John P Price <linux-guru@gcfl.net>)
|
2016-11-05 14:55:55 +00:00
|
|
|
* Fixed carriage return output to better match MSDOS with echo
|
2006-02-16 23:23:37 +00:00
|
|
|
* on or off. (marked with "JPP 19980708")
|
|
|
|
*
|
2007-05-05 11:32:25 +00:00
|
|
|
* 07-Dec-1998 (Eric Kohl)
|
2006-02-16 23:23:37 +00:00
|
|
|
* First ReactOS release.
|
|
|
|
* Extended length of commandline buffers to 512.
|
|
|
|
*
|
2007-05-05 11:32:25 +00:00
|
|
|
* 13-Dec-1998 (Eric Kohl)
|
2006-02-16 23:23:37 +00:00
|
|
|
* Added COMSPEC environment variable.
|
|
|
|
* Added "/t" support (color) on cmd command line.
|
|
|
|
*
|
2007-05-05 11:32:25 +00:00
|
|
|
* 07-Jan-1999 (Eric Kohl)
|
2006-02-16 23:23:37 +00:00
|
|
|
* Added help text ("cmd /?").
|
|
|
|
*
|
2007-05-05 11:32:25 +00:00
|
|
|
* 25-Jan-1999 (Eric Kohl)
|
2006-02-16 23:23:37 +00:00
|
|
|
* Unicode and redirection safe!
|
|
|
|
* Fixed redirections and piping.
|
|
|
|
* Piping is based on temporary files, but basic support
|
|
|
|
* for anonymous pipes already exists.
|
|
|
|
*
|
2007-05-05 11:32:25 +00:00
|
|
|
* 27-Jan-1999 (Eric Kohl)
|
2006-02-16 23:23:37 +00:00
|
|
|
* Replaced spawnl() by CreateProcess().
|
|
|
|
*
|
2007-05-05 11:32:25 +00:00
|
|
|
* 22-Oct-1999 (Eric Kohl)
|
2006-02-16 23:23:37 +00:00
|
|
|
* Added break handler.
|
|
|
|
*
|
2007-05-05 11:32:25 +00:00
|
|
|
* 15-Dec-1999 (Eric Kohl)
|
2006-02-16 23:23:37 +00:00
|
|
|
* Fixed current directory
|
|
|
|
*
|
2007-05-05 11:32:25 +00:00
|
|
|
* 28-Dec-1999 (Eric Kohl)
|
2006-02-16 23:23:37 +00:00
|
|
|
* Restore window title after program/batch execution
|
|
|
|
*
|
2007-05-05 11:32:25 +00:00
|
|
|
* 03-Feb-2001 (Eric Kohl)
|
2006-02-16 23:23:37 +00:00
|
|
|
* Workaround because argc[0] is NULL under ReactOS
|
|
|
|
*
|
|
|
|
* 23-Feb-2001 (Carl Nettelblad <cnettel@hem.passagen.se>)
|
|
|
|
* %envvar% replacement conflicted with for.
|
|
|
|
*
|
|
|
|
* 30-Apr-2004 (Filip Navara <xnavara@volny.cz>)
|
|
|
|
* Make MakeSureDirectoryPathExistsEx unicode safe.
|
|
|
|
*
|
|
|
|
* 28-Mai-2004
|
|
|
|
* Removed MakeSureDirectoryPathExistsEx.
|
|
|
|
* Use the current directory if GetTempPath fails.
|
|
|
|
*
|
|
|
|
* 12-Jul-2004 (Jens Collin <jens.collin@lakhei.com>)
|
|
|
|
* Added ShellExecute call when all else fails to be able to "launch" any file.
|
|
|
|
*
|
|
|
|
* 02-Apr-2005 (Magnus Olsen <magnus@greatlord.com>)
|
|
|
|
* Remove all hardcode string to En.rc
|
|
|
|
*
|
|
|
|
* 06-May-2005 (Klemens Friedl <frik85@gmail.com>)
|
|
|
|
* Add 'help' command (list all commands plus description)
|
|
|
|
*
|
|
|
|
* 06-jul-2005 (Magnus Olsen <magnus@greatlord.com>)
|
|
|
|
* translate '%errorlevel%' to the internal value.
|
2016-11-05 14:55:55 +00:00
|
|
|
* Add proper memory alloc ProcessInput, the error
|
|
|
|
* handling for memory handling need to be improve
|
2006-02-16 23:23:37 +00:00
|
|
|
*/
|
|
|
|
|
2013-01-24 23:00:42 +00:00
|
|
|
#include "precomp.h"
|
2011-09-15 21:26:32 +00:00
|
|
|
#include <reactos/buildno.h>
|
|
|
|
#include <reactos/version.h>
|
2006-02-16 23:23:37 +00:00
|
|
|
|
|
|
|
typedef NTSTATUS (WINAPI *NtQueryInformationProcessProc)(HANDLE, PROCESSINFOCLASS,
|
|
|
|
PVOID, ULONG, PULONG);
|
2020-03-13 21:15:56 +00:00
|
|
|
typedef NTSTATUS (WINAPI *NtReadVirtualMemoryProc)(HANDLE, PVOID, PVOID, SIZE_T, PSIZE_T);
|
2006-02-16 23:23:37 +00:00
|
|
|
|
2018-06-03 00:15:44 +00:00
|
|
|
BOOL bExit = FALSE; /* Indicates EXIT was typed */
|
|
|
|
BOOL bCanExit = TRUE; /* Indicates if this shell is exitable */
|
2018-04-28 22:53:49 +00:00
|
|
|
BOOL bCtrlBreak = FALSE; /* Ctrl-Break or Ctrl-C hit */
|
2008-05-17 20:07:31 +00:00
|
|
|
BOOL bIgnoreEcho = FALSE; /* Set this to TRUE to prevent a newline, when executing a command */
|
2018-06-03 00:15:44 +00:00
|
|
|
static BOOL fSingleCommand = 0; /* When we are executing something passed on the command line after /C or /K */
|
2020-07-29 23:44:43 +00:00
|
|
|
static BOOL bAlwaysStrip = FALSE;
|
2006-02-16 23:23:37 +00:00
|
|
|
INT nErrorLevel = 0; /* Errorlevel of last launched external program */
|
2010-05-31 18:38:48 +00:00
|
|
|
CRITICAL_SECTION ChildProcessRunningLock;
|
2009-03-26 01:14:25 +00:00
|
|
|
BOOL bDisableBatchEcho = FALSE;
|
2017-09-30 11:39:08 +00:00
|
|
|
BOOL bEnableExtensions = TRUE;
|
2009-03-06 18:05:45 +00:00
|
|
|
BOOL bDelayedExpansion = FALSE;
|
2006-02-16 23:23:37 +00:00
|
|
|
DWORD dwChildProcessId = 0;
|
2009-03-23 23:52:50 +00:00
|
|
|
LPTSTR lpOriginalEnvironment;
|
2006-02-16 23:23:37 +00:00
|
|
|
HANDLE CMD_ModuleHandle;
|
|
|
|
|
2018-06-03 00:15:44 +00:00
|
|
|
BOOL bTitleSet = FALSE; /* Indicates whether the console title has been changed and needs to be restored later */
|
|
|
|
TCHAR szCurTitle[MAX_PATH];
|
|
|
|
|
2006-02-16 23:23:37 +00:00
|
|
|
static NtQueryInformationProcessProc NtQueryInformationProcessPtr = NULL;
|
|
|
|
static NtReadVirtualMemoryProc NtReadVirtualMemoryPtr = NULL;
|
|
|
|
|
2017-10-01 16:30:39 +00:00
|
|
|
/*
|
|
|
|
* Default output file stream translation mode is UTF8, but CMD switches
|
|
|
|
* allow to change it to either UTF16 (/U) or ANSI (/A).
|
|
|
|
*/
|
|
|
|
CON_STREAM_MODE OutputStreamMode = UTF8Text; // AnsiText;
|
|
|
|
|
2006-02-16 23:23:37 +00:00
|
|
|
#ifdef INCLUDE_CMD_COLOR
|
2017-09-30 11:39:08 +00:00
|
|
|
WORD wDefColor = 0; /* Default color */
|
2006-02-16 23:23:37 +00:00
|
|
|
#endif
|
|
|
|
|
|
|
|
/*
|
|
|
|
* convert
|
|
|
|
*
|
|
|
|
* insert commas into a number
|
|
|
|
*/
|
|
|
|
INT
|
2016-11-05 14:55:55 +00:00
|
|
|
ConvertULargeInteger(ULONGLONG num, LPTSTR des, UINT len, BOOL bPutSeparator)
|
2006-02-16 23:23:37 +00:00
|
|
|
{
|
2013-06-30 13:11:20 +00:00
|
|
|
TCHAR temp[39]; /* maximum length with nNumberGroups == 1 */
|
|
|
|
UINT n, iTarget;
|
|
|
|
|
|
|
|
if (len <= 1)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
n = 0;
|
|
|
|
iTarget = nNumberGroups;
|
|
|
|
if (!nNumberGroups)
|
2016-11-05 14:55:55 +00:00
|
|
|
bPutSeparator = FALSE;
|
2013-06-30 13:11:20 +00:00
|
|
|
|
|
|
|
do
|
|
|
|
{
|
2016-11-05 14:55:55 +00:00
|
|
|
if (iTarget == n && bPutSeparator)
|
2013-06-30 13:11:20 +00:00
|
|
|
{
|
|
|
|
iTarget += nNumberGroups + 1;
|
|
|
|
temp[38 - n++] = cThousandSeparator;
|
|
|
|
}
|
|
|
|
temp[38 - n++] = (TCHAR)(num % 10) + _T('0');
|
|
|
|
num /= 10;
|
|
|
|
} while (num > 0);
|
|
|
|
if (n > len-1)
|
|
|
|
n = len-1;
|
|
|
|
|
|
|
|
memcpy(des, temp + 39 - n, n * sizeof(TCHAR));
|
|
|
|
des[n] = _T('\0');
|
|
|
|
|
|
|
|
return n;
|
2006-02-16 23:23:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Is a process a console process?
|
|
|
|
*/
|
|
|
|
static BOOL IsConsoleProcess(HANDLE Process)
|
|
|
|
{
|
2013-06-30 13:11:20 +00:00
|
|
|
NTSTATUS Status;
|
|
|
|
PROCESS_BASIC_INFORMATION Info;
|
|
|
|
PEB ProcessPeb;
|
2020-03-13 19:45:06 +00:00
|
|
|
SIZE_T BytesRead;
|
2013-06-30 13:11:20 +00:00
|
|
|
|
|
|
|
if (NULL == NtQueryInformationProcessPtr || NULL == NtReadVirtualMemoryPtr)
|
|
|
|
{
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
Status = NtQueryInformationProcessPtr (
|
|
|
|
Process, ProcessBasicInformation,
|
|
|
|
&Info, sizeof(PROCESS_BASIC_INFORMATION), NULL);
|
|
|
|
if (! NT_SUCCESS(Status))
|
|
|
|
{
|
|
|
|
WARN ("NtQueryInformationProcess failed with status %08x\n", Status);
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
Status = NtReadVirtualMemoryPtr (
|
|
|
|
Process, Info.PebBaseAddress, &ProcessPeb,
|
|
|
|
sizeof(PEB), &BytesRead);
|
|
|
|
if (! NT_SUCCESS(Status) || sizeof(PEB) != BytesRead)
|
|
|
|
{
|
2020-03-13 19:45:06 +00:00
|
|
|
WARN ("Couldn't read virt mem status %08x bytes read %Iu\n", Status, BytesRead);
|
2013-06-30 13:11:20 +00:00
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
return IMAGE_SUBSYSTEM_WINDOWS_CUI == ProcessPeb.ImageSubsystem;
|
2006-02-16 23:23:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef _UNICODE
|
2013-06-30 13:11:20 +00:00
|
|
|
#define SHELLEXECUTETEXT "ShellExecuteExW"
|
2006-02-16 23:23:37 +00:00
|
|
|
#else
|
2013-06-30 13:11:20 +00:00
|
|
|
#define SHELLEXECUTETEXT "ShellExecuteExA"
|
2006-02-16 23:23:37 +00:00
|
|
|
#endif
|
|
|
|
|
2009-03-21 02:12:23 +00:00
|
|
|
typedef BOOL (WINAPI *MYEX)(LPSHELLEXECUTEINFO lpExecInfo);
|
2006-02-16 23:23:37 +00:00
|
|
|
|
2009-03-22 01:51:29 +00:00
|
|
|
HANDLE RunFile(DWORD flags, LPTSTR filename, LPTSTR params,
|
|
|
|
LPTSTR directory, INT show)
|
2006-02-16 23:23:37 +00:00
|
|
|
{
|
2013-06-30 13:11:20 +00:00
|
|
|
SHELLEXECUTEINFO sei;
|
|
|
|
HMODULE hShell32;
|
|
|
|
MYEX hShExt;
|
|
|
|
BOOL ret;
|
|
|
|
|
|
|
|
TRACE ("RunFile(%s)\n", debugstr_aw(filename));
|
|
|
|
hShell32 = LoadLibrary(_T("SHELL32.DLL"));
|
|
|
|
if (!hShell32)
|
|
|
|
{
|
|
|
|
WARN ("RunFile: couldn't load SHELL32.DLL!\n");
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
hShExt = (MYEX)(FARPROC)GetProcAddress(hShell32, SHELLEXECUTETEXT);
|
|
|
|
if (!hShExt)
|
|
|
|
{
|
|
|
|
WARN ("RunFile: couldn't find ShellExecuteExA/W in SHELL32.DLL!\n");
|
|
|
|
FreeLibrary(hShell32);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
TRACE ("RunFile: ShellExecuteExA/W is at %x\n", hShExt);
|
|
|
|
|
|
|
|
memset(&sei, 0, sizeof sei);
|
|
|
|
sei.cbSize = sizeof sei;
|
|
|
|
sei.fMask = flags;
|
|
|
|
sei.lpFile = filename;
|
|
|
|
sei.lpParameters = params;
|
|
|
|
sei.lpDirectory = directory;
|
|
|
|
sei.nShow = show;
|
|
|
|
ret = hShExt(&sei);
|
|
|
|
|
|
|
|
TRACE ("RunFile: ShellExecuteExA/W returned 0x%p\n", ret);
|
|
|
|
|
|
|
|
FreeLibrary(hShell32);
|
|
|
|
return ret ? sei.hProcess : NULL;
|
2006-02-16 23:23:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-06-03 00:15:44 +00:00
|
|
|
static VOID
|
|
|
|
SetConTitle(LPCTSTR pszTitle)
|
|
|
|
{
|
|
|
|
TCHAR szNewTitle[MAX_PATH];
|
|
|
|
|
|
|
|
if (!pszTitle)
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* Don't do anything if we run inside a batch file, or we are just running a single command */
|
|
|
|
if (bc || (fSingleCommand == 1))
|
|
|
|
return;
|
|
|
|
|
|
|
|
/* Save the original console title and build a new one */
|
|
|
|
GetConsoleTitle(szCurTitle, ARRAYSIZE(szCurTitle));
|
|
|
|
StringCchPrintf(szNewTitle, ARRAYSIZE(szNewTitle),
|
|
|
|
_T("%s - %s"), szCurTitle, pszTitle);
|
|
|
|
bTitleSet = TRUE;
|
|
|
|
ConSetTitle(szNewTitle);
|
|
|
|
}
|
|
|
|
|
|
|
|
static VOID
|
|
|
|
ResetConTitle(VOID)
|
|
|
|
{
|
|
|
|
/* Restore the original console title */
|
|
|
|
if (!bc && bTitleSet)
|
|
|
|
{
|
|
|
|
ConSetTitle(szCurTitle);
|
|
|
|
bTitleSet = FALSE;
|
|
|
|
}
|
|
|
|
}
|
2006-02-16 23:23:37 +00:00
|
|
|
|
|
|
|
/*
|
2018-06-03 00:15:44 +00:00
|
|
|
* This command (in First) was not found in the command table
|
2006-02-16 23:23:37 +00:00
|
|
|
*
|
2018-06-03 00:15:44 +00:00
|
|
|
* Full - output buffer to hold whole command line
|
2006-02-16 23:23:37 +00:00
|
|
|
* First - first word on command line
|
|
|
|
* Rest - rest of command line
|
|
|
|
*/
|
2009-04-12 23:51:15 +00:00
|
|
|
static INT
|
2013-06-30 16:10:54 +00:00
|
|
|
Execute(LPTSTR Full, LPTSTR First, LPTSTR Rest, PARSED_COMMAND *Cmd)
|
2006-02-16 23:23:37 +00:00
|
|
|
{
|
2013-06-30 13:11:20 +00:00
|
|
|
TCHAR *first, *rest, *dot;
|
|
|
|
DWORD dwExitCode = 0;
|
|
|
|
TCHAR *FirstEnd;
|
2018-06-03 00:15:44 +00:00
|
|
|
TCHAR szFullName[MAX_PATH];
|
2017-09-30 21:37:34 +00:00
|
|
|
TCHAR szFullCmdLine[CMDLINE_LENGTH];
|
2013-06-30 13:11:20 +00:00
|
|
|
|
|
|
|
TRACE ("Execute: \'%s\' \'%s\'\n", debugstr_aw(First), debugstr_aw(Rest));
|
|
|
|
|
|
|
|
/* Though it was already parsed once, we have a different set of rules
|
2016-11-05 14:55:55 +00:00
|
|
|
for parsing before we pass to CreateProcess */
|
2013-06-30 13:11:20 +00:00
|
|
|
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
|
|
|
|
{
|
|
|
|
/* If present in the first word, spaces and ,;=/ end the program
|
|
|
|
* name and become the beginning of its parameters. */
|
|
|
|
BOOL bInside = FALSE;
|
|
|
|
for (FirstEnd = First; *FirstEnd; FirstEnd++)
|
|
|
|
{
|
|
|
|
if (!bInside && (_istspace(*FirstEnd) || _tcschr(_T(",;=/"), *FirstEnd)))
|
|
|
|
break;
|
|
|
|
bInside ^= *FirstEnd == _T('"');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Copy the new first/rest into the buffer */
|
|
|
|
rest = &Full[FirstEnd - First + 1];
|
|
|
|
_tcscpy(rest, FirstEnd);
|
|
|
|
_tcscat(rest, Rest);
|
2018-06-03 00:15:44 +00:00
|
|
|
first = Full;
|
2013-06-30 13:11:20 +00:00
|
|
|
*FirstEnd = _T('\0');
|
|
|
|
_tcscpy(first, First);
|
|
|
|
|
|
|
|
/* check for a drive change */
|
|
|
|
if ((_istalpha (first[0])) && (!_tcscmp (first + 1, _T(":"))))
|
|
|
|
{
|
|
|
|
BOOL working = TRUE;
|
|
|
|
if (!SetCurrentDirectory(first))
|
|
|
|
{
|
2018-06-03 00:15:44 +00:00
|
|
|
/* Guess they changed disc or something, handle that gracefully and get to root */
|
2013-06-30 13:11:20 +00:00
|
|
|
TCHAR str[4];
|
|
|
|
str[0]=first[0];
|
|
|
|
str[1]=_T(':');
|
|
|
|
str[2]=_T('\\');
|
|
|
|
str[3]=0;
|
|
|
|
working = SetCurrentDirectory(str);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!working) ConErrResPuts (STRING_FREE_ERROR1);
|
|
|
|
return !working;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* get the PATH environment variable and parse it */
|
|
|
|
/* search the PATH environment variable for the binary */
|
|
|
|
StripQuotes(First);
|
|
|
|
if (!SearchForExecutable(First, szFullName))
|
|
|
|
{
|
|
|
|
error_bad_command(first);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2018-06-03 00:15:44 +00:00
|
|
|
/* Set the new console title */
|
|
|
|
FirstEnd = first + (FirstEnd - First); /* Point to the separating NULL in the full built string */
|
|
|
|
*FirstEnd = _T(' ');
|
|
|
|
SetConTitle(Full);
|
2013-06-30 13:11:20 +00:00
|
|
|
|
|
|
|
/* check if this is a .BAT or .CMD file */
|
|
|
|
dot = _tcsrchr (szFullName, _T('.'));
|
|
|
|
if (dot && (!_tcsicmp (dot, _T(".bat")) || !_tcsicmp (dot, _T(".cmd"))))
|
|
|
|
{
|
|
|
|
while (*rest == _T(' '))
|
|
|
|
rest++;
|
2018-06-03 00:15:44 +00:00
|
|
|
|
|
|
|
*FirstEnd = _T('\0');
|
2013-06-30 13:11:20 +00:00
|
|
|
TRACE ("[BATCH: %s %s]\n", debugstr_aw(szFullName), debugstr_aw(rest));
|
|
|
|
dwExitCode = Batch(szFullName, first, rest, Cmd);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* exec the program */
|
|
|
|
PROCESS_INFORMATION prci;
|
|
|
|
STARTUPINFO stui;
|
|
|
|
|
|
|
|
/* build command line for CreateProcess(): FullName + " " + rest */
|
2018-06-03 00:15:44 +00:00
|
|
|
BOOL quoted = !!_tcschr(First, _T(' '));
|
2013-06-30 13:11:20 +00:00
|
|
|
_tcscpy(szFullCmdLine, quoted ? _T("\"") : _T(""));
|
2014-05-03 18:07:07 +00:00
|
|
|
_tcsncat(szFullCmdLine, First, CMDLINE_LENGTH - _tcslen(szFullCmdLine) - 1);
|
|
|
|
_tcsncat(szFullCmdLine, quoted ? _T("\"") : _T(""), CMDLINE_LENGTH - _tcslen(szFullCmdLine) - 1);
|
2013-06-30 13:11:20 +00:00
|
|
|
|
|
|
|
if (*rest)
|
|
|
|
{
|
2014-05-03 18:07:07 +00:00
|
|
|
_tcsncat(szFullCmdLine, _T(" "), CMDLINE_LENGTH - _tcslen(szFullCmdLine) - 1);
|
|
|
|
_tcsncat(szFullCmdLine, rest, CMDLINE_LENGTH - _tcslen(szFullCmdLine) - 1);
|
2013-06-30 13:11:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
TRACE ("[EXEC: %s]\n", debugstr_aw(szFullCmdLine));
|
|
|
|
|
|
|
|
/* fill startup info */
|
2018-06-03 00:15:44 +00:00
|
|
|
memset(&stui, 0, sizeof(stui));
|
|
|
|
stui.cb = sizeof(stui);
|
|
|
|
stui.lpTitle = Full;
|
2013-06-30 13:11:20 +00:00
|
|
|
stui.dwFlags = STARTF_USESHOWWINDOW;
|
|
|
|
stui.wShowWindow = SW_SHOWDEFAULT;
|
|
|
|
|
2013-07-01 00:07:39 +00:00
|
|
|
/* Set the console to standard mode */
|
2017-10-01 16:30:39 +00:00
|
|
|
SetConsoleMode(ConStreamGetOSHandle(StdIn),
|
2013-07-01 00:07:39 +00:00
|
|
|
ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT);
|
|
|
|
|
|
|
|
if (CreateProcess(szFullName,
|
|
|
|
szFullCmdLine,
|
|
|
|
NULL,
|
|
|
|
NULL,
|
|
|
|
TRUE,
|
2018-04-27 15:12:23 +00:00
|
|
|
0,
|
2013-07-01 00:07:39 +00:00
|
|
|
NULL,
|
|
|
|
NULL,
|
|
|
|
&stui,
|
|
|
|
&prci))
|
2013-06-30 13:11:20 +00:00
|
|
|
{
|
|
|
|
CloseHandle(prci.hThread);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// See if we can run this with ShellExecute() ie myfile.xls
|
|
|
|
prci.hProcess = RunFile(SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NO_CONSOLE,
|
|
|
|
szFullName,
|
|
|
|
rest,
|
|
|
|
NULL,
|
|
|
|
SW_SHOWNORMAL);
|
|
|
|
}
|
|
|
|
|
2018-06-03 00:15:44 +00:00
|
|
|
*FirstEnd = _T('\0');
|
|
|
|
|
2013-06-30 13:11:20 +00:00
|
|
|
if (prci.hProcess != NULL)
|
|
|
|
{
|
2018-06-03 00:15:44 +00:00
|
|
|
if (bc != NULL || fSingleCommand != 0 || IsConsoleProcess(prci.hProcess))
|
2013-06-30 13:11:20 +00:00
|
|
|
{
|
2016-11-17 20:47:23 +00:00
|
|
|
/* when processing a batch file or starting console processes: execute synchronously */
|
2013-06-30 13:11:20 +00:00
|
|
|
EnterCriticalSection(&ChildProcessRunningLock);
|
|
|
|
dwChildProcessId = prci.dwProcessId;
|
|
|
|
|
2013-07-01 00:07:39 +00:00
|
|
|
WaitForSingleObject(prci.hProcess, INFINITE);
|
2013-06-30 13:11:20 +00:00
|
|
|
|
|
|
|
LeaveCriticalSection(&ChildProcessRunningLock);
|
|
|
|
|
2013-07-01 00:07:39 +00:00
|
|
|
GetExitCodeProcess(prci.hProcess, &dwExitCode);
|
2013-06-30 13:11:20 +00:00
|
|
|
nErrorLevel = (INT)dwExitCode;
|
|
|
|
}
|
2013-07-01 00:07:39 +00:00
|
|
|
CloseHandle(prci.hProcess);
|
2013-06-30 13:11:20 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
TRACE ("[ShellExecute failed!: %s]\n", debugstr_aw(Full));
|
2013-07-01 00:07:39 +00:00
|
|
|
error_bad_command(first);
|
2013-06-30 13:11:20 +00:00
|
|
|
dwExitCode = 1;
|
|
|
|
}
|
|
|
|
|
2013-07-01 00:07:39 +00:00
|
|
|
/* Restore our default console mode */
|
2017-10-01 16:30:39 +00:00
|
|
|
SetConsoleMode(ConStreamGetOSHandle(StdIn),
|
2013-07-01 00:07:39 +00:00
|
|
|
ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT);
|
2017-10-01 16:30:39 +00:00
|
|
|
SetConsoleMode(ConStreamGetOSHandle(StdOut),
|
2013-07-01 00:07:39 +00:00
|
|
|
ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT);
|
2013-06-30 13:11:20 +00:00
|
|
|
}
|
|
|
|
|
2017-10-01 16:30:39 +00:00
|
|
|
/* Update our local codepage cache */
|
|
|
|
{
|
|
|
|
UINT uNewInputCodePage = GetConsoleCP();
|
|
|
|
UINT uNewOutputCodePage = GetConsoleOutputCP();
|
|
|
|
|
|
|
|
if ((InputCodePage != uNewInputCodePage) ||
|
|
|
|
(OutputCodePage != uNewOutputCodePage))
|
|
|
|
{
|
|
|
|
/* Update the locale as well */
|
|
|
|
InitLocale();
|
|
|
|
}
|
|
|
|
|
|
|
|
InputCodePage = uNewInputCodePage;
|
|
|
|
OutputCodePage = uNewOutputCodePage;
|
|
|
|
|
|
|
|
/* Update the streams codepage cache as well */
|
|
|
|
ConStreamSetCacheCodePage(StdIn , InputCodePage );
|
|
|
|
ConStreamSetCacheCodePage(StdOut, OutputCodePage);
|
|
|
|
ConStreamSetCacheCodePage(StdErr, OutputCodePage);
|
|
|
|
}
|
2017-09-30 14:04:24 +00:00
|
|
|
|
|
|
|
/* Restore the original console title */
|
2018-06-03 00:15:44 +00:00
|
|
|
ResetConTitle();
|
2013-06-30 13:11:20 +00:00
|
|
|
|
|
|
|
return dwExitCode;
|
2006-02-16 23:23:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
2020-07-26 18:30:03 +00:00
|
|
|
* Look through the internal commands and determine whether or not this
|
|
|
|
* command is one of them. If it is, call the command. If not, call
|
2006-02-16 23:23:37 +00:00
|
|
|
* execute to run it as an external program.
|
|
|
|
*
|
2009-03-29 05:13:35 +00:00
|
|
|
* first - first word on command line
|
|
|
|
* rest - rest of command line
|
2006-02-16 23:23:37 +00:00
|
|
|
*/
|
2009-04-12 23:51:15 +00:00
|
|
|
INT
|
2009-03-29 05:13:35 +00:00
|
|
|
DoCommand(LPTSTR first, LPTSTR rest, PARSED_COMMAND *Cmd)
|
2006-02-16 23:23:37 +00:00
|
|
|
{
|
2013-06-30 13:11:20 +00:00
|
|
|
TCHAR *com;
|
|
|
|
TCHAR *cp;
|
2020-07-26 18:30:03 +00:00
|
|
|
LPTSTR param; /* Pointer to command's parameters */
|
2013-06-30 13:11:20 +00:00
|
|
|
INT cl;
|
|
|
|
LPCOMMAND cmdptr;
|
|
|
|
BOOL nointernal = FALSE;
|
|
|
|
INT ret;
|
|
|
|
|
|
|
|
TRACE ("DoCommand: (\'%s\' \'%s\')\n", debugstr_aw(first), debugstr_aw(rest));
|
|
|
|
|
2020-07-26 18:30:03 +00:00
|
|
|
/* Full command line */
|
2013-06-30 13:11:20 +00:00
|
|
|
com = cmd_alloc((_tcslen(first) + _tcslen(rest) + 2) * sizeof(TCHAR));
|
|
|
|
if (com == NULL)
|
|
|
|
{
|
|
|
|
error_out_of_memory();
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 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)
|
2018-06-03 00:15:44 +00:00
|
|
|
{
|
2013-06-30 13:11:20 +00:00
|
|
|
while (_istspace(*param))
|
|
|
|
param++;
|
2018-06-03 00:15:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Set the new console title */
|
|
|
|
SetConTitle(com);
|
|
|
|
|
2013-06-30 13:11:20 +00:00
|
|
|
ret = cmdptr->func(param);
|
2018-06-03 00:15:44 +00:00
|
|
|
|
|
|
|
/* Restore the original console title */
|
|
|
|
ResetConTitle();
|
|
|
|
|
2013-06-30 13:11:20 +00:00
|
|
|
cmd_free(com);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = Execute(com, first, rest, Cmd);
|
|
|
|
cmd_free(com);
|
|
|
|
return ret;
|
2006-02-16 23:23:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* process the command line and execute the appropriate functions
|
|
|
|
* full input/output redirection and piping are supported
|
|
|
|
*/
|
2017-09-30 14:04:24 +00:00
|
|
|
INT ParseCommandLine(LPTSTR cmd)
|
2006-02-16 23:23:37 +00:00
|
|
|
{
|
2013-06-30 13:11:20 +00:00
|
|
|
INT Ret = 0;
|
2020-07-29 23:44:43 +00:00
|
|
|
PARSED_COMMAND *Cmd;
|
|
|
|
|
|
|
|
Cmd = ParseCommand(cmd);
|
|
|
|
if (!Cmd)
|
2013-06-30 13:11:20 +00:00
|
|
|
{
|
2020-07-29 23:44:43 +00:00
|
|
|
/* Return an adequate error code */
|
|
|
|
return (!bParseError ? 0 : 1);
|
2013-06-30 13:11:20 +00:00
|
|
|
}
|
2020-07-29 23:44:43 +00:00
|
|
|
|
|
|
|
Ret = ExecuteCommand(Cmd);
|
|
|
|
FreeCommand(Cmd);
|
2013-06-30 13:11:20 +00:00
|
|
|
return Ret;
|
2008-08-21 20:18:35 +00:00
|
|
|
}
|
|
|
|
|
2009-03-12 22:04:59 +00:00
|
|
|
/* Execute a command without waiting for it to finish. If it's an internal
|
|
|
|
* command or batch file, we must create a new cmd.exe process to handle it.
|
|
|
|
* TODO: For now, this just always creates a cmd.exe process.
|
|
|
|
* This works, but is inefficient for running external programs,
|
|
|
|
* which could just be run directly. */
|
|
|
|
static HANDLE
|
|
|
|
ExecuteAsync(PARSED_COMMAND *Cmd)
|
2008-08-21 20:18:35 +00:00
|
|
|
{
|
2013-06-30 13:11:20 +00:00
|
|
|
TCHAR CmdPath[MAX_PATH];
|
|
|
|
TCHAR CmdParams[CMDLINE_LENGTH], *ParamsEnd;
|
|
|
|
STARTUPINFO stui;
|
|
|
|
PROCESS_INFORMATION prci;
|
|
|
|
|
|
|
|
/* Get the path to cmd.exe */
|
2017-09-30 14:04:24 +00:00
|
|
|
GetModuleFileName(NULL, CmdPath, ARRAYSIZE(CmdPath));
|
2013-06-30 13:11:20 +00:00
|
|
|
|
|
|
|
/* Build the parameter string to pass to cmd.exe */
|
|
|
|
ParamsEnd = _stpcpy(CmdParams, _T("/S/D/C\""));
|
|
|
|
ParamsEnd = Unparse(Cmd, ParamsEnd, &CmdParams[CMDLINE_LENGTH - 2]);
|
|
|
|
if (!ParamsEnd)
|
|
|
|
{
|
|
|
|
error_out_of_memory();
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
_tcscpy(ParamsEnd, _T("\""));
|
|
|
|
|
|
|
|
memset(&stui, 0, sizeof stui);
|
|
|
|
stui.cb = sizeof(STARTUPINFO);
|
|
|
|
if (!CreateProcess(CmdPath, CmdParams, NULL, NULL, TRUE, 0,
|
|
|
|
NULL, NULL, &stui, &prci))
|
|
|
|
{
|
|
|
|
ErrorMessage(GetLastError(), NULL);
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
CloseHandle(prci.hThread);
|
|
|
|
return prci.hProcess;
|
2009-03-12 22:04:59 +00:00
|
|
|
}
|
2006-02-16 23:23:37 +00:00
|
|
|
|
2017-11-18 19:50:50 +00:00
|
|
|
static INT
|
2009-03-12 22:04:59 +00:00
|
|
|
ExecutePipeline(PARSED_COMMAND *Cmd)
|
|
|
|
{
|
|
|
|
#ifdef FEATURE_REDIRECTION
|
2013-06-30 13:11:20 +00:00
|
|
|
HANDLE hInput = NULL;
|
|
|
|
HANDLE hOldConIn = GetStdHandle(STD_INPUT_HANDLE);
|
|
|
|
HANDLE hOldConOut = GetStdHandle(STD_OUTPUT_HANDLE);
|
|
|
|
HANDLE hProcess[MAXIMUM_WAIT_OBJECTS];
|
|
|
|
INT nProcesses = 0;
|
|
|
|
DWORD dwExitCode;
|
|
|
|
|
|
|
|
/* Do all but the last pipe command */
|
|
|
|
do
|
|
|
|
{
|
|
|
|
HANDLE hPipeRead, hPipeWrite;
|
|
|
|
if (nProcesses > (MAXIMUM_WAIT_OBJECTS - 2))
|
|
|
|
{
|
|
|
|
error_too_many_parameters(_T("|"));
|
|
|
|
goto failed;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Create the pipe that this process will write into.
|
|
|
|
* Make the handles non-inheritable initially, because this
|
|
|
|
* process shouldn't inherit the reading handle. */
|
|
|
|
if (!CreatePipe(&hPipeRead, &hPipeWrite, NULL, 0))
|
|
|
|
{
|
|
|
|
error_no_pipe();
|
|
|
|
goto failed;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* The writing side of the pipe is STDOUT for this process */
|
|
|
|
SetHandleInformation(hPipeWrite, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);
|
|
|
|
SetStdHandle(STD_OUTPUT_HANDLE, hPipeWrite);
|
|
|
|
|
|
|
|
/* Execute it (error check is done later for easier cleanup) */
|
|
|
|
hProcess[nProcesses] = ExecuteAsync(Cmd->Subcommands);
|
|
|
|
CloseHandle(hPipeWrite);
|
|
|
|
if (hInput)
|
|
|
|
CloseHandle(hInput);
|
|
|
|
|
|
|
|
/* The reading side of the pipe will be STDIN for the next process */
|
|
|
|
SetHandleInformation(hPipeRead, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT);
|
|
|
|
SetStdHandle(STD_INPUT_HANDLE, hPipeRead);
|
|
|
|
hInput = hPipeRead;
|
|
|
|
|
|
|
|
if (!hProcess[nProcesses])
|
|
|
|
goto failed;
|
|
|
|
nProcesses++;
|
|
|
|
|
|
|
|
Cmd = Cmd->Subcommands->Next;
|
|
|
|
} while (Cmd->Type == C_PIPE);
|
|
|
|
|
|
|
|
/* The last process uses the original STDOUT */
|
|
|
|
SetStdHandle(STD_OUTPUT_HANDLE, hOldConOut);
|
|
|
|
hProcess[nProcesses] = ExecuteAsync(Cmd);
|
|
|
|
if (!hProcess[nProcesses])
|
|
|
|
goto failed;
|
|
|
|
nProcesses++;
|
|
|
|
CloseHandle(hInput);
|
|
|
|
SetStdHandle(STD_INPUT_HANDLE, hOldConIn);
|
|
|
|
|
|
|
|
/* Wait for all processes to complete */
|
|
|
|
EnterCriticalSection(&ChildProcessRunningLock);
|
|
|
|
WaitForMultipleObjects(nProcesses, hProcess, TRUE, INFINITE);
|
|
|
|
LeaveCriticalSection(&ChildProcessRunningLock);
|
|
|
|
|
|
|
|
/* Use the exit code of the last process in the pipeline */
|
|
|
|
GetExitCodeProcess(hProcess[nProcesses - 1], &dwExitCode);
|
|
|
|
nErrorLevel = (INT)dwExitCode;
|
|
|
|
|
|
|
|
while (--nProcesses >= 0)
|
|
|
|
CloseHandle(hProcess[nProcesses]);
|
2017-11-18 19:50:50 +00:00
|
|
|
return nErrorLevel;
|
2009-03-12 22:04:59 +00:00
|
|
|
|
|
|
|
failed:
|
2013-06-30 13:11:20 +00:00
|
|
|
if (hInput)
|
|
|
|
CloseHandle(hInput);
|
|
|
|
while (--nProcesses >= 0)
|
|
|
|
{
|
|
|
|
TerminateProcess(hProcess[nProcesses], 0);
|
|
|
|
CloseHandle(hProcess[nProcesses]);
|
|
|
|
}
|
|
|
|
SetStdHandle(STD_INPUT_HANDLE, hOldConIn);
|
|
|
|
SetStdHandle(STD_OUTPUT_HANDLE, hOldConOut);
|
2009-03-12 22:04:59 +00:00
|
|
|
#endif
|
2017-11-18 19:50:50 +00:00
|
|
|
|
|
|
|
return nErrorLevel;
|
2006-02-16 23:23:37 +00:00
|
|
|
}
|
|
|
|
|
2009-04-12 23:51:15 +00:00
|
|
|
INT
|
2020-05-18 00:03:15 +00:00
|
|
|
ExecuteCommand(
|
|
|
|
IN PARSED_COMMAND *Cmd)
|
2008-08-21 20:18:35 +00:00
|
|
|
{
|
[CMD] Fixes for Batch error execution control flow.
CORE-13713 CORE-13736
- In case execution of all batch contexts is stopped (by selecting "All"
at the Ctrl-C/Ctrl-Break prompt), notify as well the CheckCtrlBreak()
signal handler once there are no more batch contexts (this in effect
resets the internal 'bLeaveAll' static flag in CheckCtrlBreak).
This is an adaptation of the fix present in FreeCOM 1.5, first
described in https://gcfl.net/FreeDOS/command.com/bugs074g.html .
- Introduce a ParseErrorEx() helper that sets the 'bParseError' flag and
displays a customized syntax-error message, only for the first syntax
error encountered. Implement ParseError() around the *Ex function.
- In batch mode, echo the original pre-parsed batch file line if a parse
error has been encountered.
- When running a compound command - including IF, FOR, command blocks -,
and that control flow is modified by any CALL/GOTO/EXIT command,
detect this while running the compound command so as to stop it and go
back to the main batch execution loop, that will then set up the actual
new command to run.
- In GOTO, do not process any more parts of a compound command only when
we have found a valid label.
2020-05-18 00:05:53 +00:00
|
|
|
#define SeenGoto() \
|
|
|
|
(bc && bc->current == NULL)
|
|
|
|
|
2013-06-30 13:11:20 +00:00
|
|
|
PARSED_COMMAND *Sub;
|
|
|
|
LPTSTR First, Rest;
|
|
|
|
INT Ret = 0;
|
|
|
|
|
[CMD] Fixes for Batch error execution control flow.
CORE-13713 CORE-13736
- In case execution of all batch contexts is stopped (by selecting "All"
at the Ctrl-C/Ctrl-Break prompt), notify as well the CheckCtrlBreak()
signal handler once there are no more batch contexts (this in effect
resets the internal 'bLeaveAll' static flag in CheckCtrlBreak).
This is an adaptation of the fix present in FreeCOM 1.5, first
described in https://gcfl.net/FreeDOS/command.com/bugs074g.html .
- Introduce a ParseErrorEx() helper that sets the 'bParseError' flag and
displays a customized syntax-error message, only for the first syntax
error encountered. Implement ParseError() around the *Ex function.
- In batch mode, echo the original pre-parsed batch file line if a parse
error has been encountered.
- When running a compound command - including IF, FOR, command blocks -,
and that control flow is modified by any CALL/GOTO/EXIT command,
detect this while running the compound command so as to stop it and go
back to the main batch execution loop, that will then set up the actual
new command to run.
- In GOTO, do not process any more parts of a compound command only when
we have found a valid label.
2020-05-18 00:05:53 +00:00
|
|
|
/*
|
|
|
|
* Do not execute any command if we are about to exit CMD, or about to
|
|
|
|
* change batch execution context, e.g. in case of a CALL / GOTO / EXIT.
|
|
|
|
*/
|
|
|
|
if (!Cmd)
|
|
|
|
return 0;
|
|
|
|
if (bExit || SeenGoto())
|
|
|
|
return 0;
|
|
|
|
|
2013-06-30 13:11:20 +00:00
|
|
|
if (!PerformRedirection(Cmd->Redirections))
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
switch (Cmd->Type)
|
|
|
|
{
|
|
|
|
case C_COMMAND:
|
|
|
|
Ret = 1;
|
|
|
|
First = DoDelayedExpansion(Cmd->Command.First);
|
|
|
|
if (First)
|
|
|
|
{
|
|
|
|
Rest = DoDelayedExpansion(Cmd->Command.Rest);
|
|
|
|
if (Rest)
|
|
|
|
{
|
|
|
|
Ret = DoCommand(First, Rest, Cmd);
|
|
|
|
cmd_free(Rest);
|
|
|
|
}
|
|
|
|
cmd_free(First);
|
|
|
|
}
|
|
|
|
break;
|
2020-07-26 18:30:03 +00:00
|
|
|
|
2013-06-30 13:11:20 +00:00
|
|
|
case C_QUIET:
|
|
|
|
case C_BLOCK:
|
|
|
|
case C_MULTI:
|
[CMD] Fixes for Batch error execution control flow.
CORE-13713 CORE-13736
- In case execution of all batch contexts is stopped (by selecting "All"
at the Ctrl-C/Ctrl-Break prompt), notify as well the CheckCtrlBreak()
signal handler once there are no more batch contexts (this in effect
resets the internal 'bLeaveAll' static flag in CheckCtrlBreak).
This is an adaptation of the fix present in FreeCOM 1.5, first
described in https://gcfl.net/FreeDOS/command.com/bugs074g.html .
- Introduce a ParseErrorEx() helper that sets the 'bParseError' flag and
displays a customized syntax-error message, only for the first syntax
error encountered. Implement ParseError() around the *Ex function.
- In batch mode, echo the original pre-parsed batch file line if a parse
error has been encountered.
- When running a compound command - including IF, FOR, command blocks -,
and that control flow is modified by any CALL/GOTO/EXIT command,
detect this while running the compound command so as to stop it and go
back to the main batch execution loop, that will then set up the actual
new command to run.
- In GOTO, do not process any more parts of a compound command only when
we have found a valid label.
2020-05-18 00:05:53 +00:00
|
|
|
for (Sub = Cmd->Subcommands; Sub && !SeenGoto(); Sub = Sub->Next)
|
2013-06-30 13:11:20 +00:00
|
|
|
Ret = ExecuteCommand(Sub);
|
|
|
|
break;
|
2020-07-26 18:30:03 +00:00
|
|
|
|
2020-07-26 18:32:11 +00:00
|
|
|
case C_OR:
|
2013-06-30 13:11:20 +00:00
|
|
|
Sub = Cmd->Subcommands;
|
|
|
|
Ret = ExecuteCommand(Sub);
|
[CMD] Fixes for Batch error execution control flow.
CORE-13713 CORE-13736
- In case execution of all batch contexts is stopped (by selecting "All"
at the Ctrl-C/Ctrl-Break prompt), notify as well the CheckCtrlBreak()
signal handler once there are no more batch contexts (this in effect
resets the internal 'bLeaveAll' static flag in CheckCtrlBreak).
This is an adaptation of the fix present in FreeCOM 1.5, first
described in https://gcfl.net/FreeDOS/command.com/bugs074g.html .
- Introduce a ParseErrorEx() helper that sets the 'bParseError' flag and
displays a customized syntax-error message, only for the first syntax
error encountered. Implement ParseError() around the *Ex function.
- In batch mode, echo the original pre-parsed batch file line if a parse
error has been encountered.
- When running a compound command - including IF, FOR, command blocks -,
and that control flow is modified by any CALL/GOTO/EXIT command,
detect this while running the compound command so as to stop it and go
back to the main batch execution loop, that will then set up the actual
new command to run.
- In GOTO, do not process any more parts of a compound command only when
we have found a valid label.
2020-05-18 00:05:53 +00:00
|
|
|
if ((Ret != 0) && !SeenGoto())
|
2013-06-30 13:11:20 +00:00
|
|
|
{
|
|
|
|
nErrorLevel = Ret;
|
|
|
|
Ret = ExecuteCommand(Sub->Next);
|
|
|
|
}
|
|
|
|
break;
|
2020-07-26 18:30:03 +00:00
|
|
|
|
2020-07-26 18:32:11 +00:00
|
|
|
case C_AND:
|
2013-06-30 13:11:20 +00:00
|
|
|
Sub = Cmd->Subcommands;
|
|
|
|
Ret = ExecuteCommand(Sub);
|
[CMD] Fixes for Batch error execution control flow.
CORE-13713 CORE-13736
- In case execution of all batch contexts is stopped (by selecting "All"
at the Ctrl-C/Ctrl-Break prompt), notify as well the CheckCtrlBreak()
signal handler once there are no more batch contexts (this in effect
resets the internal 'bLeaveAll' static flag in CheckCtrlBreak).
This is an adaptation of the fix present in FreeCOM 1.5, first
described in https://gcfl.net/FreeDOS/command.com/bugs074g.html .
- Introduce a ParseErrorEx() helper that sets the 'bParseError' flag and
displays a customized syntax-error message, only for the first syntax
error encountered. Implement ParseError() around the *Ex function.
- In batch mode, echo the original pre-parsed batch file line if a parse
error has been encountered.
- When running a compound command - including IF, FOR, command blocks -,
and that control flow is modified by any CALL/GOTO/EXIT command,
detect this while running the compound command so as to stop it and go
back to the main batch execution loop, that will then set up the actual
new command to run.
- In GOTO, do not process any more parts of a compound command only when
we have found a valid label.
2020-05-18 00:05:53 +00:00
|
|
|
if ((Ret == 0) && !SeenGoto())
|
2013-06-30 13:11:20 +00:00
|
|
|
Ret = ExecuteCommand(Sub->Next);
|
|
|
|
break;
|
2020-07-26 18:30:03 +00:00
|
|
|
|
2013-06-30 13:11:20 +00:00
|
|
|
case C_PIPE:
|
2017-11-18 19:50:50 +00:00
|
|
|
Ret = ExecutePipeline(Cmd);
|
2013-06-30 13:11:20 +00:00
|
|
|
break;
|
2020-07-26 18:30:03 +00:00
|
|
|
|
2013-06-30 13:11:20 +00:00
|
|
|
case C_IF:
|
|
|
|
Ret = ExecuteIf(Cmd);
|
|
|
|
break;
|
2020-07-26 18:30:03 +00:00
|
|
|
|
2013-06-30 13:11:20 +00:00
|
|
|
case C_FOR:
|
|
|
|
Ret = ExecuteFor(Cmd);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
UndoRedirection(Cmd->Redirections, NULL);
|
|
|
|
return Ret;
|
[CMD] Fixes for Batch error execution control flow.
CORE-13713 CORE-13736
- In case execution of all batch contexts is stopped (by selecting "All"
at the Ctrl-C/Ctrl-Break prompt), notify as well the CheckCtrlBreak()
signal handler once there are no more batch contexts (this in effect
resets the internal 'bLeaveAll' static flag in CheckCtrlBreak).
This is an adaptation of the fix present in FreeCOM 1.5, first
described in https://gcfl.net/FreeDOS/command.com/bugs074g.html .
- Introduce a ParseErrorEx() helper that sets the 'bParseError' flag and
displays a customized syntax-error message, only for the first syntax
error encountered. Implement ParseError() around the *Ex function.
- In batch mode, echo the original pre-parsed batch file line if a parse
error has been encountered.
- When running a compound command - including IF, FOR, command blocks -,
and that control flow is modified by any CALL/GOTO/EXIT command,
detect this while running the compound command so as to stop it and go
back to the main batch execution loop, that will then set up the actual
new command to run.
- In GOTO, do not process any more parts of a compound command only when
we have found a valid label.
2020-05-18 00:05:53 +00:00
|
|
|
|
|
|
|
#undef SeenGoto
|
2008-08-21 20:18:35 +00:00
|
|
|
}
|
|
|
|
|
2020-05-18 00:03:15 +00:00
|
|
|
INT
|
|
|
|
ExecuteCommandWithEcho(
|
|
|
|
IN PARSED_COMMAND *Cmd)
|
|
|
|
{
|
|
|
|
/* Echo the reconstructed command line */
|
[CMD] Fixes for Batch error execution control flow.
CORE-13713 CORE-13736
- In case execution of all batch contexts is stopped (by selecting "All"
at the Ctrl-C/Ctrl-Break prompt), notify as well the CheckCtrlBreak()
signal handler once there are no more batch contexts (this in effect
resets the internal 'bLeaveAll' static flag in CheckCtrlBreak).
This is an adaptation of the fix present in FreeCOM 1.5, first
described in https://gcfl.net/FreeDOS/command.com/bugs074g.html .
- Introduce a ParseErrorEx() helper that sets the 'bParseError' flag and
displays a customized syntax-error message, only for the first syntax
error encountered. Implement ParseError() around the *Ex function.
- In batch mode, echo the original pre-parsed batch file line if a parse
error has been encountered.
- When running a compound command - including IF, FOR, command blocks -,
and that control flow is modified by any CALL/GOTO/EXIT command,
detect this while running the compound command so as to stop it and go
back to the main batch execution loop, that will then set up the actual
new command to run.
- In GOTO, do not process any more parts of a compound command only when
we have found a valid label.
2020-05-18 00:05:53 +00:00
|
|
|
if (bEcho && !bDisableBatchEcho && Cmd && (Cmd->Type != C_QUIET))
|
2020-05-18 00:03:15 +00:00
|
|
|
{
|
|
|
|
if (!bIgnoreEcho)
|
|
|
|
ConOutChar(_T('\n'));
|
|
|
|
PrintPrompt();
|
|
|
|
EchoCommand(Cmd);
|
|
|
|
ConOutChar(_T('\n'));
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Run the command */
|
|
|
|
return ExecuteCommand(Cmd);
|
|
|
|
}
|
|
|
|
|
2009-03-23 19:54:52 +00:00
|
|
|
LPTSTR
|
|
|
|
GetEnvVar(LPCTSTR varName)
|
2006-02-16 23:23:37 +00:00
|
|
|
{
|
2013-06-30 13:11:20 +00:00
|
|
|
static LPTSTR ret = NULL;
|
|
|
|
UINT size;
|
|
|
|
|
|
|
|
cmd_free(ret);
|
|
|
|
ret = NULL;
|
|
|
|
size = GetEnvironmentVariable(varName, NULL, 0);
|
|
|
|
if (size > 0)
|
|
|
|
{
|
|
|
|
ret = cmd_alloc(size * sizeof(TCHAR));
|
|
|
|
if (ret != NULL)
|
|
|
|
GetEnvironmentVariable(varName, ret, size + 1);
|
|
|
|
}
|
|
|
|
return ret;
|
2009-03-23 19:54:52 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
LPCTSTR
|
|
|
|
GetEnvVarOrSpecial(LPCTSTR varName)
|
|
|
|
{
|
2013-06-30 13:11:20 +00:00
|
|
|
static TCHAR ret[MAX_PATH];
|
|
|
|
|
|
|
|
LPTSTR var = GetEnvVar(varName);
|
|
|
|
if (var)
|
|
|
|
return var;
|
|
|
|
|
2020-07-12 21:27:35 +00:00
|
|
|
/* The environment variable doesn't exist, look for
|
|
|
|
* a "special" one only if extensions are enabled. */
|
|
|
|
if (!bEnableExtensions)
|
|
|
|
return NULL;
|
|
|
|
|
2013-06-30 13:11:20 +00:00
|
|
|
/* %CD% */
|
2020-07-26 18:30:03 +00:00
|
|
|
if (_tcsicmp(varName, _T("CD")) == 0)
|
2013-06-30 13:11:20 +00:00
|
|
|
{
|
2020-07-26 18:30:03 +00:00
|
|
|
GetCurrentDirectory(ARRAYSIZE(ret), ret);
|
2013-06-30 13:11:20 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
/* %DATE% */
|
2020-07-26 18:30:03 +00:00
|
|
|
else if (_tcsicmp(varName, _T("DATE")) == 0)
|
2013-06-30 13:11:20 +00:00
|
|
|
{
|
|
|
|
return GetDateString();
|
|
|
|
}
|
2020-07-26 18:30:03 +00:00
|
|
|
/* %TIME% */
|
|
|
|
else if (_tcsicmp(varName, _T("TIME")) == 0)
|
|
|
|
{
|
|
|
|
return GetTimeString();
|
|
|
|
}
|
2013-06-30 13:11:20 +00:00
|
|
|
/* %RANDOM% */
|
2020-07-26 18:30:03 +00:00
|
|
|
else if (_tcsicmp(varName, _T("RANDOM")) == 0)
|
2013-06-30 13:11:20 +00:00
|
|
|
{
|
|
|
|
/* Get random number */
|
2020-07-26 18:30:03 +00:00
|
|
|
_itot(rand(), ret, 10);
|
2013-06-30 13:11:20 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
/* %CMDCMDLINE% */
|
2020-07-26 18:30:03 +00:00
|
|
|
else if (_tcsicmp(varName, _T("CMDCMDLINE")) == 0)
|
2013-06-30 13:11:20 +00:00
|
|
|
{
|
|
|
|
return GetCommandLine();
|
|
|
|
}
|
|
|
|
/* %CMDEXTVERSION% */
|
2020-07-26 18:30:03 +00:00
|
|
|
else if (_tcsicmp(varName, _T("CMDEXTVERSION")) == 0)
|
2013-06-30 13:11:20 +00:00
|
|
|
{
|
2020-09-03 14:03:18 +00:00
|
|
|
/* Set Command Extensions version number to CMDEXTVERSION */
|
2020-07-12 19:41:07 +00:00
|
|
|
_itot(CMDEXTVERSION, ret, 10);
|
2013-06-30 13:11:20 +00:00
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
/* %ERRORLEVEL% */
|
2020-07-26 18:30:03 +00:00
|
|
|
else if (_tcsicmp(varName, _T("ERRORLEVEL")) == 0)
|
2013-06-30 13:11:20 +00:00
|
|
|
{
|
2020-07-26 18:30:03 +00:00
|
|
|
_itot(nErrorLevel, ret, 10);
|
2013-06-30 13:11:20 +00:00
|
|
|
return ret;
|
|
|
|
}
|
2020-07-12 21:37:50 +00:00
|
|
|
#if (NTDDI_VERSION >= NTDDI_WIN7)
|
|
|
|
/* Available in Win7+, even if the underlying API is available in Win2003+ */
|
|
|
|
/* %HIGHESTNUMANODENUMBER% */
|
|
|
|
else if (_tcsicmp(varName, _T("HIGHESTNUMANODENUMBER")) == 0)
|
|
|
|
{
|
|
|
|
ULONG NumaNodeNumber = 0;
|
|
|
|
GetNumaHighestNodeNumber(&NumaNodeNumber);
|
|
|
|
_itot(NumaNodeNumber, ret, 10);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
#endif
|
2013-06-30 13:11:20 +00:00
|
|
|
|
|
|
|
return NULL;
|
2006-02-16 23:23:37 +00:00
|
|
|
}
|
|
|
|
|
2009-03-15 04:54:41 +00:00
|
|
|
/* Handle the %~var syntax */
|
|
|
|
static LPTSTR
|
|
|
|
GetEnhancedVar(TCHAR **pFormat, LPTSTR (*GetVar)(TCHAR, BOOL *))
|
|
|
|
{
|
2013-06-30 13:11:20 +00:00
|
|
|
static const TCHAR ModifierTable[] = _T("dpnxfsatz");
|
|
|
|
enum {
|
|
|
|
M_DRIVE = 1, /* D: drive letter */
|
|
|
|
M_PATH = 2, /* P: path */
|
|
|
|
M_NAME = 4, /* N: filename */
|
|
|
|
M_EXT = 8, /* X: extension */
|
|
|
|
M_FULL = 16, /* F: full path (drive+path+name+ext) */
|
|
|
|
M_SHORT = 32, /* S: full path (drive+path+name+ext), use short names */
|
|
|
|
M_ATTR = 64, /* A: attributes */
|
|
|
|
M_TIME = 128, /* T: modification time */
|
|
|
|
M_SIZE = 256, /* Z: file size */
|
|
|
|
} Modifiers = 0;
|
|
|
|
|
|
|
|
TCHAR *Format, *FormatEnd;
|
|
|
|
TCHAR *PathVarName = NULL;
|
|
|
|
LPTSTR Variable;
|
|
|
|
TCHAR *VarEnd;
|
|
|
|
BOOL VariableIsParam0;
|
|
|
|
TCHAR FullPath[MAX_PATH];
|
|
|
|
TCHAR FixedPath[MAX_PATH], *Filename, *Extension;
|
|
|
|
HANDLE hFind;
|
|
|
|
WIN32_FIND_DATA w32fd;
|
|
|
|
TCHAR *In, *Out;
|
|
|
|
|
|
|
|
static TCHAR Result[CMDLINE_LENGTH];
|
|
|
|
|
|
|
|
/* There is ambiguity between modifier characters and FOR variables;
|
|
|
|
* the rule that cmd uses is to pick the longest possible match.
|
|
|
|
* For example, if there is a %n variable, then out of %~anxnd,
|
|
|
|
* %~anxn will be substituted rather than just %~an. */
|
|
|
|
|
|
|
|
/* First, go through as many modifier characters as possible */
|
|
|
|
FormatEnd = Format = *pFormat;
|
|
|
|
while (*FormatEnd && _tcschr(ModifierTable, _totlower(*FormatEnd)))
|
|
|
|
FormatEnd++;
|
|
|
|
|
|
|
|
if (*FormatEnd == _T('$'))
|
|
|
|
{
|
|
|
|
/* $PATH: syntax */
|
|
|
|
PathVarName = FormatEnd + 1;
|
|
|
|
FormatEnd = _tcschr(PathVarName, _T(':'));
|
|
|
|
if (!FormatEnd)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
/* Must be immediately followed by the variable */
|
|
|
|
Variable = GetVar(*++FormatEnd, &VariableIsParam0);
|
|
|
|
if (!Variable)
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* Backtrack if necessary to get a variable name match */
|
|
|
|
while (!(Variable = GetVar(*FormatEnd, &VariableIsParam0)))
|
|
|
|
{
|
|
|
|
if (FormatEnd == Format)
|
|
|
|
return NULL;
|
|
|
|
FormatEnd--;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (; Format < FormatEnd && *Format != _T('$'); Format++)
|
|
|
|
Modifiers |= 1 << (_tcschr(ModifierTable, _totlower(*Format)) - ModifierTable);
|
|
|
|
|
|
|
|
*pFormat = FormatEnd + 1;
|
|
|
|
|
|
|
|
/* Exclude the leading and trailing quotes */
|
|
|
|
VarEnd = &Variable[_tcslen(Variable)];
|
|
|
|
if (*Variable == _T('"'))
|
|
|
|
{
|
|
|
|
Variable++;
|
|
|
|
if (VarEnd > Variable && VarEnd[-1] == _T('"'))
|
|
|
|
VarEnd--;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((char *)VarEnd - (char *)Variable >= sizeof Result)
|
|
|
|
return _T("");
|
|
|
|
memcpy(Result, Variable, (char *)VarEnd - (char *)Variable);
|
|
|
|
Result[VarEnd - Variable] = _T('\0');
|
|
|
|
|
|
|
|
if (PathVarName)
|
|
|
|
{
|
|
|
|
/* $PATH: syntax - search the directories listed in the
|
|
|
|
* specified environment variable for the file */
|
|
|
|
LPTSTR PathVar;
|
|
|
|
FormatEnd[-1] = _T('\0');
|
|
|
|
PathVar = GetEnvVar(PathVarName);
|
|
|
|
FormatEnd[-1] = _T(':');
|
|
|
|
if (!PathVar ||
|
2020-07-26 18:30:03 +00:00
|
|
|
!SearchPath(PathVar, Result, NULL, ARRAYSIZE(FullPath), FullPath, NULL))
|
2013-06-30 13:11:20 +00:00
|
|
|
{
|
|
|
|
return _T("");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (Modifiers == 0)
|
|
|
|
{
|
|
|
|
/* For plain %~var with no modifiers, just return the variable without quotes */
|
|
|
|
return Result;
|
|
|
|
}
|
|
|
|
else if (VariableIsParam0)
|
|
|
|
{
|
|
|
|
/* Special case: If the variable is %0 and modifier characters are present,
|
|
|
|
* use the batch file's path (which includes the .bat/.cmd extension)
|
|
|
|
* rather than the actual %0 variable (which might not). */
|
|
|
|
_tcscpy(FullPath, bc->BatchFilePath);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* Convert the variable, now without quotes, to a full path */
|
2020-07-26 18:30:03 +00:00
|
|
|
if (!GetFullPathName(Result, ARRAYSIZE(FullPath), FullPath, NULL))
|
2013-06-30 13:11:20 +00:00
|
|
|
return _T("");
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Next step is to change the path to fix letter case (e.g.
|
|
|
|
* C:\ReAcToS -> C:\ReactOS) and, if requested with the S modifier,
|
|
|
|
* replace long filenames with short. */
|
|
|
|
|
|
|
|
In = FullPath;
|
|
|
|
Out = FixedPath;
|
|
|
|
|
|
|
|
/* Copy drive letter */
|
|
|
|
*Out++ = *In++;
|
|
|
|
*Out++ = *In++;
|
|
|
|
*Out++ = *In++;
|
|
|
|
/* Loop over each \-separated component in the path */
|
|
|
|
do {
|
|
|
|
TCHAR *Next = _tcschr(In, _T('\\'));
|
|
|
|
if (Next)
|
|
|
|
*Next++ = _T('\0');
|
|
|
|
/* Use FindFirstFile to get the correct name */
|
2020-07-26 18:30:03 +00:00
|
|
|
if (Out + _tcslen(In) + 1 >= &FixedPath[ARRAYSIZE(FixedPath)])
|
2013-06-30 13:11:20 +00:00
|
|
|
return _T("");
|
|
|
|
_tcscpy(Out, In);
|
|
|
|
hFind = FindFirstFile(FixedPath, &w32fd);
|
|
|
|
/* If it doesn't exist, just leave the name as it was given */
|
|
|
|
if (hFind != INVALID_HANDLE_VALUE)
|
|
|
|
{
|
|
|
|
LPTSTR FixedComponent = w32fd.cFileName;
|
|
|
|
if (*w32fd.cAlternateFileName &&
|
|
|
|
((Modifiers & M_SHORT) || !_tcsicmp(In, w32fd.cAlternateFileName)))
|
|
|
|
{
|
|
|
|
FixedComponent = w32fd.cAlternateFileName;
|
|
|
|
}
|
|
|
|
FindClose(hFind);
|
|
|
|
|
2020-07-26 18:30:03 +00:00
|
|
|
if (Out + _tcslen(FixedComponent) + 1 >= &FixedPath[ARRAYSIZE(FixedPath)])
|
2013-06-30 13:11:20 +00:00
|
|
|
return _T("");
|
|
|
|
_tcscpy(Out, FixedComponent);
|
|
|
|
}
|
|
|
|
Filename = Out;
|
|
|
|
Out += _tcslen(Out);
|
|
|
|
*Out++ = _T('\\');
|
|
|
|
|
|
|
|
In = Next;
|
|
|
|
} while (In != NULL);
|
|
|
|
Out[-1] = _T('\0');
|
|
|
|
|
|
|
|
/* Build the result string. Start with attributes, modification time, and
|
|
|
|
* file size. If the file didn't exist, these fields will all be empty. */
|
|
|
|
Out = Result;
|
|
|
|
if (hFind != INVALID_HANDLE_VALUE)
|
|
|
|
{
|
|
|
|
if (Modifiers & M_ATTR)
|
|
|
|
{
|
|
|
|
static const struct {
|
|
|
|
TCHAR Character;
|
|
|
|
WORD Value;
|
|
|
|
} *Attrib, Table[] = {
|
|
|
|
{ _T('d'), FILE_ATTRIBUTE_DIRECTORY },
|
|
|
|
{ _T('r'), FILE_ATTRIBUTE_READONLY },
|
|
|
|
{ _T('a'), FILE_ATTRIBUTE_ARCHIVE },
|
|
|
|
{ _T('h'), FILE_ATTRIBUTE_HIDDEN },
|
|
|
|
{ _T('s'), FILE_ATTRIBUTE_SYSTEM },
|
|
|
|
{ _T('c'), FILE_ATTRIBUTE_COMPRESSED },
|
|
|
|
{ _T('o'), FILE_ATTRIBUTE_OFFLINE },
|
|
|
|
{ _T('t'), FILE_ATTRIBUTE_TEMPORARY },
|
|
|
|
{ _T('l'), FILE_ATTRIBUTE_REPARSE_POINT },
|
2020-07-04 20:19:45 +00:00
|
|
|
#if (NTDDI_VERSION >= NTDDI_WIN8)
|
|
|
|
{ _T('v'), FILE_ATTRIBUTE_INTEGRITY_STREAM },
|
|
|
|
{ _T('x'), FILE_ATTRIBUTE_NO_SCRUB_DATA /* 0x20000 */ },
|
|
|
|
#endif
|
2013-06-30 13:11:20 +00:00
|
|
|
};
|
2020-07-26 18:30:03 +00:00
|
|
|
for (Attrib = Table; Attrib != &Table[ARRAYSIZE(Table)]; Attrib++)
|
2013-06-30 13:11:20 +00:00
|
|
|
{
|
|
|
|
*Out++ = w32fd.dwFileAttributes & Attrib->Value
|
|
|
|
? Attrib->Character
|
|
|
|
: _T('-');
|
|
|
|
}
|
|
|
|
*Out++ = _T(' ');
|
|
|
|
}
|
|
|
|
if (Modifiers & M_TIME)
|
|
|
|
{
|
|
|
|
FILETIME ft;
|
|
|
|
SYSTEMTIME st;
|
|
|
|
FileTimeToLocalFileTime(&w32fd.ftLastWriteTime, &ft);
|
|
|
|
FileTimeToSystemTime(&ft, &st);
|
|
|
|
|
|
|
|
Out += FormatDate(Out, &st, TRUE);
|
|
|
|
*Out++ = _T(' ');
|
|
|
|
Out += FormatTime(Out, &st);
|
|
|
|
*Out++ = _T(' ');
|
|
|
|
}
|
|
|
|
if (Modifiers & M_SIZE)
|
|
|
|
{
|
|
|
|
ULARGE_INTEGER Size;
|
|
|
|
Size.LowPart = w32fd.nFileSizeLow;
|
|
|
|
Size.HighPart = w32fd.nFileSizeHigh;
|
|
|
|
Out += _stprintf(Out, _T("%I64u "), Size.QuadPart);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* When using the path-searching syntax or the S modifier,
|
|
|
|
* at least part of the file path is always included.
|
|
|
|
* If none of the DPNX modifiers are present, include the full path */
|
|
|
|
if (PathVarName || (Modifiers & M_SHORT))
|
|
|
|
if ((Modifiers & (M_DRIVE | M_PATH | M_NAME | M_EXT)) == 0)
|
|
|
|
Modifiers |= M_FULL;
|
|
|
|
|
|
|
|
/* Now add the requested parts of the name.
|
|
|
|
* With the F modifier, add all parts to form the full path. */
|
|
|
|
Extension = _tcsrchr(Filename, _T('.'));
|
|
|
|
if (Modifiers & (M_DRIVE | M_FULL))
|
|
|
|
{
|
|
|
|
*Out++ = FixedPath[0];
|
|
|
|
*Out++ = FixedPath[1];
|
|
|
|
}
|
|
|
|
if (Modifiers & (M_PATH | M_FULL))
|
|
|
|
{
|
|
|
|
memcpy(Out, &FixedPath[2], (char *)Filename - (char *)&FixedPath[2]);
|
|
|
|
Out += Filename - &FixedPath[2];
|
|
|
|
}
|
|
|
|
if (Modifiers & (M_NAME | M_FULL))
|
|
|
|
{
|
|
|
|
while (*Filename && Filename != Extension)
|
|
|
|
*Out++ = *Filename++;
|
|
|
|
}
|
|
|
|
if (Modifiers & (M_EXT | M_FULL))
|
|
|
|
{
|
|
|
|
if (Extension)
|
|
|
|
Out = _stpcpy(Out, Extension);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Trim trailing space which otherwise would appear as a
|
|
|
|
* result of using the A/T/Z modifiers but no others. */
|
|
|
|
while (Out != &Result[0] && Out[-1] == _T(' '))
|
|
|
|
Out--;
|
|
|
|
*Out = _T('\0');
|
|
|
|
|
|
|
|
return Result;
|
2009-03-15 04:54:41 +00:00
|
|
|
}
|
2008-08-26 20:36:38 +00:00
|
|
|
|
2020-07-26 18:30:03 +00:00
|
|
|
static LPCTSTR
|
2009-03-15 04:54:41 +00:00
|
|
|
GetBatchVar(TCHAR *varName, UINT *varNameLen)
|
|
|
|
{
|
2013-06-30 13:11:20 +00:00
|
|
|
LPCTSTR ret;
|
|
|
|
TCHAR *varNameEnd;
|
|
|
|
BOOL dummy;
|
|
|
|
|
|
|
|
*varNameLen = 1;
|
|
|
|
|
|
|
|
switch ( *varName )
|
|
|
|
{
|
|
|
|
case _T('~'):
|
|
|
|
varNameEnd = varName + 1;
|
|
|
|
ret = GetEnhancedVar(&varNameEnd, FindArg);
|
|
|
|
if (!ret)
|
|
|
|
{
|
2020-05-18 00:16:40 +00:00
|
|
|
ParseErrorEx(varName);
|
2013-06-30 13:11:20 +00:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
*varNameLen = varNameEnd - varName;
|
|
|
|
return ret;
|
2020-07-26 18:30:03 +00:00
|
|
|
|
2013-06-30 13:11:20 +00:00
|
|
|
case _T('0'):
|
|
|
|
case _T('1'):
|
|
|
|
case _T('2'):
|
|
|
|
case _T('3'):
|
|
|
|
case _T('4'):
|
|
|
|
case _T('5'):
|
|
|
|
case _T('6'):
|
|
|
|
case _T('7'):
|
|
|
|
case _T('8'):
|
|
|
|
case _T('9'):
|
|
|
|
return FindArg(*varName, &dummy);
|
2007-10-19 23:21:45 +00:00
|
|
|
|
2006-06-29 02:48:52 +00:00
|
|
|
case _T('*'):
|
2020-07-26 18:30:03 +00:00
|
|
|
/* Copy over the raw params (not including the batch file name) */
|
2008-08-12 23:46:15 +00:00
|
|
|
return bc->raw_params;
|
2006-02-16 23:23:37 +00:00
|
|
|
|
2013-06-30 13:11:20 +00:00
|
|
|
case _T('%'):
|
|
|
|
return _T("%");
|
|
|
|
}
|
|
|
|
return NULL;
|
2008-08-12 23:46:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
BOOL
|
2009-02-14 01:13:17 +00:00
|
|
|
SubstituteVars(TCHAR *Src, TCHAR *Dest, TCHAR Delim)
|
2008-08-12 23:46:15 +00:00
|
|
|
{
|
|
|
|
#define APPEND(From, Length) { \
|
2013-06-30 13:11:20 +00:00
|
|
|
if (Dest + (Length) > DestEnd) \
|
|
|
|
goto too_long; \
|
|
|
|
memcpy(Dest, From, (Length) * sizeof(TCHAR)); \
|
|
|
|
Dest += Length; }
|
2008-08-12 23:46:15 +00:00
|
|
|
#define APPEND1(Char) { \
|
2013-06-30 13:11:20 +00:00
|
|
|
if (Dest >= DestEnd) \
|
|
|
|
goto too_long; \
|
|
|
|
*Dest++ = Char; }
|
|
|
|
|
|
|
|
TCHAR *DestEnd = Dest + CMDLINE_LENGTH - 1;
|
|
|
|
const TCHAR *Var;
|
|
|
|
int VarLength;
|
|
|
|
TCHAR *SubstStart;
|
|
|
|
TCHAR EndChr;
|
|
|
|
while (*Src)
|
|
|
|
{
|
|
|
|
if (*Src != Delim)
|
|
|
|
{
|
|
|
|
APPEND1(*Src++)
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
Src++;
|
|
|
|
if (bc && Delim == _T('%'))
|
|
|
|
{
|
|
|
|
UINT NameLen;
|
|
|
|
Var = GetBatchVar(Src, &NameLen);
|
2020-05-18 00:16:40 +00:00
|
|
|
if (!Var && bParseError)
|
|
|
|
{
|
|
|
|
/* Return the partially-parsed command to be
|
|
|
|
* echoed for error diagnostics purposes. */
|
|
|
|
APPEND1(Delim);
|
|
|
|
APPEND(Src, DestEnd-Dest);
|
|
|
|
return FALSE;
|
|
|
|
}
|
2013-06-30 13:11:20 +00:00
|
|
|
if (Var != NULL)
|
|
|
|
{
|
|
|
|
VarLength = _tcslen(Var);
|
|
|
|
APPEND(Var, VarLength)
|
|
|
|
Src += NameLen;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Find the end of the variable name. A colon (:) will usually
|
|
|
|
* end the name and begin the optional modifier, but not if it
|
|
|
|
* is immediately followed by the delimiter (%VAR:%). */
|
|
|
|
SubstStart = Src;
|
|
|
|
while (*Src != Delim && !(*Src == _T(':') && Src[1] != Delim))
|
|
|
|
{
|
|
|
|
if (!*Src)
|
|
|
|
goto bad_subst;
|
|
|
|
Src++;
|
|
|
|
}
|
|
|
|
|
|
|
|
EndChr = *Src;
|
|
|
|
*Src = _T('\0');
|
|
|
|
Var = GetEnvVarOrSpecial(SubstStart);
|
|
|
|
*Src++ = EndChr;
|
|
|
|
if (Var == NULL)
|
|
|
|
{
|
|
|
|
/* In a batch file, %NONEXISTENT% "expands" to an empty string */
|
|
|
|
if (bc)
|
|
|
|
continue;
|
|
|
|
goto bad_subst;
|
|
|
|
}
|
|
|
|
VarLength = _tcslen(Var);
|
|
|
|
|
|
|
|
if (EndChr == Delim)
|
|
|
|
{
|
|
|
|
/* %VAR% - use as-is */
|
|
|
|
APPEND(Var, VarLength)
|
|
|
|
}
|
|
|
|
else if (*Src == _T('~'))
|
|
|
|
{
|
|
|
|
/* %VAR:~[start][,length]% - substring
|
|
|
|
* Negative values are offsets from the end */
|
|
|
|
int Start = _tcstol(Src + 1, &Src, 0);
|
|
|
|
int End = VarLength;
|
|
|
|
if (Start < 0)
|
|
|
|
Start += VarLength;
|
|
|
|
Start = max(Start, 0);
|
|
|
|
Start = min(Start, VarLength);
|
|
|
|
if (*Src == _T(','))
|
|
|
|
{
|
|
|
|
End = _tcstol(Src + 1, &Src, 0);
|
|
|
|
End += (End < 0) ? VarLength : Start;
|
|
|
|
End = max(End, Start);
|
|
|
|
End = min(End, VarLength);
|
|
|
|
}
|
|
|
|
if (*Src++ != Delim)
|
|
|
|
goto bad_subst;
|
|
|
|
APPEND(&Var[Start], End - Start);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* %VAR:old=new% - replace all occurrences of old with new
|
|
|
|
* %VAR:*old=new% - replace first occurrence only,
|
|
|
|
* and remove everything before it */
|
|
|
|
TCHAR *Old, *New;
|
|
|
|
DWORD OldLength, NewLength;
|
|
|
|
BOOL Star = FALSE;
|
|
|
|
int LastMatch = 0, i = 0;
|
|
|
|
|
|
|
|
if (*Src == _T('*'))
|
|
|
|
{
|
|
|
|
Star = TRUE;
|
|
|
|
Src++;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* the string to replace may contain the delimiter */
|
|
|
|
Src = _tcschr(Old = Src, _T('='));
|
|
|
|
if (Src == NULL)
|
|
|
|
goto bad_subst;
|
|
|
|
OldLength = Src++ - Old;
|
|
|
|
if (OldLength == 0)
|
|
|
|
goto bad_subst;
|
|
|
|
|
|
|
|
Src = _tcschr(New = Src, Delim);
|
|
|
|
if (Src == NULL)
|
|
|
|
goto bad_subst;
|
|
|
|
NewLength = Src++ - New;
|
|
|
|
|
|
|
|
while (i < VarLength)
|
|
|
|
{
|
|
|
|
if (_tcsnicmp(&Var[i], Old, OldLength) == 0)
|
|
|
|
{
|
|
|
|
if (!Star)
|
|
|
|
APPEND(&Var[LastMatch], i - LastMatch)
|
|
|
|
APPEND(New, NewLength)
|
|
|
|
i += OldLength;
|
|
|
|
LastMatch = i;
|
|
|
|
if (Star)
|
|
|
|
break;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
i++;
|
|
|
|
}
|
|
|
|
APPEND(&Var[LastMatch], VarLength - LastMatch)
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
|
|
|
|
bad_subst:
|
|
|
|
Src = SubstStart;
|
|
|
|
if (!bc)
|
|
|
|
APPEND1(Delim)
|
|
|
|
}
|
|
|
|
*Dest = _T('\0');
|
|
|
|
return TRUE;
|
2008-08-12 23:46:15 +00:00
|
|
|
too_long:
|
2013-06-30 13:11:20 +00:00
|
|
|
ConOutResPrintf(STRING_ALIAS_ERROR);
|
|
|
|
nErrorLevel = 9023;
|
|
|
|
return FALSE;
|
2008-08-12 23:46:15 +00:00
|
|
|
#undef APPEND
|
|
|
|
#undef APPEND1
|
2006-02-16 23:23:37 +00:00
|
|
|
}
|
|
|
|
|
2009-03-15 04:54:41 +00:00
|
|
|
/* Search the list of FOR contexts for a variable */
|
2020-07-26 18:30:03 +00:00
|
|
|
static LPTSTR
|
|
|
|
FindForVar(TCHAR Var, BOOL *IsParam0)
|
2009-03-15 04:54:41 +00:00
|
|
|
{
|
2020-07-26 18:30:03 +00:00
|
|
|
PFOR_CONTEXT Ctx;
|
|
|
|
|
2013-06-30 13:11:20 +00:00
|
|
|
*IsParam0 = FALSE;
|
|
|
|
for (Ctx = fc; Ctx != NULL; Ctx = Ctx->prev)
|
|
|
|
{
|
|
|
|
if ((UINT)(Var - Ctx->firstvar) < Ctx->varcount)
|
|
|
|
return Ctx->values[Var - Ctx->firstvar];
|
|
|
|
}
|
|
|
|
return NULL;
|
2009-03-15 04:54:41 +00:00
|
|
|
}
|
|
|
|
|
2009-02-15 18:18:16 +00:00
|
|
|
BOOL
|
|
|
|
SubstituteForVars(TCHAR *Src, TCHAR *Dest)
|
|
|
|
{
|
2013-06-30 13:11:20 +00:00
|
|
|
TCHAR *DestEnd = &Dest[CMDLINE_LENGTH - 1];
|
|
|
|
while (*Src)
|
|
|
|
{
|
|
|
|
if (Src[0] == _T('%'))
|
|
|
|
{
|
|
|
|
BOOL Dummy;
|
|
|
|
LPTSTR End = &Src[2];
|
|
|
|
LPTSTR Value = NULL;
|
|
|
|
|
|
|
|
if (Src[1] == _T('~'))
|
|
|
|
Value = GetEnhancedVar(&End, FindForVar);
|
|
|
|
|
|
|
|
if (!Value)
|
|
|
|
Value = FindForVar(Src[1], &Dummy);
|
|
|
|
|
|
|
|
if (Value)
|
|
|
|
{
|
|
|
|
if (Dest + _tcslen(Value) > DestEnd)
|
|
|
|
return FALSE;
|
|
|
|
Dest = _stpcpy(Dest, Value);
|
|
|
|
Src = End;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* Not a variable; just copy the character */
|
|
|
|
if (Dest >= DestEnd)
|
|
|
|
return FALSE;
|
|
|
|
*Dest++ = *Src++;
|
|
|
|
}
|
|
|
|
*Dest = _T('\0');
|
|
|
|
return TRUE;
|
2009-02-15 18:18:16 +00:00
|
|
|
}
|
|
|
|
|
2009-02-14 01:13:17 +00:00
|
|
|
LPTSTR
|
|
|
|
DoDelayedExpansion(LPTSTR Line)
|
|
|
|
{
|
2013-06-30 13:11:20 +00:00
|
|
|
TCHAR Buf1[CMDLINE_LENGTH];
|
|
|
|
TCHAR Buf2[CMDLINE_LENGTH];
|
|
|
|
|
|
|
|
/* First, substitute FOR variables */
|
|
|
|
if (!SubstituteForVars(Line, Buf1))
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
if (!bDelayedExpansion || !_tcschr(Buf1, _T('!')))
|
|
|
|
return cmd_dup(Buf1);
|
|
|
|
|
|
|
|
/* FIXME: Delayed substitutions actually aren't quite the same as
|
|
|
|
* immediate substitutions. In particular, it's possible to escape
|
|
|
|
* the exclamation point using ^. */
|
|
|
|
if (!SubstituteVars(Buf1, Buf2, _T('!')))
|
|
|
|
return NULL;
|
|
|
|
return cmd_dup(Buf2);
|
2009-02-14 01:13:17 +00:00
|
|
|
}
|
|
|
|
|
2006-02-16 23:23:37 +00:00
|
|
|
|
|
|
|
/*
|
2020-07-26 18:30:03 +00:00
|
|
|
* Do the prompt/input/process loop.
|
2006-02-16 23:23:37 +00:00
|
|
|
*/
|
2008-08-22 14:37:11 +00:00
|
|
|
BOOL
|
2013-07-01 00:07:39 +00:00
|
|
|
ReadLine(TCHAR *commandline, BOOL bMore)
|
2006-02-16 23:23:37 +00:00
|
|
|
{
|
2013-06-30 13:11:20 +00:00
|
|
|
TCHAR readline[CMDLINE_LENGTH];
|
|
|
|
LPTSTR ip;
|
|
|
|
|
|
|
|
/* if no batch input then... */
|
|
|
|
if (bc == NULL)
|
|
|
|
{
|
|
|
|
if (bMore)
|
|
|
|
{
|
|
|
|
ConOutResPrintf(STRING_MORE);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* JPP 19980807 - if echo off, don't print prompt */
|
|
|
|
if (bEcho)
|
|
|
|
{
|
|
|
|
if (!bIgnoreEcho)
|
2013-07-02 23:07:15 +00:00
|
|
|
ConOutChar(_T('\n'));
|
2013-06-30 13:11:20 +00:00
|
|
|
PrintPrompt();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!ReadCommand(readline, CMDLINE_LENGTH - 1))
|
|
|
|
{
|
|
|
|
bExit = TRUE;
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
2018-04-28 22:57:16 +00:00
|
|
|
if (readline[0] == _T('\0'))
|
2013-07-02 23:07:15 +00:00
|
|
|
ConOutChar(_T('\n'));
|
2018-04-28 22:57:16 +00:00
|
|
|
|
|
|
|
if (CheckCtrlBreak(BREAK_INPUT))
|
2013-06-30 13:11:20 +00:00
|
|
|
return FALSE;
|
2018-04-27 15:15:38 +00:00
|
|
|
|
2018-04-27 18:01:17 +00:00
|
|
|
if (readline[0] == _T('\0'))
|
2018-04-27 15:15:38 +00:00
|
|
|
return FALSE;
|
|
|
|
|
2013-06-30 13:11:20 +00:00
|
|
|
ip = readline;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
ip = ReadBatchLine();
|
|
|
|
if (!ip)
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
return SubstituteVars(ip, commandline, _T('%'));
|
2008-08-22 14:37:11 +00:00
|
|
|
}
|
|
|
|
|
2020-07-29 23:44:43 +00:00
|
|
|
static INT
|
2017-09-30 14:04:24 +00:00
|
|
|
ProcessInput(VOID)
|
2008-08-22 14:37:11 +00:00
|
|
|
{
|
2020-07-29 23:44:43 +00:00
|
|
|
INT Ret = 0;
|
2013-06-30 13:11:20 +00:00
|
|
|
PARSED_COMMAND *Cmd;
|
2008-08-22 14:37:11 +00:00
|
|
|
|
2013-06-30 13:11:20 +00:00
|
|
|
while (!bCanExit || !bExit)
|
|
|
|
{
|
2018-04-28 22:53:49 +00:00
|
|
|
/* Reset the Ctrl-Break / Ctrl-C state */
|
|
|
|
bCtrlBreak = FALSE;
|
|
|
|
|
2013-06-30 13:11:20 +00:00
|
|
|
Cmd = ParseCommand(NULL);
|
|
|
|
if (!Cmd)
|
|
|
|
continue;
|
2006-02-16 23:23:37 +00:00
|
|
|
|
2020-07-29 23:44:43 +00:00
|
|
|
Ret = ExecuteCommand(Cmd);
|
2013-06-30 13:11:20 +00:00
|
|
|
FreeCommand(Cmd);
|
|
|
|
}
|
2020-07-29 23:44:43 +00:00
|
|
|
|
|
|
|
return Ret;
|
2006-02-16 23:23:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
2020-07-26 18:30:03 +00:00
|
|
|
* Control-break handler.
|
2006-02-16 23:23:37 +00:00
|
|
|
*/
|
2020-07-26 18:30:03 +00:00
|
|
|
static BOOL
|
|
|
|
WINAPI
|
|
|
|
BreakHandler(IN DWORD dwCtrlType)
|
2006-02-16 23:23:37 +00:00
|
|
|
{
|
2020-07-26 18:30:03 +00:00
|
|
|
DWORD dwWritten;
|
|
|
|
INPUT_RECORD rec;
|
2013-06-30 13:11:20 +00:00
|
|
|
|
|
|
|
if ((dwCtrlType != CTRL_C_EVENT) &&
|
|
|
|
(dwCtrlType != CTRL_BREAK_EVENT))
|
|
|
|
{
|
|
|
|
return FALSE;
|
|
|
|
}
|
2006-02-16 23:23:37 +00:00
|
|
|
|
2013-06-30 13:11:20 +00:00
|
|
|
if (!TryEnterCriticalSection(&ChildProcessRunningLock))
|
|
|
|
{
|
2018-04-27 15:12:23 +00:00
|
|
|
/* Child process is running and will have received the control event */
|
2013-06-30 13:11:20 +00:00
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
LeaveCriticalSection(&ChildProcessRunningLock);
|
|
|
|
}
|
2007-10-19 23:21:45 +00:00
|
|
|
|
2018-04-27 15:15:38 +00:00
|
|
|
bCtrlBreak = TRUE;
|
|
|
|
|
2006-02-16 23:23:37 +00:00
|
|
|
rec.EventType = KEY_EVENT;
|
|
|
|
rec.Event.KeyEvent.bKeyDown = TRUE;
|
|
|
|
rec.Event.KeyEvent.wRepeatCount = 1;
|
|
|
|
rec.Event.KeyEvent.wVirtualKeyCode = _T('C');
|
|
|
|
rec.Event.KeyEvent.wVirtualScanCode = _T('C') - 35;
|
|
|
|
rec.Event.KeyEvent.uChar.AsciiChar = _T('C');
|
|
|
|
rec.Event.KeyEvent.uChar.UnicodeChar = _T('C');
|
2007-10-19 23:21:45 +00:00
|
|
|
rec.Event.KeyEvent.dwControlKeyState = RIGHT_CTRL_PRESSED;
|
2006-02-16 23:23:37 +00:00
|
|
|
|
2017-10-01 16:30:39 +00:00
|
|
|
WriteConsoleInput(ConStreamGetOSHandle(StdIn),
|
2013-06-30 13:11:20 +00:00
|
|
|
&rec,
|
|
|
|
1,
|
|
|
|
&dwWritten);
|
2006-02-16 23:23:37 +00:00
|
|
|
|
2013-06-30 13:11:20 +00:00
|
|
|
/* FIXME: Handle batch files */
|
2006-02-16 23:23:37 +00:00
|
|
|
|
2018-04-28 22:53:49 +00:00
|
|
|
// ConOutPrintf(_T("^C"));
|
2006-02-16 23:23:37 +00:00
|
|
|
|
2013-06-30 13:11:20 +00:00
|
|
|
return TRUE;
|
2006-02-16 23:23:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-09-30 14:04:24 +00:00
|
|
|
VOID AddBreakHandler(VOID)
|
2006-02-16 23:23:37 +00:00
|
|
|
{
|
2017-09-30 14:04:24 +00:00
|
|
|
SetConsoleCtrlHandler(BreakHandler, TRUE);
|
2006-02-16 23:23:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-09-30 14:04:24 +00:00
|
|
|
VOID RemoveBreakHandler(VOID)
|
2006-02-16 23:23:37 +00:00
|
|
|
{
|
2017-09-30 14:04:24 +00:00
|
|
|
SetConsoleCtrlHandler(BreakHandler, FALSE);
|
2006-02-16 23:23:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
2020-07-26 18:30:03 +00:00
|
|
|
* Show commands and options that are available.
|
2006-02-16 23:23:37 +00:00
|
|
|
*/
|
|
|
|
#if 0
|
|
|
|
static VOID
|
2017-09-30 14:04:24 +00:00
|
|
|
ShowCommands(VOID)
|
2006-02-16 23:23:37 +00:00
|
|
|
{
|
2013-06-30 13:11:20 +00:00
|
|
|
/* print command list */
|
|
|
|
ConOutResPuts(STRING_CMD_HELP1);
|
|
|
|
PrintCommandList();
|
2006-02-16 23:23:37 +00:00
|
|
|
|
2013-06-30 13:11:20 +00:00
|
|
|
/* print feature list */
|
|
|
|
ConOutResPuts(STRING_CMD_HELP2);
|
2006-02-16 23:23:37 +00:00
|
|
|
|
|
|
|
#ifdef FEATURE_ALIASES
|
2013-06-30 13:11:20 +00:00
|
|
|
ConOutResPuts(STRING_CMD_HELP3);
|
2006-02-16 23:23:37 +00:00
|
|
|
#endif
|
|
|
|
#ifdef FEATURE_HISTORY
|
2013-06-30 13:11:20 +00:00
|
|
|
ConOutResPuts(STRING_CMD_HELP4);
|
2006-02-16 23:23:37 +00:00
|
|
|
#endif
|
|
|
|
#ifdef FEATURE_UNIX_FILENAME_COMPLETION
|
2013-06-30 13:11:20 +00:00
|
|
|
ConOutResPuts(STRING_CMD_HELP5);
|
2006-02-16 23:23:37 +00:00
|
|
|
#endif
|
|
|
|
#ifdef FEATURE_DIRECTORY_STACK
|
2013-06-30 13:11:20 +00:00
|
|
|
ConOutResPuts(STRING_CMD_HELP6);
|
2006-02-16 23:23:37 +00:00
|
|
|
#endif
|
|
|
|
#ifdef FEATURE_REDIRECTION
|
2013-06-30 13:11:20 +00:00
|
|
|
ConOutResPuts(STRING_CMD_HELP7);
|
2006-02-16 23:23:37 +00:00
|
|
|
#endif
|
2013-06-30 13:11:20 +00:00
|
|
|
ConOutChar(_T('\n'));
|
2006-02-16 23:23:37 +00:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2017-09-30 11:39:08 +00:00
|
|
|
|
|
|
|
static VOID
|
|
|
|
LoadRegistrySettings(HKEY hKeyRoot)
|
|
|
|
{
|
|
|
|
LONG lRet;
|
|
|
|
HKEY hKey;
|
2017-09-30 22:12:21 +00:00
|
|
|
DWORD dwType, len;
|
2017-09-30 11:39:08 +00:00
|
|
|
/*
|
|
|
|
* Buffer big enough to hold the string L"4294967295",
|
2019-12-10 12:16:26 +00:00
|
|
|
* corresponding to the literal 0xFFFFFFFF (MAXULONG) in decimal.
|
2017-09-30 11:39:08 +00:00
|
|
|
*/
|
|
|
|
DWORD Buffer[6];
|
|
|
|
|
|
|
|
lRet = RegOpenKeyEx(hKeyRoot,
|
|
|
|
_T("Software\\Microsoft\\Command Processor"),
|
|
|
|
0,
|
|
|
|
KEY_QUERY_VALUE,
|
|
|
|
&hKey);
|
|
|
|
if (lRet != ERROR_SUCCESS)
|
|
|
|
return;
|
|
|
|
|
|
|
|
#ifdef INCLUDE_CMD_COLOR
|
|
|
|
len = sizeof(Buffer);
|
|
|
|
lRet = RegQueryValueEx(hKey,
|
|
|
|
_T("DefaultColor"),
|
|
|
|
NULL,
|
|
|
|
&dwType,
|
|
|
|
(LPBYTE)&Buffer,
|
|
|
|
&len);
|
|
|
|
if (lRet == ERROR_SUCCESS)
|
|
|
|
{
|
|
|
|
/* Overwrite the default attributes */
|
|
|
|
if (dwType == REG_DWORD)
|
|
|
|
wDefColor = (WORD)*(PDWORD)Buffer;
|
|
|
|
else if (dwType == REG_SZ)
|
|
|
|
wDefColor = (WORD)_tcstol((PTSTR)Buffer, NULL, 0);
|
|
|
|
}
|
|
|
|
// else, use the default attributes retrieved before.
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#if 0
|
|
|
|
len = sizeof(Buffer);
|
|
|
|
lRet = RegQueryValueEx(hKey,
|
|
|
|
_T("DisableUNCCheck"),
|
|
|
|
NULL,
|
|
|
|
&dwType,
|
|
|
|
(LPBYTE)&Buffer,
|
|
|
|
&len);
|
|
|
|
if (lRet == ERROR_SUCCESS)
|
|
|
|
{
|
|
|
|
/* Overwrite the default setting */
|
|
|
|
if (dwType == REG_DWORD)
|
|
|
|
bDisableUNCCheck = !!*(PDWORD)Buffer;
|
|
|
|
else if (dwType == REG_SZ)
|
|
|
|
bDisableUNCCheck = (_ttol((PTSTR)Buffer) == 1);
|
|
|
|
}
|
|
|
|
// else, use the default setting set globally.
|
|
|
|
#endif
|
|
|
|
|
|
|
|
len = sizeof(Buffer);
|
|
|
|
lRet = RegQueryValueEx(hKey,
|
|
|
|
_T("DelayedExpansion"),
|
|
|
|
NULL,
|
|
|
|
&dwType,
|
|
|
|
(LPBYTE)&Buffer,
|
|
|
|
&len);
|
|
|
|
if (lRet == ERROR_SUCCESS)
|
|
|
|
{
|
|
|
|
/* Overwrite the default setting */
|
|
|
|
if (dwType == REG_DWORD)
|
|
|
|
bDelayedExpansion = !!*(PDWORD)Buffer;
|
|
|
|
else if (dwType == REG_SZ)
|
|
|
|
bDelayedExpansion = (_ttol((PTSTR)Buffer) == 1);
|
|
|
|
}
|
|
|
|
// else, use the default setting set globally.
|
|
|
|
|
|
|
|
len = sizeof(Buffer);
|
|
|
|
lRet = RegQueryValueEx(hKey,
|
|
|
|
_T("EnableExtensions"),
|
|
|
|
NULL,
|
|
|
|
&dwType,
|
|
|
|
(LPBYTE)&Buffer,
|
|
|
|
&len);
|
|
|
|
if (lRet == ERROR_SUCCESS)
|
|
|
|
{
|
|
|
|
/* Overwrite the default setting */
|
|
|
|
if (dwType == REG_DWORD)
|
|
|
|
bEnableExtensions = !!*(PDWORD)Buffer;
|
|
|
|
else if (dwType == REG_SZ)
|
|
|
|
bEnableExtensions = (_ttol((PTSTR)Buffer) == 1);
|
|
|
|
}
|
|
|
|
// else, use the default setting set globally.
|
|
|
|
|
|
|
|
len = sizeof(Buffer);
|
|
|
|
lRet = RegQueryValueEx(hKey,
|
|
|
|
_T("CompletionChar"),
|
|
|
|
NULL,
|
|
|
|
&dwType,
|
|
|
|
(LPBYTE)&Buffer,
|
|
|
|
&len);
|
|
|
|
if (lRet == ERROR_SUCCESS)
|
|
|
|
{
|
|
|
|
/* Overwrite the default setting */
|
|
|
|
if (dwType == REG_DWORD)
|
|
|
|
AutoCompletionChar = (TCHAR)*(PDWORD)Buffer;
|
|
|
|
else if (dwType == REG_SZ)
|
|
|
|
AutoCompletionChar = (TCHAR)_tcstol((PTSTR)Buffer, NULL, 0);
|
|
|
|
}
|
|
|
|
// else, use the default setting set globally.
|
|
|
|
|
|
|
|
/* Validity check */
|
|
|
|
if (IS_COMPLETION_DISABLED(AutoCompletionChar))
|
|
|
|
{
|
|
|
|
/* Disable autocompletion */
|
|
|
|
AutoCompletionChar = 0x20;
|
|
|
|
}
|
|
|
|
|
|
|
|
len = sizeof(Buffer);
|
|
|
|
lRet = RegQueryValueEx(hKey,
|
|
|
|
_T("PathCompletionChar"),
|
|
|
|
NULL,
|
|
|
|
&dwType,
|
|
|
|
(LPBYTE)&Buffer,
|
|
|
|
&len);
|
|
|
|
if (lRet == ERROR_SUCCESS)
|
|
|
|
{
|
|
|
|
/* Overwrite the default setting */
|
|
|
|
if (dwType == REG_DWORD)
|
|
|
|
PathCompletionChar = (TCHAR)*(PDWORD)Buffer;
|
|
|
|
else if (dwType == REG_SZ)
|
|
|
|
PathCompletionChar = (TCHAR)_tcstol((PTSTR)Buffer, NULL, 0);
|
|
|
|
}
|
|
|
|
// else, use the default setting set globally.
|
|
|
|
|
|
|
|
/* Validity check */
|
|
|
|
if (IS_COMPLETION_DISABLED(PathCompletionChar))
|
|
|
|
{
|
|
|
|
/* Disable autocompletion */
|
|
|
|
PathCompletionChar = 0x20;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Adjust completion chars */
|
|
|
|
if (PathCompletionChar >= 0x20 && AutoCompletionChar < 0x20)
|
|
|
|
PathCompletionChar = AutoCompletionChar;
|
|
|
|
else if (AutoCompletionChar >= 0x20 && PathCompletionChar < 0x20)
|
|
|
|
AutoCompletionChar = PathCompletionChar;
|
|
|
|
|
|
|
|
RegCloseKey(hKey);
|
|
|
|
}
|
|
|
|
|
2007-12-23 13:27:00 +00:00
|
|
|
static VOID
|
2017-09-30 11:39:08 +00:00
|
|
|
ExecuteAutoRunFile(HKEY hKeyRoot)
|
2007-12-23 13:27:00 +00:00
|
|
|
{
|
2017-09-30 22:12:21 +00:00
|
|
|
LONG lRet;
|
|
|
|
HKEY hKey;
|
|
|
|
DWORD dwType, len;
|
|
|
|
TCHAR AutoRun[2048];
|
|
|
|
|
|
|
|
lRet = RegOpenKeyEx(hKeyRoot,
|
|
|
|
_T("Software\\Microsoft\\Command Processor"),
|
|
|
|
0,
|
|
|
|
KEY_QUERY_VALUE,
|
|
|
|
&hKey);
|
|
|
|
if (lRet != ERROR_SUCCESS)
|
|
|
|
return;
|
|
|
|
|
|
|
|
len = sizeof(AutoRun);
|
|
|
|
lRet = RegQueryValueEx(hKey,
|
2007-12-23 13:27:00 +00:00
|
|
|
_T("AutoRun"),
|
2017-09-30 22:12:21 +00:00
|
|
|
NULL,
|
|
|
|
&dwType,
|
|
|
|
(LPBYTE)&AutoRun,
|
|
|
|
&len);
|
|
|
|
if ((lRet == ERROR_SUCCESS) && (dwType == REG_EXPAND_SZ || dwType == REG_SZ))
|
|
|
|
{
|
|
|
|
if (*AutoRun)
|
|
|
|
ParseCommandLine(AutoRun);
|
2007-12-23 13:27:00 +00:00
|
|
|
}
|
2017-09-30 22:12:21 +00:00
|
|
|
|
|
|
|
RegCloseKey(hKey);
|
2007-12-23 13:27:00 +00:00
|
|
|
}
|
|
|
|
|
2009-02-27 18:09:33 +00:00
|
|
|
/* Get the command that comes after a /C or /K switch */
|
|
|
|
static VOID
|
2020-07-26 18:30:03 +00:00
|
|
|
GetCmdLineCommand(
|
|
|
|
OUT LPTSTR commandline,
|
|
|
|
IN LPCTSTR ptr,
|
|
|
|
IN BOOL AlwaysStrip)
|
2009-02-27 18:09:33 +00:00
|
|
|
{
|
2020-07-26 18:30:03 +00:00
|
|
|
TCHAR* LastQuote;
|
2013-06-30 13:11:20 +00:00
|
|
|
|
|
|
|
while (_istspace(*ptr))
|
2020-07-26 18:30:03 +00:00
|
|
|
++ptr;
|
2013-06-30 13:11:20 +00:00
|
|
|
|
|
|
|
/* Remove leading quote, find final quote */
|
|
|
|
if (*ptr == _T('"') &&
|
|
|
|
(LastQuote = _tcsrchr(++ptr, _T('"'))) != NULL)
|
|
|
|
{
|
2020-07-26 18:30:03 +00:00
|
|
|
const TCHAR* Space;
|
2013-06-30 13:11:20 +00:00
|
|
|
/* Under certain circumstances, all quotes are preserved.
|
|
|
|
* CMD /? documents these conditions as follows:
|
|
|
|
* 1. No /S switch
|
|
|
|
* 2. Exactly two quotes
|
|
|
|
* 3. No "special characters" between the quotes
|
|
|
|
* (CMD /? says &<>()@^| but parentheses did not
|
|
|
|
* trigger this rule when I tested them.)
|
|
|
|
* 4. Whitespace exists between the quotes
|
|
|
|
* 5. Enclosed string is an executable filename
|
|
|
|
*/
|
|
|
|
*LastQuote = _T('\0');
|
2020-07-26 18:30:03 +00:00
|
|
|
for (Space = ptr + 1; Space < LastQuote; ++Space)
|
2013-06-30 13:11:20 +00:00
|
|
|
{
|
|
|
|
if (_istspace(*Space)) /* Rule 4 */
|
|
|
|
{
|
|
|
|
if (!AlwaysStrip && /* Rule 1 */
|
|
|
|
!_tcspbrk(ptr, _T("\"&<>@^|")) && /* Rules 2, 3 */
|
|
|
|
SearchForExecutable(ptr, commandline)) /* Rule 5 */
|
|
|
|
{
|
|
|
|
/* All conditions met: preserve both the quotes */
|
|
|
|
*LastQuote = _T('"');
|
|
|
|
_tcscpy(commandline, ptr - 1);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* The conditions were not met: remove both the
|
|
|
|
* leading quote and the last quote */
|
|
|
|
_tcscpy(commandline, ptr);
|
|
|
|
_tcscpy(&commandline[LastQuote - ptr], LastQuote + 1);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* No quotes; just copy */
|
|
|
|
_tcscpy(commandline, ptr);
|
2009-02-27 18:09:33 +00:00
|
|
|
}
|
|
|
|
|
2017-09-30 11:39:08 +00:00
|
|
|
|
2006-02-16 23:23:37 +00:00
|
|
|
/*
|
2020-07-29 23:44:43 +00:00
|
|
|
* Set up global initializations and process parameters.
|
|
|
|
* Return a pointer to the command line if present.
|
2006-02-16 23:23:37 +00:00
|
|
|
*/
|
2020-07-29 23:44:43 +00:00
|
|
|
static LPCTSTR
|
2017-09-30 11:39:08 +00:00
|
|
|
Initialize(VOID)
|
2006-02-16 23:23:37 +00:00
|
|
|
{
|
2013-06-30 13:11:20 +00:00
|
|
|
HMODULE NtDllModule;
|
2017-10-01 16:30:39 +00:00
|
|
|
HANDLE hIn, hOut;
|
2020-07-26 18:30:03 +00:00
|
|
|
LPTSTR ptr, cmdLine;
|
|
|
|
TCHAR option = 0;
|
2013-06-30 13:11:20 +00:00
|
|
|
BOOL AutoRun = TRUE;
|
2020-07-26 18:30:03 +00:00
|
|
|
TCHAR ModuleName[MAX_PATH + 1];
|
2013-06-30 13:11:20 +00:00
|
|
|
|
2014-12-31 21:22:49 +00:00
|
|
|
/* Get version information */
|
|
|
|
InitOSVersion();
|
2013-06-30 13:11:20 +00:00
|
|
|
|
|
|
|
/* Some people like to run ReactOS cmd.exe on Win98, it helps in the
|
|
|
|
* build process. So don't link implicitly against ntdll.dll, load it
|
|
|
|
* dynamically instead */
|
|
|
|
NtDllModule = GetModuleHandle(TEXT("ntdll.dll"));
|
|
|
|
if (NtDllModule != NULL)
|
|
|
|
{
|
|
|
|
NtQueryInformationProcessPtr = (NtQueryInformationProcessProc)GetProcAddress(NtDllModule, "NtQueryInformationProcess");
|
|
|
|
NtReadVirtualMemoryPtr = (NtReadVirtualMemoryProc)GetProcAddress(NtDllModule, "NtReadVirtualMemory");
|
|
|
|
}
|
|
|
|
|
2017-09-30 11:39:08 +00:00
|
|
|
/* Load the registry settings */
|
|
|
|
LoadRegistrySettings(HKEY_LOCAL_MACHINE);
|
|
|
|
LoadRegistrySettings(HKEY_CURRENT_USER);
|
|
|
|
|
|
|
|
/* Initialize our locale */
|
2013-07-01 00:07:39 +00:00
|
|
|
InitLocale();
|
2013-06-30 13:11:20 +00:00
|
|
|
|
2013-07-04 01:05:14 +00:00
|
|
|
/* Initialize prompt support */
|
|
|
|
InitPrompt();
|
2006-02-16 23:23:37 +00:00
|
|
|
|
2019-11-15 02:51:58 +00:00
|
|
|
#ifdef FEATURE_DIRECTORY_STACK
|
2017-09-30 11:39:08 +00:00
|
|
|
/* Initialize directory stack */
|
2013-07-04 01:05:14 +00:00
|
|
|
InitDirectoryStack();
|
2009-07-08 17:28:21 +00:00
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef FEATURE_HISTORY
|
2017-09-30 11:39:08 +00:00
|
|
|
/* Initialize history */
|
2013-06-30 13:11:20 +00:00
|
|
|
InitHistory();
|
2009-07-08 17:28:21 +00:00
|
|
|
#endif
|
|
|
|
|
2013-06-30 13:11:20 +00:00
|
|
|
/* Set COMSPEC environment variable */
|
2017-09-30 11:39:08 +00:00
|
|
|
if (GetModuleFileName(NULL, ModuleName, ARRAYSIZE(ModuleName)) != 0)
|
2013-06-30 13:11:20 +00:00
|
|
|
{
|
2020-07-26 18:30:03 +00:00
|
|
|
ModuleName[MAX_PATH] = _T('\0');
|
2013-06-30 13:11:20 +00:00
|
|
|
SetEnvironmentVariable (_T("COMSPEC"), ModuleName);
|
|
|
|
}
|
|
|
|
|
2013-07-01 00:07:39 +00:00
|
|
|
/* Add ctrl break handler */
|
|
|
|
AddBreakHandler();
|
2013-06-30 13:11:20 +00:00
|
|
|
|
2013-07-01 00:07:39 +00:00
|
|
|
/* Set our default console mode */
|
2017-10-01 16:30:39 +00:00
|
|
|
hOut = ConStreamGetOSHandle(StdOut);
|
|
|
|
hIn = ConStreamGetOSHandle(StdIn);
|
2013-07-01 00:07:39 +00:00
|
|
|
SetConsoleMode(hOut, 0); // Reinitialize the console output mode
|
|
|
|
SetConsoleMode(hOut, ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT);
|
|
|
|
SetConsoleMode(hIn , ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT);
|
2013-06-30 13:11:20 +00:00
|
|
|
|
|
|
|
cmdLine = GetCommandLine();
|
|
|
|
TRACE ("[command args: %s]\n", debugstr_aw(cmdLine));
|
|
|
|
|
2020-07-26 18:30:03 +00:00
|
|
|
for (ptr = cmdLine; *ptr; ++ptr)
|
2013-06-30 13:11:20 +00:00
|
|
|
{
|
|
|
|
if (*ptr == _T('/'))
|
|
|
|
{
|
|
|
|
option = _totupper(ptr[1]);
|
|
|
|
if (option == _T('?'))
|
|
|
|
{
|
2020-07-26 18:30:03 +00:00
|
|
|
ConOutResPaging(TRUE, STRING_CMD_HELP8);
|
2013-06-30 13:11:20 +00:00
|
|
|
nErrorLevel = 1;
|
|
|
|
bExit = TRUE;
|
2020-07-29 23:44:43 +00:00
|
|
|
return NULL;
|
2013-06-30 13:11:20 +00:00
|
|
|
}
|
|
|
|
else if (option == _T('P'))
|
|
|
|
{
|
2020-07-26 18:30:03 +00:00
|
|
|
if (!IsExistingFile(_T("\\autoexec.bat")))
|
2013-06-30 13:11:20 +00:00
|
|
|
{
|
2006-02-16 23:23:37 +00:00
|
|
|
#ifdef INCLUDE_CMD_DATE
|
2020-07-26 18:30:03 +00:00
|
|
|
cmd_date(_T(""));
|
2006-02-16 23:23:37 +00:00
|
|
|
#endif
|
|
|
|
#ifdef INCLUDE_CMD_TIME
|
2020-07-26 18:30:03 +00:00
|
|
|
cmd_time(_T(""));
|
2006-02-16 23:23:37 +00:00
|
|
|
#endif
|
2013-06-30 13:11:20 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-07-26 18:30:03 +00:00
|
|
|
ParseCommandLine(_T("\\autoexec.bat"));
|
2013-06-30 13:11:20 +00:00
|
|
|
}
|
|
|
|
bCanExit = FALSE;
|
|
|
|
}
|
|
|
|
else if (option == _T('A'))
|
|
|
|
{
|
2017-10-01 16:30:39 +00:00
|
|
|
OutputStreamMode = AnsiText;
|
2013-06-30 13:11:20 +00:00
|
|
|
}
|
|
|
|
else if (option == _T('C') || option == _T('K') || option == _T('R'))
|
|
|
|
{
|
|
|
|
/* Remainder of command line is a command to be run */
|
2018-06-03 00:15:44 +00:00
|
|
|
fSingleCommand = ((option == _T('K')) << 1) | 1;
|
2013-06-30 13:11:20 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
else if (option == _T('D'))
|
|
|
|
{
|
|
|
|
AutoRun = FALSE;
|
|
|
|
}
|
|
|
|
else if (option == _T('Q'))
|
|
|
|
{
|
|
|
|
bDisableBatchEcho = TRUE;
|
|
|
|
}
|
|
|
|
else if (option == _T('S'))
|
|
|
|
{
|
2020-07-29 23:44:43 +00:00
|
|
|
bAlwaysStrip = TRUE;
|
2013-06-30 13:11:20 +00:00
|
|
|
}
|
2006-02-16 23:23:37 +00:00
|
|
|
#ifdef INCLUDE_CMD_COLOR
|
2013-06-30 13:11:20 +00:00
|
|
|
else if (!_tcsnicmp(ptr, _T("/T:"), 3))
|
|
|
|
{
|
2017-09-30 11:39:08 +00:00
|
|
|
/* Process /T (color) argument; overwrite any previous settings */
|
2013-06-30 13:11:20 +00:00
|
|
|
wDefColor = (WORD)_tcstoul(&ptr[3], &ptr, 16);
|
|
|
|
}
|
2006-02-16 23:23:37 +00:00
|
|
|
#endif
|
2013-06-30 13:11:20 +00:00
|
|
|
else if (option == _T('U'))
|
|
|
|
{
|
2017-10-01 16:30:39 +00:00
|
|
|
OutputStreamMode = UTF16Text;
|
2013-06-30 13:11:20 +00:00
|
|
|
}
|
|
|
|
else if (option == _T('V'))
|
|
|
|
{
|
2017-09-30 11:39:08 +00:00
|
|
|
// FIXME: Check validity of the parameter given to V !
|
2013-06-30 13:11:20 +00:00
|
|
|
bDelayedExpansion = _tcsnicmp(&ptr[2], _T(":OFF"), 4);
|
|
|
|
}
|
2017-09-30 11:39:08 +00:00
|
|
|
else if (option == _T('E'))
|
|
|
|
{
|
|
|
|
// FIXME: Check validity of the parameter given to E !
|
|
|
|
bEnableExtensions = _tcsnicmp(&ptr[2], _T(":OFF"), 4);
|
|
|
|
}
|
|
|
|
else if (option == _T('X'))
|
|
|
|
{
|
|
|
|
/* '/X' is identical to '/E:ON' */
|
|
|
|
bEnableExtensions = TRUE;
|
|
|
|
}
|
|
|
|
else if (option == _T('Y'))
|
|
|
|
{
|
|
|
|
/* '/Y' is identical to '/E:OFF' */
|
|
|
|
bEnableExtensions = FALSE;
|
|
|
|
}
|
2013-06-30 13:11:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-30 11:39:08 +00:00
|
|
|
#ifdef INCLUDE_CMD_COLOR
|
|
|
|
if (wDefColor == 0)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* If we still do not have the console colour attribute set,
|
|
|
|
* retrieve the default one.
|
|
|
|
*/
|
|
|
|
ConGetDefaultAttributes(&wDefColor);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (wDefColor != 0)
|
2017-10-01 16:30:39 +00:00
|
|
|
ConSetScreenColor(ConStreamGetOSHandle(StdOut), wDefColor, TRUE);
|
2017-09-30 11:39:08 +00:00
|
|
|
#endif
|
|
|
|
|
2017-10-01 16:30:39 +00:00
|
|
|
/* Reset the output Standard Streams translation modes and codepage caches */
|
|
|
|
// ConStreamSetMode(StdIn , OutputStreamMode, InputCodePage );
|
|
|
|
ConStreamSetMode(StdOut, OutputStreamMode, OutputCodePage);
|
|
|
|
ConStreamSetMode(StdErr, OutputStreamMode, OutputCodePage);
|
|
|
|
|
2013-06-30 13:11:20 +00:00
|
|
|
if (!*ptr)
|
|
|
|
{
|
|
|
|
/* If neither /C or /K was given, display a simple version string */
|
2013-07-04 01:05:14 +00:00
|
|
|
ConOutChar(_T('\n'));
|
2013-06-30 13:11:20 +00:00
|
|
|
ConOutResPrintf(STRING_REACTOS_VERSION,
|
2016-05-31 22:36:48 +00:00
|
|
|
_T(KERNEL_VERSION_STR),
|
2016-05-29 17:21:44 +00:00
|
|
|
_T(KERNEL_VERSION_BUILD_STR));
|
2013-06-30 13:11:20 +00:00
|
|
|
ConOutPuts(_T("(C) Copyright 1998-") _T(COPYRIGHT_YEAR) _T(" ReactOS Team.\n"));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (AutoRun)
|
|
|
|
{
|
|
|
|
ExecuteAutoRunFile(HKEY_LOCAL_MACHINE);
|
|
|
|
ExecuteAutoRunFile(HKEY_CURRENT_USER);
|
|
|
|
}
|
|
|
|
|
2020-07-29 23:44:43 +00:00
|
|
|
/* Returns the rest of the command line */
|
|
|
|
return ptr;
|
2006-02-16 23:23:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-09-30 11:39:08 +00:00
|
|
|
static VOID Cleanup(VOID)
|
2006-02-16 23:23:37 +00:00
|
|
|
{
|
2017-09-30 14:04:24 +00:00
|
|
|
/* Run cmdexit.bat */
|
|
|
|
if (IsExistingFile(_T("cmdexit.bat")))
|
2013-06-30 13:11:20 +00:00
|
|
|
{
|
|
|
|
ConErrResPuts(STRING_CMD_ERROR5);
|
2017-09-30 14:04:24 +00:00
|
|
|
ParseCommandLine(_T("cmdexit.bat"));
|
2013-06-30 13:11:20 +00:00
|
|
|
}
|
2017-09-30 14:04:24 +00:00
|
|
|
else if (IsExistingFile(_T("\\cmdexit.bat")))
|
2013-06-30 13:11:20 +00:00
|
|
|
{
|
2017-09-30 14:04:24 +00:00
|
|
|
ConErrResPuts(STRING_CMD_ERROR5);
|
|
|
|
ParseCommandLine(_T("\\cmdexit.bat"));
|
2013-06-30 13:11:20 +00:00
|
|
|
}
|
2006-02-16 23:23:37 +00:00
|
|
|
|
2016-11-05 14:55:55 +00:00
|
|
|
#ifdef FEATURE_DIRECTORY_STACK
|
2017-09-30 14:04:24 +00:00
|
|
|
/* Destroy directory stack */
|
|
|
|
DestroyDirectoryStack();
|
2006-02-16 23:23:37 +00:00
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef FEATURE_HISTORY
|
2013-06-30 13:11:20 +00:00
|
|
|
CleanHistory();
|
2006-02-16 23:23:37 +00:00
|
|
|
#endif
|
|
|
|
|
2017-09-30 14:04:24 +00:00
|
|
|
/* Free GetEnvVar's buffer */
|
2013-06-30 13:11:20 +00:00
|
|
|
GetEnvVar(NULL);
|
2006-02-16 23:23:37 +00:00
|
|
|
|
2013-07-01 00:07:39 +00:00
|
|
|
/* Remove ctrl break handler */
|
|
|
|
RemoveBreakHandler();
|
|
|
|
|
|
|
|
/* Restore the default console mode */
|
2017-10-01 16:30:39 +00:00
|
|
|
SetConsoleMode(ConStreamGetOSHandle(StdIn),
|
2013-07-01 00:07:39 +00:00
|
|
|
ENABLE_PROCESSED_INPUT | ENABLE_LINE_INPUT | ENABLE_ECHO_INPUT);
|
2017-10-01 16:30:39 +00:00
|
|
|
SetConsoleMode(ConStreamGetOSHandle(StdOut),
|
2013-07-01 00:07:39 +00:00
|
|
|
ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT);
|
|
|
|
|
2013-06-30 13:11:20 +00:00
|
|
|
DeleteCriticalSection(&ChildProcessRunningLock);
|
2006-02-16 23:23:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* main function
|
|
|
|
*/
|
2013-05-29 10:56:25 +00:00
|
|
|
int _tmain(int argc, const TCHAR *argv[])
|
2006-02-16 23:23:37 +00:00
|
|
|
{
|
2020-07-29 23:44:43 +00:00
|
|
|
INT nExitCode;
|
|
|
|
LPCTSTR pCmdLine;
|
2013-06-30 13:11:20 +00:00
|
|
|
TCHAR startPath[MAX_PATH];
|
|
|
|
|
|
|
|
InitializeCriticalSection(&ChildProcessRunningLock);
|
|
|
|
lpOriginalEnvironment = DuplicateEnvironment();
|
|
|
|
|
2017-09-30 11:39:08 +00:00
|
|
|
GetCurrentDirectory(ARRAYSIZE(startPath), startPath);
|
2013-06-30 13:11:20 +00:00
|
|
|
_tchdir(startPath);
|
|
|
|
|
|
|
|
SetFileApisToOEM();
|
2017-10-01 16:30:39 +00:00
|
|
|
InputCodePage = GetConsoleCP();
|
2013-06-30 13:11:20 +00:00
|
|
|
OutputCodePage = GetConsoleOutputCP();
|
2017-09-30 11:39:08 +00:00
|
|
|
|
2017-10-01 16:30:39 +00:00
|
|
|
/* Initialize the Console Standard Streams */
|
|
|
|
ConStreamInit(StdIn , GetStdHandle(STD_INPUT_HANDLE) , /*OutputStreamMode*/ AnsiText, InputCodePage);
|
|
|
|
ConStreamInit(StdOut, GetStdHandle(STD_OUTPUT_HANDLE), OutputStreamMode, OutputCodePage);
|
|
|
|
ConStreamInit(StdErr, GetStdHandle(STD_ERROR_HANDLE) , OutputStreamMode, OutputCodePage);
|
|
|
|
|
2013-06-30 13:11:20 +00:00
|
|
|
CMD_ModuleHandle = GetModuleHandle(NULL);
|
|
|
|
|
2020-07-29 23:44:43 +00:00
|
|
|
/*
|
|
|
|
* Perform general initialization, parse switches on command-line.
|
|
|
|
* Initialize the exit code with the errorlevel as Initialize() can set it.
|
|
|
|
*/
|
|
|
|
pCmdLine = Initialize();
|
|
|
|
nExitCode = nErrorLevel;
|
2013-06-30 13:11:20 +00:00
|
|
|
|
2020-07-29 23:44:43 +00:00
|
|
|
if (pCmdLine && *pCmdLine)
|
|
|
|
{
|
|
|
|
TCHAR commandline[CMDLINE_LENGTH];
|
|
|
|
|
|
|
|
/* Do the /C or /K command */
|
|
|
|
GetCmdLineCommand(commandline, &pCmdLine[2], bAlwaysStrip);
|
|
|
|
nExitCode = ParseCommandLine(commandline);
|
|
|
|
if (fSingleCommand == 1)
|
|
|
|
{
|
|
|
|
// nErrorLevel = nExitCode;
|
|
|
|
bExit = TRUE;
|
|
|
|
}
|
|
|
|
fSingleCommand = 0;
|
|
|
|
}
|
|
|
|
if (!bExit)
|
|
|
|
{
|
|
|
|
/* Call prompt routine */
|
|
|
|
nExitCode = ProcessInput();
|
|
|
|
}
|
2013-06-30 13:11:20 +00:00
|
|
|
|
2017-09-30 11:39:08 +00:00
|
|
|
/* Do the cleanup */
|
2013-06-30 13:11:20 +00:00
|
|
|
Cleanup();
|
|
|
|
cmd_free(lpOriginalEnvironment);
|
|
|
|
|
2020-07-29 23:44:43 +00:00
|
|
|
cmd_exit(nExitCode);
|
|
|
|
return nExitCode;
|
2006-02-16 23:23:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* EOF */
|