text: // Copyright 2020 The Fuchsia Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include "src/sys/appmgr/crash_introspector.h"
  
  #include <fuchsia/sys/internal/cpp/fidl.h>
  #include <zircon/assert.h>
  #include <zircon/errors.h>
  #include <zircon/status.h>
  #include <zircon/syscalls/exception.h>
  #include <zircon/types.h>
  
  #include <memory>
  #include <utility>
  
  #include "lib/async/cpp/task.h"
  #include "lib/async/default.h"
  #include "lib/fidl/cpp/clone.h"
  #include "lib/fit/defer.h"
  #include "lib/fitx/result.h"
  #include "src/lib/fsl/handles/object_info.h"
  
  namespace component {
  
  using fuchsia::sys::internal::CrashIntrospect_FindComponentByThreadKoid_Response;
  using fuchsia::sys::internal::CrashIntrospect_FindComponentByThreadKoid_Result;
  
  // Exceptions have a TTL of 5 minutes in the exception handler so this timeout should be higher
  // to preserve the mapping for exceptions that have expired. There is still a chance that exception
  // handling takes longer, but we at least want to cover expired exceptions.
  const zx::duration kDefaultThreadCacheTimeout = zx::min(10);
  
  CrashIntrospector::CrashIntrospector() : weak_ptr_factory_(this) {}
  
  CrashIntrospector::~CrashIntrospector() = default;
  
  void CrashIntrospector::AddBinding(
      fidl::InterfaceRequest<fuchsia::sys::internal::CrashIntrospect> request) {
    bindings_.AddBinding(this, std::move(request));
  }
  
  void CrashIntrospector::FindComponentByThreadKoid(zx_koid_t thread_koid,
                                                    FindComponentByThreadKoidCallback callback) {
    CrashIntrospect_FindComponentByThreadKoid_Result result;
    auto status = RemoveThreadFromCache(thread_koid);
    if (status.is_ok()) {
      CrashIntrospect_FindComponentByThreadKoid_Response response;
      response.component_info = std::move(status.value());
      result.set_response(std::move(response));
    } else {
      result.set_err(ZX_ERR_NOT_FOUND);
    }
  
    callback(std::move(result));
  }
  
  void CrashIntrospector::RegisterJob(const zx::job& job,
                                      fuchsia::sys::internal::SourceIdentity component_info) {
    zx::channel exception_channel;
    job.create_exception_channel(0, &exception_channel);
    auto monitor = std::make_unique<CrashMonitor>(
        weak_ptr_factory_.GetWeakPtr(), std::move(exception_channel), std::move(component_info));
    monitors_.emplace(monitor.get(), std::move(monitor));
  }
  
  std::unique_ptr<CrashIntrospector::CrashMonitor> CrashIntrospector::ExtractMonitor(
      const CrashIntrospector::CrashMonitor* monitor) {
    auto it = monitors_.find(monitor);
    if (it == monitors_.end()) {
      return nullptr;
    }
    auto obj = std::move(it->second);
    monitors_.erase(it);
    return obj;
  }
  
  fitx::result<bool, fuchsia::sys::internal::SourceIdentity> CrashIntrospector::RemoveThreadFromCache(
      zx_koid_t thread_koid) {
    auto it = thread_cache_.find(thread_koid);
    if (it == thread_cache_.end()) {
      return fitx::error(false);
    }
    auto obj = std::move(it->second);
    // already removed, cancel auto removal task if pending
    obj.first->Cancel();
    thread_cache_.erase(it);
    return fitx::ok(std::move(obj.second));
  }
  
  void CrashIntrospector::AddThreadToCache(
      const zx::thread& thread, const fuchsia::sys::internal::SourceIdentity& component_info) {
    const auto thread_koid = fsl::GetKoid(thread.get());
    if (thread_cache_.count(thread_koid) > 0) {
      FX_LOGS(ERROR) << "Thread " << thread_koid << " already in map.";
      return;
    }
  
    auto timeout_task = std::make_unique<async::TaskClosure>([this, thread_koid]() {
      auto result = RemoveThreadFromCache(thread_koid);
      // result will die at end of this statement
    });
  
    timeout_task->PostDelayed(async_get_default_dispatcher(), kDefaultThreadCacheTimeout);
  
    thread_cache_.emplace(thread_koid,
                          std::make_pair(std::move(timeout_task), fidl::Clone(component_info)));
  }
  
  CrashIntrospector::CrashMonitor::CrashMonitor(fxl::WeakPtr<CrashIntrospector> introspector,
                                                zx::channel exception_channel,
                                                fuchsia::sys::internal::SourceIdentity component_info)
      : introspector_(std::move(introspector)),
        component_info_(std::move(component_info)),
        exception_channel_(std::move(exception_channel)),
        wait_(this, exception_channel_.get(), ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED) {
    wait_.Begin(async_get_default_dispatcher());
  }
  
  void CrashIntrospector::CrashMonitor::CrashHandler(async_dispatcher_t* dispatcher,
                                                     async::WaitBase* wait, zx_status_t status,
                                                     const zx_packet_signal* signal) {
    FX_CHECK(status == ZX_OK) << "status: " << status;
  
    if (signal->observed & ZX_CHANNEL_READABLE) {
      // wait for next signal
      auto run_again = fit::defer([&wait, &dispatcher] { wait->Begin(dispatcher); });
      zx_exception_info_t info;
      zx::exception exception;
      if (const zx_status_t status = exception_channel_.read(
              0, &info, exception.reset_and_get_address(), sizeof(info), 1, nullptr, nullptr);
          status != ZX_OK) {
        FX_LOGS(ERROR) << "Failed to read from the exception channel: " << status;
        return;
      }
      zx::thread thread;
      if (const zx_status_t status = exception.get_thread(&thread); status != ZX_OK) {
        FX_LOGS(ERROR) << "Could not get thread for exception: " << status;
  
        return;
      }
  
      if (introspector_) {
        introspector_->AddThreadToCache(thread, component_info_);
      } else {
        run_again.cancel();
        // parent is already dead
        wait_.Cancel();
      }
      return;
    }
    FX_CHECK(signal->observed & ZX_CHANNEL_PEER_CLOSED) << "signal observed: " << signal->observed;
    // job died, stop monitoring
    wait_.Cancel();
    if (introspector_) {
      auto self = introspector_->ExtractMonitor(this);
      // |self| will die when this block finishes
    }
    // this object will die when this function returns
  }
  
  CrashIntrospector::CrashMonitor::~CrashMonitor() = default;
  
  }  // namespace component
  