📄 cmd.c
字号:
/*
* 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>)
* Fixed carrage return output to better match MSDOS with echo
* on or off. (marked with "JPP 19980708")
*
* 07-Dec-1998 (Eric Kohl)
* First ReactOS release.
* Extended length of commandline buffers to 512.
*
* 13-Dec-1998 (Eric Kohl)
* Added COMSPEC environment variable.
* Added "/t" support (color) on cmd command line.
*
* 07-Jan-1999 (Eric Kohl)
* Added help text ("cmd /?").
*
* 25-Jan-1999 (Eric Kohl)
* Unicode and redirection safe!
* Fixed redirections and piping.
* Piping is based on temporary files, but basic support
* for anonymous pipes already exists.
*
* 27-Jan-1999 (Eric Kohl)
* Replaced spawnl() by CreateProcess().
*
* 22-Oct-1999 (Eric Kohl)
* Added break handler.
*
* 15-Dec-1999 (Eric Kohl)
* Fixed current directory
*
* 28-Dec-1999 (Eric Kohl)
* Restore window title after program/batch execution
*
* 03-Feb-2001 (Eric Kohl)
* 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.
* Add proper memmory alloc ProcessInput, the error
* handling for memmory handling need to be improve
*/
#include <precomp.h>
#ifndef NT_SUCCESS
#define NT_SUCCESS(StatCode) ((NTSTATUS)(StatCode) >= 0)
#endif
typedef NTSTATUS (WINAPI *NtQueryInformationProcessProc)(HANDLE, PROCESSINFOCLASS,
PVOID, ULONG, PULONG);
typedef NTSTATUS (WINAPI *NtReadVirtualMemoryProc)(HANDLE, PVOID, PVOID, ULONG, PULONG);
BOOL bExit = FALSE; /* indicates EXIT was typed */
BOOL bCanExit = TRUE; /* indicates if this shell is exitable */
BOOL bCtrlBreak = FALSE; /* Ctrl-Break or Ctrl-C hit */
BOOL bIgnoreEcho = FALSE; /* Ignore 'newline' before 'cls' */
INT nErrorLevel = 0; /* Errorlevel of last launched external program */
BOOL bChildProcessRunning = FALSE;
DWORD dwChildProcessId = 0;
OSVERSIONINFO osvi;
HANDLE hIn;
HANDLE hOut;
HANDLE hConsole;
HANDLE CMD_ModuleHandle;
HMODULE NtDllModule;
static NtQueryInformationProcessProc NtQueryInformationProcessPtr = NULL;
static NtReadVirtualMemoryProc NtReadVirtualMemoryPtr = NULL;
#ifdef INCLUDE_CMD_COLOR
WORD wColor; /* current color */
WORD wDefColor; /* default color */
#endif
/*
* convert
*
* insert commas into a number
*/
INT
ConvertULargeInteger (ULARGE_INTEGER num, LPTSTR des, INT len, BOOL bPutSeperator)
{
TCHAR temp[32];
INT c = 0;
INT n = 0;
if (len <= 1)
return 0;
if (num.QuadPart == 0)
{
des[0] = _T('0');
des[1] = _T('\0');
n = 1;
}
else
{
temp[31] = 0;
while (num.QuadPart > 0)
{
if ((((c + 1) % (nNumberGroups + 1)) == 0) && (bPutSeperator))
temp[30 - c++] = cThousandSeparator;
temp[30 - c++] = (TCHAR)(num.QuadPart % 10) + _T('0');
num.QuadPart /= 10;
}
if (c>len)
c=len;
for (n = 0; n <= c; n++)
des[n] = temp[31 - c + n];
}
return n;
}
/*
* is character a delimeter when used on first word?
*
*/
static BOOL IsDelimiter (TCHAR c)
{
return (c == _T('/') || c == _T('=') || c == _T('\0') || _istspace (c));
}
/*
* Is a process a console process?
*/
static BOOL IsConsoleProcess(HANDLE Process)
{
NTSTATUS Status;
PROCESS_BASIC_INFORMATION Info;
PEB ProcessPeb;
ULONG BytesRead;
if (NULL == NtQueryInformationProcessPtr || NULL == NtReadVirtualMemoryPtr)
{
return TRUE;
}
Status = NtQueryInformationProcessPtr (
Process, ProcessBasicInformation,
&Info, sizeof(PROCESS_BASIC_INFORMATION), NULL);
if (! NT_SUCCESS(Status))
{
#ifdef _DEBUG
DebugPrintf (_T("NtQueryInformationProcess failed with status %08x\n"), Status);
#endif
return TRUE;
}
Status = NtReadVirtualMemoryPtr (
Process, Info.PebBaseAddress, &ProcessPeb,
sizeof(PEB), &BytesRead);
if (! NT_SUCCESS(Status) || sizeof(PEB) != BytesRead)
{
#ifdef _DEBUG
DebugPrintf (_T("Couldn't read virt mem status %08x bytes read %lu\n"), Status, BytesRead);
#endif
return TRUE;
}
return IMAGE_SUBSYSTEM_WINDOWS_CUI == ProcessPeb.ImageSubSystem;
}
#ifdef _UNICODE
#define SHELLEXECUTETEXT "ShellExecuteW"
#else
#define SHELLEXECUTETEXT "ShellExecuteA"
#endif
typedef HINSTANCE (WINAPI *MYEX)(
HWND hwnd,
LPCTSTR lpOperation,
LPCTSTR lpFile,
LPCTSTR lpParameters,
LPCTSTR lpDirectory,
INT nShowCmd
);
static BOOL RunFile(LPTSTR filename)
{
HMODULE hShell32;
MYEX hShExt;
HINSTANCE ret;
#ifdef _DEBUG
DebugPrintf (_T("RunFile(%s)\n"), filename);
#endif
hShell32 = LoadLibrary(_T("SHELL32.DLL"));
if (!hShell32)
{
#ifdef _DEBUG
DebugPrintf (_T("RunFile: couldn't load SHELL32.DLL!\n"));
#endif
return FALSE;
}
hShExt = (MYEX)(FARPROC)GetProcAddress(hShell32, SHELLEXECUTETEXT);
if (!hShExt)
{
#ifdef _DEBUG
DebugPrintf (_T("RunFile: couldn't find ShellExecuteA/W in SHELL32.DLL!\n"));
#endif
FreeLibrary(hShell32);
return FALSE;
}
#ifdef _DEBUG
DebugPrintf (_T("RunFile: ShellExecuteA/W is at %x\n"), hShExt);
#endif
ret = (hShExt)(NULL, _T("open"), filename, NULL, NULL, SW_SHOWNORMAL);
#ifdef _DEBUG
DebugPrintf (_T("RunFile: ShellExecuteA/W returned %d\n"), (DWORD)ret);
#endif
FreeLibrary(hShell32);
return (((DWORD)ret) > 32);
}
/*
* This command (in first) was not found in the command table
*
* Full - whole command line
* First - first word on command line
* Rest - rest of command line
*/
static VOID
Execute (LPTSTR Full, LPTSTR First, LPTSTR Rest)
{
TCHAR *szFullName=NULL;
TCHAR *first = NULL;
TCHAR *rest = NULL;
TCHAR *full = NULL;
TCHAR *dot = NULL;
TCHAR szWindowTitle[MAX_PATH];
DWORD dwExitCode = 0;
#ifdef _DEBUG
DebugPrintf (_T("Execute: \'%s\' \'%s\'\n"), first, rest);
#endif
/* we need biger buffer that First, Rest, Full are already
need rewrite some code to use cmd_realloc when it need instead
of add 512bytes extra */
first = cmd_alloc ( (_tcslen(First) + 512) * sizeof(TCHAR));
if (first == NULL)
{
error_out_of_memory();
nErrorLevel = 1;
return ;
}
rest = cmd_alloc ( (_tcslen(Rest) + 512) * sizeof(TCHAR));
if (rest == NULL)
{
cmd_free (first);
error_out_of_memory();
nErrorLevel = 1;
return ;
}
full = cmd_alloc ( (_tcslen(Full) + 512) * sizeof(TCHAR));
if (full == NULL)
{
cmd_free (first);
cmd_free (rest);
error_out_of_memory();
nErrorLevel = 1;
return ;
}
szFullName = cmd_alloc ( (_tcslen(Full) + 512) * sizeof(TCHAR));
if (full == NULL)
{
cmd_free (first);
cmd_free (rest);
cmd_free (full);
error_out_of_memory();
nErrorLevel = 1;
return ;
}
/* Though it was already parsed once, we have a different set of rules
for parsing before we pass to CreateProccess */
if(!_tcschr(Full,_T('\"')))
{
_tcscpy(first,First);
_tcscpy(rest,Rest);
_tcscpy(full,Full);
}
else
{
UINT i = 0;
BOOL bInside = FALSE;
rest[0] = _T('\0');
full[0] = _T('\0');
first[0] = _T('\0');
_tcscpy(first,Full);
/* find the end of the command and start of the args */
for(i = 0; i < _tcslen(first); i++)
{
if(!_tcsncmp(&first[i], _T("\""), 1))
bInside = !bInside;
if(!_tcsncmp(&first[i], _T(" "), 1) && !bInside)
{
_tcscpy(rest,&first[i]);
first[i] = _T('\0');
break;
}
}
i = 0;
/* remove any slashes */
while(i < _tcslen(first))
{
if(first[i] == _T('\"'))
memmove(&first[i],&first[i + 1], _tcslen(&first[i]) * sizeof(TCHAR));
else
i++;
}
/* Drop quotes around it just in case there is a space */
_tcscpy(full,_T("\""));
_tcscat(full,first);
_tcscat(full,_T("\" "));
_tcscat(full,rest);
}
/* check for a drive change */
if ((_istalpha (first[0])) && (!_tcscmp (first + 1, _T(":"))))
{
BOOL working = TRUE;
if (!SetCurrentDirectory(first))
/* Guess they changed disc or something, handle that gracefully and get to root */
{
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);
cmd_free (first);
cmd_free (rest);
cmd_free (full);
cmd_free (szFullName);
nErrorLevel = 1;
return;
}
/* get the PATH environment variable and parse it */
/* search the PATH environment variable for the binary */
if (!SearchForExecutable (first, szFullName))
{
error_bad_command ();
cmd_free (first);
cmd_free (rest);
cmd_free (full);
cmd_free (szFullName);
nErrorLevel = 1;
return;
}
GetConsoleTitle (szWindowTitle, MAX_PATH);
/* check if this is a .BAT or .CMD file */
dot = _tcsrchr (szFullName, _T('.'));
if (dot && (!_tcsicmp (dot, _T(".bat")) || !_tcsicmp (dot, _T(".cmd"))))
{
#ifdef _DEBUG
DebugPrintf (_T("[BATCH: %s %s]\n"), szFullName, rest);
#endif
Batch (szFullName, first, rest);
}
else
{
/* exec the program */
PROCESS_INFORMATION prci;
STARTUPINFO stui;
#ifdef _DEBUG
DebugPrintf (_T("[EXEC: %s %s]\n"), full, rest);
#endif
/* build command line for CreateProcess() */
/* fill startup info */
memset (&stui, 0, sizeof (STARTUPINFO));
stui.cb = sizeof (STARTUPINFO);
stui.dwFlags = STARTF_USESHOWWINDOW;
stui.wShowWindow = SW_SHOWDEFAULT;
// return console to standard mode
SetConsoleMode (GetStdHandle(STD_INPUT_HANDLE),
ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT | ENABLE_ECHO_INPUT );
if (CreateProcess (szFullName,
full,
NULL,
NULL,
TRUE,
0, /* CREATE_NEW_PROCESS_GROUP */
NULL,
NULL,
&stui,
&prci))
{
if (IsConsoleProcess(prci.hProcess))
{
/* FIXME: Protect this with critical section */
bChildProcessRunning = TRUE;
dwChildProcessId = prci.dwProcessId;
WaitForSingleObject (prci.hProcess, INFINITE);
/* FIXME: Protect this with critical section */
bChildProcessRunning = FALSE;
GetExitCodeProcess (prci.hProcess, &dwExitCode);
nErrorLevel = (INT)dwExitCode;
}
else
{
nErrorLevel = 0;
}
CloseHandle (prci.hThread);
CloseHandle (prci.hProcess);
}
else
{
#ifdef _DEBUG
DebugPrintf (_T("[ShellExecute: %s]\n"), full);
#endif
// See if we can run this with ShellExecute() ie myfile.xls
if (!RunFile(full))
{
#ifdef _DEBUG
DebugPrintf (_T("[ShellExecute failed!: %s]\n"), full);
#endif
error_bad_command ();
nErrorLevel = 1;
}
else
{
nErrorLevel = 0;
}
}
// restore console mode
SetConsoleMode (
GetStdHandle( STD_INPUT_HANDLE ),
ENABLE_PROCESSED_INPUT );
}
/* Get code page if it has been change */
InputCodePage= GetConsoleCP();
OutputCodePage = GetConsoleOutputCP();
SetConsoleTitle (szWindowTitle);
cmd_free(first);
cmd_free(rest);
cmd_free(full);
cmd_free (szFullName);
}
/*
* 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
* execute to run it as an external program.
*
* line - the command line of the program to run
*
*/
VOID
DoCommand (LPTSTR line)
{
TCHAR *com = NULL; /* the first word in the command */
TCHAR *cp = NULL;
LPTSTR cstart;
LPTSTR rest; /* pointer to the rest of the command line */
INT cl;
LPCOMMAND cmdptr;
#ifdef _DEBUG
DebugPrintf (_T("DoCommand: (\'%s\')\n"), line);
#endif /* DEBUG */
com = cmd_alloc( (_tcslen(line) +512)*sizeof(TCHAR) );
if (com == NULL)
{
error_out_of_memory();
return;
}
cp = com;
/* Skip over initial white space */
while (_istspace (*line))
line++;
rest = line;
cstart = rest;
/* Anything to do ? */
if (*rest)
{
if (*rest == _T('"'))
{
/* treat quoted words specially */
rest++;
while(*rest != _T('\0') && *rest != _T('"'))
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -