mojo_trap.cc 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425
  1. // Copyright 2022 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "mojo/core/ipcz_driver/mojo_trap.h"
  5. #include <cstdint>
  6. #include <tuple>
  7. #include <utility>
  8. #include "base/check_op.h"
  9. #include "base/memory/ref_counted.h"
  10. #include "base/notreached.h"
  11. #include "mojo/core/ipcz_api.h"
  12. namespace mojo::core::ipcz_driver {
  13. namespace {
  14. // Translates Mojo signal conditions to equivalent IpczTrapConditions.
  15. void GetConditionsForSignals(MojoHandleSignals signals,
  16. IpczTrapConditions* conditions) {
  17. conditions->flags |= IPCZ_TRAP_DEAD;
  18. if (signals & MOJO_HANDLE_SIGNAL_WRITABLE) {
  19. // TODO: Portals should be able to set default put limits that apply to all
  20. // of their put operations unless overridden. For now this hack effectively
  21. // gives all data pipes 2 MB of capacity.
  22. conditions->flags |= IPCZ_TRAP_BELOW_MAX_REMOTE_BYTES;
  23. conditions->max_remote_bytes = 2 * 1024 * 1024;
  24. }
  25. if (signals & MOJO_HANDLE_SIGNAL_READABLE) {
  26. // Mojo's readable signal is equivalent to the condition of having more than
  27. // zero parcels available to retrieve from a portal.
  28. conditions->flags |= IPCZ_TRAP_ABOVE_MIN_LOCAL_PARCELS;
  29. conditions->min_local_parcels = 0;
  30. } else if (signals & MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE) {
  31. // Data pipe consumers often use the edge-triggered NEW_DATA_READABLE
  32. // signal, which is effectively equivalent to NEW_LOCAL_PARCEL in ipcz.
  33. conditions->flags |= IPCZ_TRAP_NEW_LOCAL_PARCEL;
  34. }
  35. if (signals & MOJO_HANDLE_SIGNAL_PEER_CLOSED) {
  36. conditions->flags |= IPCZ_TRAP_PEER_CLOSED;
  37. }
  38. }
  39. // Given an ipcz trap event resulting from an installed trigger, this translates
  40. // the event into an equivalent Mojo trap event for the containing Mojo trap.
  41. void TranslateIpczToMojoEvent(MojoHandleSignals trigger_signals,
  42. uintptr_t trigger_context,
  43. IpczTrapConditionFlags current_condition_flags,
  44. const IpczPortalStatus& current_status,
  45. MojoTrapEvent* event) {
  46. event->flags = 0;
  47. event->trigger_context = trigger_context;
  48. // In practice handles are watched for one or the other of READABALE or
  49. // NEW_DATA_READABLE, but never both.
  50. const MojoHandleSignals kRead =
  51. (trigger_signals & MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE)
  52. ? MOJO_HANDLE_SIGNAL_NEW_DATA_READABLE
  53. : MOJO_HANDLE_SIGNAL_READABLE;
  54. const MojoHandleSignals kWrite = MOJO_HANDLE_SIGNAL_WRITABLE;
  55. const MojoHandleSignals kPeerClosed = MOJO_HANDLE_SIGNAL_PEER_CLOSED;
  56. MojoHandleSignals& satisfied = event->signals_state.satisfied_signals;
  57. MojoHandleSignals& satisfiable = event->signals_state.satisfiable_signals;
  58. satisfied = 0;
  59. satisfiable = kPeerClosed | MOJO_HANDLE_SIGNAL_QUOTA_EXCEEDED |
  60. MOJO_HANDLE_SIGNAL_PEER_REMOTE;
  61. if (!(current_status.flags & IPCZ_PORTAL_STATUS_DEAD)) {
  62. satisfiable |= kRead | kPeerClosed;
  63. }
  64. if (current_status.flags & IPCZ_PORTAL_STATUS_PEER_CLOSED) {
  65. satisfied |= kPeerClosed;
  66. } else {
  67. satisfied |= kWrite;
  68. satisfiable |= kWrite;
  69. }
  70. if (current_status.num_local_parcels > 0) {
  71. satisfied |= kRead;
  72. }
  73. DCHECK((satisfied & satisfiable) == satisfied);
  74. if ((satisfiable & trigger_signals) == 0) {
  75. event->result = MOJO_RESULT_FAILED_PRECONDITION;
  76. return;
  77. }
  78. event->result = MOJO_RESULT_OK;
  79. }
  80. } // namespace
  81. // A Trigger is used as context for every trigger added to a Mojo trap. While a
  82. // trap is armed, each of its Triggers has installed a unique ipcz trap to watch
  83. // for its conditions.
  84. struct MojoTrap::Trigger : public base::RefCountedThreadSafe<Trigger> {
  85. Trigger(scoped_refptr<MojoTrap> mojo_trap,
  86. MojoHandle handle,
  87. MojoHandleSignals signals,
  88. uintptr_t trigger_context)
  89. : mojo_trap(std::move(mojo_trap)),
  90. handle(handle),
  91. signals(signals),
  92. trigger_context(trigger_context) {}
  93. uintptr_t ipcz_context() const { return reinterpret_cast<uintptr_t>(this); }
  94. static Trigger& FromEvent(const IpczTrapEvent& event) {
  95. return *reinterpret_cast<Trigger*>(event.context);
  96. }
  97. const scoped_refptr<MojoTrap> mojo_trap;
  98. const MojoHandle handle;
  99. const MojoHandleSignals signals;
  100. const uintptr_t trigger_context;
  101. IpczTrapConditions conditions = {.size = sizeof(conditions), .flags = 0};
  102. // Access is effectively guarded by the owning MojoTrap's `lock_`.
  103. bool armed = false;
  104. bool removed = false;
  105. private:
  106. friend class base::RefCountedThreadSafe<Trigger>;
  107. ~Trigger() = default;
  108. };
  109. MojoTrap::MojoTrap(MojoTrapEventHandler handler) : handler_(handler) {}
  110. MojoTrap::~MojoTrap() = default;
  111. MojoResult MojoTrap::AddTrigger(MojoHandle handle,
  112. MojoHandleSignals signals,
  113. MojoTriggerCondition condition,
  114. uintptr_t trigger_context) {
  115. if (!handle) {
  116. return MOJO_RESULT_INVALID_ARGUMENT;
  117. }
  118. auto trigger =
  119. base::MakeRefCounted<Trigger>(this, handle, signals, trigger_context);
  120. if (condition == MOJO_TRIGGER_CONDITION_SIGNALS_UNSATISFIED) {
  121. // There's only one user of MOJO_TRIGGER_CONDITION_SIGNALS_UNSATISFIED. It's
  122. // used for peer remoteness tracking in Mojo bindings lazy serialization.
  123. // That is effectively a dead feature, so we don't need to support watching
  124. // for unsatisfied signals.
  125. trigger->conditions.flags = IPCZ_NO_FLAGS;
  126. } else {
  127. GetConditionsForSignals(signals, &trigger->conditions);
  128. }
  129. IpczTrapConditionFlags flags;
  130. IpczPortalStatus status = {sizeof(status)};
  131. {
  132. base::AutoLock lock(lock_);
  133. auto [it, ok] = triggers_.try_emplace(trigger_context, trigger);
  134. if (!ok) {
  135. return MOJO_RESULT_ALREADY_EXISTS;
  136. }
  137. next_trigger_ = triggers_.begin();
  138. // Install an ipcz trap to effectively monitor the lifetime of the watched
  139. // object referenced by `handle`. Installation of the trap should always
  140. // succeed, and its resulting trap event will always mark the end of this
  141. // trigger's lifetime. This trap effectively owns a ref to the Trigger, as
  142. // added here.
  143. trigger->AddRef();
  144. IpczTrapConditions removal_conditions = {
  145. .size = sizeof(removal_conditions),
  146. .flags = IPCZ_TRAP_REMOVED,
  147. };
  148. IpczResult result = GetIpczAPI().Trap(
  149. handle, &removal_conditions, &TrapRemovalEventHandler,
  150. trigger->ipcz_context(), IPCZ_NO_FLAGS, nullptr, nullptr, nullptr);
  151. CHECK_EQ(result, IPCZ_RESULT_OK);
  152. if (!armed_) {
  153. return MOJO_RESULT_OK;
  154. }
  155. // The Mojo trap is already armed, so attempt to install an ipcz trap for
  156. // the new trigger immediately.
  157. result = ArmTrigger(*trigger, &flags, &status);
  158. if (result == IPCZ_RESULT_OK) {
  159. return MOJO_RESULT_OK;
  160. }
  161. // The new trigger already needs to fire an event. OK.
  162. armed_ = false;
  163. }
  164. MojoTrapEvent event = {.struct_size = sizeof(event)};
  165. TranslateIpczToMojoEvent(signals, trigger_context, flags, status, &event);
  166. event.flags = MOJO_TRAP_EVENT_FLAG_WITHIN_API_CALL;
  167. handler_(&event);
  168. return MOJO_RESULT_OK;
  169. }
  170. MojoResult MojoTrap::RemoveTrigger(uintptr_t trigger_context) {
  171. scoped_refptr<Trigger> trigger;
  172. {
  173. base::AutoLock lock(lock_);
  174. auto it = triggers_.find(trigger_context);
  175. if (it == triggers_.end()) {
  176. return MOJO_RESULT_NOT_FOUND;
  177. }
  178. trigger = std::move(it->second);
  179. trigger->armed = false;
  180. trigger->removed = true;
  181. triggers_.erase(it);
  182. next_trigger_ = triggers_.begin();
  183. }
  184. NotifyTriggerRemoved(*trigger);
  185. return MOJO_RESULT_OK;
  186. }
  187. MojoResult MojoTrap::Arm(MojoTrapEvent* blocking_events,
  188. uint32_t* num_blocking_events) {
  189. const uint32_t event_capacity =
  190. num_blocking_events ? *num_blocking_events : 0;
  191. if (event_capacity > 0 && !blocking_events) {
  192. return MOJO_RESULT_INVALID_ARGUMENT;
  193. }
  194. if (event_capacity > 0 &&
  195. blocking_events[0].struct_size < sizeof(blocking_events[0])) {
  196. return MOJO_RESULT_INVALID_ARGUMENT;
  197. }
  198. base::AutoLock lock(lock_);
  199. if (armed_) {
  200. return MOJO_RESULT_OK;
  201. }
  202. if (triggers_.empty()) {
  203. return MOJO_RESULT_NOT_FOUND;
  204. }
  205. uint32_t num_events_returned = 0;
  206. IpczTrapConditionFlags flags;
  207. IpczPortalStatus status = {sizeof(status)};
  208. auto increment_wrapped = [this](TriggerMap::iterator it) {
  209. lock_.AssertAcquired();
  210. if (++it != triggers_.end()) {
  211. return it;
  212. }
  213. return triggers_.begin();
  214. };
  215. TriggerMap::iterator next_trigger = next_trigger_;
  216. DCHECK(next_trigger != triggers_.end());
  217. // We iterate over all triggers, starting just beyond wherever we started last
  218. // time we were armed. This guards against any single trigger being starved.
  219. const TriggerMap::iterator end_trigger = next_trigger;
  220. do {
  221. auto& [trigger_context, trigger] = *next_trigger;
  222. next_trigger = increment_wrapped(next_trigger);
  223. const IpczResult result = ArmTrigger(*trigger, &flags, &status);
  224. if (result == IPCZ_RESULT_OK) {
  225. // Trap successfully installed, nothing else to do for this trigger.
  226. continue;
  227. }
  228. if (result != IPCZ_RESULT_FAILED_PRECONDITION) {
  229. NOTREACHED();
  230. return result;
  231. }
  232. // The ipcz trap failed to install, so this trigger's conditions are already
  233. // met. Accumulate would-be event details if there's output space.
  234. if (event_capacity == 0) {
  235. return MOJO_RESULT_FAILED_PRECONDITION;
  236. }
  237. TranslateIpczToMojoEvent(trigger->signals, trigger->trigger_context, flags,
  238. status, &blocking_events[num_events_returned++]);
  239. } while (next_trigger != end_trigger && num_events_returned < event_capacity);
  240. if (next_trigger != end_trigger) {
  241. next_trigger_ = next_trigger;
  242. } else {
  243. next_trigger_ = increment_wrapped(next_trigger);
  244. }
  245. if (num_events_returned > 0) {
  246. *num_blocking_events = num_events_returned;
  247. return MOJO_RESULT_FAILED_PRECONDITION;
  248. }
  249. // The whole Mojo trap is collectively armed if and only if all of the
  250. // triggers managed to install an ipcz trap.
  251. armed_ = true;
  252. return MOJO_RESULT_OK;
  253. }
  254. void MojoTrap::Close() {
  255. TriggerMap triggers;
  256. {
  257. // Effectively disable all triggers. A disabled trigger may have already
  258. // installed an ipcz trap which hasn't yet fired an event. This ensures that
  259. // if any such event does eventually fire, it will be ignored.
  260. base::AutoLock lock(lock_);
  261. std::swap(triggers, triggers_);
  262. next_trigger_ = triggers_.begin();
  263. for (auto& [trigger_context, trigger] : triggers) {
  264. trigger->armed = false;
  265. DCHECK(!trigger->removed);
  266. trigger->removed = true;
  267. }
  268. }
  269. for (auto& [trigger_context, trigger] : triggers) {
  270. NotifyTriggerRemoved(*trigger);
  271. }
  272. }
  273. // static
  274. void MojoTrap::TrapEventHandler(const IpczTrapEvent* event) {
  275. Trigger::FromEvent(*event).mojo_trap->HandleEvent(*event);
  276. }
  277. // static
  278. void MojoTrap::TrapRemovalEventHandler(const IpczTrapEvent* event) {
  279. Trigger& trigger = Trigger::FromEvent(*event);
  280. trigger.mojo_trap->HandleTrapRemoved(*event);
  281. // Balanced by AddRef when installing the trigger's removal ipcz trap.
  282. trigger.Release();
  283. }
  284. void MojoTrap::HandleEvent(const IpczTrapEvent& event) {
  285. Trigger& trigger = Trigger::FromEvent(event);
  286. {
  287. base::AutoLock lock(lock_);
  288. const bool trigger_active = armed_ && trigger.armed && !trigger.removed;
  289. const bool is_removal = (event.condition_flags & IPCZ_TRAP_REMOVED) != 0;
  290. trigger.armed = false;
  291. if (!trigger_active || is_removal) {
  292. // Removal events are handled separately by ipcz traps established at
  293. // trigger creation, allowing handle closure to trigger an event even when
  294. // the Mojo trap isn't armed.
  295. return;
  296. }
  297. armed_ = false;
  298. }
  299. MojoTrapEvent mojo_event = {.struct_size = sizeof(mojo_event)};
  300. TranslateIpczToMojoEvent(trigger.signals, trigger.trigger_context,
  301. event.condition_flags, *event.status, &mojo_event);
  302. mojo_event.flags |= MOJO_TRAP_EVENT_FLAG_WITHIN_API_CALL;
  303. // Balanced by AddRef when installing the trigger's ipcz trap.
  304. trigger.Release();
  305. handler_(&mojo_event);
  306. }
  307. void MojoTrap::HandleTrapRemoved(const IpczTrapEvent& event) {
  308. Trigger& trigger = Trigger::FromEvent(event);
  309. {
  310. base::AutoLock lock(lock_);
  311. if (trigger.removed) {
  312. // The Mojo trap may have already been closed, in which case this trigger
  313. // was already removed and its handler was already notified.
  314. return;
  315. }
  316. triggers_.erase(trigger.trigger_context);
  317. trigger.removed = true;
  318. next_trigger_ = triggers_.begin();
  319. }
  320. NotifyTriggerRemoved(trigger);
  321. }
  322. IpczResult MojoTrap::ArmTrigger(Trigger& trigger,
  323. IpczTrapConditionFlags* satisfied_flags,
  324. IpczPortalStatus* status) {
  325. lock_.AssertAcquired();
  326. if (trigger.armed) {
  327. return IPCZ_RESULT_OK;
  328. }
  329. // Bump the ref count on the Trigger. This ref is effectively owned by the
  330. // installed trap, if it's installed successfully.
  331. trigger.AddRef();
  332. IpczResult result = GetIpczAPI().Trap(
  333. trigger.handle, &trigger.conditions, &TrapEventHandler,
  334. trigger.ipcz_context(), IPCZ_NO_FLAGS, nullptr, satisfied_flags, status);
  335. if (result == IPCZ_RESULT_OK) {
  336. trigger.armed = true;
  337. } else {
  338. // Balances the AddRef above, since no trap was installed.
  339. trigger.Release();
  340. }
  341. return result;
  342. }
  343. void MojoTrap::NotifyTriggerRemoved(Trigger& trigger) {
  344. MojoTrapEvent mojo_event = {
  345. .struct_size = sizeof(mojo_event),
  346. .flags = MOJO_TRAP_EVENT_FLAG_WITHIN_API_CALL,
  347. .trigger_context = trigger.trigger_context,
  348. .result = MOJO_RESULT_CANCELLED,
  349. .signals_state = {.satisfied_signals = 0, .satisfiable_signals = 0},
  350. };
  351. handler_(&mojo_event);
  352. }
  353. } // namespace mojo::core::ipcz_driver