2006-02-16 23:23:37 +00:00
|
|
|
/*
|
|
|
|
* INTERNAL.C - command.com internal commands.
|
|
|
|
*
|
|
|
|
*
|
|
|
|
* History:
|
|
|
|
*
|
|
|
|
* 17/08/94 (Tim Norman)
|
|
|
|
* started.
|
|
|
|
*
|
|
|
|
* 08/08/95 (Matt Rains)
|
|
|
|
* i have cleaned up the source code. changes now bring this source into
|
|
|
|
* guidelines for recommended programming practice.
|
|
|
|
*
|
|
|
|
* cd()
|
|
|
|
* started.
|
|
|
|
*
|
|
|
|
* dir()
|
|
|
|
* i have added support for file attributes to the DIR() function. the
|
|
|
|
* routine adds "d" (directory) and "r" (read only) output. files with the
|
|
|
|
* system attribute have the filename converted to lowercase. files with
|
|
|
|
* the hidden attribute are not displayed.
|
|
|
|
*
|
|
|
|
* i have added support for directorys. now if the directory attribute is
|
|
|
|
* detected the file size if replaced with the string "<dir>".
|
|
|
|
*
|
|
|
|
* ver()
|
|
|
|
* started.
|
|
|
|
*
|
|
|
|
* md()
|
|
|
|
* started.
|
|
|
|
*
|
|
|
|
* rd()
|
|
|
|
* started.
|
|
|
|
*
|
|
|
|
* del()
|
|
|
|
* started.
|
|
|
|
*
|
|
|
|
* does not support wildcard selection.
|
|
|
|
*
|
|
|
|
* todo: add delete directory support.
|
|
|
|
* add recursive directory delete support.
|
|
|
|
*
|
|
|
|
* ren()
|
|
|
|
* started.
|
|
|
|
*
|
|
|
|
* does not support wildcard selection.
|
|
|
|
*
|
|
|
|
* todo: add rename directory support.
|
|
|
|
*
|
|
|
|
* a general structure has been used for the cd, rd and md commands. this
|
|
|
|
* will be better in the long run. it is too hard to maintain such diverse
|
|
|
|
* functions when you are involved in a group project like this.
|
|
|
|
*
|
|
|
|
* 12/14/95 (Tim Norman)
|
|
|
|
* fixed DIR so that it will stick \*.* if a directory is specified and
|
|
|
|
* that it will stick on .* if a file with no extension is specified or
|
|
|
|
* *.* if it ends in a \
|
|
|
|
*
|
|
|
|
* 1/6/96 (Tim Norman)
|
|
|
|
* added an isatty call to DIR so it won't prompt for keypresses unless
|
|
|
|
* stdin and stdout are the console.
|
|
|
|
*
|
|
|
|
* changed parameters to be mutually consistent to make calling the
|
|
|
|
* functions easier
|
|
|
|
*
|
|
|
|
* rem()
|
|
|
|
* started.
|
|
|
|
*
|
|
|
|
* doskey()
|
|
|
|
* started.
|
|
|
|
*
|
|
|
|
* 01/22/96 (Oliver Mueller)
|
|
|
|
* error messages are now handled by perror.
|
|
|
|
*
|
|
|
|
* 02/05/96 (Tim Norman)
|
|
|
|
* converted all functions to accept first/rest parameters
|
|
|
|
*
|
|
|
|
* 07/26/96 (Tim Norman)
|
|
|
|
* changed return values to int instead of void
|
|
|
|
*
|
|
|
|
* path() started.
|
|
|
|
*
|
|
|
|
* 12/23/96 (Aaron Kaufman)
|
|
|
|
* rewrote dir() to mimic MS-DOS's dir
|
|
|
|
*
|
|
|
|
* 01/28/97 (Tim Norman)
|
|
|
|
* cleaned up Aaron's DIR code
|
|
|
|
*
|
|
|
|
* 06/13/97 (Tim Norman)
|
|
|
|
* moved DIR code to dir.c
|
|
|
|
* re-implemented Aaron's DIR code
|
|
|
|
*
|
|
|
|
* 06/14/97 (Steffan Kaiser)
|
|
|
|
* ctrl-break handling
|
|
|
|
* bug fixes
|
|
|
|
*
|
|
|
|
* 27-Jul-1998 (John P Price <linux-guru@gcfl.net>)
|
|
|
|
* added config.h include
|
|
|
|
*
|
2007-05-05 11:32:25 +00:00
|
|
|
* 03-Dec-1998 (Eric Kohl)
|
2006-02-16 23:23:37 +00:00
|
|
|
* Replaced DOS calls by Win32 calls.
|
|
|
|
*
|
2007-05-05 11:32:25 +00:00
|
|
|
* 08-Dec-1998 (Eric Kohl)
|
2006-02-16 23:23:37 +00:00
|
|
|
* Added help texts ("/?").
|
|
|
|
*
|
2007-05-05 11:32:25 +00:00
|
|
|
* 18-Dec-1998 (Eric Kohl)
|
2006-02-16 23:23:37 +00:00
|
|
|
* Added support for quoted arguments (cd "program files").
|
|
|
|
*
|
2007-05-05 11:32:25 +00:00
|
|
|
* 07-Jan-1999 (Eric Kohl)
|
2006-02-16 23:23:37 +00:00
|
|
|
* Clean up.
|
|
|
|
*
|
2007-05-05 11:32:25 +00:00
|
|
|
* 26-Jan-1999 (Eric Kohl)
|
2006-02-16 23:23:37 +00:00
|
|
|
* Replaced remaining CRT io functions by Win32 io functions.
|
|
|
|
* Unicode safe!
|
|
|
|
*
|
2007-05-05 11:32:25 +00:00
|
|
|
* 30-Jan-1999 (Eric Kohl)
|
2006-02-16 23:23:37 +00:00
|
|
|
* Added "cd -" feature. Changes to the previous directory.
|
|
|
|
*
|
2007-05-05 11:32:25 +00:00
|
|
|
* 15-Mar-1999 (Eric Kohl)
|
2006-02-16 23:23:37 +00:00
|
|
|
* Fixed bug in "cd -" feature. If the previous directory was a root
|
|
|
|
* directory, it was ignored.
|
|
|
|
*
|
|
|
|
* 23-Feb-2001 (Carl Nettelblad <cnettel@hem.passagen.se>)
|
|
|
|
* Improved chdir/cd command.
|
|
|
|
*
|
|
|
|
* 02-Apr-2004 (Magnus Olsen <magnus@greatlord.com>)
|
|
|
|
* Remove all hard code string so they can be
|
2013-06-30 12:27:18 +00:00
|
|
|
* translate to other langues.
|
2006-02-16 23:23:37 +00:00
|
|
|
*
|
2007-07-18 19:41:25 +00:00
|
|
|
* 19-Jul-2005 (Brandon Turner <turnerb7@msu.edu>)
|
2006-02-16 23:23:37 +00:00
|
|
|
* Rewrite the CD, it working as Windows 2000 CMD
|
2007-10-19 23:21:45 +00:00
|
|
|
*
|
2007-07-18 19:41:25 +00:00
|
|
|
* 19-Jul-2005 (Magnus Olsen <magnus@greatlord.com>)
|
2007-10-19 23:21:45 +00:00
|
|
|
* Add SetRootPath and GetRootPath
|
2007-07-18 19:41:25 +00:00
|
|
|
*
|
|
|
|
* 14-Jul-2007 (Pierre Schweitzer <heis_spiter@hotmail.com>)
|
2007-10-19 23:21:45 +00:00
|
|
|
* Added commands help display to help command (ex. : "help cmd")
|
2006-02-16 23:23:37 +00:00
|
|
|
*/
|
|
|
|
|
2013-01-24 23:00:42 +00:00
|
|
|
#include "precomp.h"
|
2006-02-16 23:23:37 +00:00
|
|
|
|
|
|
|
#ifdef INCLUDE_CMD_CHDIR
|
|
|
|
|
2020-07-26 18:32:30 +00:00
|
|
|
/*
|
|
|
|
* Helper function for getting the current path from drive
|
|
|
|
* without changing the drive. Return code: 0 = ok, 1 = fail.
|
|
|
|
* 'InPath' can have any size; if the two first letters are
|
|
|
|
* not a drive with ':' it will get the current path on
|
|
|
|
* the current drive exactly as GetCurrentDirectory() does.
|
|
|
|
*/
|
|
|
|
INT
|
|
|
|
GetRootPath(
|
2020-07-18 21:31:50 +00:00
|
|
|
IN LPCTSTR InPath,
|
2020-07-26 18:32:30 +00:00
|
|
|
OUT LPTSTR OutPath,
|
|
|
|
IN INT size)
|
2006-02-16 23:23:37 +00:00
|
|
|
{
|
2013-06-30 12:27:18 +00:00
|
|
|
if (InPath[0] && InPath[1] == _T(':'))
|
|
|
|
{
|
2020-07-26 18:32:30 +00:00
|
|
|
INT t = 0;
|
2013-06-30 12:27:18 +00:00
|
|
|
|
|
|
|
if ((InPath[0] >= _T('0')) && (InPath[0] <= _T('9')))
|
|
|
|
{
|
2020-07-26 18:32:30 +00:00
|
|
|
t = (InPath[0] - _T('0')) + 28;
|
2013-06-30 12:27:18 +00:00
|
|
|
}
|
2020-07-18 21:31:50 +00:00
|
|
|
else if ((InPath[0] >= _T('a')) && (InPath[0] <= _T('z')))
|
2013-06-30 12:27:18 +00:00
|
|
|
{
|
2020-07-26 18:32:30 +00:00
|
|
|
t = (InPath[0] - _T('a')) + 1;
|
2013-06-30 12:27:18 +00:00
|
|
|
}
|
2020-07-18 21:31:50 +00:00
|
|
|
else if ((InPath[0] >= _T('A')) && (InPath[0] <= _T('Z')))
|
2013-06-30 12:27:18 +00:00
|
|
|
{
|
2020-07-26 18:32:30 +00:00
|
|
|
t = (InPath[0] - _T('A')) + 1;
|
2013-06-30 12:27:18 +00:00
|
|
|
}
|
|
|
|
|
2020-07-26 18:32:30 +00:00
|
|
|
return (_tgetdcwd(t, OutPath, size) == NULL);
|
2013-06-30 12:27:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Get current directory */
|
2020-07-26 18:32:30 +00:00
|
|
|
return !GetCurrentDirectory(size, OutPath);
|
2006-02-16 23:23:37 +00:00
|
|
|
}
|
2007-10-19 23:21:45 +00:00
|
|
|
|
2006-02-16 23:23:37 +00:00
|
|
|
|
2009-04-05 01:50:24 +00:00
|
|
|
BOOL SetRootPath(TCHAR *oldpath, TCHAR *InPath)
|
2006-02-16 23:23:37 +00:00
|
|
|
{
|
2020-07-12 14:23:01 +00:00
|
|
|
DWORD dwLastError;
|
2013-06-30 12:27:18 +00:00
|
|
|
TCHAR OutPath[MAX_PATH];
|
|
|
|
TCHAR OutPathTemp[MAX_PATH];
|
2007-10-19 23:21:45 +00:00
|
|
|
|
2019-11-15 02:51:58 +00:00
|
|
|
StripQuotes(InPath);
|
|
|
|
|
2015-04-03 13:27:21 +00:00
|
|
|
/* Retrieve the full path name from the (possibly relative) InPath */
|
2020-07-26 18:32:30 +00:00
|
|
|
if (GetFullPathName(InPath, ARRAYSIZE(OutPathTemp), OutPathTemp, NULL) == 0)
|
2020-07-12 14:23:01 +00:00
|
|
|
{
|
|
|
|
dwLastError = GetLastError();
|
2015-04-03 13:27:21 +00:00
|
|
|
goto Fail;
|
2020-07-12 14:23:01 +00:00
|
|
|
}
|
2013-06-30 12:27:18 +00:00
|
|
|
|
2020-07-12 18:45:59 +00:00
|
|
|
if (bEnableExtensions)
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* Convert the full path to its correct case, and
|
|
|
|
* resolve any wilcard present as well in the path
|
|
|
|
* and retrieve the first result.
|
|
|
|
* Example: c:\windows\SYSTEM32 => C:\WINDOWS\System32
|
|
|
|
* Example: C:\WINDOWS\S* => C:\WINDOWS\System,
|
|
|
|
* or C:\WINDOWS\System32, depending on the user's OS.
|
|
|
|
*/
|
|
|
|
GetPathCase(OutPathTemp, OutPath);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
_tcscpy(OutPath, OutPathTemp);
|
|
|
|
}
|
2013-06-30 12:27:18 +00:00
|
|
|
|
2020-07-26 18:32:30 +00:00
|
|
|
/* Use _tchdir(), since unlike SetCurrentDirectory() it updates
|
2015-04-03 13:27:21 +00:00
|
|
|
* the current-directory-on-drive environment variables. */
|
|
|
|
if (_tchdir(OutPath) != 0)
|
2020-07-12 14:23:01 +00:00
|
|
|
{
|
|
|
|
dwLastError = GetLastError();
|
|
|
|
if (dwLastError == ERROR_FILE_NOT_FOUND)
|
|
|
|
dwLastError = ERROR_PATH_NOT_FOUND;
|
2015-04-03 13:27:21 +00:00
|
|
|
goto Fail;
|
2020-07-12 14:23:01 +00:00
|
|
|
}
|
2015-04-03 13:27:21 +00:00
|
|
|
|
2020-07-26 18:32:30 +00:00
|
|
|
/* Keep the original drive in ordinary CD/CHDIR (without /D switch) */
|
2015-04-03 13:27:21 +00:00
|
|
|
if (oldpath != NULL && _tcsncicmp(OutPath, oldpath, 2) != 0)
|
|
|
|
SetCurrentDirectory(oldpath);
|
2013-06-30 12:27:18 +00:00
|
|
|
|
|
|
|
return TRUE;
|
2015-04-03 13:27:21 +00:00
|
|
|
|
|
|
|
Fail:
|
2020-07-12 14:23:01 +00:00
|
|
|
ConErrFormatMessage(dwLastError);
|
2015-04-03 13:27:21 +00:00
|
|
|
nErrorLevel = 1;
|
|
|
|
return FALSE;
|
2007-10-19 23:21:45 +00:00
|
|
|
}
|
2006-02-16 23:23:37 +00:00
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* CD / CHDIR
|
|
|
|
*/
|
2020-07-26 18:32:30 +00:00
|
|
|
INT cmd_chdir(LPTSTR param)
|
2006-02-16 23:23:37 +00:00
|
|
|
{
|
2013-06-30 12:27:18 +00:00
|
|
|
BOOL bChangeDrive = FALSE;
|
2015-04-03 13:27:21 +00:00
|
|
|
LPTSTR tmp;
|
|
|
|
TCHAR szCurrent[MAX_PATH];
|
2006-02-16 23:23:37 +00:00
|
|
|
|
2013-06-30 12:27:18 +00:00
|
|
|
/* Filter out special cases first */
|
2006-02-16 23:23:37 +00:00
|
|
|
|
2020-07-26 18:32:30 +00:00
|
|
|
/* Print help */
|
2013-06-30 12:27:18 +00:00
|
|
|
if (!_tcsncmp(param, _T("/?"), 2))
|
|
|
|
{
|
2020-07-26 18:32:30 +00:00
|
|
|
ConOutResPaging(TRUE, STRING_CD_HELP);
|
2013-06-30 12:27:18 +00:00
|
|
|
return 0;
|
|
|
|
}
|
2006-02-16 23:23:37 +00:00
|
|
|
|
2020-07-12 18:45:59 +00:00
|
|
|
//
|
|
|
|
// FIXME: Use the split() tokenizer if bEnableExtensions == FALSE,
|
|
|
|
// so as to cut the parameter at the first separator (space, ',', ';'):
|
|
|
|
// - When bEnableExtensions == FALSE, doing
|
|
|
|
// CD system32;winsxs
|
|
|
|
// will go into system32, (but: CD "system32;winsxs" will fail as below), while
|
|
|
|
// - When bEnableExtensions == TRUE, it will fail because the "system32;winsxs"
|
|
|
|
// directory does not exist.
|
|
|
|
//
|
|
|
|
|
|
|
|
/* Remove extra quotes */
|
2013-06-30 12:27:18 +00:00
|
|
|
StripQuotes(param);
|
2020-07-12 18:45:59 +00:00
|
|
|
|
|
|
|
if (bEnableExtensions)
|
|
|
|
{
|
|
|
|
/* Strip trailing whitespace */
|
|
|
|
tmp = param + _tcslen(param) - 1;
|
|
|
|
while (tmp > param && _istspace(*tmp))
|
|
|
|
--tmp;
|
|
|
|
*(tmp + 1) = _T('\0');
|
|
|
|
}
|
2013-06-30 12:27:18 +00:00
|
|
|
|
2020-07-26 18:32:30 +00:00
|
|
|
/* Reset the error level */
|
2013-06-30 12:27:18 +00:00
|
|
|
nErrorLevel = 0;
|
|
|
|
|
2020-07-26 18:32:30 +00:00
|
|
|
/* Print the current directory on a disk */
|
2013-06-30 12:27:18 +00:00
|
|
|
if (_tcslen(param) == 2 && param[1] == _T(':'))
|
|
|
|
{
|
2020-07-26 18:32:30 +00:00
|
|
|
if (GetRootPath(param, szCurrent, ARRAYSIZE(szCurrent)))
|
2013-06-30 12:27:18 +00:00
|
|
|
{
|
|
|
|
error_invalid_drive();
|
|
|
|
return 1;
|
|
|
|
}
|
2015-04-02 23:47:00 +00:00
|
|
|
ConOutPrintf(_T("%s\n"), szCurrent);
|
2013-06-30 12:27:18 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2020-07-26 18:32:30 +00:00
|
|
|
/* Get the current directory */
|
|
|
|
GetCurrentDirectory(ARRAYSIZE(szCurrent), szCurrent);
|
2013-06-30 12:27:18 +00:00
|
|
|
if (param[0] == _T('\0'))
|
|
|
|
{
|
2015-04-02 23:47:00 +00:00
|
|
|
ConOutPrintf(_T("%s\n"), szCurrent);
|
2013-06-30 12:27:18 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2020-07-26 18:32:30 +00:00
|
|
|
/* If the input string is prefixed with the /D switch, change the drive */
|
2013-06-30 12:27:18 +00:00
|
|
|
if (!_tcsncicmp(param, _T("/D"), 2))
|
|
|
|
{
|
|
|
|
bChangeDrive = TRUE;
|
|
|
|
param += 2;
|
|
|
|
while (_istspace(*param))
|
2020-07-26 18:32:30 +00:00
|
|
|
++param;
|
2013-06-30 12:27:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!SetRootPath(bChangeDrive ? NULL : szCurrent, param))
|
2015-04-03 13:27:21 +00:00
|
|
|
{
|
|
|
|
nErrorLevel = 1;
|
2013-06-30 12:27:18 +00:00
|
|
|
return 1;
|
2015-04-03 13:27:21 +00:00
|
|
|
}
|
2013-06-30 12:27:18 +00:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif
|
2006-02-16 23:23:37 +00:00
|
|
|
|
|
|
|
#ifdef INCLUDE_CMD_MKDIR
|
|
|
|
|
2016-11-05 14:55:55 +00:00
|
|
|
/* Helper function for mkdir to make directories in a path.
|
2017-11-18 20:32:10 +00:00
|
|
|
Don't use the api to decrease dependence on libs */
|
2007-10-19 23:21:45 +00:00
|
|
|
BOOL
|
2006-02-16 23:23:37 +00:00
|
|
|
MakeFullPath(TCHAR * DirPath)
|
|
|
|
{
|
|
|
|
TCHAR path[MAX_PATH];
|
|
|
|
TCHAR *p = DirPath;
|
2012-03-28 09:50:04 +00:00
|
|
|
INT_PTR n;
|
2006-02-16 23:23:37 +00:00
|
|
|
|
2008-12-22 23:50:53 +00:00
|
|
|
if (CreateDirectory(DirPath, NULL))
|
|
|
|
return TRUE;
|
|
|
|
else if (GetLastError() != ERROR_PATH_NOT_FOUND)
|
|
|
|
return FALSE;
|
|
|
|
|
|
|
|
/* got ERROR_PATH_NOT_FOUND, so try building it up one component at a time */
|
2007-10-19 23:21:45 +00:00
|
|
|
if (p[0] && p[1] == _T(':'))
|
2006-02-16 23:23:37 +00:00
|
|
|
p += 2;
|
2007-10-19 23:21:45 +00:00
|
|
|
while (*p == _T('\\'))
|
2006-02-16 23:23:37 +00:00
|
|
|
p++; /* skip drive root */
|
2008-07-26 17:15:37 +00:00
|
|
|
do
|
2006-02-16 23:23:37 +00:00
|
|
|
{
|
2013-06-30 12:27:18 +00:00
|
|
|
p = _tcschr(p, _T('\\'));
|
|
|
|
n = p ? p++ - DirPath : _tcslen(DirPath);
|
|
|
|
_tcsncpy(path, DirPath, n);
|
|
|
|
path[n] = _T('\0');
|
2013-06-30 16:10:54 +00:00
|
|
|
if ( !CreateDirectory(path, NULL) &&
|
2013-06-30 12:27:18 +00:00
|
|
|
(GetLastError() != ERROR_ALREADY_EXISTS))
|
|
|
|
{
|
|
|
|
return FALSE;
|
|
|
|
}
|
2008-07-26 17:15:37 +00:00
|
|
|
} while (p != NULL);
|
2006-02-16 23:23:37 +00:00
|
|
|
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* MD / MKDIR
|
|
|
|
*/
|
2008-08-25 23:22:03 +00:00
|
|
|
INT cmd_mkdir (LPTSTR param)
|
2006-02-16 23:23:37 +00:00
|
|
|
{
|
2013-06-30 12:27:18 +00:00
|
|
|
LPTSTR *p;
|
|
|
|
INT argc, i;
|
2020-07-01 00:39:25 +00:00
|
|
|
DWORD dwLastError;
|
|
|
|
|
2013-06-30 12:27:18 +00:00
|
|
|
if (!_tcsncmp (param, _T("/?"), 2))
|
|
|
|
{
|
|
|
|
ConOutResPaging(TRUE,STRING_MKDIR_HELP);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
p = split (param, &argc, FALSE, FALSE);
|
|
|
|
if (argc == 0)
|
|
|
|
{
|
|
|
|
ConErrResPuts(STRING_ERROR_REQ_PARAM_MISSING);
|
|
|
|
freep(p);
|
2015-04-03 13:27:21 +00:00
|
|
|
nErrorLevel = 1;
|
2013-06-30 12:27:18 +00:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
nErrorLevel = 0;
|
|
|
|
for (i = 0; i < argc; i++)
|
|
|
|
{
|
|
|
|
if (!MakeFullPath(p[i]))
|
|
|
|
{
|
2020-07-01 00:39:25 +00:00
|
|
|
dwLastError = GetLastError();
|
|
|
|
switch (dwLastError)
|
2013-06-30 12:27:18 +00:00
|
|
|
{
|
2020-07-01 00:39:25 +00:00
|
|
|
case ERROR_PATH_NOT_FOUND:
|
2013-06-30 12:27:18 +00:00
|
|
|
ConErrResPuts(STRING_MD_ERROR2);
|
2020-07-01 00:39:25 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case ERROR_FILE_EXISTS:
|
|
|
|
case ERROR_ALREADY_EXISTS:
|
|
|
|
ConErrResPrintf(STRING_MD_ERROR, p[i]);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
ErrorMessage(GetLastError(), NULL);
|
2013-06-30 12:27:18 +00:00
|
|
|
}
|
|
|
|
nErrorLevel = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
freep (p);
|
|
|
|
return nErrorLevel;
|
2006-02-16 23:23:37 +00:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#ifdef INCLUDE_CMD_RMDIR
|
|
|
|
/*
|
|
|
|
* RD / RMDIR
|
|
|
|
*/
|
[CMD] RMDIR: Improve some aspects of the /S option.
- First, the option and the APIs called by it can work directly on
paths relative to the current directory. So there is no need to
call GetFullPathName(), with the risk of going over MAX_PATH if the
current path is quite long (or nested) but the RMDIR is called on a
(short-length) relative sub-directory.
- Append a path-separator (backslash), only if the specified directory
does not have one already, and, that it does not specify a current
directory via the "drive-root" method, e.g. "C:" without any trailing
backslash.
- In case there are errors during deletion of sub-directories or
sub-files, print the error but continue deleting the other sub-dirs
or files.
- Monitor the Ctrl-C breaker as well, and stop deleting if it has been
triggered.
- When removing file/directory read-only attribute, just remove this
attribute, but keep the other ones.
- When deleting the directory, first try to do it directly; if it fails
with access denied, check whether it was read-only, and if so, remove
this attribute and retry deletion, otherwise fails.
- When recursively deleting a drive root directory, ultimately resolve
the dir pattern and check whether it's indeed a drive root, e.g.
"C:\\", and if so, just return success. Indeed, calling
RemoveDirectory() on such drive roots will return ERROR_ACCESS_DENIED
otherwise, but we want to succeed even if, of course, we won't
actually "delete" the drive root.
2020-07-11 21:15:20 +00:00
|
|
|
BOOL DeleteFolder(LPTSTR Directory)
|
2006-02-16 23:23:37 +00:00
|
|
|
{
|
[CMD] RMDIR: Improve some aspects of the /S option.
- First, the option and the APIs called by it can work directly on
paths relative to the current directory. So there is no need to
call GetFullPathName(), with the risk of going over MAX_PATH if the
current path is quite long (or nested) but the RMDIR is called on a
(short-length) relative sub-directory.
- Append a path-separator (backslash), only if the specified directory
does not have one already, and, that it does not specify a current
directory via the "drive-root" method, e.g. "C:" without any trailing
backslash.
- In case there are errors during deletion of sub-directories or
sub-files, print the error but continue deleting the other sub-dirs
or files.
- Monitor the Ctrl-C breaker as well, and stop deleting if it has been
triggered.
- When removing file/directory read-only attribute, just remove this
attribute, but keep the other ones.
- When deleting the directory, first try to do it directly; if it fails
with access denied, check whether it was read-only, and if so, remove
this attribute and retry deletion, otherwise fails.
- When recursively deleting a drive root directory, ultimately resolve
the dir pattern and check whether it's indeed a drive root, e.g.
"C:\\", and if so, just return success. Indeed, calling
RemoveDirectory() on such drive roots will return ERROR_ACCESS_DENIED
otherwise, but we want to succeed even if, of course, we won't
actually "delete" the drive root.
2020-07-11 21:15:20 +00:00
|
|
|
LPTSTR pFileName;
|
2013-06-30 12:27:18 +00:00
|
|
|
HANDLE hFile;
|
2006-02-16 23:23:37 +00:00
|
|
|
WIN32_FIND_DATA f;
|
[CMD] RMDIR: Improve some aspects of the /S option.
- First, the option and the APIs called by it can work directly on
paths relative to the current directory. So there is no need to
call GetFullPathName(), with the risk of going over MAX_PATH if the
current path is quite long (or nested) but the RMDIR is called on a
(short-length) relative sub-directory.
- Append a path-separator (backslash), only if the specified directory
does not have one already, and, that it does not specify a current
directory via the "drive-root" method, e.g. "C:" without any trailing
backslash.
- In case there are errors during deletion of sub-directories or
sub-files, print the error but continue deleting the other sub-dirs
or files.
- Monitor the Ctrl-C breaker as well, and stop deleting if it has been
triggered.
- When removing file/directory read-only attribute, just remove this
attribute, but keep the other ones.
- When deleting the directory, first try to do it directly; if it fails
with access denied, check whether it was read-only, and if so, remove
this attribute and retry deletion, otherwise fails.
- When recursively deleting a drive root directory, ultimately resolve
the dir pattern and check whether it's indeed a drive root, e.g.
"C:\\", and if so, just return success. Indeed, calling
RemoveDirectory() on such drive roots will return ERROR_ACCESS_DENIED
otherwise, but we want to succeed even if, of course, we won't
actually "delete" the drive root.
2020-07-11 21:15:20 +00:00
|
|
|
DWORD dwAttribs;
|
|
|
|
TCHAR szFullPath[MAX_PATH];
|
2020-07-26 18:32:30 +00:00
|
|
|
|
[CMD] RMDIR: Improve some aspects of the /S option.
- First, the option and the APIs called by it can work directly on
paths relative to the current directory. So there is no need to
call GetFullPathName(), with the risk of going over MAX_PATH if the
current path is quite long (or nested) but the RMDIR is called on a
(short-length) relative sub-directory.
- Append a path-separator (backslash), only if the specified directory
does not have one already, and, that it does not specify a current
directory via the "drive-root" method, e.g. "C:" without any trailing
backslash.
- In case there are errors during deletion of sub-directories or
sub-files, print the error but continue deleting the other sub-dirs
or files.
- Monitor the Ctrl-C breaker as well, and stop deleting if it has been
triggered.
- When removing file/directory read-only attribute, just remove this
attribute, but keep the other ones.
- When deleting the directory, first try to do it directly; if it fails
with access denied, check whether it was read-only, and if so, remove
this attribute and retry deletion, otherwise fails.
- When recursively deleting a drive root directory, ultimately resolve
the dir pattern and check whether it's indeed a drive root, e.g.
"C:\\", and if so, just return success. Indeed, calling
RemoveDirectory() on such drive roots will return ERROR_ACCESS_DENIED
otherwise, but we want to succeed even if, of course, we won't
actually "delete" the drive root.
2020-07-11 21:15:20 +00:00
|
|
|
_tcscpy(szFullPath, Directory);
|
|
|
|
pFileName = &szFullPath[_tcslen(szFullPath)];
|
|
|
|
/*
|
|
|
|
* Append a path separator if we don't have one already, and if this a drive root
|
|
|
|
* path is not specified (paths like "C:" mean the current directory on drive C:).
|
|
|
|
*/
|
|
|
|
if (*szFullPath && *(pFileName - 1) != _T(':') && *(pFileName - 1) != _T('\\'))
|
|
|
|
*pFileName++ = _T('\\');
|
|
|
|
_tcscpy(pFileName, _T("*"));
|
|
|
|
|
|
|
|
hFile = FindFirstFile(szFullPath, &f);
|
2006-02-16 23:23:37 +00:00
|
|
|
if (hFile != INVALID_HANDLE_VALUE)
|
|
|
|
{
|
|
|
|
do
|
|
|
|
{
|
[CMD] RMDIR: Improve some aspects of the /S option.
- First, the option and the APIs called by it can work directly on
paths relative to the current directory. So there is no need to
call GetFullPathName(), with the risk of going over MAX_PATH if the
current path is quite long (or nested) but the RMDIR is called on a
(short-length) relative sub-directory.
- Append a path-separator (backslash), only if the specified directory
does not have one already, and, that it does not specify a current
directory via the "drive-root" method, e.g. "C:" without any trailing
backslash.
- In case there are errors during deletion of sub-directories or
sub-files, print the error but continue deleting the other sub-dirs
or files.
- Monitor the Ctrl-C breaker as well, and stop deleting if it has been
triggered.
- When removing file/directory read-only attribute, just remove this
attribute, but keep the other ones.
- When deleting the directory, first try to do it directly; if it fails
with access denied, check whether it was read-only, and if so, remove
this attribute and retry deletion, otherwise fails.
- When recursively deleting a drive root directory, ultimately resolve
the dir pattern and check whether it's indeed a drive root, e.g.
"C:\\", and if so, just return success. Indeed, calling
RemoveDirectory() on such drive roots will return ERROR_ACCESS_DENIED
otherwise, but we want to succeed even if, of course, we won't
actually "delete" the drive root.
2020-07-11 21:15:20 +00:00
|
|
|
/* Check Breaker */
|
|
|
|
if (bCtrlBreak)
|
|
|
|
break;
|
|
|
|
|
2013-06-30 12:27:18 +00:00
|
|
|
if (!_tcscmp(f.cFileName, _T(".")) ||
|
2006-02-16 23:23:37 +00:00
|
|
|
!_tcscmp(f.cFileName, _T("..")))
|
2020-07-26 18:32:30 +00:00
|
|
|
{
|
2013-06-30 12:27:18 +00:00
|
|
|
continue;
|
2020-07-26 18:32:30 +00:00
|
|
|
}
|
|
|
|
|
[CMD] RMDIR: Improve some aspects of the /S option.
- First, the option and the APIs called by it can work directly on
paths relative to the current directory. So there is no need to
call GetFullPathName(), with the risk of going over MAX_PATH if the
current path is quite long (or nested) but the RMDIR is called on a
(short-length) relative sub-directory.
- Append a path-separator (backslash), only if the specified directory
does not have one already, and, that it does not specify a current
directory via the "drive-root" method, e.g. "C:" without any trailing
backslash.
- In case there are errors during deletion of sub-directories or
sub-files, print the error but continue deleting the other sub-dirs
or files.
- Monitor the Ctrl-C breaker as well, and stop deleting if it has been
triggered.
- When removing file/directory read-only attribute, just remove this
attribute, but keep the other ones.
- When deleting the directory, first try to do it directly; if it fails
with access denied, check whether it was read-only, and if so, remove
this attribute and retry deletion, otherwise fails.
- When recursively deleting a drive root directory, ultimately resolve
the dir pattern and check whether it's indeed a drive root, e.g.
"C:\\", and if so, just return success. Indeed, calling
RemoveDirectory() on such drive roots will return ERROR_ACCESS_DENIED
otherwise, but we want to succeed even if, of course, we won't
actually "delete" the drive root.
2020-07-11 21:15:20 +00:00
|
|
|
_tcscpy(pFileName, f.cFileName);
|
2013-06-30 12:27:18 +00:00
|
|
|
|
[CMD] RMDIR: Improve some aspects of the /S option.
- First, the option and the APIs called by it can work directly on
paths relative to the current directory. So there is no need to
call GetFullPathName(), with the risk of going over MAX_PATH if the
current path is quite long (or nested) but the RMDIR is called on a
(short-length) relative sub-directory.
- Append a path-separator (backslash), only if the specified directory
does not have one already, and, that it does not specify a current
directory via the "drive-root" method, e.g. "C:" without any trailing
backslash.
- In case there are errors during deletion of sub-directories or
sub-files, print the error but continue deleting the other sub-dirs
or files.
- Monitor the Ctrl-C breaker as well, and stop deleting if it has been
triggered.
- When removing file/directory read-only attribute, just remove this
attribute, but keep the other ones.
- When deleting the directory, first try to do it directly; if it fails
with access denied, check whether it was read-only, and if so, remove
this attribute and retry deletion, otherwise fails.
- When recursively deleting a drive root directory, ultimately resolve
the dir pattern and check whether it's indeed a drive root, e.g.
"C:\\", and if so, just return success. Indeed, calling
RemoveDirectory() on such drive roots will return ERROR_ACCESS_DENIED
otherwise, but we want to succeed even if, of course, we won't
actually "delete" the drive root.
2020-07-11 21:15:20 +00:00
|
|
|
dwAttribs = f.dwFileAttributes;
|
|
|
|
|
|
|
|
if (dwAttribs & FILE_ATTRIBUTE_DIRECTORY)
|
2020-07-26 18:32:30 +00:00
|
|
|
{
|
[CMD] RMDIR: Improve some aspects of the /S option.
- First, the option and the APIs called by it can work directly on
paths relative to the current directory. So there is no need to
call GetFullPathName(), with the risk of going over MAX_PATH if the
current path is quite long (or nested) but the RMDIR is called on a
(short-length) relative sub-directory.
- Append a path-separator (backslash), only if the specified directory
does not have one already, and, that it does not specify a current
directory via the "drive-root" method, e.g. "C:" without any trailing
backslash.
- In case there are errors during deletion of sub-directories or
sub-files, print the error but continue deleting the other sub-dirs
or files.
- Monitor the Ctrl-C breaker as well, and stop deleting if it has been
triggered.
- When removing file/directory read-only attribute, just remove this
attribute, but keep the other ones.
- When deleting the directory, first try to do it directly; if it fails
with access denied, check whether it was read-only, and if so, remove
this attribute and retry deletion, otherwise fails.
- When recursively deleting a drive root directory, ultimately resolve
the dir pattern and check whether it's indeed a drive root, e.g.
"C:\\", and if so, just return success. Indeed, calling
RemoveDirectory() on such drive roots will return ERROR_ACCESS_DENIED
otherwise, but we want to succeed even if, of course, we won't
actually "delete" the drive root.
2020-07-11 21:15:20 +00:00
|
|
|
if (!DeleteFolder(szFullPath))
|
|
|
|
{
|
|
|
|
/* Couldn't delete the file, print out the error */
|
|
|
|
ErrorMessage(GetLastError(), szFullPath);
|
|
|
|
|
|
|
|
/* Continue deleting files/subfolders */
|
|
|
|
}
|
2020-07-26 18:32:30 +00:00
|
|
|
}
|
2013-06-30 12:27:18 +00:00
|
|
|
else
|
|
|
|
{
|
[CMD] RMDIR: Improve some aspects of the /S option.
- First, the option and the APIs called by it can work directly on
paths relative to the current directory. So there is no need to
call GetFullPathName(), with the risk of going over MAX_PATH if the
current path is quite long (or nested) but the RMDIR is called on a
(short-length) relative sub-directory.
- Append a path-separator (backslash), only if the specified directory
does not have one already, and, that it does not specify a current
directory via the "drive-root" method, e.g. "C:" without any trailing
backslash.
- In case there are errors during deletion of sub-directories or
sub-files, print the error but continue deleting the other sub-dirs
or files.
- Monitor the Ctrl-C breaker as well, and stop deleting if it has been
triggered.
- When removing file/directory read-only attribute, just remove this
attribute, but keep the other ones.
- When deleting the directory, first try to do it directly; if it fails
with access denied, check whether it was read-only, and if so, remove
this attribute and retry deletion, otherwise fails.
- When recursively deleting a drive root directory, ultimately resolve
the dir pattern and check whether it's indeed a drive root, e.g.
"C:\\", and if so, just return success. Indeed, calling
RemoveDirectory() on such drive roots will return ERROR_ACCESS_DENIED
otherwise, but we want to succeed even if, of course, we won't
actually "delete" the drive root.
2020-07-11 21:15:20 +00:00
|
|
|
/* Force file deletion even if it's read-only */
|
|
|
|
if (dwAttribs & FILE_ATTRIBUTE_READONLY)
|
|
|
|
SetFileAttributes(szFullPath, dwAttribs & ~FILE_ATTRIBUTE_READONLY);
|
|
|
|
|
|
|
|
if (!DeleteFile(szFullPath))
|
2014-10-21 16:05:54 +00:00
|
|
|
{
|
[CMD] RMDIR: Improve some aspects of the /S option.
- First, the option and the APIs called by it can work directly on
paths relative to the current directory. So there is no need to
call GetFullPathName(), with the risk of going over MAX_PATH if the
current path is quite long (or nested) but the RMDIR is called on a
(short-length) relative sub-directory.
- Append a path-separator (backslash), only if the specified directory
does not have one already, and, that it does not specify a current
directory via the "drive-root" method, e.g. "C:" without any trailing
backslash.
- In case there are errors during deletion of sub-directories or
sub-files, print the error but continue deleting the other sub-dirs
or files.
- Monitor the Ctrl-C breaker as well, and stop deleting if it has been
triggered.
- When removing file/directory read-only attribute, just remove this
attribute, but keep the other ones.
- When deleting the directory, first try to do it directly; if it fails
with access denied, check whether it was read-only, and if so, remove
this attribute and retry deletion, otherwise fails.
- When recursively deleting a drive root directory, ultimately resolve
the dir pattern and check whether it's indeed a drive root, e.g.
"C:\\", and if so, just return success. Indeed, calling
RemoveDirectory() on such drive roots will return ERROR_ACCESS_DENIED
otherwise, but we want to succeed even if, of course, we won't
actually "delete" the drive root.
2020-07-11 21:15:20 +00:00
|
|
|
/* Couldn't delete the file, print out the error */
|
|
|
|
ErrorMessage(GetLastError(), szFullPath);
|
|
|
|
|
|
|
|
/* Restore file attributes */
|
|
|
|
SetFileAttributes(szFullPath, dwAttribs);
|
|
|
|
|
|
|
|
/* Continue deleting files/subfolders */
|
2014-10-21 16:05:54 +00:00
|
|
|
}
|
2013-06-30 12:27:18 +00:00
|
|
|
}
|
2006-02-16 23:23:37 +00:00
|
|
|
|
2020-07-26 18:32:30 +00:00
|
|
|
} while (FindNextFile(hFile, &f));
|
|
|
|
FindClose(hFile);
|
2006-02-16 23:23:37 +00:00
|
|
|
}
|
2020-07-26 18:32:30 +00:00
|
|
|
|
[CMD] RMDIR: Improve some aspects of the /S option.
- First, the option and the APIs called by it can work directly on
paths relative to the current directory. So there is no need to
call GetFullPathName(), with the risk of going over MAX_PATH if the
current path is quite long (or nested) but the RMDIR is called on a
(short-length) relative sub-directory.
- Append a path-separator (backslash), only if the specified directory
does not have one already, and, that it does not specify a current
directory via the "drive-root" method, e.g. "C:" without any trailing
backslash.
- In case there are errors during deletion of sub-directories or
sub-files, print the error but continue deleting the other sub-dirs
or files.
- Monitor the Ctrl-C breaker as well, and stop deleting if it has been
triggered.
- When removing file/directory read-only attribute, just remove this
attribute, but keep the other ones.
- When deleting the directory, first try to do it directly; if it fails
with access denied, check whether it was read-only, and if so, remove
this attribute and retry deletion, otherwise fails.
- When recursively deleting a drive root directory, ultimately resolve
the dir pattern and check whether it's indeed a drive root, e.g.
"C:\\", and if so, just return success. Indeed, calling
RemoveDirectory() on such drive roots will return ERROR_ACCESS_DENIED
otherwise, but we want to succeed even if, of course, we won't
actually "delete" the drive root.
2020-07-11 21:15:20 +00:00
|
|
|
/* Ignore directory deletion if the user pressed Ctrl-C */
|
|
|
|
if (bCtrlBreak)
|
|
|
|
return TRUE;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Detect whether we are trying to delete a pure root drive (e.g. "C:\\", but not "C:");
|
|
|
|
* if so, just return success. Otherwise the RemoveDirectory() call below would fail
|
|
|
|
* and return ERROR_ACCESS_DENIED.
|
|
|
|
*/
|
|
|
|
if (GetFullPathName(Directory, ARRAYSIZE(szFullPath), szFullPath, NULL) == 3 &&
|
|
|
|
szFullPath[1] == _T(':') && szFullPath[2] == _T('\\'))
|
|
|
|
{
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* First attempt to delete the directory */
|
|
|
|
if (RemoveDirectory(Directory))
|
|
|
|
return TRUE;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* It failed; if it was due to an denied access, check whether it was
|
|
|
|
* due to the directory being read-only. If so, remove its attribute
|
|
|
|
* and retry deletion.
|
|
|
|
*/
|
|
|
|
if (GetLastError() == ERROR_ACCESS_DENIED)
|
|
|
|
{
|
|
|
|
/* Force directory deletion even if it's read-only */
|
|
|
|
dwAttribs = GetFileAttributes(Directory);
|
|
|
|
if (dwAttribs & FILE_ATTRIBUTE_READONLY)
|
|
|
|
{
|
|
|
|
SetFileAttributes(Directory, dwAttribs & ~FILE_ATTRIBUTE_READONLY);
|
|
|
|
return RemoveDirectory(Directory);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return FALSE;
|
2006-02-16 23:23:37 +00:00
|
|
|
}
|
2013-06-30 12:27:18 +00:00
|
|
|
|
2020-07-26 18:32:30 +00:00
|
|
|
INT cmd_rmdir(LPTSTR param)
|
2006-02-16 23:23:37 +00:00
|
|
|
{
|
2020-07-26 18:32:30 +00:00
|
|
|
INT nError = 0;
|
|
|
|
INT res;
|
|
|
|
LPTSTR *arg;
|
2013-06-30 12:27:18 +00:00
|
|
|
INT args;
|
|
|
|
INT dirCount;
|
|
|
|
INT i;
|
2020-07-26 18:32:30 +00:00
|
|
|
TCHAR ch;
|
|
|
|
BOOL bRecurseDir = FALSE;
|
|
|
|
BOOL bQuiet = FALSE;
|
2013-06-30 12:27:18 +00:00
|
|
|
|
2020-07-26 18:32:30 +00:00
|
|
|
if (!_tcsncmp(param, _T("/?"), 2))
|
2013-06-30 12:27:18 +00:00
|
|
|
{
|
|
|
|
ConOutResPaging(TRUE,STRING_RMDIR_HELP);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2020-07-26 18:32:30 +00:00
|
|
|
arg = split(param, &args, FALSE, FALSE);
|
2013-06-30 12:27:18 +00:00
|
|
|
dirCount = 0;
|
|
|
|
|
2020-07-26 18:32:30 +00:00
|
|
|
/* Check for options anywhere in command line */
|
2013-06-30 12:27:18 +00:00
|
|
|
for (i = 0; i < args; i++)
|
|
|
|
{
|
|
|
|
if (*arg[i] == _T('/'))
|
|
|
|
{
|
2020-07-26 18:32:30 +00:00
|
|
|
/* Found an option, but check to make sure it has something after it */
|
|
|
|
if (_tcslen(arg[i]) == 2)
|
2013-06-30 12:27:18 +00:00
|
|
|
{
|
2020-07-26 18:32:30 +00:00
|
|
|
ch = _totupper(arg[i][1]);
|
2013-06-30 12:27:18 +00:00
|
|
|
|
|
|
|
if (ch == _T('S'))
|
2020-07-26 18:32:30 +00:00
|
|
|
bRecurseDir = TRUE;
|
2013-06-30 12:27:18 +00:00
|
|
|
else if (ch == _T('Q'))
|
2020-07-26 18:32:30 +00:00
|
|
|
bQuiet = TRUE;
|
2013-06-30 12:27:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
dirCount++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (dirCount == 0)
|
|
|
|
{
|
|
|
|
/* No folder to remove */
|
|
|
|
error_req_param_missing();
|
|
|
|
freep(arg);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < args; i++)
|
|
|
|
{
|
|
|
|
if (*arg[i] == _T('/'))
|
|
|
|
continue;
|
|
|
|
|
2020-07-26 18:32:30 +00:00
|
|
|
if (bRecurseDir)
|
2013-06-30 12:27:18 +00:00
|
|
|
{
|
2020-07-26 18:32:30 +00:00
|
|
|
/* Ask the user whether to delete everything in the folder */
|
|
|
|
if (!bQuiet)
|
2013-06-30 12:27:18 +00:00
|
|
|
{
|
2020-07-26 18:32:30 +00:00
|
|
|
res = FilePromptYNA(STRING_DEL_HELP2);
|
2013-06-30 12:27:18 +00:00
|
|
|
if (res == PROMPT_NO || res == PROMPT_BREAK)
|
|
|
|
{
|
|
|
|
nError = 1;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (res == PROMPT_ALL)
|
2020-07-26 18:32:30 +00:00
|
|
|
bQuiet = TRUE;
|
2013-06-30 12:27:18 +00:00
|
|
|
}
|
|
|
|
|
[CMD] RMDIR: Improve some aspects of the /S option.
- First, the option and the APIs called by it can work directly on
paths relative to the current directory. So there is no need to
call GetFullPathName(), with the risk of going over MAX_PATH if the
current path is quite long (or nested) but the RMDIR is called on a
(short-length) relative sub-directory.
- Append a path-separator (backslash), only if the specified directory
does not have one already, and, that it does not specify a current
directory via the "drive-root" method, e.g. "C:" without any trailing
backslash.
- In case there are errors during deletion of sub-directories or
sub-files, print the error but continue deleting the other sub-dirs
or files.
- Monitor the Ctrl-C breaker as well, and stop deleting if it has been
triggered.
- When removing file/directory read-only attribute, just remove this
attribute, but keep the other ones.
- When deleting the directory, first try to do it directly; if it fails
with access denied, check whether it was read-only, and if so, remove
this attribute and retry deletion, otherwise fails.
- When recursively deleting a drive root directory, ultimately resolve
the dir pattern and check whether it's indeed a drive root, e.g.
"C:\\", and if so, just return success. Indeed, calling
RemoveDirectory() on such drive roots will return ERROR_ACCESS_DENIED
otherwise, but we want to succeed even if, of course, we won't
actually "delete" the drive root.
2020-07-11 21:15:20 +00:00
|
|
|
res = DeleteFolder(arg[i]);
|
2013-06-30 12:27:18 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-07-11 18:34:18 +00:00
|
|
|
/* Without /S, do not force directory deletion even if it's read-only */
|
2013-06-30 12:27:18 +00:00
|
|
|
res = RemoveDirectory(arg[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!res)
|
|
|
|
{
|
|
|
|
/* Couldn't delete the folder, print out the error */
|
|
|
|
nError = GetLastError();
|
2020-07-01 00:39:25 +00:00
|
|
|
ErrorMessage(nError, NULL);
|
2013-06-30 12:27:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-26 18:32:30 +00:00
|
|
|
freep(arg);
|
2013-06-30 12:27:18 +00:00
|
|
|
return nError;
|
2006-02-16 23:23:37 +00:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
2020-08-07 00:13:27 +00:00
|
|
|
* Either exits the command interpreter, or quits the current batch context.
|
2006-02-16 23:23:37 +00:00
|
|
|
*/
|
2020-08-07 00:13:27 +00:00
|
|
|
|
|
|
|
/* Enable this define for supporting EXIT /B even when extensions are disabled */
|
|
|
|
// #define SUPPORT_EXIT_B_NO_EXTENSIONS
|
|
|
|
|
2017-11-18 20:32:10 +00:00
|
|
|
INT CommandExit(LPTSTR param)
|
2006-02-16 23:23:37 +00:00
|
|
|
{
|
2017-11-18 20:32:10 +00:00
|
|
|
if (!_tcsncmp(param, _T("/?"), 2))
|
2013-06-30 12:27:18 +00:00
|
|
|
{
|
2017-11-18 20:32:10 +00:00
|
|
|
ConOutResPaging(TRUE, STRING_EXIT_HELP);
|
2017-11-18 22:41:31 +00:00
|
|
|
|
|
|
|
/* Just make sure we don't exit */
|
2013-06-30 12:27:18 +00:00
|
|
|
bExit = FALSE;
|
|
|
|
return 0;
|
|
|
|
}
|
2006-02-16 23:23:37 +00:00
|
|
|
|
2020-08-07 00:13:27 +00:00
|
|
|
if (_tcsnicmp(param, _T("/B"), 2) == 0)
|
2013-06-30 12:27:18 +00:00
|
|
|
{
|
|
|
|
param += 2;
|
2017-11-18 22:41:31 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* If a current batch file is running, exit it,
|
|
|
|
* otherwise exit this command interpreter instance.
|
|
|
|
*/
|
|
|
|
if (bc)
|
[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
|
|
|
{
|
2020-08-07 00:13:27 +00:00
|
|
|
/* Windows' CMD compatibility: Use GOTO :EOF */
|
|
|
|
TCHAR EofLabel[] = _T(":EOF");
|
|
|
|
|
|
|
|
#ifdef SUPPORT_EXIT_B_NO_EXTENSIONS
|
|
|
|
/*
|
|
|
|
* Temporarily enable extensions so as to support :EOF.
|
|
|
|
*
|
|
|
|
* Our GOTO implementation ensures that, when extensions are
|
|
|
|
* enabled and the label is ':EOF', no immediate change of batch
|
|
|
|
* context (done e.g. via ExitBatch() calls) is performed.
|
|
|
|
* This will therefore ensure that we do not spoil the extensions
|
|
|
|
* state when we restore it below.
|
|
|
|
*/
|
|
|
|
BOOL bOldEnableExtensions = bEnableExtensions;
|
|
|
|
bEnableExtensions = TRUE;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
cmd_goto(EofLabel);
|
|
|
|
|
|
|
|
#ifdef SUPPORT_EXIT_B_NO_EXTENSIONS
|
|
|
|
/* Restore the original state of the extensions */
|
|
|
|
bEnableExtensions = bOldEnableExtensions;
|
|
|
|
#endif
|
[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
|
|
|
}
|
2017-11-18 22:41:31 +00:00
|
|
|
else
|
[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
|
|
|
{
|
2017-11-18 22:41:31 +00:00
|
|
|
bExit = TRUE;
|
[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
|
|
|
}
|
2013-06-30 12:27:18 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2017-11-18 22:41:31 +00:00
|
|
|
/* Exit this command interpreter instance */
|
2013-06-30 12:27:18 +00:00
|
|
|
bExit = TRUE;
|
|
|
|
}
|
|
|
|
|
2017-11-18 22:41:31 +00:00
|
|
|
/* Search for an optional exit code */
|
|
|
|
while (_istspace(*param))
|
2020-07-29 23:44:43 +00:00
|
|
|
++param;
|
2017-11-18 22:41:31 +00:00
|
|
|
|
|
|
|
/* Set the errorlevel to the exit code */
|
|
|
|
if (_istdigit(*param))
|
2020-07-29 23:44:43 +00:00
|
|
|
{
|
2017-11-18 22:41:31 +00:00
|
|
|
nErrorLevel = _ttoi(param);
|
2020-07-29 23:44:43 +00:00
|
|
|
// if (fSingleCommand == 1) return nErrorLevel;
|
|
|
|
}
|
2017-11-18 22:41:31 +00:00
|
|
|
|
2020-07-29 23:44:43 +00:00
|
|
|
return (bExit ? nErrorLevel : 0);
|
2006-02-16 23:23:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#ifdef INCLUDE_CMD_REM
|
|
|
|
/*
|
|
|
|
* does nothing
|
|
|
|
*/
|
2008-08-25 23:22:03 +00:00
|
|
|
INT CommandRem (LPTSTR param)
|
2006-02-16 23:23:37 +00:00
|
|
|
{
|
[CMD] Make the command-line parser more compatible with Windows' CMD one.
All these modifications have been verified with Windows' CMD, either
by using written cmd_rostests and the existing cmd_winetests, or
manually by enabling the flags cmd!fDumpTokens and cmd!fDumpParse
(available in the public symbols) and analyzing how the tokens are
being parsed, as well as the generated command tree.
See also the following links for more details (but remember that these
observations have to be double-checked in Windows' CMD!):
* Parser rules: https://stackoverflow.com/a/4095133/13530036
* Discussion: https://www.dostips.com/forum/viewtopic.php?f=3&t=8355
* Numbers parsing: https://www.dostips.com/forum/viewtopic.php?t=3758
* Label names vs. GOTO and CALL: https://www.dostips.com/forum/viewtopic.php?f=3&t=3803
and: https://www.dostips.com/forum/viewtopic.php?f=3&t=3803&p=55405#p55405
- Fix REM command parsing. A C_COMMAND-like structure should still
be built, so that it can show up during batch command echo. However
some specific handling needs to be done, so use instead a new C_REM
command type.
Escape carets are parsed differently than usual: they are explicitly
kept in the command line and don't participate in line continuations.
Also, the Windows' CMD behaviour is to discards everything before the
last line continuation.
- Prefix operator '@' (the "silent" operator) is parsed as a separate
command. Thus, the command @@foo@bar is parsed as: '@', '@', 'foo@bar'.
- Improve the checks for numbered redirection.
For this purpose, we check whether this is a number, that is in first
position in the current parsing buffer or is preceded by a whitespace-
like separator, including standard command operators (excepting '@' !)
and double-quotes.
- Empty command blocks, i.e. "( )", standing by themselves, or present
in IF or FOR commands, are considered invalid. (The closing parenthesis
is considered "unexpected".)
- Ignore single closing parenthesis when being outside of command blocks,
thus interpreting it as a command, and ignore explicitly everything
following on the same line, including line continuations.
This very specific situation can happen e.g. while running in batch mode,
when jumping to a label present inside a command block.
See the code for a thorough explanation.
- Detect whether a parenthesized block is not terminated at the end
of a command stream (getting a NUL character instead of a newline),
and if so, bail out early instead of entering into an infinite loop.
- Perform a similar check for the parenthesized list in FOR commands.
- Initialize the static 'InsideBlock' value to a known value.
- The '&' operator (multi-commmand) is allowed to have an empty RHS.
When such situation occurs, turn the CurrentTokenType to TOK_END
so as to avoid a parse error later on.
- The main body of a IF statement, or its 'else' clause, as well as
the main body of a FOR statement, must not be empty, otherwise this
is considered a syntax error. If so, call ParseError() that sets
the 'bParseError' flag, and forcing all batch execution to stop.
2020-05-22 22:35:54 +00:00
|
|
|
if (_tcsstr(param, _T("/?")) == param)
|
2013-06-30 12:27:18 +00:00
|
|
|
{
|
|
|
|
ConOutResPaging(TRUE,STRING_REM_HELP);
|
|
|
|
}
|
2006-02-16 23:23:37 +00:00
|
|
|
|
2013-06-30 12:27:18 +00:00
|
|
|
return 0;
|
2006-02-16 23:23:37 +00:00
|
|
|
}
|
|
|
|
#endif /* INCLUDE_CMD_REM */
|
|
|
|
|
|
|
|
|
2020-07-26 18:32:30 +00:00
|
|
|
INT CommandShowCommands(LPTSTR param)
|
2006-02-16 23:23:37 +00:00
|
|
|
{
|
2013-06-30 14:09:38 +00:00
|
|
|
PrintCommandList();
|
2013-06-30 12:27:18 +00:00
|
|
|
return 0;
|
2006-02-16 23:23:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* EOF */
|