| // Copyright 2020 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifdef UNSAFE_BUFFERS_BUILD |
| // TODO(crbug.com/40285824): Remove this and convert code to safer constructs. |
| #pragma allow_unsafe_buffers |
| #endif |
| |
| #ifndef CHROME_BROWSER_ASH_POLICY_REPORTING_INSTALL_EVENT_LOG_H_ |
| #define CHROME_BROWSER_ASH_POLICY_REPORTING_INSTALL_EVENT_LOG_H_ |
| |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #include <algorithm> |
| #include <map> |
| #include <memory> |
| #include <string> |
| #include <utility> |
| |
| #include "base/files/file_path.h" |
| #include "base/logging.h" |
| #include "chrome/browser/ash/policy/reporting/single_install_event_log.h" |
| |
| namespace policy { |
| |
| // An event log for app installs. The app refers to extension or ARC++ app. The |
| // log entries for each app are kept in a separate round-robin buffer. The log |
| // can be stored on disk and serialized for upload to a server. Log entries are |
| // pruned after upload has completed. Uses a sequence checker in |
| // |AppInstallEventLogManager| to ensure that methods are called from the |
| // right thread. |T| specifies the event type, and |C| specifies the event log |
| // class for single app. |
| template <typename T, typename C> |
| class InstallEventLog { |
| public: |
| // Restores the event log from |file_name|. If there is an error parsing the |
| // file, as many log entries as possible are restored. |
| explicit InstallEventLog(const base::FilePath& file_name); |
| ~InstallEventLog(); |
| |
| // The current total number of log entries across apps. |
| int total_size() { return total_size_; } |
| |
| // The current maximum number of log entries for a single app. |
| int max_size() { return max_size_; } |
| |
| // Add a log entry for |id|. If the buffer for that app is |
| // full, the oldest entry is removed. |
| void Add(const std::string& id, const T& event); |
| |
| // Stores the event log to the file name provided to the constructor. If the |
| // event log has not changed since it was last stored to disk (or initially |
| // loaded from disk), does nothing. |
| void Store(); |
| |
| // Clears log entries that were previously serialized. |
| void ClearSerialized(); |
| |
| static constexpr int64_t kLogFileVersion = 3; |
| static constexpr ssize_t kMaxLogs = 1024; |
| |
| protected: |
| // The round-robin log event buffers for individual apps. |
| std::map<std::string, std::unique_ptr<C>> logs_; |
| |
| const base::FilePath file_name_; |
| |
| // The current total number of log entries, across all apps. |
| int total_size_ = 0; |
| |
| // The current maximum number of log entries for a single app. |
| int max_size_ = 0; |
| |
| // Whether the event log changed since it was last stored to disk (or |
| // initially loaded from disk). |
| bool dirty_ = false; |
| }; |
| |
| // Implementation details below here. |
| |
| template <typename T, typename C> |
| constexpr int64_t InstallEventLog<T, C>::kLogFileVersion; |
| template <typename T, typename C> |
| constexpr ssize_t InstallEventLog<T, C>::kMaxLogs; |
| |
| template <typename T, typename C> |
| InstallEventLog<T, C>::InstallEventLog(const base::FilePath& file_name) |
| : file_name_(file_name) { |
| base::File file(file_name_, base::File::FLAG_OPEN | base::File::FLAG_READ); |
| if (!file.IsValid()) |
| return; |
| |
| int64_t version; |
| if (!file.ReadAtCurrentPosAndCheck( |
| base::as_writable_bytes(base::span_from_ref(version)))) { |
| LOG(WARNING) << "Corrupted install log."; |
| return; |
| } |
| |
| if (version != kLogFileVersion) { |
| LOG(WARNING) << "Log file version mismatch."; |
| return; |
| } |
| |
| ssize_t entries; |
| if (!file.ReadAtCurrentPosAndCheck( |
| base::as_writable_bytes(base::span_from_ref(entries)))) { |
| LOG(WARNING) << "Corrupted install log."; |
| return; |
| } |
| |
| for (int i = 0; i < std::min(entries, kMaxLogs); ++i) { |
| std::unique_ptr<C> log; |
| const bool file_ok = C::Load(&file, &log); |
| const bool log_ok = |
| log && !log->id().empty() && logs_.find(log->id()) == logs_.end(); |
| if (!file_ok || !log_ok) { |
| LOG(WARNING) << "Corrupted install log."; |
| } |
| if (log_ok) { |
| total_size_ += log->size(); |
| max_size_ = std::max(max_size_, log->size()); |
| logs_[log->id()] = std::move(log); |
| } |
| if (!file_ok) { |
| return; |
| } |
| } |
| |
| if (entries >= kMaxLogs) { |
| LOG(WARNING) << "Corrupted install log."; |
| } |
| } |
| |
| template <typename T, typename C> |
| InstallEventLog<T, C>::~InstallEventLog() = default; |
| |
| template <typename T, typename C> |
| void InstallEventLog<T, C>::Add(const std::string& extension_id, |
| const T& event) { |
| if (logs_.size() == kMaxLogs && logs_.find(extension_id) == logs_.end()) { |
| LOG(WARNING) << "Install log overflow."; |
| return; |
| } |
| |
| auto& log = logs_[extension_id]; |
| if (!log) |
| log = std::make_unique<C>(extension_id); |
| total_size_ -= log->size(); |
| log->Add(event); |
| total_size_ += log->size(); |
| max_size_ = std::max(max_size_, log->size()); |
| dirty_ = true; |
| } |
| |
| template <typename T, typename C> |
| void InstallEventLog<T, C>::Store() { |
| if (!dirty_) { |
| return; |
| } |
| |
| base::File file(file_name_, |
| base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE); |
| if (!file.IsValid()) { |
| LOG(WARNING) << "Unable to store install log."; |
| return; |
| } |
| |
| if (!file.WriteAtCurrentPosAndCheck( |
| base::byte_span_from_ref(kLogFileVersion))) { |
| LOG(WARNING) << "Unable to store install log."; |
| return; |
| } |
| |
| ssize_t entries = logs_.size(); |
| if (!file.WriteAtCurrentPosAndCheck(base::byte_span_from_ref(entries))) { |
| LOG(WARNING) << "Unable to store install log."; |
| return; |
| } |
| |
| for (const auto& log : logs_) { |
| if (!log.second->Store(&file)) { |
| LOG(WARNING) << "Unable to store install log."; |
| return; |
| } |
| } |
| |
| dirty_ = false; |
| } |
| |
| template <typename T, typename C> |
| void InstallEventLog<T, C>::ClearSerialized() { |
| int total_size = 0; |
| max_size_ = 0; |
| |
| auto log = logs_.begin(); |
| while (log != logs_.end()) { |
| log->second->ClearSerialized(); |
| if (log->second->empty()) { |
| log = logs_.erase(log); |
| } else { |
| total_size += log->second->size(); |
| max_size_ = std::max(max_size_, log->second->size()); |
| ++log; |
| } |
| } |
| |
| if (total_size != total_size_) { |
| total_size_ = total_size; |
| dirty_ = true; |
| } |
| } |
| |
| } // namespace policy |
| |
| #endif // CHROME_BROWSER_ASH_POLICY_REPORTING_INSTALL_EVENT_LOG_H_ |