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 "ppapi/native_client/src/trusted/plugin/plugin.h"
6
7#include <sys/stat.h>
8#include <sys/types.h>
9
10#include <string>
11
12#include "native_client/src/include/nacl_base.h"
13#include "native_client/src/include/nacl_macros.h"
14#include "native_client/src/include/nacl_scoped_ptr.h"
15#include "native_client/src/include/portability.h"
16#include "native_client/src/include/portability_io.h"
17#include "native_client/src/shared/platform/nacl_check.h"
18#include "native_client/src/trusted/desc/nacl_desc_wrapper.h"
19
20#include "ppapi/c/pp_errors.h"
21#include "ppapi/c/private/ppb_nacl_private.h"
22#include "ppapi/cpp/module.h"
23
24#include "ppapi/native_client/src/trusted/plugin/nacl_subprocess.h"
25#include "ppapi/native_client/src/trusted/plugin/plugin_error.h"
26#include "ppapi/native_client/src/trusted/plugin/service_runtime.h"
27#include "ppapi/native_client/src/trusted/plugin/utility.h"
28
29namespace plugin {
30
31namespace {
32
33// Up to 20 seconds
34const int64_t kTimeSmallMin = 1;         // in ms
35const int64_t kTimeSmallMax = 20000;     // in ms
36const uint32_t kTimeSmallBuckets = 100;
37
38}  // namespace
39
40void Plugin::ShutDownSubprocesses() {
41  PLUGIN_PRINTF(("Plugin::ShutDownSubprocesses (this=%p)\n",
42                 static_cast<void*>(this)));
43
44  // Shut down service runtime. This must be done before all other calls so
45  // they don't block forever when waiting for the upcall thread to exit.
46  main_subprocess_.Shutdown();
47
48  PLUGIN_PRINTF(("Plugin::ShutDownSubprocess (this=%p, return)\n",
49                 static_cast<void*>(this)));
50}
51
52void Plugin::HistogramTimeSmall(const std::string& name,
53                                int64_t ms) {
54  if (ms < 0) return;
55  uma_interface_.HistogramCustomTimes(name,
56                                      ms,
57                                      kTimeSmallMin, kTimeSmallMax,
58                                      kTimeSmallBuckets);
59}
60
61bool Plugin::LoadHelperNaClModuleInternal(NaClSubprocess* subprocess,
62                                          const SelLdrStartParams& params) {
63  CHECK(!pp::Module::Get()->core()->IsMainThread());
64  ServiceRuntime* service_runtime =
65      new ServiceRuntime(this,
66                         pp_instance(),
67                         false,  // No main_service_runtime.
68                         false,  // No non-SFI mode (i.e. in SFI-mode).
69                         pp::BlockUntilComplete());
70  subprocess->set_service_runtime(service_runtime);
71
72  // Now start the SelLdr instance.  This must be created on the main thread.
73  bool service_runtime_started = false;
74  pp::CompletionCallback sel_ldr_callback =
75      callback_factory_.NewCallback(&Plugin::SignalStartSelLdrDone,
76                                    &service_runtime_started,
77                                    service_runtime);
78  pp::CompletionCallback callback =
79      callback_factory_.NewCallback(&Plugin::StartSelLdrOnMainThread,
80                                    service_runtime, params,
81                                    sel_ldr_callback);
82  pp::Module::Get()->core()->CallOnMainThread(0, callback, 0);
83  if (!service_runtime->WaitForSelLdrStart()) {
84    PLUGIN_PRINTF(("Plugin::LoadHelperNaClModule "
85                   "WaitForSelLdrStart timed out!\n"));
86    return false;
87  }
88  PLUGIN_PRINTF(("Plugin::LoadHelperNaClModule (service_runtime_started=%d)\n",
89                 service_runtime_started));
90  if (!service_runtime_started)
91    return false;
92
93  // Now actually start the nexe.
94  //
95  // We can't use pp::BlockUntilComplete() inside an in-process plugin, so we
96  // have to roll our own blocking logic, similar to WaitForSelLdrStart()
97  // above, except without timeout logic.
98  pp::Module::Get()->core()->CallOnMainThread(
99      0,
100      callback_factory_.NewCallback(&Plugin::StartNexe, service_runtime));
101  return service_runtime->WaitForNexeStart();
102}
103
104void Plugin::StartSelLdrOnMainThread(int32_t pp_error,
105                                     ServiceRuntime* service_runtime,
106                                     const SelLdrStartParams& params,
107                                     pp::CompletionCallback callback) {
108  CHECK(pp_error == PP_OK);
109  service_runtime->StartSelLdr(params, callback);
110}
111
112void Plugin::SignalStartSelLdrDone(int32_t pp_error,
113                                   bool* started,
114                                   ServiceRuntime* service_runtime) {
115  *started = (pp_error == PP_OK);
116  service_runtime->SignalStartSelLdrDone();
117}
118
119void Plugin::LoadNaClModule(PP_NaClFileInfo file_info,
120                            bool uses_nonsfi_mode,
121                            bool enable_dyncode_syscalls,
122                            bool enable_exception_handling,
123                            bool enable_crash_throttling,
124                            const pp::CompletionCallback& init_done_cb) {
125  CHECK(pp::Module::Get()->core()->IsMainThread());
126  // Before forking a new sel_ldr process, ensure that we do not leak
127  // the ServiceRuntime object for an existing subprocess, and that any
128  // associated listener threads do not go unjoined because if they
129  // outlive the Plugin object, they will not be memory safe.
130  ShutDownSubprocesses();
131  pp::Var manifest_base_url =
132      pp::Var(pp::PASS_REF, nacl_interface_->GetManifestBaseURL(pp_instance()));
133  std::string manifest_base_url_str = manifest_base_url.AsString();
134
135  SelLdrStartParams params(manifest_base_url_str,
136                           file_info,
137                           true /* uses_irt */,
138                           true /* uses_ppapi */,
139                           enable_dyncode_syscalls,
140                           enable_exception_handling,
141                           enable_crash_throttling);
142  ErrorInfo error_info;
143  ServiceRuntime* service_runtime = new ServiceRuntime(
144      this, pp_instance(), true, uses_nonsfi_mode, init_done_cb);
145  main_subprocess_.set_service_runtime(service_runtime);
146  if (NULL == service_runtime) {
147    error_info.SetReport(
148        PP_NACL_ERROR_SEL_LDR_INIT,
149        "sel_ldr init failure " + main_subprocess_.description());
150    ReportLoadError(error_info);
151    return;
152  }
153
154  // We don't take any action once nexe loading has completed, so pass an empty
155  // callback here for |callback|.
156  pp::CompletionCallback callback = callback_factory_.NewCallback(
157      &Plugin::StartNexe, service_runtime);
158  StartSelLdrOnMainThread(
159      static_cast<int32_t>(PP_OK), service_runtime, params, callback);
160}
161
162void Plugin::StartNexe(int32_t pp_error, ServiceRuntime* service_runtime) {
163  CHECK(pp::Module::Get()->core()->IsMainThread());
164  if (pp_error != PP_OK)
165    return;
166  service_runtime->StartNexe();
167}
168
169bool Plugin::LoadNaClModuleContinuationIntern() {
170  ErrorInfo error_info;
171  if (!uses_nonsfi_mode_) {
172    if (!main_subprocess_.StartSrpcServices()) {
173      // The NaCl process probably crashed. On Linux, a crash causes this
174      // error, while on other platforms, the error is detected below, when we
175      // attempt to start the proxy. Report a module initialization error here,
176      // to make it less confusing for developers.
177      NaClLog(LOG_ERROR, "LoadNaClModuleContinuationIntern: "
178              "StartSrpcServices failed\n");
179      error_info.SetReport(PP_NACL_ERROR_START_PROXY_MODULE,
180                           "could not initialize module.");
181      ReportLoadError(error_info);
182      return false;
183    }
184  }
185
186  return PP_ToBool(nacl_interface_->StartPpapiProxy(pp_instance()));
187}
188
189NaClSubprocess* Plugin::LoadHelperNaClModule(const std::string& helper_url,
190                                             PP_NaClFileInfo file_info,
191                                             ErrorInfo* error_info) {
192  nacl::scoped_ptr<NaClSubprocess> nacl_subprocess(
193      new NaClSubprocess("helper module", NULL, NULL));
194  if (NULL == nacl_subprocess.get()) {
195    error_info->SetReport(PP_NACL_ERROR_SEL_LDR_INIT,
196                          "unable to allocate helper subprocess.");
197    return NULL;
198  }
199
200  // Do not report UMA stats for translator-related nexes.
201  // TODO(sehr): define new UMA stats for translator related nexe events.
202  // NOTE: The PNaCl translator nexes are not built to use the IRT.  This is
203  // done to save on address space and swap space.
204  SelLdrStartParams params(helper_url,
205                           file_info,
206                           false /* uses_irt */,
207                           false /* uses_ppapi */,
208                           false /* enable_dyncode_syscalls */,
209                           false /* enable_exception_handling */,
210                           true /* enable_crash_throttling */);
211
212  // Helper NaCl modules always use the PNaCl manifest, as there is no
213  // corresponding NMF.
214  if (!LoadHelperNaClModuleInternal(nacl_subprocess.get(), params))
215    return NULL;
216
217  // We need not wait for the init_done callback.  We can block
218  // here in StartSrpcServices, since helper NaCl modules
219  // are spawned from a private thread.
220  //
221  // TODO(bsy): if helper module crashes, we should abort.
222  // crash_cb is not used here, so we are relying on crashes
223  // being detected in StartSrpcServices or later.
224  //
225  // NB: More refactoring might be needed, however, if helper
226  // NaCl modules have their own manifest.  Currently the
227  // manifest is a per-plugin-instance object, not a per
228  // NaClSubprocess object.
229  if (!nacl_subprocess->StartSrpcServices()) {
230    error_info->SetReport(PP_NACL_ERROR_SRPC_CONNECTION_FAIL,
231                          "SRPC connection failure for " +
232                          nacl_subprocess->description());
233    return NULL;
234  }
235
236  PLUGIN_PRINTF(("Plugin::LoadHelperNaClModule (%s, %s)\n",
237                 helper_url.c_str(),
238                 nacl_subprocess.get()->detailed_description().c_str()));
239
240  return nacl_subprocess.release();
241}
242
243// All failures of this function will show up as "Missing Plugin-in", so
244// there is no need to log to JS console that there was an initialization
245// failure. Note that module loading functions will log their own errors.
246bool Plugin::Init(uint32_t argc, const char* argn[], const char* argv[]) {
247  nacl_interface_->InitializePlugin(pp_instance(), argc, argn, argv);
248  wrapper_factory_ = new nacl::DescWrapperFactory();
249  pp::CompletionCallback open_cb =
250      callback_factory_.NewCallback(&Plugin::NaClManifestFileDidOpen);
251  nacl_interface_->RequestNaClManifest(pp_instance(),
252                                       open_cb.pp_completion_callback());
253  return true;
254}
255
256Plugin::Plugin(PP_Instance pp_instance)
257    : pp::Instance(pp_instance),
258      main_subprocess_("main subprocess", NULL, NULL),
259      uses_nonsfi_mode_(false),
260      wrapper_factory_(NULL),
261      nacl_interface_(NULL),
262      uma_interface_(this) {
263  callback_factory_.Initialize(this);
264  nacl_interface_ = GetNaClInterface();
265  CHECK(nacl_interface_ != NULL);
266
267  // Notify PPB_NaCl_Private that the instance is created before altering any
268  // state that it tracks.
269  nacl_interface_->InstanceCreated(pp_instance);
270  nexe_file_info_ = kInvalidNaClFileInfo;
271}
272
273Plugin::~Plugin() {
274  int64_t shutdown_start = NaClGetTimeOfDayMicroseconds();
275
276  // Destroy the coordinator while the rest of the data is still there
277  pnacl_coordinator_.reset(NULL);
278
279  nacl_interface_->InstanceDestroyed(pp_instance());
280
281  // ShutDownSubprocesses shuts down the main subprocess, which shuts
282  // down the main ServiceRuntime object, which kills the subprocess.
283  // As a side effect of the subprocess being killed, the reverse
284  // services thread(s) will get EOF on the reverse channel(s), and
285  // the thread(s) will exit.  In ServiceRuntime::Shutdown, we invoke
286  // ReverseService::WaitForServiceThreadsToExit(), so that there will
287  // not be an extent thread(s) hanging around.  This means that the
288  // ~Plugin will block until this happens.  This is a requirement,
289  // since the renderer should be free to unload the plugin code, and
290  // we cannot have threads running code that gets unloaded before
291  // they exit.
292  //
293  // By waiting for the threads here, we also ensure that the Plugin
294  // object and the subprocess and ServiceRuntime objects is not
295  // (fully) destroyed while the threads are running, so resources
296  // that are destroyed after ShutDownSubprocesses (below) are
297  // guaranteed to be live and valid for access from the service
298  // threads.
299  //
300  // The main_subprocess object, which wraps the main service_runtime
301  // object, is dtor'd implicitly after the explicit code below runs,
302  // so the main service runtime object will not have been dtor'd,
303  // though the Shutdown method may have been called, during the
304  // lifetime of the service threads.
305  ShutDownSubprocesses();
306
307  delete wrapper_factory_;
308
309  HistogramTimeSmall(
310      "NaCl.Perf.ShutdownTime.Total",
311      (NaClGetTimeOfDayMicroseconds() - shutdown_start)
312          / NACL_MICROS_PER_MILLI);
313}
314
315bool Plugin::HandleDocumentLoad(const pp::URLLoader& url_loader) {
316  // We don't know if the plugin will handle the document load, but return
317  // true in order to give it a chance to respond once the proxy is started.
318  return true;
319}
320
321void Plugin::NexeFileDidOpen(int32_t pp_error) {
322  if (pp_error != PP_OK)
323    return;
324  LoadNaClModule(
325      nexe_file_info_,
326      uses_nonsfi_mode_,
327      true, /* enable_dyncode_syscalls */
328      true, /* enable_exception_handling */
329      false, /* enable_crash_throttling */
330      callback_factory_.NewCallback(&Plugin::NexeFileDidOpenContinuation));
331}
332
333void Plugin::NexeFileDidOpenContinuation(int32_t pp_error) {
334  UNREFERENCED_PARAMETER(pp_error);
335  NaClLog(4, "Entered NexeFileDidOpenContinuation\n");
336  if (LoadNaClModuleContinuationIntern()) {
337    NaClLog(4, "NexeFileDidOpenContinuation: success;"
338            " setting histograms\n");
339    int64_t nexe_size = nacl_interface_->GetNexeSize(pp_instance());
340    nacl_interface_->ReportLoadSuccess(
341        pp_instance(), nexe_size, nexe_size);
342  } else {
343    NaClLog(4, "NexeFileDidOpenContinuation: failed.");
344  }
345  NaClLog(4, "Leaving NexeFileDidOpenContinuation\n");
346}
347
348void Plugin::BitcodeDidTranslate(int32_t pp_error) {
349  PLUGIN_PRINTF(("Plugin::BitcodeDidTranslate (pp_error=%" NACL_PRId32 ")\n",
350                 pp_error));
351  if (pp_error != PP_OK) {
352    // Error should have been reported by pnacl. Just return.
353    return;
354  }
355
356  // Inform JavaScript that we successfully translated the bitcode to a nexe.
357  PP_FileHandle handle = pnacl_coordinator_->TakeTranslatedFileHandle();
358
359  PP_NaClFileInfo info;
360  info.handle = handle;
361  info.token_lo = 0;
362  info.token_hi = 0;
363  LoadNaClModule(
364      info,
365      false, /* uses_nonsfi_mode */
366      false, /* enable_dyncode_syscalls */
367      false, /* enable_exception_handling */
368      true, /* enable_crash_throttling */
369      callback_factory_.NewCallback(&Plugin::BitcodeDidTranslateContinuation));
370}
371
372void Plugin::BitcodeDidTranslateContinuation(int32_t pp_error) {
373  NaClLog(4, "Entered BitcodeDidTranslateContinuation\n");
374  UNREFERENCED_PARAMETER(pp_error);
375  if (LoadNaClModuleContinuationIntern()) {
376    int64_t loaded;
377    int64_t total;
378    // TODO(teravest): Tighten this up so we can get rid of
379    // GetCurrentProgress(). loaded should always equal total.
380    pnacl_coordinator_->GetCurrentProgress(&loaded, &total);
381    nacl_interface_->ReportLoadSuccess(pp_instance(), loaded, total);
382  }
383}
384
385void Plugin::NaClManifestFileDidOpen(int32_t pp_error) {
386  PLUGIN_PRINTF(("Plugin::NaClManifestFileDidOpen (pp_error=%"
387                 NACL_PRId32 ")\n", pp_error));
388  if (pp_error != PP_OK)
389    return;
390
391  PP_Var pp_program_url;
392  PP_PNaClOptions pnacl_options = {PP_FALSE, PP_FALSE, 2};
393  PP_Bool uses_nonsfi_mode;
394  if (nacl_interface_->GetManifestProgramURL(
395          pp_instance(), &pp_program_url, &pnacl_options, &uses_nonsfi_mode)) {
396    std::string program_url = pp::Var(pp::PASS_REF, pp_program_url).AsString();
397    // TODO(teravest): Make ProcessNaClManifest take responsibility for more of
398    // this function.
399    nacl_interface_->ProcessNaClManifest(pp_instance(), program_url.c_str());
400    uses_nonsfi_mode_ = PP_ToBool(uses_nonsfi_mode);
401    if (pnacl_options.translate) {
402      pp::CompletionCallback translate_callback =
403          callback_factory_.NewCallback(&Plugin::BitcodeDidTranslate);
404      pnacl_coordinator_.reset(
405          PnaclCoordinator::BitcodeToNative(this,
406                                            program_url,
407                                            pnacl_options,
408                                            translate_callback));
409      return;
410    } else {
411      pp::CompletionCallback open_callback =
412          callback_factory_.NewCallback(&Plugin::NexeFileDidOpen);
413      // Will always call the callback on success or failure.
414      nacl_interface_->DownloadNexe(pp_instance(),
415                                    program_url.c_str(),
416                                    &nexe_file_info_,
417                                    open_callback.pp_completion_callback());
418      return;
419    }
420  }
421}
422
423void Plugin::ReportLoadError(const ErrorInfo& error_info) {
424  nacl_interface_->ReportLoadError(pp_instance(),
425                                   error_info.error_code(),
426                                   error_info.message().c_str());
427}
428
429}  // namespace plugin
430