Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(72)

Side by Side Diff: chrome/nacl/nacl_ipc_adapter.cc

Issue 16881004: Move chrome/nacl to components/nacl. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Create a zygote folder Created 7 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « chrome/nacl/nacl_ipc_adapter.h ('k') | chrome/nacl/nacl_ipc_adapter_unittest.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright (c) 2012 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 "chrome/nacl/nacl_ipc_adapter.h"
6
7 #include <limits.h>
8 #include <string.h>
9
10 #include "base/basictypes.h"
11 #include "base/bind.h"
12 #include "base/location.h"
13 #include "base/memory/scoped_ptr.h"
14 #include "base/shared_memory.h"
15 #include "build/build_config.h"
16 #include "ipc/ipc_channel.h"
17 #include "ipc/ipc_platform_file.h"
18 #include "native_client/src/trusted/desc/nacl_desc_base.h"
19 #include "native_client/src/trusted/desc/nacl_desc_custom.h"
20 #include "native_client/src/trusted/desc/nacl_desc_imc_shm.h"
21 #include "native_client/src/trusted/desc/nacl_desc_io.h"
22 #include "native_client/src/trusted/desc/nacl_desc_sync_socket.h"
23 #include "native_client/src/trusted/desc/nacl_desc_wrapper.h"
24 #include "native_client/src/trusted/service_runtime/include/sys/fcntl.h"
25 #include "ppapi/c/ppb_file_io.h"
26 #include "ppapi/proxy/ppapi_messages.h"
27 #include "ppapi/proxy/serialized_handle.h"
28
29 namespace {
30
31 enum BufferSizeStatus {
32 // The buffer contains a full message with no extra bytes.
33 MESSAGE_IS_COMPLETE,
34
35 // The message doesn't fit and the buffer contains only some of it.
36 MESSAGE_IS_TRUNCATED,
37
38 // The buffer contains a full message + extra data.
39 MESSAGE_HAS_EXTRA_DATA
40 };
41
42 BufferSizeStatus GetBufferStatus(const char* data, size_t len) {
43 if (len < sizeof(NaClIPCAdapter::NaClMessageHeader))
44 return MESSAGE_IS_TRUNCATED;
45
46 const NaClIPCAdapter::NaClMessageHeader* header =
47 reinterpret_cast<const NaClIPCAdapter::NaClMessageHeader*>(data);
48 uint32 message_size =
49 sizeof(NaClIPCAdapter::NaClMessageHeader) + header->payload_size;
50
51 if (len == message_size)
52 return MESSAGE_IS_COMPLETE;
53 if (len > message_size)
54 return MESSAGE_HAS_EXTRA_DATA;
55 return MESSAGE_IS_TRUNCATED;
56 }
57
58 // This object allows the NaClDesc to hold a reference to a NaClIPCAdapter and
59 // forward calls to it.
60 struct DescThunker {
61 explicit DescThunker(NaClIPCAdapter* adapter_param)
62 : adapter(adapter_param) {
63 }
64 scoped_refptr<NaClIPCAdapter> adapter;
65 };
66
67 NaClIPCAdapter* ToAdapter(void* handle) {
68 return static_cast<DescThunker*>(handle)->adapter.get();
69 }
70
71 // NaClDescCustom implementation.
72 void NaClDescCustomDestroy(void* handle) {
73 delete static_cast<DescThunker*>(handle);
74 }
75
76 ssize_t NaClDescCustomSendMsg(void* handle, const NaClImcTypedMsgHdr* msg,
77 int /* flags */) {
78 return static_cast<ssize_t>(ToAdapter(handle)->Send(msg));
79 }
80
81 ssize_t NaClDescCustomRecvMsg(void* handle, NaClImcTypedMsgHdr* msg,
82 int /* flags */) {
83 return static_cast<ssize_t>(ToAdapter(handle)->BlockingReceive(msg));
84 }
85
86 NaClDesc* MakeNaClDescCustom(NaClIPCAdapter* adapter) {
87 NaClDescCustomFuncs funcs = NACL_DESC_CUSTOM_FUNCS_INITIALIZER;
88 funcs.Destroy = NaClDescCustomDestroy;
89 funcs.SendMsg = NaClDescCustomSendMsg;
90 funcs.RecvMsg = NaClDescCustomRecvMsg;
91 // NaClDescMakeCustomDesc gives us a reference on the returned NaClDesc.
92 return NaClDescMakeCustomDesc(new DescThunker(adapter), &funcs);
93 }
94
95 void DeleteChannel(IPC::Channel* channel) {
96 delete channel;
97 }
98
99 // Translates Pepper's read/write open flags into NaCl's ones. The other open
100 // flags are discarded. If neither of the read/write flags is specified, just
101 // returns NACL_ABI_O_RDONLY as a safe fallback.
102 int TranslatePepperFileReadWriteOpenFlags(int32_t pp_open_flags) {
103 int nacl_open_flag;
104 if ((pp_open_flags & (PP_FILEOPENFLAG_READ | PP_FILEOPENFLAG_WRITE)) ==
105 (PP_FILEOPENFLAG_READ | PP_FILEOPENFLAG_WRITE)) {
106 nacl_open_flag = NACL_ABI_O_RDWR;
107 } else if (pp_open_flags & PP_FILEOPENFLAG_READ) {
108 nacl_open_flag = NACL_ABI_O_RDONLY;
109 } else if (pp_open_flags & PP_FILEOPENFLAG_WRITE) {
110 nacl_open_flag = NACL_ABI_O_WRONLY;
111 } else {
112 DLOG(WARNING) << "PP_FILEOPENFLAG_READ and/or PP_FILEOPENFLAG_WRITE "
113 << "should be specified.";
114 // NACL_ABI_O_RDONLY == 0, so make this ambiguous case readonly as a safe
115 // fallback.
116 nacl_open_flag = NACL_ABI_O_RDONLY;
117 }
118 return nacl_open_flag;
119 }
120
121 class NaClDescWrapper {
122 public:
123 explicit NaClDescWrapper(NaClDesc* desc): desc_(desc) {}
124 ~NaClDescWrapper() {
125 NaClDescUnref(desc_);
126 }
127
128 NaClDesc* desc() { return desc_; }
129
130 private:
131 NaClDesc* desc_;
132 DISALLOW_COPY_AND_ASSIGN(NaClDescWrapper);
133 };
134
135 } // namespace
136
137 class NaClIPCAdapter::RewrittenMessage
138 : public base::RefCounted<RewrittenMessage> {
139 public:
140 RewrittenMessage();
141
142 bool is_consumed() const { return data_read_cursor_ == data_len_; }
143
144 void SetData(const NaClIPCAdapter::NaClMessageHeader& header,
145 const void* payload, size_t payload_length);
146
147 int Read(NaClImcTypedMsgHdr* msg);
148
149 void AddDescriptor(NaClDescWrapper* desc) { descs_.push_back(desc); }
150
151 size_t desc_count() const { return descs_.size(); }
152
153 private:
154 friend class base::RefCounted<RewrittenMessage>;
155 ~RewrittenMessage() {}
156
157 scoped_ptr<char[]> data_;
158 size_t data_len_;
159
160 // Offset into data where the next read will happen. This will be equal to
161 // data_len_ when all data has been consumed.
162 size_t data_read_cursor_;
163
164 // Wrapped descriptors for transfer to untrusted code.
165 ScopedVector<NaClDescWrapper> descs_;
166 };
167
168 NaClIPCAdapter::RewrittenMessage::RewrittenMessage()
169 : data_len_(0),
170 data_read_cursor_(0) {
171 }
172
173 void NaClIPCAdapter::RewrittenMessage::SetData(
174 const NaClIPCAdapter::NaClMessageHeader& header,
175 const void* payload,
176 size_t payload_length) {
177 DCHECK(!data_.get() && data_len_ == 0);
178 size_t header_len = sizeof(NaClIPCAdapter::NaClMessageHeader);
179 data_len_ = header_len + payload_length;
180 data_.reset(new char[data_len_]);
181
182 memcpy(data_.get(), &header, sizeof(NaClIPCAdapter::NaClMessageHeader));
183 memcpy(&data_[header_len], payload, payload_length);
184 }
185
186 int NaClIPCAdapter::RewrittenMessage::Read(NaClImcTypedMsgHdr* msg) {
187 CHECK(data_len_ >= data_read_cursor_);
188 char* dest_buffer = static_cast<char*>(msg->iov[0].base);
189 size_t dest_buffer_size = msg->iov[0].length;
190 size_t bytes_to_write = std::min(dest_buffer_size,
191 data_len_ - data_read_cursor_);
192 if (bytes_to_write == 0)
193 return 0;
194
195 memcpy(dest_buffer, &data_[data_read_cursor_], bytes_to_write);
196 data_read_cursor_ += bytes_to_write;
197
198 // Once all data has been consumed, transfer any file descriptors.
199 if (is_consumed()) {
200 nacl_abi_size_t desc_count = static_cast<nacl_abi_size_t>(descs_.size());
201 CHECK(desc_count <= msg->ndesc_length);
202 msg->ndesc_length = desc_count;
203 for (nacl_abi_size_t i = 0; i < desc_count; i++) {
204 // Copy the NaClDesc to the buffer and add a ref so it won't be freed
205 // when we clear our ScopedVector.
206 msg->ndescv[i] = descs_[i]->desc();
207 NaClDescRef(descs_[i]->desc());
208 }
209 descs_.clear();
210 } else {
211 msg->ndesc_length = 0;
212 }
213 return static_cast<int>(bytes_to_write);
214 }
215
216 NaClIPCAdapter::LockedData::LockedData()
217 : channel_closed_(false) {
218 }
219
220 NaClIPCAdapter::LockedData::~LockedData() {
221 }
222
223 NaClIPCAdapter::IOThreadData::IOThreadData() {
224 }
225
226 NaClIPCAdapter::IOThreadData::~IOThreadData() {
227 }
228
229 NaClIPCAdapter::NaClIPCAdapter(const IPC::ChannelHandle& handle,
230 base::TaskRunner* runner)
231 : lock_(),
232 cond_var_(&lock_),
233 task_runner_(runner),
234 locked_data_() {
235 io_thread_data_.channel_.reset(
236 new IPC::Channel(handle, IPC::Channel::MODE_SERVER, this));
237 // Note, we can not PostTask for ConnectChannelOnIOThread here. If we did,
238 // and that task ran before this constructor completes, the reference count
239 // would go to 1 and then to 0 because of the Task, before we've been returned
240 // to the owning scoped_refptr, which is supposed to give us our first
241 // ref-count.
242 }
243
244 NaClIPCAdapter::NaClIPCAdapter(scoped_ptr<IPC::Channel> channel,
245 base::TaskRunner* runner)
246 : lock_(),
247 cond_var_(&lock_),
248 task_runner_(runner),
249 locked_data_() {
250 io_thread_data_.channel_ = channel.Pass();
251 }
252
253 void NaClIPCAdapter::ConnectChannel() {
254 task_runner_->PostTask(FROM_HERE,
255 base::Bind(&NaClIPCAdapter::ConnectChannelOnIOThread, this));
256 }
257
258 // Note that this message is controlled by the untrusted code. So we should be
259 // skeptical of anything it contains and quick to give up if anything is fishy.
260 int NaClIPCAdapter::Send(const NaClImcTypedMsgHdr* msg) {
261 if (msg->iov_length != 1)
262 return -1;
263
264 base::AutoLock lock(lock_);
265
266 const char* input_data = static_cast<char*>(msg->iov[0].base);
267 size_t input_data_len = msg->iov[0].length;
268 if (input_data_len > IPC::Channel::kMaximumMessageSize) {
269 ClearToBeSent();
270 return -1;
271 }
272
273 // current_message[_len] refers to the total input data received so far.
274 const char* current_message;
275 size_t current_message_len;
276 bool did_append_input_data;
277 if (locked_data_.to_be_sent_.empty()) {
278 // No accumulated data, we can avoid a copy by referring to the input
279 // buffer (the entire message fitting in one call is the common case).
280 current_message = input_data;
281 current_message_len = input_data_len;
282 did_append_input_data = false;
283 } else {
284 // We've already accumulated some data, accumulate this new data and
285 // point to the beginning of the buffer.
286
287 // Make sure our accumulated message size doesn't overflow our max. Since
288 // we know that data_len < max size (checked above) and our current
289 // accumulated value is also < max size, we just need to make sure that
290 // 2x max size can never overflow.
291 COMPILE_ASSERT(IPC::Channel::kMaximumMessageSize < (UINT_MAX / 2),
292 MaximumMessageSizeWillOverflow);
293 size_t new_size = locked_data_.to_be_sent_.size() + input_data_len;
294 if (new_size > IPC::Channel::kMaximumMessageSize) {
295 ClearToBeSent();
296 return -1;
297 }
298
299 locked_data_.to_be_sent_.append(input_data, input_data_len);
300 current_message = &locked_data_.to_be_sent_[0];
301 current_message_len = locked_data_.to_be_sent_.size();
302 did_append_input_data = true;
303 }
304
305 // Check the total data we've accumulated so far to see if it contains a full
306 // message.
307 switch (GetBufferStatus(current_message, current_message_len)) {
308 case MESSAGE_IS_COMPLETE: {
309 // Got a complete message, can send it out. This will be the common case.
310 bool success = SendCompleteMessage(current_message, current_message_len);
311 ClearToBeSent();
312 return success ? static_cast<int>(input_data_len) : -1;
313 }
314 case MESSAGE_IS_TRUNCATED:
315 // For truncated messages, just accumulate the new data (if we didn't
316 // already do so above) and go back to waiting for more.
317 if (!did_append_input_data)
318 locked_data_.to_be_sent_.append(input_data, input_data_len);
319 return static_cast<int>(input_data_len);
320 case MESSAGE_HAS_EXTRA_DATA:
321 default:
322 // When the plugin gives us too much data, it's an error.
323 ClearToBeSent();
324 return -1;
325 }
326 }
327
328 int NaClIPCAdapter::BlockingReceive(NaClImcTypedMsgHdr* msg) {
329 if (msg->iov_length != 1)
330 return -1;
331
332 int retval = 0;
333 {
334 base::AutoLock lock(lock_);
335 while (locked_data_.to_be_received_.empty() &&
336 !locked_data_.channel_closed_)
337 cond_var_.Wait();
338 if (locked_data_.channel_closed_) {
339 retval = -1;
340 } else {
341 retval = LockedReceive(msg);
342 DCHECK(retval > 0);
343 }
344 }
345 cond_var_.Signal();
346 return retval;
347 }
348
349 void NaClIPCAdapter::CloseChannel() {
350 {
351 base::AutoLock lock(lock_);
352 locked_data_.channel_closed_ = true;
353 }
354 cond_var_.Signal();
355
356 task_runner_->PostTask(FROM_HERE,
357 base::Bind(&NaClIPCAdapter::CloseChannelOnIOThread, this));
358 }
359
360 NaClDesc* NaClIPCAdapter::MakeNaClDesc() {
361 return MakeNaClDescCustom(this);
362 }
363
364 #if defined(OS_POSIX)
365 int NaClIPCAdapter::TakeClientFileDescriptor() {
366 return io_thread_data_.channel_->TakeClientFileDescriptor();
367 }
368 #endif
369
370 bool NaClIPCAdapter::OnMessageReceived(const IPC::Message& msg) {
371 {
372 base::AutoLock lock(lock_);
373
374 scoped_refptr<RewrittenMessage> rewritten_msg(new RewrittenMessage);
375
376 typedef std::vector<ppapi::proxy::SerializedHandle> Handles;
377 Handles handles;
378 scoped_ptr<IPC::Message> new_msg_ptr;
379 bool success = locked_data_.handle_converter_.ConvertNativeHandlesToPosix(
380 msg, &handles, &new_msg_ptr);
381 if (!success)
382 return false;
383
384 // Now add any descriptors we found to rewritten_msg. |handles| is usually
385 // empty, unless we read a message containing a FD or handle.
386 for (Handles::const_iterator iter = handles.begin();
387 iter != handles.end();
388 ++iter) {
389 scoped_ptr<NaClDescWrapper> nacl_desc;
390 switch (iter->type()) {
391 case ppapi::proxy::SerializedHandle::SHARED_MEMORY: {
392 const base::SharedMemoryHandle& shm_handle = iter->shmem();
393 uint32_t size = iter->size();
394 nacl_desc.reset(new NaClDescWrapper(NaClDescImcShmMake(
395 #if defined(OS_WIN)
396 shm_handle,
397 #else
398 shm_handle.fd,
399 #endif
400 static_cast<size_t>(size))));
401 break;
402 }
403 case ppapi::proxy::SerializedHandle::SOCKET: {
404 nacl_desc.reset(new NaClDescWrapper(NaClDescSyncSocketMake(
405 #if defined(OS_WIN)
406 iter->descriptor()
407 #else
408 iter->descriptor().fd
409 #endif
410 )));
411 break;
412 }
413 case ppapi::proxy::SerializedHandle::CHANNEL_HANDLE: {
414 // Check that this came from a PpapiMsg_CreateNaClChannel message.
415 // This code here is only appropriate for that message.
416 DCHECK(msg.type() == PpapiMsg_CreateNaClChannel::ID);
417 IPC::ChannelHandle channel_handle =
418 IPC::Channel::GenerateVerifiedChannelID("nacl");
419 scoped_refptr<NaClIPCAdapter> ipc_adapter(
420 new NaClIPCAdapter(channel_handle, task_runner_.get()));
421 ipc_adapter->ConnectChannel();
422 #if defined(OS_POSIX)
423 channel_handle.socket = base::FileDescriptor(
424 ipc_adapter->TakeClientFileDescriptor(), true);
425 #endif
426 nacl_desc.reset(new NaClDescWrapper(ipc_adapter->MakeNaClDesc()));
427 // Send back a message that the channel was created.
428 scoped_ptr<IPC::Message> response(
429 new PpapiHostMsg_ChannelCreated(channel_handle));
430 task_runner_->PostTask(FROM_HERE,
431 base::Bind(&NaClIPCAdapter::SendMessageOnIOThread, this,
432 base::Passed(&response)));
433 break;
434 }
435 case ppapi::proxy::SerializedHandle::FILE:
436 nacl_desc.reset(new NaClDescWrapper(NaClDescIoDescFromHandleAllocCtor(
437 #if defined(OS_WIN)
438 iter->descriptor(),
439 #else
440 iter->descriptor().fd,
441 #endif
442 TranslatePepperFileReadWriteOpenFlags(iter->open_flag()))));
443 break;
444 case ppapi::proxy::SerializedHandle::INVALID: {
445 // Nothing to do. TODO(dmichael): Should we log this? Or is it
446 // sometimes okay to pass an INVALID handle?
447 break;
448 }
449 // No default, so the compiler will warn us if new types get added.
450 }
451 if (nacl_desc.get())
452 rewritten_msg->AddDescriptor(nacl_desc.release());
453 }
454 if (new_msg_ptr && !handles.empty())
455 SaveMessage(*new_msg_ptr, rewritten_msg.get());
456 else
457 SaveMessage(msg, rewritten_msg.get());
458 }
459 cond_var_.Signal();
460 return true;
461 }
462
463 void NaClIPCAdapter::OnChannelConnected(int32 peer_pid) {
464 }
465
466 void NaClIPCAdapter::OnChannelError() {
467 CloseChannel();
468 }
469
470 NaClIPCAdapter::~NaClIPCAdapter() {
471 // Make sure the channel is deleted on the IO thread.
472 task_runner_->PostTask(FROM_HERE,
473 base::Bind(&DeleteChannel, io_thread_data_.channel_.release()));
474 }
475
476 int NaClIPCAdapter::LockedReceive(NaClImcTypedMsgHdr* msg) {
477 lock_.AssertAcquired();
478
479 if (locked_data_.to_be_received_.empty())
480 return 0;
481 scoped_refptr<RewrittenMessage> current =
482 locked_data_.to_be_received_.front();
483
484 int retval = current->Read(msg);
485
486 // When a message is entirely consumed, remove if from the waiting queue.
487 if (current->is_consumed())
488 locked_data_.to_be_received_.pop();
489
490 return retval;
491 }
492
493 bool NaClIPCAdapter::SendCompleteMessage(const char* buffer,
494 size_t buffer_len) {
495 lock_.AssertAcquired();
496 // The message will have already been validated, so we know it's large enough
497 // for our header.
498 const NaClMessageHeader* header =
499 reinterpret_cast<const NaClMessageHeader*>(buffer);
500
501 // Length of the message not including the body. The data passed to us by the
502 // plugin should match that in the message header. This should have already
503 // been validated by GetBufferStatus.
504 int body_len = static_cast<int>(buffer_len - sizeof(NaClMessageHeader));
505 DCHECK(body_len == static_cast<int>(header->payload_size));
506
507 // We actually discard the flags and only copy the ones we care about. This
508 // is just because message doesn't have a constructor that takes raw flags.
509 scoped_ptr<IPC::Message> msg(
510 new IPC::Message(header->routing, header->type,
511 IPC::Message::PRIORITY_NORMAL));
512 if (header->flags & IPC::Message::SYNC_BIT)
513 msg->set_sync();
514 if (header->flags & IPC::Message::REPLY_BIT)
515 msg->set_reply();
516 if (header->flags & IPC::Message::REPLY_ERROR_BIT)
517 msg->set_reply_error();
518 if (header->flags & IPC::Message::UNBLOCK_BIT)
519 msg->set_unblock(true);
520
521 msg->WriteBytes(&buffer[sizeof(NaClMessageHeader)], body_len);
522
523 // Technically we didn't have to do any of the previous work in the lock. But
524 // sometimes our buffer will point to the to_be_sent_ string which is
525 // protected by the lock, and it's messier to factor Send() such that it can
526 // unlock for us. Holding the lock for the message construction, which is
527 // just some memcpys, shouldn't be a big deal.
528 lock_.AssertAcquired();
529 if (locked_data_.channel_closed_)
530 return false; // TODO(brettw) clean up handles here when we add support!
531
532 if (msg->is_sync()) {
533 locked_data_.handle_converter_.RegisterSyncMessageForReply(*msg);
534 }
535 // Actual send must be done on the I/O thread.
536 task_runner_->PostTask(FROM_HERE,
537 base::Bind(&NaClIPCAdapter::SendMessageOnIOThread, this,
538 base::Passed(&msg)));
539 return true;
540 }
541
542 void NaClIPCAdapter::ClearToBeSent() {
543 lock_.AssertAcquired();
544
545 // Don't let the string keep its buffer behind our back.
546 std::string empty;
547 locked_data_.to_be_sent_.swap(empty);
548 }
549
550 void NaClIPCAdapter::ConnectChannelOnIOThread() {
551 if (!io_thread_data_.channel_->Connect())
552 NOTREACHED();
553 }
554
555 void NaClIPCAdapter::CloseChannelOnIOThread() {
556 io_thread_data_.channel_->Close();
557 }
558
559 void NaClIPCAdapter::SendMessageOnIOThread(scoped_ptr<IPC::Message> message) {
560 io_thread_data_.channel_->Send(message.release());
561 }
562
563 void NaClIPCAdapter::SaveMessage(const IPC::Message& msg,
564 RewrittenMessage* rewritten_msg) {
565 lock_.AssertAcquired();
566 // There is some padding in this structure (the "padding" member is 16
567 // bits but this then gets padded to 32 bits). We want to be sure not to
568 // leak data to the untrusted plugin, so zero everything out first.
569 NaClMessageHeader header;
570 memset(&header, 0, sizeof(NaClMessageHeader));
571
572 header.payload_size = static_cast<uint32>(msg.payload_size());
573 header.routing = msg.routing_id();
574 header.type = msg.type();
575 header.flags = msg.flags();
576 header.num_fds = static_cast<int>(rewritten_msg->desc_count());
577
578 rewritten_msg->SetData(header, msg.payload(), msg.payload_size());
579 locked_data_.to_be_received_.push(rewritten_msg);
580 }
581
582 int TranslatePepperFileReadWriteOpenFlagsForTesting(int32_t pp_open_flags) {
583 return TranslatePepperFileReadWriteOpenFlags(pp_open_flags);
584 }
OLDNEW
« no previous file with comments | « chrome/nacl/nacl_ipc_adapter.h ('k') | chrome/nacl/nacl_ipc_adapter_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698