192 lines
8.6 KiB
C++
192 lines
8.6 KiB
C++
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT license.
|
|
|
|
#include "precomp.h"
|
|
#include "writeData.hpp"
|
|
|
|
#include "_stream.h"
|
|
#include "../types/inc/convert.hpp"
|
|
|
|
#include "../interactivity/inc/ServiceLocator.hpp"
|
|
|
|
// Routine Description:
|
|
// - Creates a new write data object for used in servicing write console requests
|
|
// Arguments:
|
|
// - siContext - The output buffer to write text data to
|
|
// - pwchContext - The string information that the client application sent us to be written
|
|
// - cbContext - Byte count of the string above
|
|
// - uiOutputCodepage - When the wait is completed, we *might* have to convert the byte count
|
|
// back into a specific codepage if the initial call was an A call.
|
|
// We need to remember what output codepage was set at the moment in time
|
|
// when the write was delayed as it might change by the time it is serviced.
|
|
// Return Value:
|
|
// - THROW: Throws if space cannot be allocated to copy the given string
|
|
WriteData::WriteData(SCREEN_INFORMATION& siContext,
|
|
_In_reads_bytes_(cbContext) wchar_t* const pwchContext,
|
|
const size_t cbContext,
|
|
const UINT uiOutputCodepage,
|
|
const bool requiresVtQuirk) :
|
|
IWaitRoutine(ReplyDataType::Write),
|
|
_siContext(siContext),
|
|
_pwchContext(THROW_IF_NULL_ALLOC(reinterpret_cast<wchar_t*>(new byte[cbContext]))),
|
|
_cbContext(cbContext),
|
|
_uiOutputCodepage(uiOutputCodepage),
|
|
_requiresVtQuirk(requiresVtQuirk),
|
|
_fLeadByteCaptured(false),
|
|
_fLeadByteConsumed(false),
|
|
_cchUtf8Consumed(0)
|
|
{
|
|
memmove(_pwchContext, pwchContext, _cbContext);
|
|
}
|
|
|
|
// Routine Description:
|
|
// - Destroys the write data object
|
|
// - Frees the string copy we made on creation
|
|
WriteData::~WriteData()
|
|
{
|
|
if (nullptr != _pwchContext)
|
|
{
|
|
delete[] _pwchContext;
|
|
}
|
|
}
|
|
|
|
// Routine Description:
|
|
// - Stores some additional information about lead byte adjustments from the conversion
|
|
// in WriteConsoleA before the real WriteConsole processing (always W) is reached
|
|
// so we can restore an accurate A byte count at the very end when the wait is serviced.
|
|
// Arguments:
|
|
// - fLeadByteCaptured - A lead byte was removed from the string before converted it and saved it.
|
|
// We need to report to the original caller that we "wrote" the byte
|
|
// even though it is held in escrow for the next call because it was
|
|
// the last character in the stream.
|
|
// - fLeadByteConsumed - We had a lead byte in escrow from the previous call that we stitched onto the
|
|
// front of the input string even though the caller didn't write it in this call.
|
|
// We need to report the byte count back to the caller without including this byte
|
|
// in the calculation as it wasn't a part of what was given in this exact call.
|
|
// Return Value:
|
|
// - <none>
|
|
void WriteData::SetLeadByteAdjustmentStatus(const bool fLeadByteCaptured,
|
|
const bool fLeadByteConsumed)
|
|
{
|
|
_fLeadByteCaptured = fLeadByteCaptured;
|
|
_fLeadByteConsumed = fLeadByteConsumed;
|
|
}
|
|
|
|
// Routine Description:
|
|
// - For UTF-8 codepages, remembers how many bytes that the UTF-8 parser said it consumed from the input stream.
|
|
// This will allow us to give back the correct value after the wait routine Notify services the data later.
|
|
// Arguments:
|
|
// - cchUtf8Consumed - Count of characters consumed by the UTF-8 parser off the input stream to generate the
|
|
// wide character string that is stowed in this object for consumption in the notify routine later.
|
|
// Return Value:
|
|
// - <none>
|
|
void WriteData::SetUtf8ConsumedCharacters(const size_t cchUtf8Consumed)
|
|
{
|
|
_cchUtf8Consumed = cchUtf8Consumed;
|
|
}
|
|
|
|
// Routine Description:
|
|
// - Called back at a later time to resume the writing operation when the output object becomes unblocked.
|
|
// Arguments:
|
|
// - TerminationReason - if this routine is called because a ctrl-c or ctrl-break was seen, this argument
|
|
// contains CtrlC or CtrlBreak. If the owning thread is exiting, it will have ThreadDying. Otherwise 0.
|
|
// - fIsUnicode - Input data was in UCS-2 unicode or it needs to be converted with the current Output Codepage
|
|
// - pReplyStatus - The status code to return to the client application that originally called the API (before it was queued to wait)
|
|
// - pNumBytes - The number of bytes of data that the server/driver will need to transmit back to the client process
|
|
// - pControlKeyState - Unused for write operations. Set to 0.
|
|
// - pOutputData - not used.
|
|
// - true if the wait is done and result buffer/status code can be sent back to the client.
|
|
// - false if we need to continue to wait because the output object blocked again
|
|
bool WriteData::Notify(const WaitTerminationReason TerminationReason,
|
|
const bool fIsUnicode,
|
|
_Out_ NTSTATUS* const pReplyStatus,
|
|
_Out_ size_t* const pNumBytes,
|
|
_Out_ DWORD* const pControlKeyState,
|
|
_Out_ void* const /*pOutputData*/)
|
|
{
|
|
*pNumBytes = _cbContext;
|
|
*pControlKeyState = 0;
|
|
|
|
if (WI_IsFlagSet(TerminationReason, WaitTerminationReason::ThreadDying))
|
|
{
|
|
*pReplyStatus = STATUS_THREAD_IS_TERMINATING;
|
|
return true;
|
|
}
|
|
|
|
// if we get to here, this routine was called by the input
|
|
// thread, which grabs the current console lock.
|
|
|
|
// This routine should be called by a thread owning the same lock on the
|
|
// same console as we're reading from.
|
|
|
|
FAIL_FAST_IF(!(Microsoft::Console::Interactivity::ServiceLocator::LocateGlobals().getConsoleInformation().IsConsoleLocked()));
|
|
|
|
std::unique_ptr<WriteData> waiter;
|
|
size_t cbContext = _cbContext;
|
|
NTSTATUS Status = DoWriteConsole(_pwchContext,
|
|
&cbContext,
|
|
_siContext,
|
|
_requiresVtQuirk,
|
|
waiter);
|
|
|
|
if (Status == CONSOLE_STATUS_WAIT)
|
|
{
|
|
// an extra waiter will be created by DoWriteConsole, but we're already a waiter so discard it.
|
|
waiter.reset();
|
|
return false;
|
|
}
|
|
|
|
// There's extra work to do to correct the byte counts if the original call was an A-version call.
|
|
// We always process and hold text in the waiter as W-version text, but the A call is expecting
|
|
// a byte value in its own codepage of how much we have written in that codepage.
|
|
if (!fIsUnicode)
|
|
{
|
|
if (CP_UTF8 != _uiOutputCodepage)
|
|
{
|
|
// At this level with WriteConsole, everything is byte counts, so change back to char counts for
|
|
// GetALengthFromW to work correctly.
|
|
const size_t cchContext = cbContext / sizeof(wchar_t);
|
|
|
|
// For non-UTF-8 codepages, we need to back convert the amount consumed and then
|
|
// correlate that with any lead bytes we may have kept for later or reintroduced
|
|
// from previous calls.
|
|
size_t cchTextBufferRead = 0;
|
|
|
|
// Start by counting the number of A bytes we used in printing our W string to the screen.
|
|
try
|
|
{
|
|
cchTextBufferRead = GetALengthFromW(_uiOutputCodepage, { _pwchContext, cchContext });
|
|
}
|
|
CATCH_LOG();
|
|
|
|
// If we captured a byte off the string this time around up above, it means we didn't feed
|
|
// it into the WriteConsoleW above, and therefore its consumption isn't accounted for
|
|
// in the count we just made. Add +1 to compensate.
|
|
if (_fLeadByteCaptured)
|
|
{
|
|
cchTextBufferRead++;
|
|
}
|
|
|
|
// If we consumed an internally-stored lead byte this time around up above, it means that we
|
|
// fed a byte into WriteConsoleW that wasn't a part of this particular call's request.
|
|
// We need to -1 to compensate and tell the caller the right number of bytes consumed this request.
|
|
if (_fLeadByteConsumed)
|
|
{
|
|
cchTextBufferRead--;
|
|
}
|
|
|
|
cbContext = cchTextBufferRead;
|
|
}
|
|
else
|
|
{
|
|
// For UTF-8, we were told exactly how many valid bytes were consumed before we got into the wait state.
|
|
// Just give that value back now.
|
|
cbContext = _cchUtf8Consumed;
|
|
}
|
|
}
|
|
|
|
*pNumBytes = cbContext;
|
|
*pReplyStatus = Status;
|
|
return true;
|
|
}
|