#include "ash/system/diagnostics/diagnostics_log_controller.h"
#include <memory>
#include <string>
#include <vector>
#include "ash/login_status.h"
#include "ash/session/session_controller_impl.h"
#include "ash/shell.h"
#include "ash/system/diagnostics/diagnostics_browser_delegate.h"
#include "ash/system/diagnostics/keyboard_input_log.h"
#include "ash/system/diagnostics/networking_log.h"
#include "ash/system/diagnostics/routine_log.h"
#include "ash/system/diagnostics/telemetry_log.h"
#include "base/check_op.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/sequence_checker.h"
#include "base/strings/string_util.h"
#include "base/task/thread_pool.h"
#include "components/session_manager/session_manager_types.h"
namespace ash {
namespace diagnostics {
namespace {
DiagnosticsLogController* g_instance = nullptr;
const char kDiaganosticsTmpDir[] = "/tmp/diagnostics";
const char kDiaganosticsDirName[] = "diagnostics";
const char kRoutineLogSubsectionHeader[] = "--- Test Routines --- \n";
const char kSystemLogSectionHeader[] = "=== System === \n";
const char kNetworkingLogSectionHeader[] = "=== Networking === \n";
const char kKeyboardLogSectionHeader[] = "=== Keyboard === \n";
const char kNoRoutinesRun[] =
"No routines of this type were run in the session.\n";
std::string GetRoutineResultsString(const std::string& results) {
const std::string section_header =
std::string(kRoutineLogSubsectionHeader) + "\n";
if (results.empty()) {
return section_header + kNoRoutinesRun;
}
return section_header + results;
}
bool ShouldResetAndInitializeLogWritersForLoginStatus(
LoginStatus previous_status,
LoginStatus current_status) {
if (previous_status == current_status)
return false;
switch (current_status) {
case ash::LoginStatus::LOCKED:
return false;
case LoginStatus::GUEST:
case LoginStatus::PUBLIC:
case LoginStatus::KIOSK_APP:
case LoginStatus::USER:
case LoginStatus::CHILD:
return previous_status != ash::LoginStatus::LOCKED;
case LoginStatus::NOT_LOGGED_IN:
return true;
}
}
bool ShouldUseActiveUserProfileDir(session_manager::SessionState state,
LoginStatus status) {
return state == session_manager::SessionState::ACTIVE &&
status == ash::LoginStatus::USER;
}
}
DiagnosticsLogController::DiagnosticsLogController()
: log_base_path_(kDiaganosticsTmpDir) {
DCHECK_EQ(nullptr, g_instance);
ash::Shell::Get()->session_controller()->AddObserver(this);
g_instance = this;
g_instance->previous_status_ =
ash::Shell::Get()->session_controller()->login_status();
}
DiagnosticsLogController::~DiagnosticsLogController() {
DCHECK_EQ(this, g_instance);
ash::Shell::Get()->session_controller()->RemoveObserver(this);
g_instance = nullptr;
}
DiagnosticsLogController* DiagnosticsLogController::Get() {
return g_instance;
}
bool DiagnosticsLogController::IsInitialized() {
return g_instance && g_instance->delegate_;
}
void DiagnosticsLogController::Initialize(
std::unique_ptr<DiagnosticsBrowserDelegate> delegate) {
DCHECK(g_instance);
DCHECK_CALLED_ON_VALID_SEQUENCE(g_instance->sequence_checker_);
g_instance->delegate_ = std::move(delegate);
g_instance->previous_status_ =
ash::Shell::Get()->session_controller()->login_status();
g_instance->ResetAndInitializeLogWriters();
base::ThreadPool::PostTask(
FROM_HERE, {base::MayBlock()},
base::BindOnce(&DiagnosticsLogController::RemoveDirectory,
g_instance->weak_ptr_factory_.GetWeakPtr(),
g_instance->log_base_path_));
}
std::string DiagnosticsLogController::GenerateSessionStringOnBlockingPool()
const {
std::vector<std::string> log_pieces;
const std::string system_log_contents = telemetry_log_->GetContents();
log_pieces.push_back(kSystemLogSectionHeader);
if (!system_log_contents.empty()) {
log_pieces.push_back(system_log_contents);
}
const std::string system_routines = routine_log_->GetContentsForCategory(
RoutineLog::RoutineCategory::kSystem);
log_pieces.push_back(GetRoutineResultsString(system_routines));
log_pieces.push_back(kNetworkingLogSectionHeader);
log_pieces.push_back(networking_log_->GetNetworkInfo());
const std::string network_routines = routine_log_->GetContentsForCategory(
RoutineLog::RoutineCategory::kNetwork);
log_pieces.push_back(GetRoutineResultsString(network_routines));
log_pieces.push_back(networking_log_->GetNetworkEvents());
std::string input_log_contents = keyboard_input_log_->GetLogContents();
if (!input_log_contents.empty()) {
log_pieces.push_back(kKeyboardLogSectionHeader);
log_pieces.push_back(std::move(input_log_contents));
}
return base::JoinString(log_pieces, "\n");
}
bool DiagnosticsLogController::GenerateSessionLogOnBlockingPool(
const base::FilePath& save_file_path) {
DCHECK(!save_file_path.empty());
return base::WriteFile(save_file_path, GenerateSessionStringOnBlockingPool());
}
void DiagnosticsLogController::ResetAndInitializeLogWriters() {
if (!DiagnosticsLogController::IsInitialized()) {
return;
}
ResetLogBasePath();
keyboard_input_log_ = std::make_unique<KeyboardInputLog>(log_base_path_);
networking_log_ = std::make_unique<NetworkingLog>(log_base_path_);
routine_log_ = std::make_unique<RoutineLog>(log_base_path_);
telemetry_log_ = std::make_unique<TelemetryLog>();
}
KeyboardInputLog& DiagnosticsLogController::GetKeyboardInputLog() {
return *keyboard_input_log_;
}
NetworkingLog& DiagnosticsLogController::GetNetworkingLog() {
return *networking_log_;
}
RoutineLog& DiagnosticsLogController::GetRoutineLog() {
return *routine_log_;
}
TelemetryLog& DiagnosticsLogController::GetTelemetryLog() {
return *telemetry_log_;
}
void DiagnosticsLogController::ResetLogBasePath() {
const session_manager::SessionState state =
ash::Shell::Get()->session_controller()->GetSessionState();
const LoginStatus status =
ash::Shell::Get()->session_controller()->login_status();
if (ShouldUseActiveUserProfileDir(state, status)) {
base::FilePath user_dir = g_instance->delegate_->GetActiveUserProfileDir();
if (!user_dir.empty()) {
g_instance->log_base_path_ = user_dir.Append(kDiaganosticsDirName);
return;
}
}
g_instance->log_base_path_ = base::FilePath(kDiaganosticsTmpDir);
}
void DiagnosticsLogController::OnLoginStatusChanged(LoginStatus login_status) {
if (!DiagnosticsLogController::IsInitialized()) {
return;
}
if (ShouldResetAndInitializeLogWritersForLoginStatus(
g_instance->previous_status_, login_status)) {
g_instance->ResetAndInitializeLogWriters();
base::ThreadPool::PostTask(
FROM_HERE, {base::MayBlock()},
base::BindOnce(&DiagnosticsLogController::RemoveDirectory,
g_instance->weak_ptr_factory_.GetWeakPtr(),
g_instance->log_base_path_));
}
g_instance->previous_status_ = login_status;
}
void DiagnosticsLogController::RemoveDirectory(const base::FilePath& path) {
DCHECK(!path.empty());
if (base::PathExists(path)) {
base::DeletePathRecursively(path);
}
}
void DiagnosticsLogController::SetKeyboardInputLogForTesting(
std::unique_ptr<KeyboardInputLog> keyboard_input_log) {
keyboard_input_log_ = std::move(keyboard_input_log);
}
void DiagnosticsLogController::SetNetworkingLogForTesting(
std::unique_ptr<NetworkingLog> networking_log) {
networking_log_ = std::move(networking_log);
}
void DiagnosticsLogController::SetRoutineLogForTesting(
std::unique_ptr<RoutineLog> routine_log) {
routine_log_ = std::move(routine_log);
}
void DiagnosticsLogController::SetTelemetryLogForTesting(
std::unique_ptr<TelemetryLog> telemetry_log) {
telemetry_log_ = std::move(telemetry_log);
}
}
}