• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2013 The Flutter 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 "dart_component_controller.h"
6 
7 #include <fcntl.h>
8 #include <lib/async-loop/loop.h>
9 #include <lib/async/cpp/task.h>
10 #include <lib/async/default.h>
11 #include <lib/fdio/directory.h>
12 #include <lib/fdio/fd.h>
13 #include <lib/fdio/namespace.h>
14 #include <lib/fidl/cpp/optional.h>
15 #include <lib/fidl/cpp/string.h>
16 #include <lib/sys/cpp/service_directory.h>
17 #include <lib/syslog/global.h>
18 #include <lib/zx/clock.h>
19 #include <lib/zx/thread.h>
20 #include <sys/stat.h>
21 #include <sys/types.h>
22 #include <unistd.h>
23 #include <zircon/status.h>
24 #include <regex>
25 #include <utility>
26 
27 #include "runtime/dart/utils/handle_exception.h"
28 #include "runtime/dart/utils/inlines.h"
29 #include "runtime/dart/utils/tempfs.h"
30 #include "third_party/tonic/converter/dart_converter.h"
31 #include "third_party/tonic/dart_message_handler.h"
32 #include "third_party/tonic/dart_microtask_queue.h"
33 #include "third_party/tonic/dart_state.h"
34 #include "third_party/tonic/logging/dart_error.h"
35 
36 #include "builtin_libraries.h"
37 #include "logging.h"
38 
39 using tonic::ToDart;
40 
41 namespace dart_runner {
42 
43 constexpr char kDataKey[] = "data";
44 
45 namespace {
46 
AfterTask(async_loop_t *,void *)47 void AfterTask(async_loop_t*, void*) {
48   tonic::DartMicrotaskQueue* queue =
49       tonic::DartMicrotaskQueue::GetForCurrentThread();
50   // Verify that the queue exists, as this method could have been called back as
51   // part of the exit routine, after the destruction of the microtask queue.
52   if (queue) {
53     queue->RunMicrotasks();
54   }
55 }
56 
57 constexpr async_loop_config_t kLoopConfig = {
58     .make_default_for_current_thread = true,
59     .epilogue = &AfterTask,
60 };
61 
62 // Find the last path component.
63 // fuchsia-pkg://fuchsia.com/hello_dart#meta/hello_dart.cmx -> hello_dart.cmx
GetLabelFromURL(const std::string & url)64 std::string GetLabelFromURL(const std::string& url) {
65   for (size_t i = url.length() - 1; i > 0; i--) {
66     if (url[i] == '/') {
67       return url.substr(i + 1, url.length() - 1);
68     }
69   }
70   return url;
71 }
72 
73 }  // namespace
74 
DartComponentController(fuchsia::sys::Package package,fuchsia::sys::StartupInfo startup_info,std::shared_ptr<sys::ServiceDirectory> runner_incoming_services,fidl::InterfaceRequest<fuchsia::sys::ComponentController> controller)75 DartComponentController::DartComponentController(
76     fuchsia::sys::Package package,
77     fuchsia::sys::StartupInfo startup_info,
78     std::shared_ptr<sys::ServiceDirectory> runner_incoming_services,
79     fidl::InterfaceRequest<fuchsia::sys::ComponentController> controller)
80     : loop_(new async::Loop(&kLoopConfig)),
81       label_(GetLabelFromURL(package.resolved_url)),
82       url_(std::move(package.resolved_url)),
83       package_(std::move(package)),
84       startup_info_(std::move(startup_info)),
85       runner_incoming_services_(runner_incoming_services),
86       binding_(this) {
87   for (size_t i = 0; i < startup_info_.program_metadata->size(); ++i) {
88     auto pg = startup_info_.program_metadata->at(i);
89     if (pg.key.compare(kDataKey) == 0) {
90       data_path_ = "pkg/" + pg.value;
91     }
92   }
93   if (data_path_.empty()) {
94     FX_LOGF(ERROR, LOG_TAG, "Could not find a /pkg/data directory for %s",
95             url_.c_str());
96     return;
97   }
98   if (controller.is_valid()) {
99     binding_.Bind(std::move(controller));
100     binding_.set_error_handler([this](zx_status_t status) { Kill(); });
101   }
102 
103   zx_status_t status =
104       zx::timer::create(ZX_TIMER_SLACK_LATE, ZX_CLOCK_MONOTONIC, &idle_timer_);
105   if (status != ZX_OK) {
106     FX_LOGF(INFO, LOG_TAG, "Idle timer creation failed: %s",
107             zx_status_get_string(status));
108   } else {
109     idle_wait_.set_object(idle_timer_.get());
110     idle_wait_.set_trigger(ZX_TIMER_SIGNALED);
111     idle_wait_.Begin(async_get_default_dispatcher());
112   }
113 }
114 
~DartComponentController()115 DartComponentController::~DartComponentController() {
116   if (namespace_) {
117     fdio_ns_destroy(namespace_);
118     namespace_ = nullptr;
119   }
120   close(stdoutfd_);
121   close(stderrfd_);
122 }
123 
Setup()124 bool DartComponentController::Setup() {
125   // Name the thread after the url of the component being launched.
126   zx::thread::self()->set_property(ZX_PROP_NAME, label_.c_str(), label_.size());
127   Dart_SetThreadName(label_.c_str());
128 
129   if (!SetupNamespace()) {
130     return false;
131   }
132 
133   if (SetupFromAppSnapshot()) {
134     FX_LOGF(INFO, LOG_TAG, "%s is running from an app snapshot", url_.c_str());
135   } else if (SetupFromKernel()) {
136     FX_LOGF(INFO, LOG_TAG, "%s is running from kernel", url_.c_str());
137   } else {
138     FX_LOGF(ERROR, LOG_TAG,
139             "Could not find a program in %s. Was data specified"
140             " correctly in the component manifest?",
141             url_.c_str());
142     return false;
143   }
144 
145   return true;
146 }
147 
148 constexpr char kTmpPath[] = "/tmp";
149 constexpr char kServiceRootPath[] = "/svc";
150 
SetupNamespace()151 bool DartComponentController::SetupNamespace() {
152   fuchsia::sys::FlatNamespace* flat = &startup_info_.flat_namespace;
153   zx_status_t status = fdio_ns_create(&namespace_);
154   if (status != ZX_OK) {
155     FX_LOG(ERROR, LOG_TAG, "Failed to create namespace");
156     return false;
157   }
158 
159   dart_utils::SetupComponentTemp(namespace_);
160 
161   for (size_t i = 0; i < flat->paths.size(); ++i) {
162     if (flat->paths.at(i) == kTmpPath) {
163       // /tmp is covered by the local memfs.
164       continue;
165     }
166 
167     zx::channel dir;
168     if (flat->paths.at(i) == kServiceRootPath) {
169       // clone /svc so component_context can still use it below
170       dir = zx::channel(fdio_service_clone(flat->directories.at(i).get()));
171     } else {
172       dir = std::move(flat->directories.at(i));
173     }
174 
175     zx_handle_t dir_handle = dir.release();
176     const char* path = flat->paths.at(i).data();
177     status = fdio_ns_bind(namespace_, path, dir_handle);
178     if (status != ZX_OK) {
179       FX_LOGF(ERROR, LOG_TAG, "Failed to bind %s to namespace: %s",
180               flat->paths.at(i).c_str(), zx_status_get_string(status));
181       zx_handle_close(dir_handle);
182       return false;
183     }
184   }
185 
186   return true;
187 }
188 
SetupFromKernel()189 bool DartComponentController::SetupFromKernel() {
190   MappedResource manifest;
191   if (!MappedResource::LoadFromNamespace(
192           namespace_, data_path_ + "/app.dilplist", manifest)) {
193     return false;
194   }
195 
196   if (!MappedResource::LoadFromNamespace(
197           nullptr, "pkg/data/isolate_core_snapshot_data.bin",
198           isolate_snapshot_data_)) {
199     return false;
200   }
201   if (!MappedResource::LoadFromNamespace(
202           nullptr, "pkg/data/isolate_core_snapshot_instructions.bin",
203           isolate_snapshot_instructions_, true /* executable */)) {
204     return false;
205   }
206 
207   if (!CreateIsolate(isolate_snapshot_data_.address(),
208                      isolate_snapshot_instructions_.address(), nullptr,
209                      nullptr)) {
210     return false;
211   }
212 
213   Dart_EnterScope();
214 
215   std::string str(reinterpret_cast<const char*>(manifest.address()),
216                   manifest.size());
217   Dart_Handle library = Dart_Null();
218   for (size_t start = 0; start < manifest.size();) {
219     size_t end = str.find("\n", start);
220     if (end == std::string::npos) {
221       FX_LOG(ERROR, LOG_TAG, "Malformed manifest");
222       Dart_ExitScope();
223       return false;
224     }
225 
226     std::string path = data_path_ + "/" + str.substr(start, end - start);
227     start = end + 1;
228 
229     MappedResource kernel;
230     if (!MappedResource::LoadFromNamespace(namespace_, path, kernel)) {
231       FX_LOGF(ERROR, LOG_TAG, "Failed to find kernel: %s", path.c_str());
232       Dart_ExitScope();
233       return false;
234     }
235     library = Dart_LoadLibraryFromKernel(kernel.address(), kernel.size());
236     if (Dart_IsError(library)) {
237       FX_LOGF(ERROR, LOG_TAG, "Failed to load kernel: %s",
238               Dart_GetError(library));
239       Dart_ExitScope();
240       return false;
241     }
242 
243     kernel_peices_.emplace_back(std::move(kernel));
244   }
245   Dart_SetRootLibrary(library);
246 
247   Dart_Handle result = Dart_FinalizeLoading(false);
248   if (Dart_IsError(result)) {
249     FX_LOGF(ERROR, LOG_TAG, "Failed to FinalizeLoading: %s",
250             Dart_GetError(result));
251     Dart_ExitScope();
252     return false;
253   }
254 
255   return true;
256 }
257 
SetupFromAppSnapshot()258 bool DartComponentController::SetupFromAppSnapshot() {
259 #if !defined(AOT_RUNTIME)
260   // If we start generating app-jit snapshots, the code below should be able
261   // handle that case without modification.
262   return false;
263 #else
264 
265   if (!MappedResource::LoadFromNamespace(
266           namespace_, data_path_ + "/isolate_snapshot_data.bin",
267           isolate_snapshot_data_)) {
268     return false;
269   }
270 
271   if (!MappedResource::LoadFromNamespace(
272           namespace_, data_path_ + "/isolate_snapshot_instructions.bin",
273           isolate_snapshot_instructions_, true /* executable */)) {
274     return false;
275   }
276 
277   if (!MappedResource::LoadFromNamespace(
278           namespace_, data_path_ + "/shared_snapshot_data.bin",
279           shared_snapshot_data_)) {
280     return false;
281   }
282 
283   if (!MappedResource::LoadFromNamespace(
284           namespace_, data_path_ + "/shared_snapshot_instructions.bin",
285           shared_snapshot_instructions_, true /* executable */)) {
286     return false;
287   }
288 
289   return CreateIsolate(isolate_snapshot_data_.address(),
290                        isolate_snapshot_instructions_.address(),
291                        shared_snapshot_data_.address(),
292                        shared_snapshot_instructions_.address());
293 #endif  // defined(AOT_RUNTIME)
294 }
295 
SetupFileDescriptor(fuchsia::sys::FileDescriptorPtr fd)296 int DartComponentController::SetupFileDescriptor(
297     fuchsia::sys::FileDescriptorPtr fd) {
298   if (!fd) {
299     return -1;
300   }
301   // fd->handle1 and fd->handle2 are no longer used.
302   int outfd = -1;
303   zx_status_t status = fdio_fd_create(fd->handle0.release(), &outfd);
304   if (status != ZX_OK) {
305     FX_LOGF(ERROR, LOG_TAG, "Failed to extract output fd: %s",
306             zx_status_get_string(status));
307     return -1;
308   }
309   return outfd;
310 }
311 
CreateIsolate(const uint8_t * isolate_snapshot_data,const uint8_t * isolate_snapshot_instructions,const uint8_t * shared_snapshot_data,const uint8_t * shared_snapshot_instructions)312 bool DartComponentController::CreateIsolate(
313     const uint8_t* isolate_snapshot_data,
314     const uint8_t* isolate_snapshot_instructions,
315     const uint8_t* shared_snapshot_data,
316     const uint8_t* shared_snapshot_instructions) {
317   // Create the isolate from the snapshot.
318   char* error = nullptr;
319 
320   // TODO(dart_runner): Pass if we start using tonic's loader.
321   intptr_t namespace_fd = -1;
322   // Freed in IsolateShutdownCallback.
323   auto state = new std::shared_ptr<tonic::DartState>(new tonic::DartState(
324       namespace_fd, [this](Dart_Handle result) { MessageEpilogue(result); }));
325 
326   isolate_ = Dart_CreateIsolateGroup(
327       url_.c_str(), label_.c_str(), isolate_snapshot_data,
328       isolate_snapshot_instructions, shared_snapshot_data,
329       shared_snapshot_instructions, nullptr /* flags */,
330       state /* isolate_group_data */, state /* isolate_data */, &error);
331   if (!isolate_) {
332     FX_LOGF(ERROR, LOG_TAG, "Dart_CreateIsolateGroup failed: %s", error);
333     return false;
334   }
335 
336   state->get()->SetIsolate(isolate_);
337 
338   tonic::DartMessageHandler::TaskDispatcher dispatcher =
339       [loop = loop_.get()](auto callback) {
340         async::PostTask(loop->dispatcher(), std::move(callback));
341       };
342   state->get()->message_handler().Initialize(dispatcher);
343 
344   state->get()->SetReturnCodeCallback(
345       [this](uint32_t return_code) { return_code_ = return_code; });
346 
347   return true;
348 }
349 
Run()350 void DartComponentController::Run() {
351   async::PostTask(loop_->dispatcher(), [loop = loop_.get(), app = this] {
352     if (!app->Main()) {
353       loop->Quit();
354     }
355   });
356   loop_->Run();
357   SendReturnCode();
358 }
359 
Main()360 bool DartComponentController::Main() {
361   Dart_EnterScope();
362 
363   tonic::DartMicrotaskQueue::StartForCurrentThread();
364 
365   std::vector<std::string> arguments =
366       std::move(startup_info_.launch_info.arguments);
367 
368   stdoutfd_ = SetupFileDescriptor(std::move(startup_info_.launch_info.out));
369   stderrfd_ = SetupFileDescriptor(std::move(startup_info_.launch_info.err));
370   auto directory_request =
371       std::move(startup_info_.launch_info.directory_request);
372 
373   auto* flat = &startup_info_.flat_namespace;
374   std::unique_ptr<sys::ServiceDirectory> svc;
375   for (size_t i = 0; i < flat->paths.size(); ++i) {
376     zx::channel dir;
377     if (flat->paths.at(i) == kServiceRootPath) {
378       svc = std::make_unique<sys::ServiceDirectory>(
379           std::move(flat->directories.at(i)));
380       break;
381     }
382   }
383   if (!svc) {
384     FX_LOG(ERROR, LOG_TAG, "Unable to get /svc for dart component");
385     return false;
386   }
387 
388   fidl::InterfaceHandle<fuchsia::sys::Environment> environment;
389   svc->Connect(environment.NewRequest());
390 
391   InitBuiltinLibrariesForIsolate(
392       url_, namespace_, stdoutfd_, stderrfd_, std::move(environment),
393       std::move(directory_request), false /* service_isolate */);
394   namespace_ = nullptr;
395 
396   Dart_ExitScope();
397   Dart_ExitIsolate();
398   char* error = Dart_IsolateMakeRunnable(isolate_);
399   if (error != nullptr) {
400     Dart_EnterIsolate(isolate_);
401     Dart_ShutdownIsolate();
402     FX_LOGF(ERROR, LOG_TAG, "Unable to make isolate runnable: %s", error);
403     free(error);
404     return false;
405   }
406   Dart_EnterIsolate(isolate_);
407   Dart_EnterScope();
408 
409   Dart_Handle dart_arguments =
410       Dart_NewListOf(Dart_CoreType_String, arguments.size());
411   if (Dart_IsError(dart_arguments)) {
412     FX_LOGF(ERROR, LOG_TAG, "Failed to allocate Dart arguments list: %s",
413             Dart_GetError(dart_arguments));
414     Dart_ExitScope();
415     return false;
416   }
417   for (size_t i = 0; i < arguments.size(); i++) {
418     tonic::LogIfError(
419         Dart_ListSetAt(dart_arguments, i, ToDart(arguments.at(i))));
420   }
421 
422   Dart_Handle argv[] = {
423       dart_arguments,
424   };
425 
426   Dart_Handle main_result = Dart_Invoke(Dart_RootLibrary(), ToDart("main"),
427                                         dart_utils::ArraySize(argv), argv);
428   if (Dart_IsError(main_result)) {
429     auto dart_state = tonic::DartState::Current();
430     if (!dart_state->has_set_return_code()) {
431       // The program hasn't set a return code meaning this exit is unexpected.
432       FX_LOG(ERROR, LOG_TAG, Dart_GetError(main_result));
433       return_code_ = tonic::GetErrorExitCode(main_result);
434 
435       dart_utils::HandleIfException(runner_incoming_services_, url_,
436                                     main_result);
437     }
438     Dart_ExitScope();
439     return false;
440   }
441 
442   Dart_ExitScope();
443   return true;
444 }
445 
Kill()446 void DartComponentController::Kill() {
447   if (Dart_CurrentIsolate()) {
448     tonic::DartMicrotaskQueue::GetForCurrentThread()->Destroy();
449 
450     loop_->Quit();
451 
452     // TODO(rosswang): The docs warn of threading issues if doing this again,
453     // but without this, attempting to shut down the isolate finalizes app
454     // contexts that can't tell a shutdown is in progress and so fatal.
455     Dart_SetMessageNotifyCallback(nullptr);
456 
457     Dart_ShutdownIsolate();
458   }
459 }
460 
Detach()461 void DartComponentController::Detach() {
462   binding_.set_error_handler([](zx_status_t status) {});
463 }
464 
SendReturnCode()465 void DartComponentController::SendReturnCode() {
466   binding_.events().OnTerminated(return_code_,
467                                  fuchsia::sys::TerminationReason::EXITED);
468 }
469 
470 const zx::duration kIdleWaitDuration = zx::sec(2);
471 const zx::duration kIdleNotifyDuration = zx::msec(500);
472 const zx::duration kIdleSlack = zx::sec(1);
473 
MessageEpilogue(Dart_Handle result)474 void DartComponentController::MessageEpilogue(Dart_Handle result) {
475   auto dart_state = tonic::DartState::Current();
476   // If the Dart program has set a return code, then it is intending to shut
477   // down by way of a fatal error, and so there is no need to override
478   // return_code_.
479   if (dart_state->has_set_return_code()) {
480     Dart_ShutdownIsolate();
481     return;
482   }
483 
484   dart_utils::HandleIfException(runner_incoming_services_, url_, result);
485 
486   // Otherwise, see if there was any other error.
487   return_code_ = tonic::GetErrorExitCode(result);
488   if (return_code_ != 0) {
489     Dart_ShutdownIsolate();
490     return;
491   }
492 
493   idle_start_ = zx::clock::get_monotonic();
494   zx_status_t status =
495       idle_timer_.set(idle_start_ + kIdleWaitDuration, kIdleSlack);
496   if (status != ZX_OK) {
497     FX_LOGF(INFO, LOG_TAG, "Idle timer set failed: %s",
498             zx_status_get_string(status));
499   }
500 }
501 
OnIdleTimer(async_dispatcher_t * dispatcher,async::WaitBase * wait,zx_status_t status,const zx_packet_signal * signal)502 void DartComponentController::OnIdleTimer(async_dispatcher_t* dispatcher,
503                                           async::WaitBase* wait,
504                                           zx_status_t status,
505                                           const zx_packet_signal* signal) {
506   if ((status != ZX_OK) || !(signal->observed & ZX_TIMER_SIGNALED) ||
507       !Dart_CurrentIsolate()) {
508     // Timer closed or isolate shutdown.
509     return;
510   }
511 
512   zx::time deadline = idle_start_ + kIdleWaitDuration;
513   zx::time now = zx::clock::get_monotonic();
514   if (now >= deadline) {
515     // No Dart message has been processed for kIdleWaitDuration: assume we'll
516     // stay idle for kIdleNotifyDuration.
517     Dart_NotifyIdle((now + kIdleNotifyDuration).get());
518     idle_start_ = zx::time(0);
519     idle_timer_.cancel();  // De-assert signal.
520   } else {
521     // Early wakeup or message pushed idle time forward: reschedule.
522     zx_status_t status = idle_timer_.set(deadline, kIdleSlack);
523     if (status != ZX_OK) {
524       FX_LOGF(INFO, LOG_TAG, "Idle timer set failed: %s",
525               zx_status_get_string(status));
526     }
527   }
528   wait->Begin(dispatcher);  // ignore errors
529 }
530 
531 }  // namespace dart_runner
532