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 "component.h"
6
7 #include <dlfcn.h>
8 #include <fuchsia/mem/cpp/fidl.h>
9 #include <lib/async-loop/cpp/loop.h>
10 #include <lib/async/cpp/task.h>
11 #include <lib/fdio/directory.h>
12 #include <lib/fdio/namespace.h>
13 #include <lib/ui/scenic/cpp/view_token_pair.h>
14 #include <lib/vfs/cpp/remote_dir.h>
15 #include <lib/vfs/cpp/service.h>
16 #include <sys/stat.h>
17 #include <zircon/dlfcn.h>
18 #include <zircon/status.h>
19 #include <regex>
20 #include <sstream>
21
22 #include "flutter/fml/synchronization/waitable_event.h"
23 #include "flutter/runtime/dart_vm_lifecycle.h"
24 #include "flutter/shell/common/switches.h"
25 #include "runtime/dart/utils/files.h"
26 #include "runtime/dart/utils/handle_exception.h"
27 #include "runtime/dart/utils/tempfs.h"
28 #include "runtime/dart/utils/vmo.h"
29
30 #include "service_provider_dir.h"
31 #include "task_observers.h"
32 #include "task_runner_adapter.h"
33 #include "thread.h"
34
35 namespace flutter_runner {
36
37 constexpr char kDataKey[] = "data";
38 constexpr char kTmpPath[] = "/tmp";
39 constexpr char kServiceRootPath[] = "/svc";
40
41 std::pair<std::unique_ptr<Thread>, std::unique_ptr<Application>>
Create(TerminationCallback termination_callback,fuchsia::sys::Package package,fuchsia::sys::StartupInfo startup_info,std::shared_ptr<sys::ServiceDirectory> runner_incoming_services,fidl::InterfaceRequest<fuchsia::sys::ComponentController> controller)42 Application::Create(
43 TerminationCallback termination_callback,
44 fuchsia::sys::Package package,
45 fuchsia::sys::StartupInfo startup_info,
46 std::shared_ptr<sys::ServiceDirectory> runner_incoming_services,
47 fidl::InterfaceRequest<fuchsia::sys::ComponentController> controller) {
48 std::unique_ptr<Thread> thread = std::make_unique<Thread>();
49 std::unique_ptr<Application> application;
50
51 fml::AutoResetWaitableEvent latch;
52 async::PostTask(thread->dispatcher(), [&]() mutable {
53 application.reset(
54 new Application(std::move(termination_callback), std::move(package),
55 std::move(startup_info), runner_incoming_services,
56 std::move(controller)));
57 latch.Signal();
58 });
59
60 latch.Wait();
61 return {std::move(thread), std::move(application)};
62 }
63
DebugLabelForURL(const std::string & url)64 static std::string DebugLabelForURL(const std::string& url) {
65 auto found = url.rfind("/");
66 if (found == std::string::npos) {
67 return url;
68 } else {
69 return {url, found + 1};
70 }
71 }
72
Application(TerminationCallback termination_callback,fuchsia::sys::Package package,fuchsia::sys::StartupInfo startup_info,std::shared_ptr<sys::ServiceDirectory> runner_incoming_services,fidl::InterfaceRequest<fuchsia::sys::ComponentController> application_controller_request)73 Application::Application(
74 TerminationCallback termination_callback,
75 fuchsia::sys::Package package,
76 fuchsia::sys::StartupInfo startup_info,
77 std::shared_ptr<sys::ServiceDirectory> runner_incoming_services,
78 fidl::InterfaceRequest<fuchsia::sys::ComponentController>
79 application_controller_request)
80 : termination_callback_(std::move(termination_callback)),
81 debug_label_(DebugLabelForURL(startup_info.launch_info.url)),
82 application_controller_(this),
83 outgoing_dir_(new vfs::PseudoDir()),
84 runner_incoming_services_(runner_incoming_services),
85 weak_factory_(this) {
86 application_controller_.set_error_handler(
87 [this](zx_status_t status) { Kill(); });
88
89 FML_DCHECK(fdio_ns_.is_valid());
90 // LaunchInfo::url non-optional.
91 auto& launch_info = startup_info.launch_info;
92
93 // LaunchInfo::arguments optional.
94 if (auto& arguments = launch_info.arguments) {
95 settings_ = flutter::SettingsFromCommandLine(
96 fml::CommandLineFromIterators(arguments->begin(), arguments->end()));
97 }
98
99 // Determine /pkg/data directory from StartupInfo.
100 std::string data_path;
101 for (size_t i = 0; i < startup_info.program_metadata->size(); ++i) {
102 auto pg = startup_info.program_metadata->at(i);
103 if (pg.key.compare(kDataKey) == 0) {
104 data_path = "pkg/" + pg.value;
105 }
106 }
107 if (data_path.empty()) {
108 FML_DLOG(ERROR) << "Could not find a /pkg/data directory for "
109 << package.resolved_url;
110 return;
111 }
112
113 // Setup /tmp to be mapped to the process-local memfs.
114 dart_utils::SetupComponentTemp(fdio_ns_.get());
115
116 // LaunchInfo::flat_namespace optional.
117 for (size_t i = 0; i < startup_info.flat_namespace.paths.size(); ++i) {
118 const auto& path = startup_info.flat_namespace.paths.at(i);
119 if (path == kTmpPath) {
120 continue;
121 }
122
123 zx::channel dir;
124 if (path == kServiceRootPath) {
125 svc_ = std::make_unique<sys::ServiceDirectory>(
126 std::move(startup_info.flat_namespace.directories.at(i)));
127 dir = svc_->CloneChannel().TakeChannel();
128 } else {
129 dir = std::move(startup_info.flat_namespace.directories.at(i));
130 }
131
132 zx_handle_t dir_handle = dir.release();
133 if (fdio_ns_bind(fdio_ns_.get(), path.data(), dir_handle) != ZX_OK) {
134 FML_DLOG(ERROR) << "Could not bind path to namespace: " << path;
135 zx_handle_close(dir_handle);
136 }
137 }
138
139 application_directory_.reset(fdio_ns_opendir(fdio_ns_.get()));
140 FML_DCHECK(application_directory_.is_valid());
141
142 application_assets_directory_.reset(openat(
143 application_directory_.get(), data_path.c_str(), O_RDONLY | O_DIRECTORY));
144
145 // TODO: LaunchInfo::out.
146
147 // TODO: LaunchInfo::err.
148
149 // LaunchInfo::service_request optional.
150 if (launch_info.directory_request) {
151 outgoing_dir_->Serve(fuchsia::io::OPEN_RIGHT_READABLE |
152 fuchsia::io::OPEN_RIGHT_WRITABLE |
153 fuchsia::io::OPEN_FLAG_DIRECTORY,
154 std::move(launch_info.directory_request));
155 }
156
157 directory_request_ = directory_ptr_.NewRequest();
158
159 fidl::InterfaceHandle<fuchsia::io::Directory> flutter_public_dir;
160 // TODO(anmittal): when fixing enumeration using new c++ vfs, make sure that
161 // flutter_public_dir is only accessed once we receive OnOpen Event.
162 // That will prevent FL-175 for public directory
163 auto request = flutter_public_dir.NewRequest().TakeChannel();
164 fdio_service_connect_at(directory_ptr_.channel().get(), "svc",
165 request.release());
166
167 auto service_provider_dir = std::make_unique<ServiceProviderDir>();
168 service_provider_dir->set_fallback(std::move(flutter_public_dir));
169
170 // Clone and check if client is servicing the directory.
171 directory_ptr_->Clone(fuchsia::io::OPEN_FLAG_DESCRIBE |
172 fuchsia::io::OPEN_RIGHT_READABLE |
173 fuchsia::io::OPEN_RIGHT_WRITABLE,
174 cloned_directory_ptr_.NewRequest());
175
176 cloned_directory_ptr_.events().OnOpen =
177 [this](zx_status_t status, std::unique_ptr<fuchsia::io::NodeInfo> info) {
178 cloned_directory_ptr_.Unbind();
179 if (status != ZX_OK) {
180 FML_LOG(ERROR) << "could not bind out directory for flutter app("
181 << debug_label_
182 << "): " << zx_status_get_string(status);
183 return;
184 }
185 const char* other_dirs[] = {"debug", "ctrl"};
186 // add other directories as RemoteDirs.
187 for (auto& dir_str : other_dirs) {
188 fidl::InterfaceHandle<fuchsia::io::Directory> dir;
189 auto request = dir.NewRequest().TakeChannel();
190 fdio_service_connect_at(directory_ptr_.channel().get(), dir_str,
191 request.release());
192 outgoing_dir_->AddEntry(
193 dir_str, std::make_unique<vfs::RemoteDir>(dir.TakeChannel()));
194 }
195 };
196
197 cloned_directory_ptr_.set_error_handler(
198 [this](zx_status_t status) { cloned_directory_ptr_.Unbind(); });
199
200 // TODO: LaunchInfo::additional_services optional.
201
202 // All launch arguments have been read. Perform service binding and
203 // final settings configuration. The next call will be to create a view
204 // for this application.
205 service_provider_dir->AddService(
206 fuchsia::ui::app::ViewProvider::Name_,
207 std::make_unique<vfs::Service>(
208 [this](zx::channel channel, async_dispatcher_t* dispatcher) {
209 shells_bindings_.AddBinding(
210 this, fidl::InterfaceRequest<fuchsia::ui::app::ViewProvider>(
211 std::move(channel)));
212 }));
213
214 outgoing_dir_->AddEntry("svc", std::move(service_provider_dir));
215
216 // Setup the application controller binding.
217 if (application_controller_request) {
218 application_controller_.Bind(std::move(application_controller_request));
219 }
220
221 // Compare flutter_jit_runner in BUILD.gn.
222 settings_.vm_snapshot_data_path = "pkg/data/vm_snapshot_data.bin";
223 settings_.vm_snapshot_instr_path = "pkg/data/vm_snapshot_instructions.bin";
224 settings_.isolate_snapshot_data_path =
225 "pkg/data/isolate_core_snapshot_data.bin";
226 settings_.isolate_snapshot_instr_path =
227 "pkg/data/isolate_core_snapshot_instructions.bin";
228
229 {
230 // Check if we can use the snapshot with the framework already loaded.
231 std::string runner_framework;
232 std::string app_framework;
233 if (dart_utils::ReadFileToString("pkg/data/runner.frameworkversion",
234 &runner_framework) &&
235 dart_utils::ReadFileToStringAt(application_assets_directory_.get(),
236 "app.frameworkversion",
237 &app_framework) &&
238 (runner_framework.compare(app_framework) == 0)) {
239 settings_.vm_snapshot_data_path =
240 "pkg/data/framework_vm_snapshot_data.bin";
241 settings_.vm_snapshot_instr_path =
242 "pkg/data/framework_vm_snapshot_instructions.bin";
243 settings_.isolate_snapshot_data_path =
244 "pkg/data/framework_isolate_core_snapshot_data.bin";
245 settings_.isolate_snapshot_instr_path =
246 "pkg/data/framework_isolate_core_snapshot_instructions.bin";
247
248 FML_LOG(INFO) << "Using snapshot with framework for "
249 << package.resolved_url;
250 } else {
251 FML_LOG(INFO) << "Using snapshot without framework for "
252 << package.resolved_url;
253 }
254 }
255
256 #if defined(DART_PRODUCT)
257 settings_.enable_observatory = false;
258 #else
259 settings_.enable_observatory = true;
260 #endif
261
262 settings_.icu_data_path = "";
263
264 settings_.assets_dir = application_assets_directory_.get();
265
266 // Compare flutter_jit_app in flutter_app.gni.
267 settings_.application_kernel_list_asset = "app.dilplist";
268
269 settings_.log_tag = debug_label_ + std::string{"(flutter)"};
270
271 // No asserts in debug or release product.
272 // No asserts in release with flutter_profile=true (non-product)
273 // Yes asserts in non-product debug.
274 #if !defined(DART_PRODUCT) && (!defined(FLUTTER_PROFILE) || !defined(NDEBUG))
275 // Debug mode
276 settings_.disable_dart_asserts = false;
277 #else
278 // Release mode
279 settings_.disable_dart_asserts = true;
280 #endif
281
282 settings_.task_observer_add =
283 std::bind(&CurrentMessageLoopAddAfterTaskObserver, std::placeholders::_1,
284 std::placeholders::_2);
285
286 settings_.task_observer_remove = std::bind(
287 &CurrentMessageLoopRemoveAfterTaskObserver, std::placeholders::_1);
288
289 // TODO(FL-117): Re-enable causal async stack traces when this issue is
290 // addressed.
291 settings_.dart_flags = {"--no_causal_async_stacks"};
292
293 // Disable code collection as it interferes with JIT code warmup
294 // by decreasing usage counters and flushing code which is still useful.
295 settings_.dart_flags.push_back("--no-collect_code");
296
297 if (!flutter::DartVM::IsRunningPrecompiledCode()) {
298 // The interpreter is enabled unconditionally in JIT mode. If an app is
299 // built for debugging (that is, with no bytecode), the VM will fall back on
300 // ASTs.
301 settings_.dart_flags.push_back("--enable_interpreter");
302 }
303
304 // Don't collect CPU samples from Dart VM C++ code.
305 settings_.dart_flags.push_back("--no_profile_vm");
306
307 // Scale back CPU profiler sampling period on ARM64 to avoid overloading
308 // the tracing engine.
309 #if defined(__aarch64__)
310 settings_.dart_flags.push_back("--profile_period=10000");
311 #endif // defined(__aarch64__)
312
313 auto weak_application = weak_factory_.GetWeakPtr();
314 auto platform_task_runner =
315 CreateFMLTaskRunner(async_get_default_dispatcher());
316 const std::string component_url = package.resolved_url;
317 settings_.unhandled_exception_callback =
318 [weak_application, platform_task_runner, runner_incoming_services,
319 component_url](const std::string& error,
320 const std::string& stack_trace) {
321 if (weak_application) {
322 // TODO(cbracken): unsafe. The above check and the PostTask below are
323 // happening on the UI thread. If the Application dtor and thread
324 // termination happen (on the platform thread) between the previous
325 // line and the next line, a crash will occur since we'll be posting
326 // to a dead thread. See Runner::OnApplicationTerminate() in
327 // runner.cc.
328 platform_task_runner->PostTask([weak_application,
329 runner_incoming_services,
330 component_url, error, stack_trace]() {
331 if (weak_application) {
332 dart_utils::HandleException(runner_incoming_services,
333 component_url, error, stack_trace);
334 } else {
335 FML_LOG(ERROR)
336 << "Unhandled exception after application shutdown: "
337 << error;
338 }
339 });
340 } else {
341 FML_LOG(ERROR) << "Unhandled exception after application shutdown: "
342 << error;
343 }
344 // Ideally we would return whether HandleException returned ZX_OK, but
345 // short of knowing if the exception was correctly handled, we return
346 // false to have the error and stack trace printed in the logs.
347 return false;
348 };
349
350 AttemptVMLaunchWithCurrentSettings(settings_);
351 }
352
353 Application::~Application() = default;
354
GetDebugLabel() const355 const std::string& Application::GetDebugLabel() const {
356 return debug_label_;
357 }
358
359 class FileInNamespaceBuffer final : public fml::Mapping {
360 public:
FileInNamespaceBuffer(int namespace_fd,const char * path,bool executable)361 FileInNamespaceBuffer(int namespace_fd, const char* path, bool executable)
362 : address_(nullptr), size_(0) {
363 fuchsia::mem::Buffer buffer;
364 if (!dart_utils::VmoFromFilenameAt(namespace_fd, path, &buffer)) {
365 return;
366 }
367 if (buffer.size == 0) {
368 return;
369 }
370
371 uint32_t flags = ZX_VM_PERM_READ;
372 if (executable) {
373 flags |= ZX_VM_PERM_EXECUTE;
374
375 // VmoFromFilenameAt will return VMOs without ZX_RIGHT_EXECUTE,
376 // so we need replace_as_executable to be able to map them as
377 // ZX_VM_PERM_EXECUTE.
378 // TODO(mdempsky): Update comment once SEC-42 is fixed.
379 zx_status_t status =
380 buffer.vmo.replace_as_executable(zx::handle(), &buffer.vmo);
381 if (status != ZX_OK) {
382 FML_LOG(FATAL) << "Failed to make VMO executable: "
383 << zx_status_get_string(status);
384 }
385 }
386 uintptr_t addr;
387 zx_status_t status =
388 zx::vmar::root_self()->map(0, buffer.vmo, 0, buffer.size, flags, &addr);
389 if (status != ZX_OK) {
390 FML_LOG(FATAL) << "Failed to map " << path << ": "
391 << zx_status_get_string(status);
392 }
393
394 address_ = reinterpret_cast<void*>(addr);
395 size_ = buffer.size;
396 }
397
~FileInNamespaceBuffer()398 ~FileInNamespaceBuffer() {
399 if (address_ != nullptr) {
400 zx::vmar::root_self()->unmap(reinterpret_cast<uintptr_t>(address_),
401 size_);
402 address_ = nullptr;
403 size_ = 0;
404 }
405 }
406
407 // |fml::Mapping|
GetMapping() const408 const uint8_t* GetMapping() const override {
409 return reinterpret_cast<const uint8_t*>(address_);
410 }
411
412 // |fml::Mapping|
GetSize() const413 size_t GetSize() const override { return size_; }
414
415 private:
416 void* address_;
417 size_t size_;
418
419 FML_DISALLOW_COPY_AND_ASSIGN(FileInNamespaceBuffer);
420 };
421
CreateWithContentsOfFile(int namespace_fd,const char * file_path,bool executable)422 std::unique_ptr<fml::Mapping> CreateWithContentsOfFile(int namespace_fd,
423 const char* file_path,
424 bool executable) {
425 FML_TRACE_EVENT("flutter", "LoadFile", "path", file_path);
426 auto source = std::make_unique<FileInNamespaceBuffer>(namespace_fd, file_path,
427 executable);
428 return source->GetMapping() == nullptr ? nullptr : std::move(source);
429 }
430
AttemptVMLaunchWithCurrentSettings(const flutter::Settings & settings)431 void Application::AttemptVMLaunchWithCurrentSettings(
432 const flutter::Settings& settings) {
433 if (!flutter::DartVM::IsRunningPrecompiledCode()) {
434 // We will be initializing the VM lazily in this case.
435 return;
436 }
437
438 // Compare flutter_aot_app in flutter_app.gni.
439 fml::RefPtr<flutter::DartSnapshot> vm_snapshot =
440 fml::MakeRefCounted<flutter::DartSnapshot>(
441 CreateWithContentsOfFile(
442 application_assets_directory_.get() /* /pkg/data */,
443 "vm_snapshot_data.bin", false),
444 CreateWithContentsOfFile(
445 application_assets_directory_.get() /* /pkg/data */,
446 "vm_snapshot_instructions.bin", true));
447
448 isolate_snapshot_ = fml::MakeRefCounted<flutter::DartSnapshot>(
449 CreateWithContentsOfFile(
450 application_assets_directory_.get() /* /pkg/data */,
451 "isolate_snapshot_data.bin", false),
452 CreateWithContentsOfFile(
453 application_assets_directory_.get() /* /pkg/data */,
454 "isolate_snapshot_instructions.bin", true));
455
456 shared_snapshot_ = fml::MakeRefCounted<flutter::DartSnapshot>(
457 CreateWithContentsOfFile(
458 application_assets_directory_.get() /* /pkg/data */,
459 "shared_snapshot_data.bin", false),
460 CreateWithContentsOfFile(
461 application_assets_directory_.get() /* /pkg/data */,
462 "shared_snapshot_instructions.bin", true));
463
464 auto vm = flutter::DartVMRef::Create(settings_, //
465 std::move(vm_snapshot), //
466 isolate_snapshot_, //
467 shared_snapshot_ //
468 );
469 FML_CHECK(vm) << "Mut be able to initialize the VM.";
470 }
471
472 // |fuchsia::sys::ComponentController|
Kill()473 void Application::Kill() {
474 application_controller_.events().OnTerminated(
475 last_return_code_.second, fuchsia::sys::TerminationReason::EXITED);
476
477 termination_callback_(this);
478 // WARNING: Don't do anything past this point as this instance may have been
479 // collected.
480 }
481
482 // |fuchsia::sys::ComponentController|
Detach()483 void Application::Detach() {
484 application_controller_.set_error_handler(nullptr);
485 }
486
487 // |flutter::Engine::Delegate|
OnEngineTerminate(const Engine * shell_holder)488 void Application::OnEngineTerminate(const Engine* shell_holder) {
489 auto found = std::find_if(shell_holders_.begin(), shell_holders_.end(),
490 [shell_holder](const auto& holder) {
491 return holder.get() == shell_holder;
492 });
493
494 if (found == shell_holders_.end()) {
495 return;
496 }
497
498 // We may launch multiple shell in this application. However, we will
499 // terminate when the last shell goes away. The error code return to the
500 // application controller will be the last isolate that had an error.
501 auto return_code = shell_holder->GetEngineReturnCode();
502 if (return_code.first) {
503 last_return_code_ = return_code;
504 }
505
506 shell_holders_.erase(found);
507
508 if (shell_holders_.size() == 0) {
509 Kill();
510 // WARNING: Don't do anything past this point because the delegate may have
511 // collected this instance via the termination callback.
512 }
513 }
514
515 // |fuchsia::ui::app::ViewProvider|
CreateView(zx::eventpair view_token,fidl::InterfaceRequest<fuchsia::sys::ServiceProvider> incoming_services,fidl::InterfaceHandle<fuchsia::sys::ServiceProvider> outgoing_services)516 void Application::CreateView(
517 zx::eventpair view_token,
518 fidl::InterfaceRequest<fuchsia::sys::ServiceProvider> incoming_services,
519 fidl::InterfaceHandle<fuchsia::sys::ServiceProvider> outgoing_services) {
520 if (!svc_) {
521 FML_DLOG(ERROR)
522 << "Component incoming services was invalid when attempting to "
523 "create a shell for a view provider request.";
524 return;
525 }
526
527 shell_holders_.emplace(std::make_unique<Engine>(
528 *this, // delegate
529 debug_label_, // thread label
530 svc_, // Component incoming services
531 settings_, // settings
532 std::move(isolate_snapshot_), // isolate snapshot
533 std::move(shared_snapshot_), // shared snapshot
534 scenic::ToViewToken(std::move(view_token)), // view token
535 std::move(fdio_ns_), // FDIO namespace
536 std::move(directory_request_) // outgoing request
537 ));
538 }
539
540 #if !defined(DART_PRODUCT)
WriteProfileToTrace() const541 void Application::WriteProfileToTrace() const {
542 for (const auto& engine : shell_holders_) {
543 engine->WriteProfileToTrace();
544 }
545 }
546 #endif // !defined(DART_PRODUCT)
547
548 } // namespace flutter_runner
549