master
cpp 160 lines 5.72 KB
Raw
1 /*++
2
3 Copyright (c) Microsoft. All rights reserved.
4
5 Module Name:
6
7 LxssUserCallback.cpp
8
9 Abstract:
10
11 This file contains kernel->user callback function definitions.
12
13 --*/
14
15 #include "precomp.h"
16 #include "LxssUserCallback.h"
17
18 LxssUserCallback::LxssUserCallback(_In_ HANDLE Handle, _In_ LXBUS_USER_CALLBACK_TYPE CallbackType, _In_ const LXSS_USER_CALLBACK& Callback, _In_ ULONG OutputBufferSize) :
19 m_callback(Callback), m_exiting(false), m_callbackType(CallbackType), m_event(wil::EventOptions::ManualReset | wil::EventOptions::Signaled)
20 {
21 // Keep a local copy of the handle so the request can be requeued.
22 THROW_IF_WIN32_BOOL_FALSE(::DuplicateHandle(GetCurrentProcess(), Handle, GetCurrentProcess(), &m_handle, 0, TRUE, DUPLICATE_SAME_ACCESS));
23
24 // All result buffers are derivatives of LXBUS_USER_CALLBACK_DATA.
25 WI_ASSERT(OutputBufferSize >= sizeof(LXBUS_USER_CALLBACK_DATA));
26
27 // Allocate a buffer of the requested size.
28 m_buffer.resize(OutputBufferSize);
29
30 // Set up the threadpool wait callback.
31 PTP_WAIT_CALLBACK ThreadpoolCallback;
32 ThreadpoolCallback = reinterpret_cast<PTP_WAIT_CALLBACK>(&ThreadpoolCallbackProxy);
33
34 // N.B. Using unreferenced 'this' as context parameter since the destructor
35 // should wait for the threadpool thread to complete/unregister.
36 m_threadpoolWait.reset(CreateThreadpoolWait(ThreadpoolCallback, this, nullptr));
37
38 THROW_LAST_ERROR_IF(!m_threadpoolWait);
39 return;
40 }
41
42 LxssUserCallback::~LxssUserCallback()
43 {
44 {
45 // synchronize with the callback thread to avoid a race where m_event
46 // is signalled but the callback is in the middle of queueing up
47 // another IO request.
48 std::lock_guard<std::mutex> lock(m_lock);
49 m_exiting = true;
50 }
51
52 SetThreadpoolWait(m_threadpoolWait.get(), nullptr, nullptr);
53 IO_STATUS_BLOCK ioCancelStatus{};
54 const NTSTATUS status = NtCancelIoFileEx(m_handle.get(), &m_ioStatus, &ioCancelStatus);
55
56 // If the instance has been terminated, the request may already have been
57 // cancelled.
58 if (status != STATUS_NOT_FOUND)
59 {
60 LOG_IF_NTSTATUS_FAILED_MSG(status, "Failed to cancel user callback IO");
61 }
62
63 // Wait for outstanding IO to complete since it references memory owned by
64 // this instance.
65 m_event.wait();
66 }
67
68 VOID LxssUserCallback::QueueRequest()
69 {
70 auto setEventOnFailure = m_event.SetEvent_scope_exit();
71 m_event.ResetEvent();
72 LXBUS_REGISTER_USER_CALLBACK_PARAMETERS parameters;
73 parameters.Input.CallbackType = m_callbackType;
74 const ULONG outputBufferSize = std::min<ULONG>(static_cast<ULONG>(m_buffer.size()), ULONG_MAX);
75
76 THROW_IF_NTSTATUS_FAILED(LxBusClientRegisterUserCallbackAsync(
77 m_handle.get(), m_event.get(), &m_ioStatus, &parameters, &m_buffer.front(), outputBufferSize));
78
79 SetThreadpoolWait(m_threadpoolWait.get(), m_event.get(), nullptr);
80 setEventOnFailure.release();
81 }
82
83 std::unique_ptr<LxssUserCallback> LxssUserCallback::Register(
84 _In_ HANDLE Handle, _In_ LXBUS_USER_CALLBACK_TYPE CallbackType, _In_ const LXSS_USER_CALLBACK& Callback, _In_ ULONG OutputBufferSize)
85 {
86 std::unique_ptr<LxssUserCallback> userCallback(new LxssUserCallback(Handle, CallbackType, Callback, OutputBufferSize));
87
88 userCallback->QueueRequest();
89 return userCallback;
90 }
91
92 VOID LxssUserCallback::ThreadpoolCallback(_Inout_ PTP_CALLBACK_INSTANCE Instance, _Inout_ PTP_WAIT Wait, _In_ TP_WAIT_RESULT WaitResult)
93 {
94 UNREFERENCED_PARAMETER(Instance);
95 UNREFERENCED_PARAMETER(Wait);
96 UNREFERENCED_PARAMETER(WaitResult);
97
98 WI_ASSERT(Wait == m_threadpoolWait.get());
99 WI_ASSERT(WaitResult == WAIT_OBJECT_0);
100
101 if (NT_SUCCESS(m_ioStatus.Status))
102 {
103 WI_ASSERT(m_ioStatus.Information >= sizeof(LXBUS_USER_CALLBACK_DATA));
104
105 const auto callbackData = static_cast<PLXBUS_USER_CALLBACK_DATA>(static_cast<PVOID>(&m_buffer.front()));
106
107 const unsigned long long callbackId = callbackData->CallbackId;
108 NTSTATUS status = STATUS_INTERNAL_ERROR;
109 try
110 {
111 status = m_callback(&m_buffer.front(), m_ioStatus.Information);
112 }
113 CATCH_LOG()
114
115 LXBUS_REGISTER_USER_CALLBACK_PARAMETERS parameters{};
116 parameters.Input.CallbackType = LxBusUserCallbackTypeResult;
117 parameters.Input.ResultData.CallbackId = callbackId;
118 parameters.Input.ResultData.Result = status;
119 LOG_IF_NTSTATUS_FAILED(LxBusClientUserCallbackSendResponse(m_handle.get(), &parameters));
120 }
121 else if (m_ioStatus.Status == STATUS_CANCELLED)
122 {
123 // Don't queue another request if the previous one was canceled.
124 // Cancel should only occur when the instance is shutting down or
125 // when the destructor runs.
126 return;
127 }
128 else
129 {
130 LOG_NTSTATUS_MSG(m_ioStatus.Status, "User callback IO completed with failure");
131 }
132
133 // Requeue another request.
134 //
135 // N.B. If queueing the request fails, the instance will no longer be able
136 // to perform up-calls to the usermode service for this type of
137 // operation. This is benign if the request fails because rundown
138 // could not be acquired due to the instance terminating.
139 //
140 // TODO_LX: use telemetry to determine what other failures can occur so
141 // can be handled gracefully.
142 {
143 std::lock_guard<std::mutex> lock(m_lock);
144 if (!m_exiting)
145 {
146 try
147 {
148 QueueRequest();
149 }
150 CATCH_LOG()
151 }
152 }
153 }
154
155 VOID CALLBACK LxssUserCallback::ThreadpoolCallbackProxy(
156 _Inout_ PTP_CALLBACK_INSTANCE Instance, _Inout_opt_ PVOID Context, _Inout_ PTP_WAIT Wait, _In_ TP_WAIT_RESULT WaitResult)
157 {
158 const auto Self = static_cast<LxssUserCallback*>(Context);
159 Self->ThreadpoolCallback(Instance, Wait, WaitResult);
160 }