Skip to content
Draft
Show file tree
Hide file tree
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
23 changes: 23 additions & 0 deletions src/windows/common/ExecutionContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -422,6 +422,29 @@ bool COMServiceExecutionContext::CanCollectUserErrorMessage()
return true;
}

std::atomic<HANDLE> COMServiceExecutionContext::s_warningsPipe{nullptr};
wil::srwlock COMServiceExecutionContext::s_warningsPipeLock;

void COMServiceExecutionContext::SetWarningsPipe(HANDLE pipe)
{
s_warningsPipe.store(pipe, std::memory_order_release);
}

bool COMServiceExecutionContext::CollectUserWarning(const std::wstring& warning)
{
auto pipe = s_warningsPipe.load(std::memory_order_acquire);
if (pipe != nullptr)
{
// Serialize writes so concurrent warnings don't interleave.
auto lock = s_warningsPipeLock.lock_exclusive();
LOG_IF_WIN32_BOOL_FALSE(WriteFile(
pipe, warning.c_str(), gsl::narrow_cast<DWORD>(warning.size() * sizeof(wchar_t)), nullptr, nullptr));
return true;
}

return ExecutionContext::CollectUserWarning(warning);
}

LXSS_ERROR_INFO* ClientExecutionContext::OutError() noexcept
{
return &m_outError;
Expand Down
12 changes: 12 additions & 0 deletions src/windows/common/ExecutionContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,18 @@ class COMServiceExecutionContext : public ExecutionContext
~COMServiceExecutionContext() override;

bool CanCollectUserErrorMessage() override;

// Set a process-wide warnings pipe for streaming warnings to the CLI.
// The pipe is owned by the caller and must outlive all COMServiceExecutionContext instances.
// Must be called before any COM methods are serviced (e.g., during Initialize).
static void SetWarningsPipe(HANDLE pipe);

protected:
bool CollectUserWarning(const std::wstring& warning) override;

private:
static std::atomic<HANDLE> s_warningsPipe;
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.

Unfortunately I don't think a global handle will work here (long term, we should add support for warnings when creating a container, and in that code paths, multiple containers can start at the same time), so I would recommend making the warning pipe / COM pointer a regular field, so only the thread that sets it can use it

static wil::srwlock s_warningsPipeLock;
};

