terminal/src/host/history.cpp

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)
{
til::at(historyBuffer, 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 > 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)
{
til::at(commandHistory, 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() > 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();
}