terminal/src/host/history.cpp
Michael Niksa f141d86280 Merged PR 4447792: Fix two bugs with DOSKEY
The first issue is in the console host: when we erase a command history,
we also clear its _allocated_ flag. It's supposed to remain allocated
but become "reset". When we later check that a command history that
exists in the list is allocated, we fail loudly because allocated has
been cleared.

The second is that in Windows Server 2003, we rewrote the console client
APIs (in kernelbase!) regarding command history and changed one internal
function from taking char** to taking char*. Since the signature was
_actually_ void** and that changed to void*, the compiler didn't notice
when in only one single place we continued to pass a char** instead of a
char*.  This caused us to send the wrong filename length for the ExeName
in SetConsoleNumberOfCommands.

Fixes MSFT:25265854
Retrieved from https://microsoft.visualstudio.com os OS official/rs_onecore_dep_uxp b493fb5a06975c53b2fbb7b9fc0546244b551fa9
2020-03-19 18:14:52 +00:00

925 lines
30 KiB
C++

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "history.h"
#include "_output.h"
#include "output.h"
#include "stream.h"
#include "_stream.h"
#include "dbcs.h"
#include "handle.h"
#include "misc.h"
#include "../types/inc/convert.hpp"
#include "srvinit.h"
#include "resource.h"
#include "ApiRoutines.h"
#include "..\interactivity\inc\ServiceLocator.hpp"
#pragma hdrstop
using Microsoft::Console::Interactivity::ServiceLocator;
// I need to be a list because we rearrange elements inside to maintain a
// "least recently used" state. Doing many rearrangement operations with
// a list will maintain the iterator pointers as valid to the elements
// (where other collections like deque do not.)
// If CommandHistory::s_Allocate and friends stop shuffling elements
// for maintaining LRU, then this datatype can be changed.
std::list<CommandHistory> CommandHistory::s_historyLists;
CommandHistory* CommandHistory::s_Find(const HANDLE processHandle)
{
for (auto& historyList : s_historyLists)
{
if (historyList._processHandle == processHandle)
{
FAIL_FAST_IF(WI_IsFlagClear(historyList.Flags, CLE_ALLOCATED));
return &historyList;
}
}
return nullptr;
}
// Routine Description:
// - This routine marks the command history buffer freed.
// Arguments:
// - processHandle - handle to client process.
void CommandHistory::s_Free(const HANDLE processHandle)
{
CommandHistory* const History = CommandHistory::s_Find(processHandle);
if (History)
{
WI_ClearFlag(History->Flags, CLE_ALLOCATED);
History->_processHandle = nullptr;
}
}
void CommandHistory::s_ResizeAll(const size_t commands)
{
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
FAIL_FAST_IF(commands > SHORT_MAX);
gci.SetHistoryBufferSize(gsl::narrow<UINT>(commands));
for (auto& historyList : s_historyLists)
{
historyList.Realloc(commands);
}
}
static bool CaseInsensitiveEquality(wchar_t a, wchar_t b)
{
return ::towlower(a) == ::towlower(b);
}
bool CommandHistory::IsAppNameMatch(const std::wstring_view other) const
{
return std::equal(_appName.cbegin(), _appName.cend(), other.cbegin(), other.cend(), CaseInsensitiveEquality);
}
// Routine Description:
// - This routine is called when escape is entered or a command is added.
void CommandHistory::_Reset()
{
LastDisplayed = gsl::narrow<SHORT>(_commands.size()) - 1;
WI_SetFlag(Flags, CLE_RESET);
}
[[nodiscard]] HRESULT CommandHistory::Add(const std::wstring_view newCommand,
const bool suppressDuplicates)
{
RETURN_HR_IF(E_OUTOFMEMORY, _maxCommands == 0);
FAIL_FAST_IF(WI_IsFlagClear(Flags, CLE_ALLOCATED));
if (newCommand.size() == 0)
{
return S_OK;
}
try
{
if (_commands.size() == 0 ||
_commands.back().size() != newCommand.size() ||
!std::equal(_commands.back().cbegin(),
_commands.back().cbegin() + newCommand.size(),
newCommand.cbegin(),
newCommand.cend()))
{
std::wstring reuse{};
if (suppressDuplicates)
{
SHORT index;
if (FindMatchingCommand(newCommand, LastDisplayed, index, CommandHistory::MatchOptions::ExactMatch))
{
reuse = Remove(index);
}
}
// find free record. if all records are used, free the lru one.
if ((SHORT)_commands.size() == _maxCommands)
{
_commands.erase(_commands.cbegin());
// move LastDisplayed back one in order to stay synced with the
// command it referred to before erasing the lru one
--LastDisplayed;
}
// add newCommand to array
if (!reuse.empty())
{
_commands.emplace_back(reuse);
}
else
{
_commands.emplace_back(newCommand);
}
if (LastDisplayed == -1 ||
_commands.at(LastDisplayed).size() != newCommand.size() ||
!std::equal(_commands.at(LastDisplayed).cbegin(),
_commands.at(LastDisplayed).cbegin() + newCommand.size(),
newCommand.cbegin(),
newCommand.cend()))
{
_Reset();
}
}
}
CATCH_RETURN();
WI_SetFlag(Flags, CLE_RESET); // remember that we've returned a cmd
return S_OK;
}
std::wstring_view CommandHistory::GetNth(const SHORT index) const
{
try
{
return _commands.at(index);
}
CATCH_LOG();
return {};
}
[[nodiscard]] HRESULT CommandHistory::RetrieveNth(const SHORT index,
gsl::span<wchar_t> buffer,
size_t& commandSize)
{
LastDisplayed = index;
try
{
const auto& cmd = _commands.at(index);
if (cmd.size() > (size_t)buffer.size())
{
commandSize = buffer.size(); // room for CRLF?
}
else
{
commandSize = cmd.size();
}
std::copy_n(cmd.cbegin(), commandSize, buffer.begin());
commandSize *= sizeof(wchar_t);
return S_OK;
}
CATCH_RETURN();
}
[[nodiscard]] HRESULT CommandHistory::Retrieve(const SearchDirection searchDirection,
const gsl::span<wchar_t> buffer,
size_t& commandSize)
{
FAIL_FAST_IF(!(WI_IsFlagSet(Flags, CLE_ALLOCATED)));
if (_commands.size() == 0)
{
return E_FAIL;
}
if (_commands.size() == 1)
{
LastDisplayed = 0;
}
else if (searchDirection == SearchDirection::Previous)
{
// if this is the first time for this read that a command has
// been retrieved, return the current command. otherwise, return
// the previous command.
if (WI_IsFlagSet(Flags, CLE_RESET))
{
WI_ClearFlag(Flags, CLE_RESET);
}
else
{
_Prev(LastDisplayed);
}
}
else
{
_Next(LastDisplayed);
}
return RetrieveNth(LastDisplayed, buffer, commandSize);
}
std::wstring_view CommandHistory::GetLastCommand() const
{
if (_commands.size() != 0)
{
try
{
return _commands.at(LastDisplayed);
}
CATCH_LOG();
}
return {};
}
void CommandHistory::Empty()
{
_commands.clear();
LastDisplayed = -1;
WI_SetFlag(Flags, CLE_RESET);
}
bool CommandHistory::AtFirstCommand() const
{
if (WI_IsFlagSet(Flags, CLE_RESET))
{
return FALSE;
}
SHORT i = (SHORT)(LastDisplayed - 1);
if (i == -1)
{
i = ((SHORT)_commands.size()) - 1i16;
}
return (i == ((SHORT)_commands.size()) - 1i16);
}
bool CommandHistory::AtLastCommand() const
{
return LastDisplayed == ((SHORT)_commands.size()) - 1i16;
}
void CommandHistory::Realloc(const size_t commands)
{
// To protect ourselves from overflow and general arithmetic errors, a limit of SHORT_MAX is put on the size of the command history.
if (_maxCommands == (SHORT)commands || commands > SHORT_MAX)
{
return;
}
const auto oldCommands = _commands;
const auto newNumberOfCommands = gsl::narrow<SHORT>(std::min(_commands.size(), commands));
_commands.clear();
for (SHORT i = 0; i < newNumberOfCommands; i++)
{
_commands.emplace_back(oldCommands[i]);
}
WI_SetFlag(Flags, CLE_RESET);
LastDisplayed = gsl::narrow<SHORT>(_commands.size()) - 1;
_maxCommands = (SHORT)commands;
}
void CommandHistory::s_ReallocExeToFront(const std::wstring_view appName, const size_t commands)
{
for (auto it = s_historyLists.begin(); it != s_historyLists.end(); it++)
{
if (WI_IsFlagSet(it->Flags, CLE_ALLOCATED) && it->IsAppNameMatch(appName))
{
CommandHistory backup = *it;
backup.Realloc(commands);
s_historyLists.erase(it);
s_historyLists.push_front(backup);
return;
}
}
}
CommandHistory* CommandHistory::s_FindByExe(const std::wstring_view appName)
{
for (auto& historyList : s_historyLists)
{
if (WI_IsFlagSet(historyList.Flags, CLE_ALLOCATED) && historyList.IsAppNameMatch(appName))
{
return &historyList;
}
}
return nullptr;
}
size_t CommandHistory::s_CountOfHistories()
{
return s_historyLists.size();
}
// Routine Description:
// - This routine returns the LRU command history buffer, or the command history buffer that corresponds to the app name.
// Arguments:
// - Console - pointer to console.
// Return Value:
// - Pointer to command history buffer. if none are available, returns nullptr.
CommandHistory* CommandHistory::s_Allocate(const std::wstring_view appName, const HANDLE processHandle)
{
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
// Reuse a history buffer. The buffer must be !CLE_ALLOCATED.
// If possible, the buffer should have the same app name.
std::optional<CommandHistory> BestCandidate;
bool SameApp = false;
for (auto it = s_historyLists.cbegin(); it != s_historyLists.cend(); it++)
{
if (WI_IsFlagClear(it->Flags, CLE_ALLOCATED))
{
// use LRU history buffer with same app name
if (it->IsAppNameMatch(appName))
{
BestCandidate = *it;
SameApp = true;
s_historyLists.erase(it);
break;
}
}
}
// if there isn't a free buffer for the app name and the maximum number of
// command history buffers hasn't been allocated, allocate a new one.
if (!SameApp && s_historyLists.size() < gci.GetNumberOfHistoryBuffers())
{
CommandHistory History;
History._appName = appName;
History.Flags = CLE_ALLOCATED;
History.LastDisplayed = -1;
History._maxCommands = gsl::narrow<SHORT>(gci.GetHistoryBufferSize());
History._processHandle = processHandle;
return &s_historyLists.emplace_front(History);
}
else if (!BestCandidate.has_value() && s_historyLists.size() > 0)
{
// If we have no candidate already and we need one, take the LRU (which is the back/last one) which isn't allocated.
for (auto it = s_historyLists.crbegin(); it != s_historyLists.crend(); it++)
{
if (WI_IsFlagClear(it->Flags, CLE_ALLOCATED))
{
BestCandidate = *it;
s_historyLists.erase(std::next(it).base()); // trickery to turn reverse iterator into forward iterator for erase.
break;
}
}
}
// If the app name doesn't match, copy in the new app name and free the old commands.
if (BestCandidate.has_value())
{
if (!SameApp)
{
BestCandidate->_commands.clear();
BestCandidate->LastDisplayed = -1;
BestCandidate->_appName = appName;
}
BestCandidate->_processHandle = processHandle;
WI_SetFlag(BestCandidate->Flags, CLE_ALLOCATED);
return &s_historyLists.emplace_front(BestCandidate.value());
}
return nullptr;
}
size_t CommandHistory::GetNumberOfCommands() const
{
return _commands.size();
}
void CommandHistory::_Prev(SHORT& ind) const
{
if (ind <= 0)
{
ind = gsl::narrow<SHORT>(_commands.size());
}
ind--;
}
void CommandHistory::_Next(SHORT& ind) const
{
++ind;
if (ind >= (SHORT)_commands.size())
{
ind = 0;
}
}
void CommandHistory::_Dec(SHORT& ind) const
{
if (ind <= 0)
{
ind = _maxCommands;
}
ind--;
}
void CommandHistory::_Inc(SHORT& ind) const
{
++ind;
if (ind >= _maxCommands)
{
ind = 0;
}
}
std::wstring CommandHistory::Remove(const SHORT iDel)
{
SHORT iFirst = 0;
SHORT iLast = gsl::narrow<SHORT>(_commands.size() - 1);
SHORT iDisp = LastDisplayed;
if (_commands.size() == 0)
{
return {};
}
SHORT const nDel = iDel;
if ((nDel < iFirst) || (nDel > iLast))
{
return {};
}
if (iDisp == iDel)
{
LastDisplayed = -1;
}
try
{
const auto str = _commands.at(iDel);
if (iDel < iLast)
{
_commands.erase(_commands.cbegin() + iDel);
if ((iDisp > iDel) && (iDisp <= iLast))
{
_Dec(iDisp);
}
_Dec(iLast);
}
else if (iFirst <= iDel)
{
_commands.erase(_commands.cbegin() + iDel);
if ((iDisp >= iFirst) && (iDisp < iDel))
{
_Inc(iDisp);
}
_Inc(iFirst);
}
LastDisplayed = iDisp;
return str;
}
CATCH_LOG();
return {};
}
// Routine Description:
// - this routine finds the most recent command that starts with the letters already in the current command. it returns the array index (no mod needed).
[[nodiscard]] bool CommandHistory::FindMatchingCommand(const std::wstring_view givenCommand,
const SHORT startingIndex,
SHORT& indexFound,
const MatchOptions options)
{
indexFound = startingIndex;
if (_commands.size() == 0)
{
return false;
}
if (WI_IsFlagClear(options, MatchOptions::JustLooking) && WI_IsFlagSet(Flags, CLE_RESET))
{
WI_ClearFlag(Flags, CLE_RESET);
}
else
{
_Prev(indexFound);
}
if (givenCommand.empty())
{
return true;
}
try
{
for (size_t i = 0; i < _commands.size(); i++)
{
const auto& storedCommand = _commands.at(indexFound);
if ((WI_IsFlagClear(options, MatchOptions::ExactMatch) && (givenCommand.size() <= storedCommand.size())) || (givenCommand.size() == storedCommand.size()))
{
if (std::equal(storedCommand.begin(),
storedCommand.begin() + givenCommand.size(),
givenCommand.begin(),
givenCommand.end(),
CaseInsensitiveEquality))
{
return true;
}
}
_Prev(indexFound);
}
}
CATCH_LOG();
return false;
}
#ifdef UNIT_TESTING
void CommandHistory::s_ClearHistoryListStorage()
{
s_historyLists.clear();
}
#endif
// Routine Description:
// - swaps the locations of two history items
// Arguments:
// - indexA - index of one history item to swap
// - indexB - index of one history item to swap
void CommandHistory::Swap(const short indexA, const short indexB)
{
std::swap(_commands.at(indexA), _commands.at(indexB));
}
// Routine Description:
// - Clears all command history for the given EXE name
// - Will convert input parameters and call the W version of this method
// Arguments:
// - exeName - The client EXE application attached to the host whose history we should clear
// Return Value:
// - Check HRESULT with SUCCEEDED. Can return memory, safe math, safe string, or locale conversion errors.
HRESULT ApiRoutines::ExpungeConsoleCommandHistoryAImpl(const std::string_view exeName) noexcept
{
const CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
try
{
const auto exeNameW = ConvertToW(gci.CP, exeName);
return ExpungeConsoleCommandHistoryWImpl(exeNameW);
}
CATCH_RETURN();
}
// Routine Description:
// - Clears all command history for the given EXE name
// Arguments:
// - exeName - The client EXE application attached to the host whose history we should clear
// Return Value:
// - Check HRESULT with SUCCEEDED. Can return memory, safe math, safe string, or locale conversion errors.
HRESULT ApiRoutines::ExpungeConsoleCommandHistoryWImpl(const std::wstring_view exeName) noexcept
{
LockConsole();
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
try
{
const auto history = CommandHistory::s_FindByExe(exeName);
if (history)
{
history->Empty();
}
return S_OK;
}
CATCH_RETURN();
}
// Routine Description:
// - Sets the number of commands that will be stored in history for a given EXE name
// - Will convert input parameters and call the W version of this method
// Arguments:
// - exeName - A client EXE application attached to the host
// - numberOfCommands - Specifies the maximum length of the associated history buffer
// Return Value:
// - Check HRESULT with SUCCEEDED. Can return memory, safe math, safe string, or locale conversion errors.
HRESULT ApiRoutines::SetConsoleNumberOfCommandsAImpl(const std::string_view exeName,
const size_t numberOfCommands) noexcept
{
const CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
try
{
const auto exeNameW = ConvertToW(gci.CP, exeName);
return SetConsoleNumberOfCommandsWImpl(exeNameW, numberOfCommands);
}
CATCH_RETURN();
}
// Routine Description:
// - Sets the number of commands that will be stored in history for a given EXE name
// Arguments:
// - exeName - A client EXE application attached to the host
// - numberOfCommands - Specifies the maximum length of the associated history buffer
// Return Value:
// - Check HRESULT with SUCCEEDED. Can return memory, safe math, safe string, or locale conversion errors.
HRESULT ApiRoutines::SetConsoleNumberOfCommandsWImpl(const std::wstring_view exeName,
const size_t numberOfCommands) noexcept
{
LockConsole();
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
try
{
CommandHistory::s_ReallocExeToFront(exeName, numberOfCommands);
return S_OK;
}
CATCH_RETURN();
}
// Routine Description:
// - Retrieves the amount of space needed to retrieve all command history for a given EXE name
// - Works for both Unicode and Multibyte text.
// - This method configuration is called for both A/W routines to allow us an efficient way of asking the system
// the lengths of how long each conversion would be without actually performing the full allocations/conversions.
// Arguments:
// - exeName - The client EXE application attached to the host whose set we should check
// - countInUnicode - True for W version (UCS-2 Unicode) calls. False for A version calls (all multibyte formats.)
// - codepage - Set to valid Windows Codepage for A version calls. Ignored for W (but typically just set to 0.)
// - historyLength - Receives the length of buffer that would be required to retrieve all history for the given exe.
// Return Value:
// - Check HRESULT with SUCCEEDED. Can return memory, safe math, safe string, or locale conversion errors.
HRESULT GetConsoleCommandHistoryLengthImplHelper(const std::wstring_view exeName,
const bool countInUnicode,
const UINT codepage,
size_t& historyLength)
{
// Ensure output variables are initialized
historyLength = 0;
LockConsole();
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
CommandHistory* const pCommandHistory = CommandHistory::s_FindByExe(exeName);
if (nullptr != pCommandHistory)
{
size_t cchNeeded = 0;
// Every command history item is made of a string length followed by 1 null character.
size_t const cchNull = 1;
for (SHORT i = 0; i < gsl::narrow<SHORT>(pCommandHistory->GetNumberOfCommands()); i++)
{
const auto command = pCommandHistory->GetNth(i);
size_t cchCommand = command.size();
// This is the proposed length of the whole string.
size_t cchProposed;
RETURN_IF_FAILED(SizeTAdd(cchCommand, cchNull, &cchProposed));
// If we're counting how much multibyte space will be needed, trial convert the command string before we add.
if (!countInUnicode)
{
cchCommand = GetALengthFromW(codepage, command);
}
// Accumulate the result
RETURN_IF_FAILED(SizeTAdd(cchNeeded, cchProposed, &cchNeeded));
}
historyLength = cchNeeded;
}
return S_OK;
}
// Routine Description:
// - Retrieves the amount of space needed to retrieve all command history for a given EXE name
// - Converts input text from A to W then makes the call to the W implementation.
// Arguments:
// - exeName - The client EXE application attached to the host whose set we should check
// - length - Receives the length of buffer that would be required to retrieve all history for the given exe.
// Return Value:
// - Check HRESULT with SUCCEEDED. Can return memory, safe math, safe string, or locale conversion errors.
HRESULT ApiRoutines::GetConsoleCommandHistoryLengthAImpl(const std::string_view exeName,
size_t& length) noexcept
{
const CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
UINT const codepage = gci.CP;
// Ensure output variables are initialized
length = 0;
LockConsole();
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
try
{
const auto exeNameW = ConvertToW(codepage, exeName);
return GetConsoleCommandHistoryLengthImplHelper(exeNameW, false, codepage, length);
}
CATCH_RETURN();
}
// Routine Description:
// - Retrieves the amount of space needed to retrieve all command history for a given EXE name
// Arguments:
// - exeName - The client EXE application attached to the host whose set we should check
// - length - Receives the length of buffer that would be required to retrieve all history for the given exe.
// Return Value:
// - Check HRESULT with SUCCEEDED. Can return memory, safe math, safe string, or locale conversion errors.
HRESULT ApiRoutines::GetConsoleCommandHistoryLengthWImpl(const std::wstring_view exeName,
size_t& length) noexcept
{
LockConsole();
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
try
{
return GetConsoleCommandHistoryLengthImplHelper(exeName, true, 0, length);
}
CATCH_RETURN();
}
// Routine Description:
// - Retrieves the full command history for a given EXE name known to the console.
// - It is permitted to call this function without having a target buffer. Use the result to allocate
// the appropriate amount of space and call again.
// - This behavior exists to allow the A version of the function to help allocate the right temp buffer for conversion of
// the output/result data.
// Arguments:
// - exeName - The client EXE application attached to the host whose set we should check
// - historyBuffer - The target buffer for data we are attempting to retrieve. Optionally empty to retrieve needed space.
// - writtenOrNeeded - Will specify how many characters were written (if buffer is valid)
// or how many characters would have been consumed.
// Return Value:
// - Check HRESULT with SUCCEEDED. Can return memory, safe math, safe string, or locale conversion errors.
HRESULT GetConsoleCommandHistoryWImplHelper(const std::wstring_view exeName,
gsl::span<wchar_t> historyBuffer,
size_t& writtenOrNeeded)
{
// Ensure output variables are initialized
writtenOrNeeded = 0;
if (historyBuffer.size() > 0)
{
historyBuffer.at(0) = UNICODE_NULL;
}
CommandHistory* const CommandHistory = CommandHistory::s_FindByExe(exeName);
if (nullptr != CommandHistory)
{
PWCHAR CommandBufferW = historyBuffer.data();
size_t cchTotalLength = 0;
size_t const cchNull = 1;
for (SHORT i = 0; i < gsl::narrow<SHORT>(CommandHistory->GetNumberOfCommands()); i++)
{
const auto command = CommandHistory->GetNth(i);
size_t const cchCommand = command.size();
size_t cchNeeded;
RETURN_IF_FAILED(SizeTAdd(cchCommand, cchNull, &cchNeeded));
// If we can return the data, attempt to do so until we're done or it overflows.
// If we cannot return data, we're just going to loop anyway and count how much space we'd need.
if (historyBuffer.size() > 0)
{
// Calculate what the new total would be after we add what we need.
size_t cchNewTotal;
RETURN_IF_FAILED(SizeTAdd(cchTotalLength, cchNeeded, &cchNewTotal));
RETURN_HR_IF(HRESULT_FROM_WIN32(ERROR_BUFFER_OVERFLOW), cchNewTotal > gsl::narrow<size_t>(historyBuffer.size()));
size_t cchRemaining;
RETURN_IF_FAILED(SizeTSub(historyBuffer.size(),
cchTotalLength,
&cchRemaining));
RETURN_IF_FAILED(StringCchCopyNW(CommandBufferW,
cchRemaining,
command.data(),
command.size()));
CommandBufferW += cchNeeded;
}
RETURN_IF_FAILED(SizeTAdd(cchTotalLength, cchNeeded, &cchTotalLength));
}
writtenOrNeeded = cchTotalLength;
}
return S_OK;
}
// Routine Description:
// - Retrieves the full command history for a given EXE name known to the console.
// - Converts inputs from A to W, calls the W version of this method, and then converts the resulting text W to A.
// Arguments:
// - exeName - The client EXE application attached to the host whose set we should check
// - commandHistory - The target buffer for data we are attempting to retrieve.
// - written - Will specify how many characters were written
// Return Value:
// - Check HRESULT with SUCCEEDED. Can return memory, safe math, safe string, or locale conversion errors.
HRESULT ApiRoutines::GetConsoleCommandHistoryAImpl(const std::string_view exeName,
gsl::span<char> commandHistory,
size_t& written) noexcept
{
const CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
UINT const codepage = gci.CP;
// Ensure output variables are initialized
written = 0;
try
{
if (commandHistory.size() > 0)
{
commandHistory.at(0) = ANSI_NULL;
}
LockConsole();
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
// Convert our input parameters to Unicode.
const auto exeNameW = ConvertToW(codepage, exeName);
// Figure out how big our temporary Unicode buffer must be to retrieve output
size_t bufferNeeded;
RETURN_IF_FAILED(GetConsoleCommandHistoryWImplHelper(exeNameW, {}, bufferNeeded));
// If there's nothing to get, then simply return.
RETURN_HR_IF(S_OK, 0 == bufferNeeded);
// Allocate a unicode buffer of the right size.
std::unique_ptr<wchar_t[]> buffer = std::make_unique<wchar_t[]>(bufferNeeded);
RETURN_IF_NULL_ALLOC(buffer);
// Call the Unicode version of this method
size_t bufferWritten;
RETURN_IF_FAILED(GetConsoleCommandHistoryWImplHelper(exeNameW, gsl::span<wchar_t>(buffer.get(), bufferNeeded), bufferWritten));
// Convert result to A
const auto converted = ConvertToA(codepage, { buffer.get(), bufferWritten });
// Copy safely to output buffer
// - CommandHistory are a series of null terminated strings. We cannot use a SafeString function to copy.
// So instead, validate and use raw memory copy.
RETURN_HR_IF(HRESULT_FROM_WIN32(ERROR_BUFFER_OVERFLOW), converted.size() > gsl::narrow<size_t>(commandHistory.size()));
memcpy_s(commandHistory.data(), commandHistory.size(), converted.data(), converted.size());
// And return the size copied.
written = converted.size();
return S_OK;
}
CATCH_RETURN();
}
// Routine Description:
// - Retrieves the full command history for a given EXE name known to the console.
// - Converts inputs from A to W, calls the W version of this method, and then converts the resulting text W to A.
// Arguments:
// - exeName - The client EXE application attached to the host whose set we should check
// - commandHistory - The target buffer for data we are attempting to retrieve.
// - written - Will specify how many characters were written
// Return Value:
// - Check HRESULT with SUCCEEDED. Can return memory, safe math, safe string, or locale conversion errors.
HRESULT ApiRoutines::GetConsoleCommandHistoryWImpl(const std::wstring_view exeName,
gsl::span<wchar_t> commandHistory,
size_t& written) noexcept
{
LockConsole();
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
try
{
return GetConsoleCommandHistoryWImplHelper(exeName, commandHistory, written);
}
CATCH_RETURN();
}