From 2d5a30fb171d2b28fee9bfa0a737d2f850588a90 Mon Sep 17 00:00:00 2001 From: Justin Cohen Date: Mon, 25 Oct 2021 11:35:19 -0400 Subject: [PATCH] ios: Create a PruneIntermediateDumpsAndCrashReportsThread. This will prune the database on a daily basis, in accordance with the specified condition. This will also unlock any leftover intermediate dump files. Bug: crashpad:31 Change-Id: I229f8b8006b44d31062fbf73bb9d316d69ab2dcf Reviewed-on: https://chromium-review.googlesource.com/c/crashpad/crashpad/+/3231618 Commit-Queue: Justin Cohen Reviewed-by: Mark Mentovai --- client/BUILD.gn | 2 + client/ios_handler/in_process_handler.cc | 9 ++ client/ios_handler/in_process_handler.h | 2 + ...rmediate_dumps_and_crash_reports_thread.cc | 126 ++++++++++++++++++ ...ermediate_dumps_and_crash_reports_thread.h | 102 ++++++++++++++ util/ios/ios_system_data_collector.h | 2 + util/ios/ios_system_data_collector.mm | 1 + 7 files changed, 244 insertions(+) create mode 100644 client/ios_handler/prune_intermediate_dumps_and_crash_reports_thread.cc create mode 100644 client/ios_handler/prune_intermediate_dumps_and_crash_reports_thread.h diff --git a/client/BUILD.gn b/client/BUILD.gn index ad2d71c7..b60e9044 100644 --- a/client/BUILD.gn +++ b/client/BUILD.gn @@ -39,6 +39,8 @@ crashpad_static_library("client") { "ios_handler/in_process_handler.h", "ios_handler/in_process_intermediate_dump_handler.cc", "ios_handler/in_process_intermediate_dump_handler.h", + "ios_handler/prune_intermediate_dumps_and_crash_reports_thread.cc", + "ios_handler/prune_intermediate_dumps_and_crash_reports_thread.h", "simulate_crash_ios.h", ] } diff --git a/client/ios_handler/in_process_handler.cc b/client/ios_handler/in_process_handler.cc index d4c4af43..353e24b2 100644 --- a/client/ios_handler/in_process_handler.cc +++ b/client/ios_handler/in_process_handler.cc @@ -19,6 +19,7 @@ #include "base/logging.h" #include "client/ios_handler/in_process_intermediate_dump_handler.h" +#include "client/prune_crash_reports.h" #include "client/settings.h" #include "minidump/minidump_file_writer.h" #include "util/file/directory_reader.h" @@ -85,6 +86,14 @@ bool InProcessHandler::Initialize( base_dir_ = database.Append(kPendingSerializediOSDump); CreateDirectory(base_dir_); + prune_thread_.reset(new PruneIntermediateDumpsAndCrashReportsThread( + database_.get(), + PruneCondition::GetDefault(), + base_dir_, + bundle_identifier_and_seperator_, + system_data.IsExtension())); + prune_thread_->Start(); + if (!OpenNewFile()) return false; diff --git a/client/ios_handler/in_process_handler.h b/client/ios_handler/in_process_handler.h index a9a3094b..1e6ebc4b 100644 --- a/client/ios_handler/in_process_handler.h +++ b/client/ios_handler/in_process_handler.h @@ -19,6 +19,7 @@ #include #include "base/files/file_path.h" +#include "client/ios_handler/prune_intermediate_dumps_and_crash_reports_thread.h" #include "handler/crash_report_upload_thread.h" #include "snapshot/ios/process_snapshot_ios_intermediate_dump.h" #include "util/ios/ios_intermediate_dump_writer.h" @@ -193,6 +194,7 @@ class InProcessHandler { std::unique_ptr writer_; std::unique_ptr alternate_mach_writer_; std::unique_ptr upload_thread_; + std::unique_ptr prune_thread_; std::unique_ptr database_; std::string bundle_identifier_and_seperator_; InitializationStateDcheck initialized_; diff --git a/client/ios_handler/prune_intermediate_dumps_and_crash_reports_thread.cc b/client/ios_handler/prune_intermediate_dumps_and_crash_reports_thread.cc new file mode 100644 index 00000000..d6cfe885 --- /dev/null +++ b/client/ios_handler/prune_intermediate_dumps_and_crash_reports_thread.cc @@ -0,0 +1,126 @@ +// Copyright 2021 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "client/ios_handler/prune_intermediate_dumps_and_crash_reports_thread.h" + +#include + +#include "client/prune_crash_reports.h" +#include "util/file/directory_reader.h" +#include "util/file/filesystem.h" + +namespace crashpad { + +namespace { + +// The file extension used to indicate a file is locked. +constexpr char kLockedExtension[] = ".locked"; + +// If the client finds a locked file matching it's own bundle id, unlock it +// after 24 hours. +constexpr time_t matching_bundle_locked_ttl = 60 * 60 * 24; + +// Unlock any locked intermediate dump after 60 days. +constexpr time_t max_locked_ttl = 60 * 60 * 24 * 60; + +// The initial thread delay for applications. Delay the thread's file i/o to +// not interfere with application startup. +constexpr double app_delay = 60; + +// The initial thread delay for app extensions. Because iOS extensions are often +// very short lived, do not wait the full |app_delay|, and instead use a shorter +// time. +constexpr double extension_delay = 5; + + +//! \brief Unlocks old intermediate dumps. +//! +//! This function can unlock (remove the .locked extension) intermediate dumps +//! that are either too old to be useful, or are likely leftover dumps from +//! clean app exits. +//! +//! \param[in] pending_path The path to any locked intermediate dump files. +//! \param[in] bundle_identifier_and_seperator The identifier for this client, +//! used to determine when locked files are considered stale. +void UnlockOldIntermediateDumps(base::FilePath pending_path, + std::string bundle_identifier_and_seperator) { + DirectoryReader reader; + std::vector files; + if (!reader.Open(pending_path)) { + return; + } + base::FilePath file; + DirectoryReader::Result result; + while ((result = reader.NextFile(&file)) == + DirectoryReader::Result::kSuccess) { + if (file.FinalExtension() != kLockedExtension) + continue; + + const base::FilePath file_path(pending_path.Append(file)); + timespec file_time; + time_t now = time(nullptr); + if (!FileModificationTime(file_path, &file_time)) { + continue; + } + + if ((file.value().compare(0, + bundle_identifier_and_seperator.size(), + bundle_identifier_and_seperator) == 0 && + file_time.tv_sec <= now - matching_bundle_locked_ttl) || + (file_time.tv_sec <= now - max_locked_ttl)) { + base::FilePath new_path = file_path.RemoveFinalExtension(); + MoveFileOrDirectory(file_path, new_path); + continue; + } + } +} + +} // namespace + +PruneIntermediateDumpsAndCrashReportsThread:: + PruneIntermediateDumpsAndCrashReportsThread( + CrashReportDatabase* database, + std::unique_ptr condition, + base::FilePath pending_path, + std::string bundle_identifier_and_seperator, + bool is_extension) + : thread_(60 * 60 * 24, this), + condition_(std::move(condition)), + pending_path_(pending_path), + bundle_identifier_and_seperator_(bundle_identifier_and_seperator), + initial_work_delay_(is_extension ? extension_delay : app_delay), + database_(database) {} + +PruneIntermediateDumpsAndCrashReportsThread:: + ~PruneIntermediateDumpsAndCrashReportsThread() {} + +void PruneIntermediateDumpsAndCrashReportsThread::Start() { + thread_.Start(initial_work_delay_); +} + +void PruneIntermediateDumpsAndCrashReportsThread::Stop() { + thread_.Stop(); +} + +void PruneIntermediateDumpsAndCrashReportsThread::DoWork( + const WorkerThread* thread) { + database_->CleanDatabase(60 * 60 * 24 * 3); + PruneCrashReportDatabase(database_, condition_.get()); + if (!clean_old_intermediate_dumps_) { + clean_old_intermediate_dumps_ = true; + UnlockOldIntermediateDumps(pending_path_, bundle_identifier_and_seperator_); + } +} + +} // namespace crashpad diff --git a/client/ios_handler/prune_intermediate_dumps_and_crash_reports_thread.h b/client/ios_handler/prune_intermediate_dumps_and_crash_reports_thread.h new file mode 100644 index 00000000..b2764234 --- /dev/null +++ b/client/ios_handler/prune_intermediate_dumps_and_crash_reports_thread.h @@ -0,0 +1,102 @@ +// Copyright 2021 The Crashpad Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef CRASHPAD_HANDLER_PRUNE_CRASH_REPORTS_THREAD_H_ +#define CRASHPAD_HANDLER_PRUNE_CRASH_REPORTS_THREAD_H_ + +#include + +#include "base/files/file_path.h" +#include "util/thread/stoppable.h" +#include "util/thread/worker_thread.h" + +namespace crashpad { + +class CrashReportDatabase; +class PruneCondition; + +//! \brief A thread that periodically prunes crash reports from the database +//! using the specified condition, and any leftover locked intermediate +//! dumps. +//! +//! After the thread is started, the database is pruned using the condition +//! every 24 hours. Upon calling Start(), the thread waits 5 seconds before +//! performing the initial prune operation. +//! +//! Locked intermediate dump files are unlocked only once, not periodically. +//! Locked dumps that match this bundle id can be unlocked if they are over a +//! day old. Otherwise, unlock dumps that are over 60 days old. +class PruneIntermediateDumpsAndCrashReportsThread + : public WorkerThread::Delegate, + public Stoppable { + public: + //! \brief Constructs a new object. + //! + //! \param[in] database The database to prune crash reports from. + //! \param[in] condition The condition used to evaluate crash reports for + //! pruning. + //! \param[in] pending_path The path to any locked intermediate dump files. + //! \param[in] bundle_identifier_and_seperator The identifier for this client, + //! used to determine when locked files are considered stale, with a + //! seperator at the end to allow for substring searches. + PruneIntermediateDumpsAndCrashReportsThread( + CrashReportDatabase* database, + std::unique_ptr condition, + base::FilePath pending_path, + std::string bundle_identifier_and_seperator, + bool is_extension); + + PruneIntermediateDumpsAndCrashReportsThread( + const PruneIntermediateDumpsAndCrashReportsThread&) = delete; + PruneIntermediateDumpsAndCrashReportsThread& operator=( + const PruneIntermediateDumpsAndCrashReportsThread&) = delete; + + ~PruneIntermediateDumpsAndCrashReportsThread(); + + // Stoppable: + + //! \brief Starts a dedicated pruning thread. + //! + //! The thread waits before running the initial prune, so as to not interfere + //! with any startup-related IO performed by the client. + //! + //! This method may only be be called on a newly-constructed object or after + //! a call to Stop(). + void Start() override; + + //! \brief Stops the pruning thread. + //! + //! This method must only be called after Start(). If Start() has been called, + //! this method must be called before destroying an object of this class. + //! + //! This method may be called from any thread other than the pruning thread. + //! It is expected to only be called from the same thread that called Start(). + void Stop() override; + + private: + // WorkerThread::Delegate: + void DoWork(const WorkerThread* thread) override; + + WorkerThread thread_; + std::unique_ptr condition_; + base::FilePath pending_path_; + std::string bundle_identifier_and_seperator_; + bool clean_old_intermediate_dumps_; + double initial_work_delay_; + CrashReportDatabase* database_; // weak +}; + +} // namespace crashpad + +#endif // CRASHPAD_HANDLER_PRUNE_CRASH_REPORTS_THREAD_H_ diff --git a/util/ios/ios_system_data_collector.h b/util/ios/ios_system_data_collector.h index ab25a68a..035f9d81 100644 --- a/util/ios/ios_system_data_collector.h +++ b/util/ios/ios_system_data_collector.h @@ -33,6 +33,7 @@ class IOSSystemDataCollector { int ProcessorCount() const { return processor_count_; } const std::string& Build() const { return build_; } const std::string& BundleIdentifier() const { return bundle_identifier_; } + bool IsExtension() const { return is_extension_; } const std::string& CPUVendor() const { return cpu_vendor_; } bool HasDaylightSavingTime() const { return has_next_daylight_saving_time_; } bool IsDaylightSavingTime() const { return is_daylight_saving_time_; } @@ -68,6 +69,7 @@ class IOSSystemDataCollector { int patch_version_; std::string build_; std::string bundle_identifier_; + bool is_extension_; std::string machine_description_; int orientation_; int processor_count_; diff --git a/util/ios/ios_system_data_collector.mm b/util/ios/ios_system_data_collector.mm index ed818499..366be464 100644 --- a/util/ios/ios_system_data_collector.mm +++ b/util/ios/ios_system_data_collector.mm @@ -79,6 +79,7 @@ IOSSystemDataCollector::IOSSystemDataCollector() build_ = ReadStringSysctlByName("kern.osversion"); bundle_identifier_ = base::SysNSStringToUTF8([[NSBundle mainBundle] bundleIdentifier]); + is_extension_ = [[NSBundle mainBundle].bundlePath hasSuffix:@"appex"]; #if defined(ARCH_CPU_X86_64) cpu_vendor_ = ReadStringSysctlByName("machdep.cpu.vendor");