• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2013 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 
5 #include "mojo/system/raw_channel.h"
6 
7 #include <windows.h>
8 
9 #include "base/auto_reset.h"
10 #include "base/bind.h"
11 #include "base/compiler_specific.h"
12 #include "base/lazy_instance.h"
13 #include "base/location.h"
14 #include "base/logging.h"
15 #include "base/macros.h"
16 #include "base/memory/scoped_ptr.h"
17 #include "base/message_loop/message_loop.h"
18 #include "base/synchronization/lock.h"
19 #include "base/win/windows_version.h"
20 #include "mojo/embedder/platform_handle.h"
21 
22 namespace mojo {
23 namespace system {
24 
25 namespace {
26 
27 class VistaOrHigherFunctions {
28  public:
29   VistaOrHigherFunctions();
30 
is_vista_or_higher() const31   bool is_vista_or_higher() const { return is_vista_or_higher_; }
32 
SetFileCompletionNotificationModes(HANDLE handle,UCHAR flags)33   BOOL SetFileCompletionNotificationModes(HANDLE handle, UCHAR flags) {
34     return set_file_completion_notification_modes_(handle, flags);
35   }
36 
CancelIoEx(HANDLE handle,LPOVERLAPPED overlapped)37   BOOL CancelIoEx(HANDLE handle, LPOVERLAPPED overlapped) {
38     return cancel_io_ex_(handle, overlapped);
39   }
40 
41  private:
42   typedef BOOL(WINAPI* SetFileCompletionNotificationModesFunc)(HANDLE, UCHAR);
43   typedef BOOL(WINAPI* CancelIoExFunc)(HANDLE, LPOVERLAPPED);
44 
45   bool is_vista_or_higher_;
46   SetFileCompletionNotificationModesFunc
47       set_file_completion_notification_modes_;
48   CancelIoExFunc cancel_io_ex_;
49 };
50 
VistaOrHigherFunctions()51 VistaOrHigherFunctions::VistaOrHigherFunctions()
52     : is_vista_or_higher_(base::win::GetVersion() >= base::win::VERSION_VISTA),
53       set_file_completion_notification_modes_(nullptr),
54       cancel_io_ex_(nullptr) {
55   if (!is_vista_or_higher_)
56     return;
57 
58   HMODULE module = GetModuleHandleW(L"kernel32.dll");
59   set_file_completion_notification_modes_ =
60       reinterpret_cast<SetFileCompletionNotificationModesFunc>(
61           GetProcAddress(module, "SetFileCompletionNotificationModes"));
62   DCHECK(set_file_completion_notification_modes_);
63 
64   cancel_io_ex_ =
65       reinterpret_cast<CancelIoExFunc>(GetProcAddress(module, "CancelIoEx"));
66   DCHECK(cancel_io_ex_);
67 }
68 
69 base::LazyInstance<VistaOrHigherFunctions> g_vista_or_higher_functions =
70     LAZY_INSTANCE_INITIALIZER;
71 
72 class RawChannelWin : public RawChannel {
73  public:
74   RawChannelWin(embedder::ScopedPlatformHandle handle);
75   virtual ~RawChannelWin();
76 
77   // |RawChannel| public methods:
78   virtual size_t GetSerializedPlatformHandleSize() const OVERRIDE;
79 
80  private:
81   // RawChannelIOHandler receives OS notifications for I/O completion. It must
82   // be created on the I/O thread.
83   //
84   // It manages its own destruction. Destruction happens on the I/O thread when
85   // all the following conditions are satisfied:
86   //   - |DetachFromOwnerNoLock()| has been called;
87   //   - there is no pending read;
88   //   - there is no pending write.
89   class RawChannelIOHandler : public base::MessageLoopForIO::IOHandler {
90    public:
91     RawChannelIOHandler(RawChannelWin* owner,
92                         embedder::ScopedPlatformHandle handle);
93 
handle() const94     HANDLE handle() const { return handle_.get().handle; }
95 
96     // The following methods are only called by the owner on the I/O thread.
97     bool pending_read() const;
98     base::MessageLoopForIO::IOContext* read_context();
99     // Instructs the object to wait for an |OnIOCompleted()| notification.
100     void OnPendingReadStarted();
101 
102     // The following methods are only called by the owner under
103     // |owner_->write_lock()|.
104     bool pending_write_no_lock() const;
105     base::MessageLoopForIO::IOContext* write_context_no_lock();
106     // Instructs the object to wait for an |OnIOCompleted()| notification.
107     void OnPendingWriteStartedNoLock();
108 
109     // |base::MessageLoopForIO::IOHandler| implementation:
110     // Must be called on the I/O thread. It could be called before or after
111     // detached from the owner.
112     virtual void OnIOCompleted(base::MessageLoopForIO::IOContext* context,
113                                DWORD bytes_transferred,
114                                DWORD error) OVERRIDE;
115 
116     // Must be called on the I/O thread under |owner_->write_lock()|.
117     // After this call, the owner must not make any further calls on this
118     // object, and therefore the object is used on the I/O thread exclusively
119     // (if it stays alive).
120     void DetachFromOwnerNoLock(scoped_ptr<ReadBuffer> read_buffer,
121                                scoped_ptr<WriteBuffer> write_buffer);
122 
123    private:
124     virtual ~RawChannelIOHandler();
125 
126     // Returns true if |owner_| has been reset and there is not pending read or
127     // write.
128     // Must be called on the I/O thread.
129     bool ShouldSelfDestruct() const;
130 
131     // Must be called on the I/O thread. It may be called before or after
132     // detaching from the owner.
133     void OnReadCompleted(DWORD bytes_read, DWORD error);
134     // Must be called on the I/O thread. It may be called before or after
135     // detaching from the owner.
136     void OnWriteCompleted(DWORD bytes_written, DWORD error);
137 
138     embedder::ScopedPlatformHandle handle_;
139 
140     // |owner_| is reset on the I/O thread under |owner_->write_lock()|.
141     // Therefore, it may be used on any thread under lock; or on the I/O thread
142     // without locking.
143     RawChannelWin* owner_;
144 
145     // The following members must be used on the I/O thread.
146     scoped_ptr<ReadBuffer> preserved_read_buffer_after_detach_;
147     scoped_ptr<WriteBuffer> preserved_write_buffer_after_detach_;
148     bool suppress_self_destruct_;
149 
150     bool pending_read_;
151     base::MessageLoopForIO::IOContext read_context_;
152 
153     // The following members must be used under |owner_->write_lock()| while the
154     // object is still attached to the owner, and only on the I/O thread
155     // afterwards.
156     bool pending_write_;
157     base::MessageLoopForIO::IOContext write_context_;
158 
159     DISALLOW_COPY_AND_ASSIGN(RawChannelIOHandler);
160   };
161 
162   // |RawChannel| private methods:
163   virtual IOResult Read(size_t* bytes_read) OVERRIDE;
164   virtual IOResult ScheduleRead() OVERRIDE;
165   virtual embedder::ScopedPlatformHandleVectorPtr GetReadPlatformHandles(
166       size_t num_platform_handles,
167       const void* platform_handle_table) OVERRIDE;
168   virtual IOResult WriteNoLock(size_t* platform_handles_written,
169                                size_t* bytes_written) OVERRIDE;
170   virtual IOResult ScheduleWriteNoLock() OVERRIDE;
171   virtual bool OnInit() OVERRIDE;
172   virtual void OnShutdownNoLock(scoped_ptr<ReadBuffer> read_buffer,
173                                 scoped_ptr<WriteBuffer> write_buffer) OVERRIDE;
174 
175   // Passed to |io_handler_| during initialization.
176   embedder::ScopedPlatformHandle handle_;
177 
178   RawChannelIOHandler* io_handler_;
179 
180   const bool skip_completion_port_on_success_;
181 
182   DISALLOW_COPY_AND_ASSIGN(RawChannelWin);
183 };
184 
RawChannelIOHandler(RawChannelWin * owner,embedder::ScopedPlatformHandle handle)185 RawChannelWin::RawChannelIOHandler::RawChannelIOHandler(
186     RawChannelWin* owner,
187     embedder::ScopedPlatformHandle handle)
188     : handle_(handle.Pass()),
189       owner_(owner),
190       suppress_self_destruct_(false),
191       pending_read_(false),
192       pending_write_(false) {
193   memset(&read_context_.overlapped, 0, sizeof(read_context_.overlapped));
194   read_context_.handler = this;
195   memset(&write_context_.overlapped, 0, sizeof(write_context_.overlapped));
196   write_context_.handler = this;
197 
198   owner_->message_loop_for_io()->RegisterIOHandler(handle_.get().handle, this);
199 }
200 
~RawChannelIOHandler()201 RawChannelWin::RawChannelIOHandler::~RawChannelIOHandler() {
202   DCHECK(ShouldSelfDestruct());
203 }
204 
pending_read() const205 bool RawChannelWin::RawChannelIOHandler::pending_read() const {
206   DCHECK(owner_);
207   DCHECK_EQ(base::MessageLoop::current(), owner_->message_loop_for_io());
208   return pending_read_;
209 }
210 
211 base::MessageLoopForIO::IOContext*
read_context()212 RawChannelWin::RawChannelIOHandler::read_context() {
213   DCHECK(owner_);
214   DCHECK_EQ(base::MessageLoop::current(), owner_->message_loop_for_io());
215   return &read_context_;
216 }
217 
OnPendingReadStarted()218 void RawChannelWin::RawChannelIOHandler::OnPendingReadStarted() {
219   DCHECK(owner_);
220   DCHECK_EQ(base::MessageLoop::current(), owner_->message_loop_for_io());
221   DCHECK(!pending_read_);
222   pending_read_ = true;
223 }
224 
pending_write_no_lock() const225 bool RawChannelWin::RawChannelIOHandler::pending_write_no_lock() const {
226   DCHECK(owner_);
227   owner_->write_lock().AssertAcquired();
228   return pending_write_;
229 }
230 
231 base::MessageLoopForIO::IOContext*
write_context_no_lock()232 RawChannelWin::RawChannelIOHandler::write_context_no_lock() {
233   DCHECK(owner_);
234   owner_->write_lock().AssertAcquired();
235   return &write_context_;
236 }
237 
OnPendingWriteStartedNoLock()238 void RawChannelWin::RawChannelIOHandler::OnPendingWriteStartedNoLock() {
239   DCHECK(owner_);
240   owner_->write_lock().AssertAcquired();
241   DCHECK(!pending_write_);
242   pending_write_ = true;
243 }
244 
OnIOCompleted(base::MessageLoopForIO::IOContext * context,DWORD bytes_transferred,DWORD error)245 void RawChannelWin::RawChannelIOHandler::OnIOCompleted(
246     base::MessageLoopForIO::IOContext* context,
247     DWORD bytes_transferred,
248     DWORD error) {
249   DCHECK(!owner_ ||
250          base::MessageLoop::current() == owner_->message_loop_for_io());
251 
252   {
253     // Suppress self-destruction inside |OnReadCompleted()|, etc. (in case they
254     // result in a call to |Shutdown()|).
255     base::AutoReset<bool> resetter(&suppress_self_destruct_, true);
256 
257     if (context == &read_context_)
258       OnReadCompleted(bytes_transferred, error);
259     else if (context == &write_context_)
260       OnWriteCompleted(bytes_transferred, error);
261     else
262       NOTREACHED();
263   }
264 
265   if (ShouldSelfDestruct())
266     delete this;
267 }
268 
DetachFromOwnerNoLock(scoped_ptr<ReadBuffer> read_buffer,scoped_ptr<WriteBuffer> write_buffer)269 void RawChannelWin::RawChannelIOHandler::DetachFromOwnerNoLock(
270     scoped_ptr<ReadBuffer> read_buffer,
271     scoped_ptr<WriteBuffer> write_buffer) {
272   DCHECK(owner_);
273   DCHECK_EQ(base::MessageLoop::current(), owner_->message_loop_for_io());
274   owner_->write_lock().AssertAcquired();
275 
276   // If read/write is pending, we have to retain the corresponding buffer.
277   if (pending_read_)
278     preserved_read_buffer_after_detach_ = read_buffer.Pass();
279   if (pending_write_)
280     preserved_write_buffer_after_detach_ = write_buffer.Pass();
281 
282   owner_ = nullptr;
283   if (ShouldSelfDestruct())
284     delete this;
285 }
286 
ShouldSelfDestruct() const287 bool RawChannelWin::RawChannelIOHandler::ShouldSelfDestruct() const {
288   if (owner_ || suppress_self_destruct_)
289     return false;
290 
291   // Note: Detached, hence no lock needed for |pending_write_|.
292   return !pending_read_ && !pending_write_;
293 }
294 
OnReadCompleted(DWORD bytes_read,DWORD error)295 void RawChannelWin::RawChannelIOHandler::OnReadCompleted(DWORD bytes_read,
296                                                          DWORD error) {
297   DCHECK(!owner_ ||
298          base::MessageLoop::current() == owner_->message_loop_for_io());
299   DCHECK(suppress_self_destruct_);
300 
301   CHECK(pending_read_);
302   pending_read_ = false;
303   if (!owner_)
304     return;
305 
306   if (error == ERROR_SUCCESS) {
307     DCHECK_GT(bytes_read, 0u);
308     owner_->OnReadCompleted(IO_SUCCEEDED, bytes_read);
309   } else if (error == ERROR_BROKEN_PIPE) {
310     DCHECK_EQ(bytes_read, 0u);
311     owner_->OnReadCompleted(IO_FAILED_SHUTDOWN, 0);
312   } else {
313     DCHECK_EQ(bytes_read, 0u);
314     LOG(WARNING) << "ReadFile: " << logging::SystemErrorCodeToString(error);
315     owner_->OnReadCompleted(IO_FAILED_UNKNOWN, 0);
316   }
317 }
318 
OnWriteCompleted(DWORD bytes_written,DWORD error)319 void RawChannelWin::RawChannelIOHandler::OnWriteCompleted(DWORD bytes_written,
320                                                           DWORD error) {
321   DCHECK(!owner_ ||
322          base::MessageLoop::current() == owner_->message_loop_for_io());
323   DCHECK(suppress_self_destruct_);
324 
325   if (!owner_) {
326     // No lock needed.
327     CHECK(pending_write_);
328     pending_write_ = false;
329     return;
330   }
331 
332   {
333     base::AutoLock locker(owner_->write_lock());
334     CHECK(pending_write_);
335     pending_write_ = false;
336   }
337 
338   if (error == ERROR_SUCCESS) {
339     owner_->OnWriteCompleted(IO_SUCCEEDED, 0, bytes_written);
340   } else if (error == ERROR_BROKEN_PIPE) {
341     owner_->OnWriteCompleted(IO_FAILED_SHUTDOWN, 0, 0);
342   } else {
343     LOG(WARNING) << "WriteFile: " << logging::SystemErrorCodeToString(error);
344     owner_->OnWriteCompleted(IO_FAILED_UNKNOWN, 0, 0);
345   }
346 }
347 
RawChannelWin(embedder::ScopedPlatformHandle handle)348 RawChannelWin::RawChannelWin(embedder::ScopedPlatformHandle handle)
349     : handle_(handle.Pass()),
350       io_handler_(nullptr),
351       skip_completion_port_on_success_(
352           g_vista_or_higher_functions.Get().is_vista_or_higher()) {
353   DCHECK(handle_.is_valid());
354 }
355 
~RawChannelWin()356 RawChannelWin::~RawChannelWin() {
357   DCHECK(!io_handler_);
358 }
359 
GetSerializedPlatformHandleSize() const360 size_t RawChannelWin::GetSerializedPlatformHandleSize() const {
361   // TODO(vtl): Implement.
362   return 0;
363 }
364 
Read(size_t * bytes_read)365 RawChannel::IOResult RawChannelWin::Read(size_t* bytes_read) {
366   DCHECK_EQ(base::MessageLoop::current(), message_loop_for_io());
367   DCHECK(io_handler_);
368   DCHECK(!io_handler_->pending_read());
369 
370   char* buffer = nullptr;
371   size_t bytes_to_read = 0;
372   read_buffer()->GetBuffer(&buffer, &bytes_to_read);
373 
374   DWORD bytes_read_dword = 0;
375   BOOL result = ReadFile(io_handler_->handle(),
376                          buffer,
377                          static_cast<DWORD>(bytes_to_read),
378                          &bytes_read_dword,
379                          &io_handler_->read_context()->overlapped);
380   if (!result) {
381     DCHECK_EQ(bytes_read_dword, 0u);
382     DWORD error = GetLastError();
383     if (error == ERROR_BROKEN_PIPE)
384       return IO_FAILED_SHUTDOWN;
385     if (error != ERROR_IO_PENDING) {
386       LOG(WARNING) << "ReadFile: " << logging::SystemErrorCodeToString(error);
387       return IO_FAILED_UNKNOWN;
388     }
389   }
390 
391   if (result && skip_completion_port_on_success_) {
392     *bytes_read = bytes_read_dword;
393     return IO_SUCCEEDED;
394   }
395 
396   // If the read is pending or the read has succeeded but we don't skip
397   // completion port on success, instruct |io_handler_| to wait for the
398   // completion packet.
399   //
400   // TODO(yzshen): It seems there isn't document saying that all error cases
401   // (other than ERROR_IO_PENDING) are guaranteed to *not* queue a completion
402   // packet. If we do get one for errors, |RawChannelIOHandler::OnIOCompleted()|
403   // will crash so we will learn about it.
404 
405   io_handler_->OnPendingReadStarted();
406   return IO_PENDING;
407 }
408 
ScheduleRead()409 RawChannel::IOResult RawChannelWin::ScheduleRead() {
410   DCHECK_EQ(base::MessageLoop::current(), message_loop_for_io());
411   DCHECK(io_handler_);
412   DCHECK(!io_handler_->pending_read());
413 
414   size_t bytes_read = 0;
415   IOResult io_result = Read(&bytes_read);
416   if (io_result == IO_SUCCEEDED) {
417     DCHECK(skip_completion_port_on_success_);
418 
419     // We have finished reading successfully. Queue a notification manually.
420     io_handler_->OnPendingReadStarted();
421     // |io_handler_| won't go away before the task is run, so it is safe to use
422     // |base::Unretained()|.
423     message_loop_for_io()->PostTask(
424         FROM_HERE,
425         base::Bind(&RawChannelIOHandler::OnIOCompleted,
426                    base::Unretained(io_handler_),
427                    base::Unretained(io_handler_->read_context()),
428                    static_cast<DWORD>(bytes_read),
429                    ERROR_SUCCESS));
430     return IO_PENDING;
431   }
432 
433   return io_result;
434 }
435 
GetReadPlatformHandles(size_t num_platform_handles,const void * platform_handle_table)436 embedder::ScopedPlatformHandleVectorPtr RawChannelWin::GetReadPlatformHandles(
437     size_t num_platform_handles,
438     const void* platform_handle_table) {
439   // TODO(vtl): Implement.
440   NOTIMPLEMENTED();
441   return embedder::ScopedPlatformHandleVectorPtr();
442 }
443 
WriteNoLock(size_t * platform_handles_written,size_t * bytes_written)444 RawChannel::IOResult RawChannelWin::WriteNoLock(
445     size_t* platform_handles_written,
446     size_t* bytes_written) {
447   write_lock().AssertAcquired();
448 
449   DCHECK(io_handler_);
450   DCHECK(!io_handler_->pending_write_no_lock());
451 
452   if (write_buffer_no_lock()->HavePlatformHandlesToSend()) {
453     // TODO(vtl): Implement.
454     NOTIMPLEMENTED();
455   }
456 
457   std::vector<WriteBuffer::Buffer> buffers;
458   write_buffer_no_lock()->GetBuffers(&buffers);
459   DCHECK(!buffers.empty());
460 
461   // TODO(yzshen): Handle multi-segment writes more efficiently.
462   DWORD bytes_written_dword = 0;
463   BOOL result = WriteFile(io_handler_->handle(),
464                           buffers[0].addr,
465                           static_cast<DWORD>(buffers[0].size),
466                           &bytes_written_dword,
467                           &io_handler_->write_context_no_lock()->overlapped);
468   if (!result) {
469     DWORD error = GetLastError();
470     if (error == ERROR_BROKEN_PIPE)
471       return IO_FAILED_SHUTDOWN;
472     if (error != ERROR_IO_PENDING) {
473       LOG(WARNING) << "WriteFile: " << logging::SystemErrorCodeToString(error);
474       return IO_FAILED_UNKNOWN;
475     }
476   }
477 
478   if (result && skip_completion_port_on_success_) {
479     *platform_handles_written = 0;
480     *bytes_written = bytes_written_dword;
481     return IO_SUCCEEDED;
482   }
483 
484   // If the write is pending or the write has succeeded but we don't skip
485   // completion port on success, instruct |io_handler_| to wait for the
486   // completion packet.
487   //
488   // TODO(yzshen): it seems there isn't document saying that all error cases
489   // (other than ERROR_IO_PENDING) are guaranteed to *not* queue a completion
490   // packet. If we do get one for errors, |RawChannelIOHandler::OnIOCompleted()|
491   // will crash so we will learn about it.
492 
493   io_handler_->OnPendingWriteStartedNoLock();
494   return IO_PENDING;
495 }
496 
ScheduleWriteNoLock()497 RawChannel::IOResult RawChannelWin::ScheduleWriteNoLock() {
498   write_lock().AssertAcquired();
499 
500   DCHECK(io_handler_);
501   DCHECK(!io_handler_->pending_write_no_lock());
502 
503   // TODO(vtl): Do something with |platform_handles_written|.
504   size_t platform_handles_written = 0;
505   size_t bytes_written = 0;
506   IOResult io_result = WriteNoLock(&platform_handles_written, &bytes_written);
507   if (io_result == IO_SUCCEEDED) {
508     DCHECK(skip_completion_port_on_success_);
509 
510     // We have finished writing successfully. Queue a notification manually.
511     io_handler_->OnPendingWriteStartedNoLock();
512     // |io_handler_| won't go away before that task is run, so it is safe to use
513     // |base::Unretained()|.
514     message_loop_for_io()->PostTask(
515         FROM_HERE,
516         base::Bind(&RawChannelIOHandler::OnIOCompleted,
517                    base::Unretained(io_handler_),
518                    base::Unretained(io_handler_->write_context_no_lock()),
519                    static_cast<DWORD>(bytes_written),
520                    ERROR_SUCCESS));
521     return IO_PENDING;
522   }
523 
524   return io_result;
525 }
526 
OnInit()527 bool RawChannelWin::OnInit() {
528   DCHECK_EQ(base::MessageLoop::current(), message_loop_for_io());
529 
530   DCHECK(handle_.is_valid());
531   if (skip_completion_port_on_success_ &&
532       !g_vista_or_higher_functions.Get().SetFileCompletionNotificationModes(
533           handle_.get().handle, FILE_SKIP_COMPLETION_PORT_ON_SUCCESS)) {
534     return false;
535   }
536 
537   DCHECK(!io_handler_);
538   io_handler_ = new RawChannelIOHandler(this, handle_.Pass());
539 
540   return true;
541 }
542 
OnShutdownNoLock(scoped_ptr<ReadBuffer> read_buffer,scoped_ptr<WriteBuffer> write_buffer)543 void RawChannelWin::OnShutdownNoLock(scoped_ptr<ReadBuffer> read_buffer,
544                                      scoped_ptr<WriteBuffer> write_buffer) {
545   DCHECK_EQ(base::MessageLoop::current(), message_loop_for_io());
546   DCHECK(io_handler_);
547 
548   write_lock().AssertAcquired();
549 
550   if (io_handler_->pending_read() || io_handler_->pending_write_no_lock()) {
551     // |io_handler_| will be alive until pending read/write (if any) completes.
552     // Call |CancelIoEx()| or |CancelIo()| so that resources can be freed up as
553     // soon as possible.
554     // Note: |CancelIo()| only cancels read/write requests started from this
555     // thread.
556     if (g_vista_or_higher_functions.Get().is_vista_or_higher()) {
557       g_vista_or_higher_functions.Get().CancelIoEx(io_handler_->handle(),
558                                                    nullptr);
559     } else {
560       CancelIo(io_handler_->handle());
561     }
562   }
563 
564   io_handler_->DetachFromOwnerNoLock(read_buffer.Pass(), write_buffer.Pass());
565   io_handler_ = nullptr;
566 }
567 
568 }  // namespace
569 
570 // -----------------------------------------------------------------------------
571 
572 // Static factory method declared in raw_channel.h.
573 // static
Create(embedder::ScopedPlatformHandle handle)574 scoped_ptr<RawChannel> RawChannel::Create(
575     embedder::ScopedPlatformHandle handle) {
576   return scoped_ptr<RawChannel>(new RawChannelWin(handle.Pass()));
577 }
578 
579 }  // namespace system
580 }  // namespace mojo
581