Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 52 additions & 59 deletions src/windows/common/interop.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Module Name:

#include "precomp.h"
#include "interop.hpp"
#include "HandleIO.h"
#include "helpers.hpp"
#include "socket.hpp"
#include "hvsocket.hpp"
Expand Down Expand Up @@ -408,78 +409,70 @@ std::string FormatCommandLine(gsl::span<gsl::byte> CommandLineData, USHORT Comma
DWORD
ProcessInteropMessages(_In_ HANDLE MessageHandle, _Inout_ CreateProcessResult* Result)
{
OVERLAPPED Overlapped = {0};
const wil::unique_event OverlappedEvent(wil::EventOptions::ManualReset);
Overlapped.hEvent = OverlappedEvent.get();
const HANDLE WaitHandles[] = {Overlapped.hEvent, Result->Process.get()};
namespace io = wsl::windows::common::io;

// Read messages from the message handle. Break out of the loop if the pipe
// is connection is closed or the process exits.
//
// N.B. ReadFile will automatically reset the event in the overlapped
// structure.
DWORD ExitCode = 1;
for (;;)
{
DWORD BytesRead;
LX_INIT_WINDOW_SIZE_CHANGED WindowSizeMessage;
bool Success = ReadFile(MessageHandle, &WindowSizeMessage, sizeof(WindowSizeMessage), &BytesRead, &Overlapped);
if (!Success)
{
const auto LastError = GetLastError();
if ((LastError == ERROR_BROKEN_PIPE) || (LastError == ERROR_HANDLE_EOF))
{
if (WI_IsFlagClear(Result->Flags, LX_INIT_CREATE_PROCESS_RESULT_FLAG_GUI_APPLICATION))
{
THROW_IF_WIN32_BOOL_FALSE(TerminateProcess(Result->Process.get(), 1));
}
DWORD exitCode = 1;
size_t pendingSize = 0;
LX_INIT_WINDOW_SIZE_CHANGED pendingMessage{};

break;
}
auto processExit = [&] {
THROW_IF_WIN32_BOOL_FALSE(GetExitCodeProcess(Result->Process.get(), &exitCode));

THROW_LAST_ERROR_IF(LastError != ERROR_IO_PENDING);

auto CancelIo = wil::scope_exit_log(WI_DIAGNOSTICS_INFO, [&] {
CancelIoEx(MessageHandle, &Overlapped);
GetOverlappedResult(MessageHandle, &Overlapped, &BytesRead, TRUE);
});
// Close the pseudoconsole, this causes all pending data to be flushed.
Result->PseudoConsole.reset();
};

const DWORD WaitStatus = WaitForMultipleObjects(RTL_NUMBER_OF(WaitHandles), WaitHandles, FALSE, INFINITE);
if (WaitStatus == WAIT_OBJECT_0)
{
Success = GetOverlappedResult(MessageHandle, &Overlapped, &BytesRead, FALSE);
CancelIo.release();
if ((!Success) || (BytesRead == 0))
io::MultiHandleWait wait;
wait.AddHandle(
std::make_unique<io::ReadHandle>(
io::HandleWrapper{MessageHandle},
[&](const gsl::span<char>& input) {
if (input.empty())
{
if (WI_IsFlagClear(Result->Flags, LX_INIT_CREATE_PROCESS_RESULT_FLAG_GUI_APPLICATION))
const DWORD waitStatus = WaitForSingleObject(Result->Process.get(), 0);
if (waitStatus == WAIT_OBJECT_0)
{
processExit();
}
else
{
THROW_IF_WIN32_BOOL_FALSE(TerminateProcess(Result->Process.get(), 1));
THROW_HR_IF(E_UNEXPECTED, waitStatus != WAIT_TIMEOUT);
if (WI_IsFlagClear(Result->Flags, LX_INIT_CREATE_PROCESS_RESULT_FLAG_GUI_APPLICATION))
{
THROW_IF_WIN32_BOOL_FALSE(TerminateProcess(Result->Process.get(), 1));
}
}

break;
return;
}

WI_ASSERT((BytesRead == sizeof(WindowSizeMessage)) && (WindowSizeMessage.Header.MessageType == LxInitMessageWindowSizeChanged));
auto remaining = input;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remaining should be stored outside of this lambda, since it's possible for a read to be partial, and if that happens, we want the bytes to be stored until the next call.

In an ideal world, we'd reuse ReadSocketMessageHandle, but it currently only supports sockets. Maybe we can add a flag to add support for non-socket handles, but that's outside the scope of this change

while (!remaining.empty())
{
const size_t bytesToCopy = (std::min)(remaining.size(), sizeof(pendingMessage) - pendingSize);
std::copy_n(remaining.data(), bytesToCopy, reinterpret_cast<char*>(&pendingMessage) + pendingSize);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can skip the copy into pendingMessage here. If we have enough bytes for a full message, we could just access it via:

const auto& message = gslhelper::get_struct<LX_INIT_WINDOW_SIZE_CHANGED>(remaining); 

pendingSize += bytesToCopy;
remaining = remaining.subspan(bytesToCopy);

const COORD Size{static_cast<SHORT>(WindowSizeMessage.Columns), static_cast<SHORT>(WindowSizeMessage.Rows)};
THROW_IF_FAILED(ResizePseudoConsole(Result->PseudoConsole.get(), Size));
}
else if (WaitStatus == (WAIT_OBJECT_0 + 1))
{
THROW_IF_WIN32_BOOL_FALSE(GetExitCodeProcess(Result->Process.get(), &ExitCode));
if (pendingSize == sizeof(pendingMessage))
{
THROW_HR_IF(
E_UNEXPECTED,
(pendingMessage.Header.MessageType != LxInitMessageWindowSizeChanged) ||
(pendingMessage.Header.MessageSize != sizeof(pendingMessage)));

const COORD size{static_cast<SHORT>(pendingMessage.Columns), static_cast<SHORT>(pendingMessage.Rows)};
THROW_IF_FAILED(ResizePseudoConsole(Result->PseudoConsole.get(), size));
pendingSize = 0;
}
}
}),
io::MultiHandleWait::CancelOnCompleted);

// Close the pseudoconsole, this causes all pending data to be flushed.
Result->PseudoConsole.reset();
break;
}
else
{
THROW_HR(E_UNEXPECTED);
}
}
}
wait.AddHandle(std::make_unique<io::EventHandle>(io::HandleWrapper{Result->Process.get()}, processExit), io::MultiHandleWait::CancelOnCompleted);

return ExitCode;
wait.Run(std::nullopt);
return exitCode;
}

} // namespace
Expand Down