OLD | NEW |
---|---|
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "components/nacl/browser/nacl_process_host.h" | 5 #include "components/nacl/browser/nacl_process_host.h" |
6 | 6 |
7 #include <algorithm> | 7 #include <algorithm> |
8 #include <string> | 8 #include <string> |
9 #include <vector> | 9 #include <vector> |
10 | 10 |
(...skipping 185 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
196 | 196 |
197 void CloseFile(base::File file) { | 197 void CloseFile(base::File file) { |
198 // The base::File destructor will close the file for us. | 198 // The base::File destructor will close the file for us. |
199 } | 199 } |
200 | 200 |
201 } // namespace | 201 } // namespace |
202 | 202 |
203 unsigned NaClProcessHost::keepalive_throttle_interval_milliseconds_ = | 203 unsigned NaClProcessHost::keepalive_throttle_interval_milliseconds_ = |
204 ppapi::kKeepaliveThrottleIntervalDefaultMilliseconds; | 204 ppapi::kKeepaliveThrottleIntervalDefaultMilliseconds; |
205 | 205 |
206 // Unfortunately, we cannot use ScopedGeneric directly for IPC::ChannelHandle, | |
207 // because there is neither operator== nor operator != definition for it. | |
208 // Instead, define a simple wrapper for IPC::ChannelHandle with an assumption | |
209 // that this only takes a transferred IPC::ChannelHandle or one to be | |
210 // transferred via IPC. | |
211 class NaClProcessHost::ScopedChannelHandle { | |
212 MOVE_ONLY_TYPE_FOR_CPP_03(ScopedChannelHandle, RValue); | |
213 public: | |
214 ScopedChannelHandle() { | |
215 } | |
216 explicit ScopedChannelHandle(const IPC::ChannelHandle& handle) | |
217 : handle_(handle) { | |
218 DCHECK(IsSupportedHandle(handle_)); | |
219 } | |
220 ScopedChannelHandle(RValue other) : handle_(other.object->handle_) { | |
221 other.object->handle_ = IPC::ChannelHandle(); | |
222 DCHECK(IsSupportedHandle(handle_)); | |
223 } | |
224 ~ScopedChannelHandle() { | |
225 CloseIfNecessary(); | |
226 } | |
227 | |
228 const IPC::ChannelHandle& get() const { return handle_; } | |
229 IPC::ChannelHandle release() WARN_UNUSED_RESULT { | |
230 IPC::ChannelHandle result = handle_; | |
231 handle_ = IPC::ChannelHandle(); | |
232 return result; | |
233 } | |
234 | |
235 void reset(const IPC::ChannelHandle& handle = IPC::ChannelHandle()) { | |
236 DCHECK(IsSupportedHandle(handle)); | |
237 #if defined(OS_POSIX) | |
238 // Following the manner of base::ScopedGeneric, we do not support | |
239 // reset() with same handle for simplicity of the implementation. | |
240 CHECK(handle.socket.fd == -1 || handle.socket.fd != handle_.socket.fd); | |
241 #endif | |
242 CloseIfNecessary(); | |
243 handle_ = handle; | |
244 } | |
245 | |
246 private: | |
247 // Returns true if the given handle is closable automatically by this | |
248 // class. This function is just a helper for validation. | |
249 static bool IsSupportedHandle(const IPC::ChannelHandle& handle) { | |
250 #if defined(OS_WIN) | |
251 // On Windows, it is not supported to marshal the |pipe.handle|. | |
252 // In our case, we wrap a transferred ChannelHandle (or one to be | |
253 // transferred) via IPC, so we can assume |handle.pipe.handle| is NULL. | |
254 return handle.pipe.handle == NULL; | |
255 #else | |
256 return true; | |
257 #endif | |
258 } | |
259 | |
260 void CloseIfNecessary() { | |
261 #if defined(OS_POSIX) | |
262 if (handle_.socket.auto_close) { | |
263 // Defer closing task to the ScopedFD. | |
264 base::ScopedFD(handle_.socket.fd); | |
265 } | |
266 #endif | |
267 } | |
268 | |
269 IPC::ChannelHandle handle_; | |
270 }; | |
271 | |
206 NaClProcessHost::NaClProcessHost( | 272 NaClProcessHost::NaClProcessHost( |
207 const GURL& manifest_url, | 273 const GURL& manifest_url, |
208 base::File nexe_file, | 274 base::File nexe_file, |
209 const NaClFileToken& nexe_token, | 275 const NaClFileToken& nexe_token, |
210 const std::vector<NaClResourcePrefetchResult>& prefetched_resource_files, | 276 const std::vector<NaClResourcePrefetchResult>& prefetched_resource_files, |
211 ppapi::PpapiPermissions permissions, | 277 ppapi::PpapiPermissions permissions, |
212 int render_view_id, | 278 int render_view_id, |
213 uint32 permission_bits, | 279 uint32 permission_bits, |
214 bool uses_nonsfi_mode, | 280 bool uses_nonsfi_mode, |
215 bool off_the_record, | 281 bool off_the_record, |
(...skipping 433 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
649 void NaClProcessHost::OnResourcesReady() { | 715 void NaClProcessHost::OnResourcesReady() { |
650 NaClBrowser* nacl_browser = NaClBrowser::GetInstance(); | 716 NaClBrowser* nacl_browser = NaClBrowser::GetInstance(); |
651 if (!nacl_browser->IsReady()) { | 717 if (!nacl_browser->IsReady()) { |
652 SendErrorToRenderer("could not acquire shared resources needed by NaCl"); | 718 SendErrorToRenderer("could not acquire shared resources needed by NaCl"); |
653 delete this; | 719 delete this; |
654 } else if (!StartNaClExecution()) { | 720 } else if (!StartNaClExecution()) { |
655 delete this; | 721 delete this; |
656 } | 722 } |
657 } | 723 } |
658 | 724 |
659 bool NaClProcessHost::ReplyToRenderer( | 725 void NaClProcessHost::ReplyToRenderer( |
660 const IPC::ChannelHandle& ppapi_channel_handle, | 726 ScopedChannelHandle ppapi_channel_handle, |
661 const IPC::ChannelHandle& trusted_channel_handle, | 727 ScopedChannelHandle trusted_channel_handle, |
662 const IPC::ChannelHandle& manifest_service_channel_handle) { | 728 ScopedChannelHandle manifest_service_channel_handle) { |
663 #if defined(OS_WIN) | 729 #if defined(OS_WIN) |
664 // If we are on 64-bit Windows, the NaCl process's sandbox is | 730 // If we are on 64-bit Windows, the NaCl process's sandbox is |
665 // managed by a different process from the renderer's sandbox. We | 731 // managed by a different process from the renderer's sandbox. We |
666 // need to inform the renderer's sandbox about the NaCl process so | 732 // need to inform the renderer's sandbox about the NaCl process so |
667 // that the renderer can send handles to the NaCl process using | 733 // that the renderer can send handles to the NaCl process using |
668 // BrokerDuplicateHandle(). | 734 // BrokerDuplicateHandle(). |
669 if (RunningOnWOW64()) { | 735 if (RunningOnWOW64()) { |
670 if (!content::BrokerAddTargetPeer(process_->GetData().handle)) { | 736 if (!content::BrokerAddTargetPeer(process_->GetData().handle)) { |
671 SendErrorToRenderer("BrokerAddTargetPeer() failed"); | 737 SendErrorToRenderer("BrokerAddTargetPeer() failed"); |
672 return false; | 738 return; |
673 } | 739 } |
674 } | 740 } |
675 #endif | 741 #endif |
676 | 742 |
677 FileDescriptor imc_handle_for_renderer; | 743 // Hereafter, we always send an IPC message with handles which, on Windows, |
678 #if defined(OS_WIN) | 744 // are not closable in this process. |
679 // Copy the handle into the renderer process. | 745 IPC::PlatformFileForTransit imc_handle_for_renderer = |
680 HANDLE handle_in_renderer; | 746 IPC::TakeFileHandleForProcess(socket_for_renderer_.Pass(), |
Mark Seaborn
2015/05/07 22:19:56
Looking again, I noticed that you're not checking
hidehiko
2015/05/11 12:08:35
Ah, you're right. I'll send another CL.
| |
681 if (!DuplicateHandle(base::GetCurrentProcessHandle(), | 747 nacl_host_message_filter_->PeerHandle()); |
682 socket_for_renderer_.TakePlatformFile(), | |
683 nacl_host_message_filter_->PeerHandle(), | |
684 &handle_in_renderer, | |
685 0, // Unused given DUPLICATE_SAME_ACCESS. | |
686 FALSE, | |
687 DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) { | |
688 SendErrorToRenderer("DuplicateHandle() failed"); | |
689 return false; | |
690 } | |
691 imc_handle_for_renderer = reinterpret_cast<FileDescriptor>( | |
692 handle_in_renderer); | |
693 #else | |
694 // No need to dup the imc_handle - we don't pass it anywhere else so | |
695 // it cannot be closed. | |
696 FileDescriptor imc_handle; | |
697 imc_handle.fd = socket_for_renderer_.TakePlatformFile(); | |
698 imc_handle.auto_close = true; | |
699 imc_handle_for_renderer = imc_handle; | |
700 #endif | |
701 | 748 |
702 const ChildProcessData& data = process_->GetData(); | 749 std::string error_message; |
703 base::SharedMemoryHandle crash_info_shmem_renderer_handle; | 750 base::SharedMemoryHandle crash_info_shmem_renderer_handle; |
704 if (!crash_info_shmem_.ShareToProcess(nacl_host_message_filter_->PeerHandle(), | 751 if (!crash_info_shmem_.ShareToProcess(nacl_host_message_filter_->PeerHandle(), |
705 &crash_info_shmem_renderer_handle)) { | 752 &crash_info_shmem_renderer_handle)) { |
706 SendErrorToRenderer("ShareToProcess() failed"); | 753 // On error, we do not send "IPC::ChannelHandle"s to the renderer process. |
707 return false; | 754 // Note that some other FDs/handles still get sent to the renderer, but |
755 // will be closed there. | |
756 ppapi_channel_handle.reset(); | |
757 trusted_channel_handle.reset(); | |
758 manifest_service_channel_handle.reset(); | |
759 error_message = "ShareToProcess() failed"; | |
708 } | 760 } |
709 | 761 |
762 const ChildProcessData& data = process_->GetData(); | |
710 SendMessageToRenderer( | 763 SendMessageToRenderer( |
711 NaClLaunchResult(imc_handle_for_renderer, | 764 NaClLaunchResult(imc_handle_for_renderer, |
712 ppapi_channel_handle, | 765 ppapi_channel_handle.release(), |
713 trusted_channel_handle, | 766 trusted_channel_handle.release(), |
714 manifest_service_channel_handle, | 767 manifest_service_channel_handle.release(), |
715 base::GetProcId(data.handle), | 768 base::GetProcId(data.handle), |
716 data.id, | 769 data.id, |
717 crash_info_shmem_renderer_handle), | 770 crash_info_shmem_renderer_handle), |
718 std::string() /* error_message */); | 771 error_message); |
719 | 772 |
720 // Now that the crash information shmem handles have been shared with the | 773 // Now that the crash information shmem handles have been shared with the |
721 // plugin and the renderer, the browser can close its handle. | 774 // plugin and the renderer, the browser can close its handle. |
722 crash_info_shmem_.Close(); | 775 crash_info_shmem_.Close(); |
723 return true; | |
724 } | 776 } |
725 | 777 |
726 void NaClProcessHost::SendErrorToRenderer(const std::string& error_message) { | 778 void NaClProcessHost::SendErrorToRenderer(const std::string& error_message) { |
727 LOG(ERROR) << "NaCl process launch failed: " << error_message; | 779 LOG(ERROR) << "NaCl process launch failed: " << error_message; |
728 SendMessageToRenderer(NaClLaunchResult(), error_message); | 780 SendMessageToRenderer(NaClLaunchResult(), error_message); |
729 } | 781 } |
730 | 782 |
731 void NaClProcessHost::SendMessageToRenderer( | 783 void NaClProcessHost::SendMessageToRenderer( |
732 const NaClLaunchResult& result, | 784 const NaClLaunchResult& result, |
733 const std::string& error_message) { | 785 const std::string& error_message) { |
734 DCHECK(nacl_host_message_filter_.get()); | 786 DCHECK(nacl_host_message_filter_.get()); |
735 DCHECK(reply_msg_); | 787 DCHECK(reply_msg_); |
736 if (nacl_host_message_filter_.get() != NULL && reply_msg_ != NULL) { | 788 if (nacl_host_message_filter_.get() == NULL || reply_msg_ == NULL) { |
737 NaClHostMsg_LaunchNaCl::WriteReplyParams( | 789 // As DCHECKed above, this case should not happen in general. |
738 reply_msg_, result, error_message); | 790 // Though, in this case, unfortunately there is no proper way to release |
739 nacl_host_message_filter_->Send(reply_msg_); | 791 // resources which are already created in |result|. We just give up on |
740 nacl_host_message_filter_ = NULL; | 792 // releasing them, and leak them. |
741 reply_msg_ = NULL; | 793 return; |
742 } | 794 } |
795 | |
796 NaClHostMsg_LaunchNaCl::WriteReplyParams(reply_msg_, result, error_message); | |
797 nacl_host_message_filter_->Send(reply_msg_); | |
798 nacl_host_message_filter_ = NULL; | |
799 reply_msg_ = NULL; | |
743 } | 800 } |
744 | 801 |
745 void NaClProcessHost::SetDebugStubPort(int port) { | 802 void NaClProcessHost::SetDebugStubPort(int port) { |
746 NaClBrowser* nacl_browser = NaClBrowser::GetInstance(); | 803 NaClBrowser* nacl_browser = NaClBrowser::GetInstance(); |
747 nacl_browser->SetProcessGdbDebugStubPort(process_->GetData().id, port); | 804 nacl_browser->SetProcessGdbDebugStubPort(process_->GetData().id, port); |
748 } | 805 } |
749 | 806 |
750 #if defined(OS_POSIX) | 807 #if defined(OS_POSIX) |
751 // TCP port we chose for NaCl debug stub. It can be any other number. | 808 // TCP port we chose for NaCl debug stub. It can be any other number. |
752 static const uint16_t kInitialDebugStubPort = 4014; | 809 static const uint16_t kInitialDebugStubPort = 4014; |
(...skipping 181 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
934 } else { | 991 } else { |
935 params.nexe_file = IPC::TakeFileHandleForProcess( | 992 params.nexe_file = IPC::TakeFileHandleForProcess( |
936 nexe_file_.Pass(), process_->GetData().handle); | 993 nexe_file_.Pass(), process_->GetData().handle); |
937 } | 994 } |
938 process_->Send(new NaClProcessMsg_Start(params)); | 995 process_->Send(new NaClProcessMsg_Start(params)); |
939 } | 996 } |
940 | 997 |
941 // This method is called when NaClProcessHostMsg_PpapiChannelCreated is | 998 // This method is called when NaClProcessHostMsg_PpapiChannelCreated is |
942 // received. | 999 // received. |
943 void NaClProcessHost::OnPpapiChannelsCreated( | 1000 void NaClProcessHost::OnPpapiChannelsCreated( |
944 const IPC::ChannelHandle& browser_channel_handle, | 1001 const IPC::ChannelHandle& raw_browser_channel_handle, |
945 const IPC::ChannelHandle& ppapi_renderer_channel_handle, | 1002 const IPC::ChannelHandle& raw_ppapi_renderer_channel_handle, |
946 const IPC::ChannelHandle& trusted_renderer_channel_handle, | 1003 const IPC::ChannelHandle& raw_trusted_renderer_channel_handle, |
947 const IPC::ChannelHandle& manifest_service_channel_handle) { | 1004 const IPC::ChannelHandle& raw_manifest_service_channel_handle) { |
1005 ScopedChannelHandle browser_channel_handle(raw_browser_channel_handle); | |
1006 ScopedChannelHandle ppapi_renderer_channel_handle( | |
1007 raw_ppapi_renderer_channel_handle); | |
1008 ScopedChannelHandle trusted_renderer_channel_handle( | |
1009 raw_trusted_renderer_channel_handle); | |
1010 ScopedChannelHandle manifest_service_channel_handle( | |
1011 raw_manifest_service_channel_handle); | |
1012 | |
948 if (!enable_ppapi_proxy()) { | 1013 if (!enable_ppapi_proxy()) { |
949 ReplyToRenderer(IPC::ChannelHandle(), | 1014 ReplyToRenderer(ScopedChannelHandle(), |
950 trusted_renderer_channel_handle, | 1015 trusted_renderer_channel_handle.Pass(), |
951 manifest_service_channel_handle); | 1016 manifest_service_channel_handle.Pass()); |
952 return; | 1017 return; |
953 } | 1018 } |
954 | 1019 |
955 if (!ipc_proxy_channel_.get()) { | 1020 if (ipc_proxy_channel_.get()) { |
956 DCHECK_EQ(PROCESS_TYPE_NACL_LOADER, process_->GetData().process_type); | |
957 | |
958 ipc_proxy_channel_ = | |
959 IPC::ChannelProxy::Create(browser_channel_handle, | |
960 IPC::Channel::MODE_CLIENT, | |
961 NULL, | |
962 base::MessageLoopProxy::current().get()); | |
963 // Create the browser ppapi host and enable PPAPI message dispatching to the | |
964 // browser process. | |
965 ppapi_host_.reset(content::BrowserPpapiHost::CreateExternalPluginProcess( | |
966 ipc_proxy_channel_.get(), // sender | |
967 permissions_, | |
968 process_->GetData().handle, | |
969 ipc_proxy_channel_.get(), | |
970 nacl_host_message_filter_->render_process_id(), | |
971 render_view_id_, | |
972 profile_directory_)); | |
973 ppapi_host_->SetOnKeepaliveCallback( | |
974 NaClBrowser::GetDelegate()->GetOnKeepaliveCallback()); | |
975 | |
976 ppapi::PpapiNaClPluginArgs args; | |
977 args.off_the_record = nacl_host_message_filter_->off_the_record(); | |
978 args.permissions = permissions_; | |
979 args.keepalive_throttle_interval_milliseconds = | |
980 keepalive_throttle_interval_milliseconds_; | |
981 base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess(); | |
982 DCHECK(cmdline); | |
983 std::string flag_whitelist[] = { | |
984 switches::kV, | |
985 switches::kVModule, | |
986 }; | |
987 for (size_t i = 0; i < arraysize(flag_whitelist); ++i) { | |
988 std::string value = cmdline->GetSwitchValueASCII(flag_whitelist[i]); | |
989 if (!value.empty()) { | |
990 args.switch_names.push_back(flag_whitelist[i]); | |
991 args.switch_values.push_back(value); | |
992 } | |
993 } | |
994 | |
995 ppapi_host_->GetPpapiHost()->AddHostFactoryFilter( | |
996 scoped_ptr<ppapi::host::HostFactory>( | |
997 NaClBrowser::GetDelegate()->CreatePpapiHostFactory( | |
998 ppapi_host_.get()))); | |
999 | |
1000 // Send a message to initialize the IPC dispatchers in the NaCl plugin. | |
1001 ipc_proxy_channel_->Send(new PpapiMsg_InitializeNaClDispatcher(args)); | |
1002 | |
1003 // Let the renderer know that the IPC channels are established. | |
1004 ReplyToRenderer(ppapi_renderer_channel_handle, | |
1005 trusted_renderer_channel_handle, | |
1006 manifest_service_channel_handle); | |
1007 } else { | |
1008 // Attempt to open more than 1 browser channel is not supported. | 1021 // Attempt to open more than 1 browser channel is not supported. |
1009 // Shut down the NaCl process. | 1022 // Shut down the NaCl process. |
1010 process_->GetHost()->ForceShutdown(); | 1023 process_->GetHost()->ForceShutdown(); |
1024 return; | |
1011 } | 1025 } |
1026 | |
1027 DCHECK_EQ(PROCESS_TYPE_NACL_LOADER, process_->GetData().process_type); | |
1028 | |
1029 ipc_proxy_channel_ = | |
1030 IPC::ChannelProxy::Create(browser_channel_handle.release(), | |
1031 IPC::Channel::MODE_CLIENT, | |
1032 NULL, | |
1033 base::MessageLoopProxy::current().get()); | |
1034 // Create the browser ppapi host and enable PPAPI message dispatching to the | |
1035 // browser process. | |
1036 ppapi_host_.reset(content::BrowserPpapiHost::CreateExternalPluginProcess( | |
1037 ipc_proxy_channel_.get(), // sender | |
1038 permissions_, | |
1039 process_->GetData().handle, | |
1040 ipc_proxy_channel_.get(), | |
1041 nacl_host_message_filter_->render_process_id(), | |
1042 render_view_id_, | |
1043 profile_directory_)); | |
1044 ppapi_host_->SetOnKeepaliveCallback( | |
1045 NaClBrowser::GetDelegate()->GetOnKeepaliveCallback()); | |
1046 | |
1047 ppapi::PpapiNaClPluginArgs args; | |
1048 args.off_the_record = nacl_host_message_filter_->off_the_record(); | |
1049 args.permissions = permissions_; | |
1050 args.keepalive_throttle_interval_milliseconds = | |
1051 keepalive_throttle_interval_milliseconds_; | |
1052 base::CommandLine* cmdline = base::CommandLine::ForCurrentProcess(); | |
1053 DCHECK(cmdline); | |
1054 std::string flag_whitelist[] = { | |
1055 switches::kV, | |
1056 switches::kVModule, | |
1057 }; | |
1058 for (size_t i = 0; i < arraysize(flag_whitelist); ++i) { | |
1059 std::string value = cmdline->GetSwitchValueASCII(flag_whitelist[i]); | |
1060 if (!value.empty()) { | |
1061 args.switch_names.push_back(flag_whitelist[i]); | |
1062 args.switch_values.push_back(value); | |
1063 } | |
1064 } | |
1065 | |
1066 ppapi_host_->GetPpapiHost()->AddHostFactoryFilter( | |
1067 scoped_ptr<ppapi::host::HostFactory>( | |
1068 NaClBrowser::GetDelegate()->CreatePpapiHostFactory( | |
1069 ppapi_host_.get()))); | |
1070 | |
1071 // Send a message to initialize the IPC dispatchers in the NaCl plugin. | |
1072 ipc_proxy_channel_->Send(new PpapiMsg_InitializeNaClDispatcher(args)); | |
1073 | |
1074 // Let the renderer know that the IPC channels are established. | |
1075 ReplyToRenderer(ppapi_renderer_channel_handle.Pass(), | |
1076 trusted_renderer_channel_handle.Pass(), | |
1077 manifest_service_channel_handle.Pass()); | |
1012 } | 1078 } |
1013 | 1079 |
1014 bool NaClProcessHost::StartWithLaunchedProcess() { | 1080 bool NaClProcessHost::StartWithLaunchedProcess() { |
1015 NaClBrowser* nacl_browser = NaClBrowser::GetInstance(); | 1081 NaClBrowser* nacl_browser = NaClBrowser::GetInstance(); |
1016 | 1082 |
1017 if (nacl_browser->IsReady()) { | 1083 if (nacl_browser->IsReady()) { |
1018 return StartNaClExecution(); | 1084 return StartNaClExecution(); |
1019 } else if (nacl_browser->IsOk()) { | 1085 } else if (nacl_browser->IsOk()) { |
1020 nacl_browser->WaitForResources( | 1086 nacl_browser->WaitForResources( |
1021 base::Bind(&NaClProcessHost::OnResourcesReady, | 1087 base::Bind(&NaClProcessHost::OnResourcesReady, |
(...skipping 160 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1182 process.Pass(), info, | 1248 process.Pass(), info, |
1183 base::MessageLoopProxy::current(), | 1249 base::MessageLoopProxy::current(), |
1184 base::Bind(&NaClProcessHost::OnDebugExceptionHandlerLaunchedByBroker, | 1250 base::Bind(&NaClProcessHost::OnDebugExceptionHandlerLaunchedByBroker, |
1185 weak_factory_.GetWeakPtr())); | 1251 weak_factory_.GetWeakPtr())); |
1186 return true; | 1252 return true; |
1187 } | 1253 } |
1188 } | 1254 } |
1189 #endif | 1255 #endif |
1190 | 1256 |
1191 } // namespace nacl | 1257 } // namespace nacl |
OLD | NEW |