787 lines
17 KiB
C++
787 lines
17 KiB
C++
/*++
|
|
|
|
Copyright (c) 1992-2000 Microsoft Corporation
|
|
|
|
Module Name:
|
|
|
|
doskey.cxx
|
|
|
|
Abstract:
|
|
|
|
Edits command lines, recalls Windows 2000 commands, and creates macros.
|
|
|
|
Author:
|
|
|
|
Norbert P. Kusters (norbertk) 02-April-1992
|
|
|
|
Revision History:
|
|
|
|
--*/
|
|
|
|
#include "ulib.hxx"
|
|
#include "error.hxx"
|
|
|
|
#include "arg.hxx"
|
|
#include "smsg.hxx"
|
|
#include "rtmsg.h"
|
|
#include "wstring.hxx"
|
|
#include "path.hxx"
|
|
#include "filestrm.hxx"
|
|
#include "system.hxx"
|
|
#include "file.hxx"
|
|
|
|
#include "ulibcl.hxx"
|
|
|
|
extern "C" {
|
|
#include "conapi.h"
|
|
#include <ctype.h>
|
|
#ifdef FE_SB // isspace patch
|
|
// isspace() causes access violation when x is Unicode char
|
|
// that is not included in ASCII charset.
|
|
#define isspace(x) ( (x) == ' ' ) ? TRUE : FALSE
|
|
#endif
|
|
};
|
|
|
|
VOID
|
|
StripQuotesFromString(
|
|
IN PWSTRING String
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine removes leading and trailing quote marks (if
|
|
present) from a quoted string. If the string is not a quoted
|
|
string, it is left unchanged.
|
|
|
|
--*/
|
|
{
|
|
if( String->QueryChCount() >= 2 &&
|
|
String->QueryChAt( 0 ) == '\"' &&
|
|
String->QueryChAt( String->QueryChCount() - 1 ) == '\"' ) {
|
|
|
|
String->DeleteChAt( String->QueryChCount() - 1 );
|
|
String->DeleteChAt( 0 );
|
|
}
|
|
}
|
|
|
|
|
|
BOOLEAN
|
|
DisplayPackedString(
|
|
IN PCWSTR PackedStrings,
|
|
IN ULONG PackedStringsLength,
|
|
IN BOOLEAN IndentStrings,
|
|
IN OUT PMESSAGE Message
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine outputs the given strings. One line per string.
|
|
|
|
Arguments:
|
|
|
|
PackedStrings - Supplies the null-separated strings to output.
|
|
PackedStringsLength - Supplies the number of characters in the strings.
|
|
NumIndentSpaces - Supplies whether or not to indent the strings.
|
|
Message - Supplies the outlet for the strings.
|
|
|
|
Return Value:
|
|
|
|
FALSE - Failure.
|
|
TRUE - Success.
|
|
|
|
--*/
|
|
{
|
|
DSTRING display_string;
|
|
DSTRING raw_string;
|
|
ULONG i;
|
|
PCWSTR p;
|
|
|
|
p = PackedStrings;
|
|
|
|
for (i = 0; i < PackedStringsLength; i++) {
|
|
|
|
if (i > 0 && p[i - 1]) {
|
|
continue;
|
|
}
|
|
|
|
if (IndentStrings) {
|
|
|
|
if (!display_string.Initialize(" ")) {
|
|
return FALSE;
|
|
}
|
|
} else {
|
|
|
|
if (!display_string.Initialize("")) {
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
|
|
if (!raw_string.Initialize(&p[i]) ||
|
|
!display_string.Strcat(&raw_string)) {
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
Message->Set(MSG_ONE_STRING_NEWLINE);
|
|
Message->Display("%W", &display_string);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
BOOLEAN
|
|
QuerySourceAndTarget(
|
|
IN PCWSTRING MacroLine,
|
|
OUT PWSTR* Source,
|
|
OUT PWSTR* Target
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine computes the sources and target string from the given macro line
|
|
by isolating the '=' and then making sure that the source is a single token.
|
|
|
|
Arguments:
|
|
|
|
MacroLine - Supplies the macro.
|
|
Source - Returns the source part of the macro.
|
|
Target - Returns the target part of the macro.
|
|
|
|
Return Value:
|
|
|
|
FALSE - Failure.
|
|
TRUE - Success.
|
|
|
|
--*/
|
|
{
|
|
LONG src_start, src_end, dst_start;
|
|
ULONG i, n;
|
|
WCHAR w;
|
|
|
|
*Source = NULL;
|
|
*Target = NULL;
|
|
|
|
i = 0;
|
|
n = MacroLine->QueryChCount();
|
|
|
|
for (; i < n; i++) {
|
|
w = MacroLine->QueryChAt(i);
|
|
if (!isspace(w)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
src_start = i;
|
|
|
|
for (; i < n; i++) {
|
|
w = MacroLine->QueryChAt(i);
|
|
if (isspace(w) || w == '=') {
|
|
break;
|
|
}
|
|
}
|
|
|
|
src_end = i;
|
|
|
|
if (src_start == src_end) {
|
|
return FALSE;
|
|
}
|
|
|
|
for (; i < n; i++) {
|
|
w = MacroLine->QueryChAt(i);
|
|
if (!isspace(w)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (w != '=') {
|
|
return FALSE;
|
|
}
|
|
|
|
i++;
|
|
|
|
for (; i < n; i++) {
|
|
w = MacroLine->QueryChAt(i);
|
|
if (!isspace(w)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
dst_start = i;
|
|
|
|
*Source = MacroLine->QueryWSTR(src_start, src_end - src_start);
|
|
*Target = MacroLine->QueryWSTR(dst_start);
|
|
|
|
if (!*Source || !*Target) {
|
|
DELETE(*Source);
|
|
DELETE(*Target);
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
BOOLEAN
|
|
DisplayMacros(
|
|
IN PWSTR TargetExe,
|
|
IN BOOLEAN IndentString,
|
|
IN OUT PMESSAGE Message
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine displays the macros for the given exe.
|
|
|
|
Arguments:
|
|
|
|
TargetExe - Supplies the exe for which to display the macros.
|
|
IndentStrings - Supplies whether or not to indent the macro strings.
|
|
Message - Supplies an outlet for the macro strings.
|
|
|
|
Return Value:
|
|
|
|
FALSE - Failure.
|
|
TRUE - Success.
|
|
|
|
--*/
|
|
{
|
|
ULONG buffer_length = 0;
|
|
PWSTR buffer;
|
|
|
|
buffer_length = GetConsoleAliasesLength(TargetExe);
|
|
if (!(buffer = (PWCHAR) MALLOC(buffer_length + 1))) {
|
|
Message->Set(MSG_CHK_NO_MEMORY);
|
|
Message->Display();
|
|
return FALSE;
|
|
}
|
|
|
|
if (buffer_length) {
|
|
buffer_length = GetConsoleAliases(buffer, buffer_length, TargetExe);
|
|
}
|
|
|
|
if (!DisplayPackedString(buffer, buffer_length/sizeof(WCHAR),
|
|
IndentString, Message)) {
|
|
Message->Set(MSG_CHK_NO_MEMORY);
|
|
Message->Display();
|
|
return FALSE;
|
|
}
|
|
|
|
FREE(buffer);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
BOOLEAN
|
|
DisplayAllMacros(
|
|
IN OUT PMESSAGE Message
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine displays all of the macros for all of the exes which have macros
|
|
defined for them.
|
|
|
|
Arguments:
|
|
|
|
Message - Supplies an outlet for the macros.
|
|
|
|
Return Value:
|
|
|
|
FALSE - Failure.
|
|
TRUE - Success.
|
|
|
|
--*/
|
|
{
|
|
PWSTR exes_buffer;
|
|
ULONG exes_length;
|
|
ULONG i;
|
|
DSTRING exe_name;
|
|
DSTRING display_name;
|
|
|
|
exes_length = GetConsoleAliasExesLength();
|
|
if (!(exes_buffer = (PWSTR) MALLOC(exes_length + 1))) {
|
|
Message->Set(MSG_CHK_NO_MEMORY);
|
|
Message->Display();
|
|
return FALSE;
|
|
}
|
|
|
|
if (exes_length) {
|
|
exes_length = GetConsoleAliasExes(exes_buffer, exes_length);
|
|
}
|
|
|
|
exes_length /= sizeof(WCHAR);
|
|
|
|
for (i = 0; i < exes_length; i++) {
|
|
|
|
if (i > 0 && exes_buffer[i - 1]) {
|
|
continue;
|
|
}
|
|
|
|
if (!exe_name.Initialize(&exes_buffer[i]) ||
|
|
!display_name.Initialize("[") ||
|
|
!display_name.Strcat(&exe_name) ||
|
|
!exe_name.Initialize("]") ||
|
|
!display_name.Strcat(&exe_name)) {
|
|
|
|
Message->Set(MSG_CHK_NO_MEMORY);
|
|
Message->Display();
|
|
return FALSE;
|
|
}
|
|
|
|
Message->Set(MSG_ONE_STRING_NEWLINE);
|
|
Message->Display("%W", &display_name);
|
|
|
|
if (!DisplayMacros(&exes_buffer[i], TRUE, Message)) {
|
|
return FALSE;
|
|
}
|
|
|
|
Message->Set(MSG_BLANK_LINE);
|
|
Message->Display();
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
BOOLEAN
|
|
ReadInMacrosFromFile(
|
|
IN PPATH FilePath,
|
|
IN PWSTR TargetExe,
|
|
IN OUT PMESSAGE Message
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine reads in the macros in the given file. The file must have the same
|
|
format as the output from DOSKEY /macros or DOSKEY /macros:all.
|
|
|
|
Arguments:
|
|
|
|
FilePath - Supplies the file with the macros.
|
|
TargetExe - Supplies the exe for which the unclaimed macros are.
|
|
Message - Supplies an outlet for messages.
|
|
|
|
Return Value:
|
|
|
|
FALSE - Failure.
|
|
TRUE - Success.
|
|
|
|
--*/
|
|
{
|
|
PFSN_FILE file;
|
|
PFILE_STREAM file_stream;
|
|
DSTRING line;
|
|
PWSTR target_exe;
|
|
DSTRING target_string;
|
|
PWSTR source, target;
|
|
|
|
if (!(file = SYSTEM::QueryFile(FilePath)) ||
|
|
!(file_stream = file->QueryStream(READ_ACCESS))) {
|
|
|
|
DELETE(file);
|
|
|
|
Message->Set(MSG_ATTRIB_FILE_NOT_FOUND);
|
|
Message->Display("%W", FilePath->GetPathString());
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
// Set up the target exe.
|
|
|
|
if (!line.Initialize("") ||
|
|
!target_string.Initialize(TargetExe) ||
|
|
!(target_exe = target_string.QueryWSTR())) {
|
|
|
|
Message->Set(MSG_CHK_NO_MEMORY);
|
|
Message->Display();
|
|
return FALSE;
|
|
}
|
|
|
|
while (!file_stream->IsAtEnd() && file_stream->ReadLine(&line)) {
|
|
|
|
// First see if the current line will define a new exe name.
|
|
|
|
if (!line.QueryChCount()) {
|
|
continue;
|
|
}
|
|
|
|
if (line.QueryChAt(0) == '[' &&
|
|
line.Strchr(']') != INVALID_CHNUM &&
|
|
line.Strchr(']') > 1) {
|
|
|
|
DELETE(target_exe);
|
|
if (!target_string.Initialize(&line, 1, line.Strchr(']') - 1) ||
|
|
!(target_exe = target_string.QueryWSTR())) {
|
|
|
|
Message->Set(MSG_CHK_NO_MEMORY);
|
|
Message->Display();
|
|
return FALSE;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
|
|
if (!QuerySourceAndTarget(&line, &source, &target) ||
|
|
!AddConsoleAlias(source, *target ? target : NULL, target_exe)) {
|
|
|
|
Message->Set(MSG_DOSKEY_INVALID_MACRO_DEFINITION);
|
|
Message->Display();
|
|
|
|
continue;
|
|
}
|
|
}
|
|
|
|
DELETE(file_stream);
|
|
DELETE(file);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
int __cdecl
|
|
main(
|
|
)
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This routine provides equivalent functionality to DOS 5's DOSKEY utility.
|
|
|
|
Arguments:
|
|
|
|
None.
|
|
|
|
Return Value:
|
|
|
|
0 - Success.
|
|
1 - Failure.
|
|
|
|
--*/
|
|
{
|
|
STREAM_MESSAGE msg;
|
|
ARGUMENT_LEXEMIZER arglex;
|
|
ARRAY lex_array;
|
|
ARRAY arg_array;
|
|
STRING_ARGUMENT progname;
|
|
FLAG_ARGUMENT help;
|
|
FLAG_ARGUMENT reinstall;
|
|
LONG_ARGUMENT bufsize;
|
|
LONG_ARGUMENT listsize;
|
|
STRING_ARGUMENT m_plus;
|
|
FLAG_ARGUMENT m;
|
|
STRING_ARGUMENT macros_plus;
|
|
FLAG_ARGUMENT macros;
|
|
FLAG_ARGUMENT history;
|
|
FLAG_ARGUMENT h;
|
|
FLAG_ARGUMENT insert;
|
|
FLAG_ARGUMENT overstrike;
|
|
STRING_ARGUMENT exename;
|
|
PATH_ARGUMENT filename;
|
|
REST_OF_LINE_ARGUMENT macro;
|
|
PWSTRING pwstring;
|
|
PWSTR target_exe;
|
|
PWSTR source, target;
|
|
PWSTR buffer;
|
|
ULONG buffer_length;
|
|
DSTRING all_string;
|
|
|
|
|
|
//
|
|
// DOSKEY /INSERT | /OVERSTRIKE changes the keyboard mode
|
|
// and we do not want the keyboard mode to be restored on exit
|
|
//
|
|
Get_Standard_Input_Stream()->DoNotRestoreConsoleMode();
|
|
|
|
// Initialize the error stack and the stream message object.
|
|
|
|
if (!msg.Initialize(Get_Standard_Output_Stream(),
|
|
Get_Standard_Input_Stream())) {
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
// Initialize the parsing machinery.
|
|
|
|
if (!lex_array.Initialize() ||
|
|
!arg_array.Initialize() ||
|
|
!arglex.Initialize(&lex_array)) {
|
|
|
|
return 1;
|
|
}
|
|
|
|
arglex.SetCaseSensitive(FALSE);
|
|
arglex.PutStartQuotes( "\"" );
|
|
arglex.PutEndQuotes( "\"" );
|
|
arglex.PutSeparators( " \t" );
|
|
|
|
|
|
// Tokenize the command line.
|
|
|
|
if (!arglex.PrepareToParse()) {
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
// Initialize the argument patterns to be accepted.
|
|
|
|
if (!progname.Initialize("*") ||
|
|
!help.Initialize("/?") ||
|
|
!reinstall.Initialize("/reinstall") ||
|
|
!bufsize.Initialize("/bufsize=*") ||
|
|
!listsize.Initialize("/listsize=*") ||
|
|
!macros_plus.Initialize("/macros:*") ||
|
|
!m_plus.Initialize("/m:*") ||
|
|
!macros.Initialize("/macros") ||
|
|
!m.Initialize("/m") ||
|
|
!history.Initialize("/history") ||
|
|
!h.Initialize("/h") ||
|
|
!insert.Initialize("/insert") ||
|
|
!overstrike.Initialize("/overstrike") ||
|
|
!exename.Initialize("/exename=*") ||
|
|
!filename.Initialize("/macrofile=*") ||
|
|
!macro.Initialize()) {
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
// Feed the arguments into the argument array.
|
|
|
|
if (!arg_array.Put(&progname) ||
|
|
!arg_array.Put(&help) ||
|
|
!arg_array.Put(&reinstall) ||
|
|
!arg_array.Put(&bufsize) ||
|
|
!arg_array.Put(&listsize) ||
|
|
!arg_array.Put(¯os_plus) ||
|
|
!arg_array.Put(&m_plus) ||
|
|
!arg_array.Put(¯os) ||
|
|
!arg_array.Put(&m) ||
|
|
!arg_array.Put(&history) ||
|
|
!arg_array.Put(&h) ||
|
|
!arg_array.Put(&insert) ||
|
|
!arg_array.Put(&overstrike) ||
|
|
!arg_array.Put(&exename) ||
|
|
!arg_array.Put(&filename) ||
|
|
!arg_array.Put(¯o)) {
|
|
|
|
return 1;
|
|
}
|
|
|
|
|
|
// Parse the command line.
|
|
|
|
if (!arglex.DoParsing(&arg_array)) {
|
|
|
|
msg.Set(MSG_INVALID_PARAMETER);
|
|
msg.Display("%W", pwstring = arglex.QueryInvalidArgument());
|
|
DELETE(pwstring);
|
|
return 1;
|
|
}
|
|
|
|
|
|
// Interpret the command line.
|
|
|
|
if (bufsize.IsValueSet()) {
|
|
|
|
msg.Set(MSG_DOSKEY_CANT_DO_BUFSIZE);
|
|
msg.Display();
|
|
}
|
|
|
|
|
|
if (help.QueryFlag()) {
|
|
|
|
msg.Set(MSG_DOSKEY_HELP);
|
|
msg.Display();
|
|
return 0;
|
|
}
|
|
|
|
|
|
if ((m.QueryFlag() || macros.QueryFlag()) &&
|
|
(macros_plus.IsValueSet() || m_plus.IsValueSet()) ||
|
|
(macros_plus.IsValueSet() && m_plus.IsValueSet())) {
|
|
|
|
msg.Set(MSG_INCOMPATIBLE_PARAMETERS);
|
|
msg.Display();
|
|
return 1;
|
|
}
|
|
|
|
|
|
// Compute the target exe name.
|
|
|
|
if (exename.IsValueSet()) {
|
|
|
|
StripQuotesFromString( (PWSTRING)exename.GetString() );
|
|
if (!(target_exe = exename.GetString()->QueryWSTR())) {
|
|
return 1;
|
|
}
|
|
|
|
} else {
|
|
|
|
target_exe = (PWSTR) L"cmd.exe";
|
|
}
|
|
|
|
|
|
// Interpret reinstall switch.
|
|
|
|
if (reinstall.QueryFlag()) {
|
|
|
|
ExpungeConsoleCommandHistory(target_exe);
|
|
}
|
|
|
|
|
|
// Interpret list size switch.
|
|
|
|
if (listsize.IsValueSet()) {
|
|
if (!SetConsoleNumberOfCommands(listsize.QueryLong(), target_exe)) {
|
|
msg.Set(MSG_DOSKEY_CANT_SIZE_LIST);
|
|
msg.Display();
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
|
|
// Interpret insert and overstrike switches.
|
|
|
|
if (insert.QueryFlag()) {
|
|
SetConsoleCommandHistoryMode(0);
|
|
}
|
|
|
|
if (overstrike.QueryFlag()) {
|
|
SetConsoleCommandHistoryMode(CONSOLE_OVERSTRIKE);
|
|
}
|
|
|
|
|
|
// Interpret the macro if any.
|
|
|
|
if (macro.IsValueSet()) {
|
|
|
|
if (!QuerySourceAndTarget(macro.GetRestOfLine(), &source, &target) ||
|
|
!AddConsoleAlias(source, *target ? target : NULL, target_exe)) {
|
|
|
|
msg.Set(MSG_DOSKEY_INVALID_MACRO_DEFINITION);
|
|
msg.Display();
|
|
}
|
|
|
|
DELETE(source);
|
|
DELETE(target);
|
|
}
|
|
|
|
|
|
// pull the stuff out from the file if provided.
|
|
|
|
if (filename.IsValueSet()) {
|
|
|
|
StripQuotesFromString((PWSTRING)filename.GetPath()->GetPathString());
|
|
if (!ReadInMacrosFromFile(filename.GetPath(), target_exe, &msg)) {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
|
|
// Print out history buffer.
|
|
|
|
if (history.QueryFlag() || h.QueryFlag()) {
|
|
|
|
buffer_length = GetConsoleCommandHistoryLength(target_exe);
|
|
if (!(buffer = (PWSTR) MALLOC(buffer_length))) {
|
|
msg.Set(MSG_CHK_NO_MEMORY);
|
|
msg.Display();
|
|
return 1;
|
|
}
|
|
|
|
if (buffer_length) {
|
|
buffer_length = GetConsoleCommandHistory(buffer,
|
|
buffer_length,
|
|
target_exe);
|
|
}
|
|
|
|
if (!DisplayPackedString(buffer, buffer_length/sizeof(WCHAR),
|
|
FALSE, &msg)) {
|
|
|
|
msg.Set(MSG_CHK_NO_MEMORY);
|
|
msg.Display();
|
|
return 1;
|
|
}
|
|
|
|
FREE(buffer);
|
|
}
|
|
|
|
|
|
// Echo macros for target_exe.
|
|
|
|
if (macros.QueryFlag() || m.QueryFlag()) {
|
|
if (!DisplayMacros(target_exe, FALSE, &msg)) {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
|
|
// Echo macros for specific exe.
|
|
|
|
if (macros_plus.IsValueSet()) {
|
|
|
|
StripQuotesFromString(macros_plus.GetString());
|
|
if (!all_string.Initialize("all")) {
|
|
return 1;
|
|
}
|
|
|
|
if (!macros_plus.GetString()->Stricmp(&all_string)) {
|
|
|
|
if (!DisplayAllMacros(&msg)) {
|
|
return 1;
|
|
}
|
|
|
|
} else {
|
|
|
|
target_exe = macros_plus.GetString()->QueryWSTR();
|
|
|
|
if (!DisplayMacros(target_exe, FALSE, &msg)) {
|
|
return 1;
|
|
}
|
|
|
|
DELETE(target_exe);
|
|
}
|
|
}
|
|
|
|
|
|
// Echo macros for specific exe.
|
|
|
|
if (m_plus.IsValueSet()) {
|
|
|
|
StripQuotesFromString(m_plus.GetString());
|
|
if (!all_string.Initialize("all")) {
|
|
return 1;
|
|
}
|
|
|
|
if (!m_plus.GetString()->Stricmp(&all_string)) {
|
|
|
|
if (!DisplayAllMacros(&msg)) {
|
|
return 1;
|
|
}
|
|
|
|
} else {
|
|
|
|
target_exe = m_plus.GetString()->QueryWSTR();
|
|
|
|
if (!DisplayMacros(target_exe, FALSE, &msg)) {
|
|
return 1;
|
|
}
|
|
|
|
DELETE(target_exe);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|