void EnableContextualizedErrors(bool service, bool useComErrors = false);
Expand Down
15 changes: 12 additions & 3 deletions src/windows/service/exe/WSLCSessionManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -132,17 +132,25 @@ void WSLCSessionManagerImpl::CreateSession(const WSLCSessionSettings* Settings,
auto tokenInfo = GetCallingProcessTokenInfo();
const auto callerToken = wsl::windows::common::security::GetUserToken(TokenImpersonation);

// Save the warnings pipe before resolving settings (it may be present even with default sessions).
WSLCHandle warningsPipe{};
if (Settings != nullptr)
{
warningsPipe = Settings->WarningsPipe;
}

// Resolve display name upfront (for both default and custom sessions).
std::wstring resolvedDisplayName;
if (Settings == nullptr)
const bool isDefaultSession = (Settings == nullptr || Settings->DisplayName == nullptr || wcslen(Settings->DisplayName) == 0);
if (isDefaultSession)
{
// Default session: name determined from token, qualified with username.
resolvedDisplayName = ResolveDefaultSessionName(tokenInfo);
Flags = WSLCSessionFlagsOpenExisting | WSLCSessionFlagsPersistent;
Settings = nullptr; // Use server-side defaults for all other settings.
}
else
{
THROW_HR_IF(WSLC_E_INVALID_SESSION_NAME, Settings->DisplayName == nullptr || wcslen(Settings->DisplayName) == 0);
THROW_HR_IF(E_INVALIDARG, Settings->StoragePath != nullptr && wcslen(Settings->StoragePath) == 0);
THROW_HR_IF(WSLC_E_INVALID_SESSION_NAME, wcslen(Settings->DisplayName) >= std::size(WSLCSessionInformation{}.DisplayName));
THROW_HR_IF_MSG(
Expand Down Expand Up @@ -216,7 +224,8 @@ void WSLCSessionManagerImpl::CreateSession(const WSLCSessionSettings* Settings,
AddSessionProcessToJobObject(factory.get());

// Create the session via the factory.
const auto sessionSettings = CreateSessionSettings(sessionId, creatorPid, Settings, resolvedDisplayName.c_str());
auto sessionSettings = CreateSessionSettings(sessionId, creatorPid, Settings, resolvedDisplayName.c_str());
sessionSettings.WarningsPipe = warningsPipe;
wil::com_ptr<IWSLCSession> session;
wil::com_ptr<IWSLCSessionReference> serviceRef;
THROW_IF_FAILED(factory->CreateSession(&sessionSettings, vm.Get(), &session, &serviceRef));
Expand Down
6 changes: 6 additions & 0 deletions src/windows/service/inc/wslc.idl
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,9 @@ typedef struct _WSLCSessionSettings {
WSLCHandle DmesgOutput;
WSLCSessionStorageFlags StorageFlags;

// Optional pipe handle for streaming user warnings to the CLI in real-time.
WSLCHandle WarningsPipe;

// Below options are used for debugging purposes only.
[unique] LPCWSTR RootVhdOverride;
[unique] LPCSTR RootVhdTypeOverride;
Expand Down Expand Up @@ -719,6 +722,9 @@ typedef struct _WSLCSessionInitSettings
WSLCNetworkingMode NetworkingMode;
WSLCFeatureFlags FeatureFlags;
[unique] LPCSTR RootVhdTypeOverride;

// Optional pipe handle for streaming user warnings to the CLI in real-time.
WSLCHandle WarningsPipe;
} WSLCSessionInitSettings;

[
Expand Down
2 changes: 1 addition & 1 deletion src/windows/wslc/core/CLIExecutionContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ namespace wsl::windows::wslc::execution {
// Contains arguments via Args.
struct CLIExecutionContext : public wsl::windows::common::ExecutionContext
{
CLIExecutionContext() : wsl::windows::common::ExecutionContext(wsl::windows::common::Context::WslC)
CLIExecutionContext() : wsl::windows::common::ExecutionContext(wsl::windows::common::Context::WslC, stderr)
{
}
~CLIExecutionContext() override = default;
Expand Down
44 changes: 43 additions & 1 deletion src/windows/wslc/services/SessionModel.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,55 @@ struct Session
explicit Session(wil::com_ptr<IWSLCSession> session) : m_session(std::move(session))
{
}

Session(wil::com_ptr<IWSLCSession> session, wil::unique_handle warningsPipeRead, wil::unique_handle warningsPipeWrite) :
m_session(std::move(session)), m_warningsPipeWrite(std::move(warningsPipeWrite))
{
// Spawn a background thread that reads warnings from the pipe and prints them to stderr in real-time.
m_warningsThread = std::thread([read = std::move(warningsPipeRead)]() {
try
{
wchar_t buffer[1024] = {0};
DWORD bytesRead{};
while (ReadFile(read.get(), buffer, sizeof(buffer) - sizeof(wchar_t), &bytesRead, nullptr) && bytesRead > 0)
{
const auto endIndex = bytesRead / sizeof(wchar_t);
buffer[endIndex] = UNICODE_NULL;
fwprintf(stderr, L"%ls", buffer);
}
}
CATCH_LOG();
});
}

~Session()
{
// Close the write end of the pipe so the reader thread exits.
m_warningsPipeWrite.reset();
if (m_warningsThread.joinable())
{
m_warningsThread.join();
}
}

NON_COPYABLE(Session);
Session(Session&&) = default;
Session& operator=(Session&&) = default;

IWSLCSession* Get() const noexcept
{
return m_session.get();
}

HANDLE WarningsPipeWrite() const noexcept
{
return m_warningsPipeWrite.get();
}

private:
wil::com_ptr<IWSLCSession> m_session;
wil::unique_handle m_warningsPipeWrite;
std::thread m_warningsThread;
};

} // namespace wsl::windows::wslc::models
} // namespace wsl::windows::wslc::models
13 changes: 10 additions & 3 deletions src/windows/wslc/services/SessionService.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,11 +111,18 @@ Session SessionService::CreateDefaultSession()
THROW_IF_FAILED(CoCreateInstance(__uuidof(WSLCSessionManager), nullptr, CLSCTX_LOCAL_SERVER, IID_PPV_ARGS(&sessionManager)));
wsl::windows::common::security::ConfigureForCOMImpersonation(sessionManager.get());

// Null Settings = default session with server-determined name and settings.
// Create a pipe for streaming warnings from the service to the CLI in real-time.
wil::unique_handle pipeRead;
wil::unique_handle pipeWrite;
THROW_IF_WIN32_BOOL_FALSE(CreatePipe(&pipeRead, &pipeWrite, nullptr, 0));

WSLCSessionSettings settings{};
settings.WarningsPipe = wslutil::ToCOMInputHandle(pipeWrite.get());

wil::com_ptr<IWSLCSession> session;
THROW_IF_FAILED(sessionManager->CreateSession(nullptr, WSLCSessionFlagsNone, &session));
THROW_IF_FAILED(sessionManager->CreateSession(&settings, WSLCSessionFlagsNone, &session));
wsl::windows::common::security::ConfigureForCOMImpersonation(session.get());
return Session(std::move(session));
return Session(std::move(session), std::move(pipeRead), std::move(pipeWrite));
}

int SessionService::Enter(const std::wstring& storagePath, const std::wstring& displayName)
Expand Down
9 changes: 9 additions & 0 deletions src/windows/wslcsession/WSLCSession.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,15 @@ try
m_displayName = Settings->DisplayName ? Settings->DisplayName : L"";
m_featureFlags = Settings->FeatureFlags;

// Set up the warnings pipe for streaming warnings to the CLI.
// The handle is duplicated because COM closes the original when the call completes.
if (Settings->WarningsPipe.Type != WSLCHandleTypeUnknown)
{
const auto warningsPipeHandle = wslutil::FromCOMInputHandle(Settings->WarningsPipe);
m_warningsPipe.reset(wslutil::DuplicateHandle(warningsPipeHandle, GENERIC_WRITE | SYNCHRONIZE));
wsl::windows::common::COMServiceExecutionContext::SetWarningsPipe(m_warningsPipe.get());
}

// Get user token for the current process
const auto tokenInfo = wil::get_token_information<TOKEN_USER>(GetCurrentProcessToken());

Expand Down
3 changes: 3 additions & 0 deletions src/windows/wslcsession/WSLCSession.h
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,9 @@ class DECLSPEC_UUID("4877FEFC-4977-4929-A958-9F36AA1892A4") WSLCSession
std::atomic<bool> m_terminating{false};
std::atomic<bool> m_terminated{false};

// Pipe handle for streaming user warnings to the CLI in real-time.
wil::unique_handle m_warningsPipe;

// User-provided handles that the session is currently doing IO on.
std::mutex m_userHandlesLock;
__guarded_by(m_userHandlesLock) std::vector<HANDLE> m_userHandles;
Expand Down
Loading