| // Copyright 2019 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/developer/forensics/exceptions/process_limbo_manager.h" |
| |
| #include <lib/syslog/cpp/macros.h> |
| |
| #include "fuchsia/exception/cpp/fidl.h" |
| #include "src/lib/fsl/handles/object_info.h" |
| |
| namespace forensics { |
| namespace exceptions { |
| |
| namespace { |
| |
| using fuchsia::exception::MAX_EXCEPTIONS; |
| using fuchsia::exception::ProcessException; |
| using fuchsia::exception::ProcessExceptionInfo; |
| using fuchsia::exception::ProcessExceptionMetadata; |
| |
| // Removes all stale weak pointers from the handler list. |
| void PruneStaleHandlers(std::vector<fxl::WeakPtr<ProcessLimboHandler>>* handlers) { |
| // We only move active handlers to the new list. |
| std::vector<fxl::WeakPtr<ProcessLimboHandler>> new_handlers; |
| for (auto& handler : *handlers) { |
| if (handler) |
| new_handlers.push_back(std::move(handler)); |
| } |
| |
| *handlers = std::move(new_handlers); |
| } |
| |
| // This verification was getting repeated for every call. |
| template <typename CallbackType> |
| bool VerifyState(const fxl::WeakPtr<ProcessLimboManager> limbo_manager, CallbackType* cb) { |
| if (!limbo_manager) { |
| (*cb)(fpromise::error(ZX_ERR_UNAVAILABLE)); |
| return false; |
| } |
| return true; |
| } |
| |
| std::vector<std::string> CreateFilterVector(const std::set<std::string>& filter_set) { |
| std::vector<std::string> filters; |
| filters.reserve(filter_set.size()); |
| |
| for (auto& filter : filter_set) { |
| filters.emplace_back(filter); |
| } |
| |
| return filters; |
| } |
| |
| } // namespace |
| |
| ProcessLimboManager::ProcessLimboManager() : weak_factory_(this) { |
| // Set the default function for getting process names. |
| obtain_process_name_fn_ = [](zx_handle_t handle) -> std::string { |
| return fsl::GetObjectName(handle); |
| }; |
| } |
| |
| fxl::WeakPtr<ProcessLimboManager> ProcessLimboManager::GetWeakPtr() { |
| return weak_factory_.GetWeakPtr(); |
| } |
| |
| void ProcessLimboManager::AddToLimbo(ProcessException process_exception) { |
| // Check filters. |
| auto process_name = obtain_process_name_fn_(process_exception.process().get()); |
| |
| // Empty names will be stored within the limbo. |
| if (!process_name.empty() && !filters_.empty()) { |
| // Search for a partial match over the filters. |
| bool filter_found = false; |
| for (auto& filter : filters_) { |
| if (process_name.find(filter) != std::string::npos) { |
| filter_found = true; |
| break; |
| } |
| } |
| |
| // If a matching filter was found, we will not store this process. |
| if (filter_found) |
| return; |
| } |
| |
| limbo_[process_exception.info().process_koid] = std::move(process_exception); |
| |
| NotifyLimboChanged(); |
| } |
| |
| void ProcessLimboManager::NotifyLimboChanged() { |
| // Notify the handlers of the new list of processes in limbo. |
| PruneStaleHandlers(&handlers_); |
| for (auto& handler : handlers_) { |
| auto limbo_list = ListProcessesInLimbo(); |
| handler->LimboChanged(std::move(limbo_list)); |
| } |
| } |
| |
| void ProcessLimboManager::AddHandler(fxl::WeakPtr<ProcessLimboHandler> handler) { |
| handlers_.push_back(std::move(handler)); |
| } |
| |
| void ProcessLimboManager::AppendFiltersForTesting(const std::vector<std::string>& filters) { |
| for (auto& filter : filters) { |
| filters_.insert(filter); |
| } |
| } |
| |
| std::vector<ProcessExceptionMetadata> ProcessLimboManager::ListProcessesInLimbo() { |
| std::vector<ProcessExceptionMetadata> exceptions; |
| |
| size_t max_size = limbo_.size() <= MAX_EXCEPTIONS ? limbo_.size() : MAX_EXCEPTIONS; |
| exceptions.reserve(max_size); |
| |
| // The new rights of the handles we're going to duplicate. |
| zx_rights_t rights = |
| ZX_RIGHT_READ | ZX_RIGHT_GET_PROPERTY | ZX_RIGHT_TRANSFER | ZX_RIGHT_DUPLICATE; |
| for (const auto& [process_koid, limbo_exception] : limbo_) { |
| ProcessExceptionMetadata metadata = {}; |
| |
| zx::process process; |
| if (auto res = limbo_exception.process().duplicate(rights, &process); res != ZX_OK) { |
| FX_PLOGS(ERROR, res) << "Could not duplicate process handle."; |
| continue; |
| } |
| |
| zx::thread thread; |
| if (auto res = limbo_exception.thread().duplicate(rights, &thread); res != ZX_OK) { |
| FX_PLOGS(ERROR, res) << "Could not duplicate thread handle."; |
| continue; |
| } |
| |
| metadata.set_info(limbo_exception.info()); |
| metadata.set_process(std::move(process)); |
| metadata.set_thread(std::move(thread)); |
| |
| exceptions.push_back(std::move(metadata)); |
| |
| if (exceptions.size() >= MAX_EXCEPTIONS) |
| break; |
| } |
| |
| return exceptions; |
| } |
| |
| bool ProcessLimboManager::SetActive(bool active) { |
| // Ignore if no change. |
| if (active == active_) |
| return false; |
| active_ = active; |
| |
| // If the limbo was disabled, free all the exceptions. |
| if (!active_) |
| limbo_.clear(); |
| |
| // Notify the handlers of the new activa state. |
| PruneStaleHandlers(&handlers_); |
| for (auto& handler : handlers_) { |
| handler->ActiveStateChanged(active); |
| } |
| |
| return true; |
| } |
| |
| // ProcessLimboHandler ----------------------------------------------------------------------------- |
| |
| ProcessLimboHandler::ProcessLimboHandler(fxl::WeakPtr<ProcessLimboManager> limbo_manager) |
| : limbo_manager_(std::move(limbo_manager)), weak_factory_(this) {} |
| |
| fxl::WeakPtr<ProcessLimboHandler> ProcessLimboHandler::GetWeakPtr() { |
| return weak_factory_.GetWeakPtr(); |
| } |
| |
| void ProcessLimboHandler::SetActive(bool active, SetActiveCallback cb) { |
| // Call the callback before so that the response of this call is sent before any hanging gets. |
| cb(); |
| limbo_manager_->SetActive(active); |
| } |
| |
| void ProcessLimboHandler::ActiveStateChanged(bool active) { |
| if (!is_active_callback_) { |
| // Reset the WatchActive state as the state is different from the last time the get was called. |
| watch_active_dirty_bit_ = true; |
| } else { |
| is_active_callback_(active); |
| is_active_callback_ = {}; |
| watch_active_dirty_bit_ = false; |
| } |
| |
| // If there is a limbo call waiting, we tell them that it's canceled. |
| if (!active) { |
| if (watch_limbo_callback_) { |
| watch_limbo_callback_(fpromise::error(ZX_ERR_CANCELED)); |
| watch_limbo_callback_ = {}; |
| watch_limbo_dirty_bit_ = false; |
| } else { |
| watch_limbo_dirty_bit_ = true; |
| } |
| } |
| } |
| |
| void ProcessLimboHandler::LimboChanged(std::vector<ProcessExceptionMetadata> limbo_list) { |
| if (!watch_limbo_callback_) { |
| // Reset the hanging get state as the state is different from the first time the get was called. |
| watch_limbo_dirty_bit_ = true; |
| return; |
| } |
| |
| watch_limbo_callback_(fpromise::ok(std::move(limbo_list))); |
| watch_limbo_callback_ = {}; |
| watch_limbo_dirty_bit_ = false; |
| } |
| |
| void ProcessLimboHandler::GetActive(WatchActiveCallback cb) { |
| cb(limbo_manager_ ? limbo_manager_->active() : false); |
| } |
| |
| void ProcessLimboHandler::WatchActive(WatchActiveCallback cb) { |
| if (watch_active_dirty_bit_) { |
| watch_active_dirty_bit_ = false; |
| |
| bool is_active = !!limbo_manager_ ? limbo_manager_->active() : false; |
| cb(is_active); |
| return; |
| } |
| |
| // We store the latest callback for when the active state changes. |
| is_active_callback_ = std::move(cb); |
| } |
| |
| void ProcessLimboHandler::ListProcessesWaitingOnException( |
| ListProcessesWaitingOnExceptionCallback cb) { |
| if (!limbo_manager_) { |
| cb(fpromise::error(ZX_ERR_BAD_STATE)); |
| return; |
| } |
| if (!limbo_manager_->active()) { |
| cb(fpromise::error(ZX_ERR_UNAVAILABLE)); |
| return; |
| } |
| std::vector<ProcessExceptionInfo> exceptions; |
| for (const auto& [process_koid, limbo_exception] : limbo_manager_->limbo()) { |
| ProcessExceptionInfo info; |
| info.set_info(limbo_exception.info()); |
| |
| char name[ZX_MAX_NAME_LEN]; |
| if (auto res = limbo_exception.process().get_property(ZX_PROP_NAME, name, sizeof(name)); |
| res != ZX_OK) { |
| FX_PLOGS(ERROR, res) << "Could not get process name."; |
| continue; |
| } |
| info.set_process_name(name); |
| |
| if (auto res = limbo_exception.thread().get_property(ZX_PROP_NAME, name, sizeof(name)); |
| res != ZX_OK) { |
| FX_PLOGS(ERROR, res) << "Could not get thread name."; |
| continue; |
| } |
| info.set_thread_name(name); |
| |
| exceptions.push_back(std::move(info)); |
| |
| if (exceptions.size() >= MAX_EXCEPTIONS) |
| break; |
| } |
| cb(fpromise::ok(std::move(exceptions))); |
| } |
| |
| void ProcessLimboHandler::WatchProcessesWaitingOnException( |
| WatchProcessesWaitingOnExceptionCallback cb) { |
| if (watch_limbo_dirty_bit_) { |
| watch_limbo_dirty_bit_ = false; |
| |
| if (!limbo_manager_) { |
| cb(fpromise::error(ZX_ERR_BAD_STATE)); |
| return; |
| } |
| |
| if (!limbo_manager_->active()) { |
| cb(fpromise::error(ZX_ERR_UNAVAILABLE)); |
| return; |
| } |
| |
| auto processes = limbo_manager_->ListProcessesInLimbo(); |
| cb(fpromise::ok(std::move(processes))); |
| return; |
| } |
| |
| // Store the latest callback for when the processes enter the limbo. |
| watch_limbo_callback_ = std::move(cb); |
| } |
| |
| void ProcessLimboHandler::RetrieveException(zx_koid_t process_koid, RetrieveExceptionCallback cb) { |
| if (!VerifyState(limbo_manager_, &cb)) |
| return; |
| |
| fuchsia::exception::ProcessLimbo_RetrieveException_Result result; |
| |
| auto& limbo = limbo_manager_->limbo_; |
| |
| auto it = limbo.find(process_koid); |
| if (it == limbo.end()) { |
| FX_LOGS(WARNING) << "Could not find process " << process_koid << " in limbo."; |
| cb(fpromise::error(ZX_ERR_NOT_FOUND)); |
| return; |
| } |
| |
| auto res = fpromise::ok(std::move(it->second)); |
| limbo.erase(it); |
| cb(std::move(res)); |
| |
| limbo_manager_->NotifyLimboChanged(); |
| } |
| |
| void ProcessLimboHandler::ReleaseProcess(zx_koid_t process_koid, ReleaseProcessCallback cb) { |
| if (!VerifyState(limbo_manager_, &cb)) |
| return; |
| |
| auto& limbo = limbo_manager_->limbo_; |
| |
| auto it = limbo.find(process_koid); |
| if (it == limbo.end()) { |
| return cb(fpromise::error(ZX_ERR_NOT_FOUND)); |
| } |
| |
| limbo.erase(it); |
| cb(fpromise::ok()); |
| |
| limbo_manager_->NotifyLimboChanged(); |
| } |
| |
| void ProcessLimboHandler::GetFilters(GetFiltersCallback cb) { |
| if (!limbo_manager_) { |
| cb({}); |
| return; |
| } |
| |
| cb(CreateFilterVector(limbo_manager_->filters_)); |
| } |
| |
| void ProcessLimboHandler::AppendFilters(std::vector<std::string> new_filters, |
| AppendFiltersCallback cb) { |
| if (!VerifyState(limbo_manager_, &cb)) |
| return; |
| |
| auto current_filters = limbo_manager_->filters_; |
| |
| for (auto& filter : new_filters) { |
| current_filters.insert(filter); |
| if (current_filters.size() >= ProcessLimboManager::kMaxFilters) { |
| cb(fpromise::error(ZX_ERR_NO_RESOURCES)); |
| return; |
| } |
| } |
| |
| limbo_manager_->filters_ = std::move(current_filters); |
| cb(fpromise::ok()); |
| } |
| |
| void ProcessLimboHandler::RemoveFilters(std::vector<std::string> filters, |
| RemoveFiltersCallback cb) { |
| if (!VerifyState(limbo_manager_, &cb)) |
| return; |
| |
| for (auto& filter : filters) { |
| limbo_manager_->filters_.erase(filter); |
| } |
| |
| cb(fpromise::ok()); |
| } |
| |
| } // namespace exceptions |
| } // namespace forensics |