blob: 1827f805b9df30b31dbb1d13e2abfab336f23d87 [file] [log] [blame]
// Copyright 2014 The Chromium 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 "chrome/browser/chromeos/system_logs/debug_log_writer.h"
#include <stdint.h>
#include <utility>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/callback.h"
#include "base/command_line.h"
#include "base/files/file.h"
#include "base/files/file_util.h"
#include "base/process/kill.h"
#include "base/process/launch.h"
#include "base/sequenced_task_runner.h"
#include "base/task_scheduler/lazy_task_runner.h"
#include "base/task_scheduler/post_task.h"
#include "chrome/common/logging_chrome.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/dbus/debug_daemon_client.h"
#include "content/public/browser/browser_thread.h"
namespace chromeos {
namespace {
// Callback for returning status of executed external command.
typedef base::Callback<void(bool succeeded)> CommandCompletionCallback;
const char kGzipCommand[] = "/bin/gzip";
const char kTarCommand[] = "/bin/tar";
base::LazySequencedTaskRunner g_sequenced_task_runner =
LAZY_SEQUENCED_TASK_RUNNER_INITIALIZER(
base::TaskTraits(base::MayBlock(),
base::TaskPriority::BEST_EFFORT,
base::TaskShutdownBehavior::BLOCK_SHUTDOWN));
// Called upon completion of |WriteDebugLogToFile|. Closes file
// descriptor, deletes log file in the case of failure and calls
// |callback|.
void WriteDebugLogToFileCompleted(
const base::FilePath& file_path,
const std::string& sequence_token_name,
const DebugLogWriter::StoreLogsCallback& callback,
bool succeeded) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!succeeded) {
bool posted = g_sequenced_task_runner.Get()->PostTaskAndReply(
FROM_HERE,
base::Bind(base::IgnoreResult(&base::DeleteFile), file_path, false),
base::Bind(callback, file_path, false));
DCHECK(posted);
return;
}
if (!callback.is_null())
callback.Run(file_path, true);
}
// Stores into |file_path| debug logs in the .tgz format. Calls
// |callback| upon completion.
void WriteDebugLogToFile(std::unique_ptr<base::File> file,
const std::string& sequence_token_name,
const base::FilePath& file_path,
bool should_compress,
const DebugLogWriter::StoreLogsCallback& callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!file->IsValid()) {
LOG(ERROR) << "Can't create debug log file: " << file_path.AsUTF8Unsafe()
<< ", "
<< "error: " << file->error_details();
return;
}
chromeos::DBusThreadManager::Get()->GetDebugDaemonClient()->DumpDebugLogs(
should_compress, file->GetPlatformFile(),
base::Bind(&WriteDebugLogToFileCompleted, file_path, sequence_token_name,
callback));
// Close the file on an IO-allowed thread.
g_sequenced_task_runner.Get()->DeleteSoon(FROM_HERE, file.release());
}
// Runs command with its parameters as defined in |argv|.
// Upon completion, it will report command run outcome via |callback| on the
// same thread from where it was initially called from.
void RunCommand(const std::vector<std::string>& argv,
const CommandCompletionCallback& callback) {
base::Process process = base::LaunchProcess(argv, base::LaunchOptions());
if (!process.IsValid()) {
LOG(ERROR) << "Failed to execute command " << argv[0];
if (!callback.is_null())
callback.Run(false);
return;
}
int exit_code = 0;
if (!process.WaitForExit(&exit_code)) {
LOG(ERROR) << "Can't get exit code for pid " << process.Pid();
if (!callback.is_null())
callback.Run(false);
return;
}
if (!callback.is_null())
callback.Run(exit_code == 0);
}
// Callback for handling the outcome of CompressArchive(). It reports
// the final outcome of log retreival process at via |callback|.
void OnCompressArchiveCompleted(
const base::FilePath& tar_file_path,
const base::FilePath& compressed_output_path,
const DebugLogWriter::StoreLogsCallback& callback,
bool compression_command_success) {
if (!compression_command_success) {
LOG(ERROR) << "Failed compressing " << compressed_output_path.value();
content::BrowserThread::PostTask(
content::BrowserThread::UI,
FROM_HERE,
base::Bind(callback, base::FilePath(), false));
base::DeleteFile(tar_file_path, false);
base::DeleteFile(compressed_output_path, false);
return;
}
content::BrowserThread::PostTask(
content::BrowserThread::UI,
FROM_HERE,
base::Bind(callback, compressed_output_path, true));
}
// Gzips |tar_file_path| and stores results in |compressed_output_path|.
void CompressArchive(const base::FilePath& tar_file_path,
const base::FilePath& compressed_output_path,
const DebugLogWriter::StoreLogsCallback& callback,
bool add_user_logs_command_success) {
if (!add_user_logs_command_success) {
LOG(ERROR) << "Failed adding user logs to " << tar_file_path.value();
content::BrowserThread::PostTask(
content::BrowserThread::UI,
FROM_HERE,
base::Bind(callback, base::FilePath(), false));
base::DeleteFile(tar_file_path, false);
return;
}
std::vector<std::string> argv;
argv.push_back(kGzipCommand);
argv.push_back(tar_file_path.value());
RunCommand(argv,
base::Bind(&OnCompressArchiveCompleted,
tar_file_path,
compressed_output_path,
callback));
}
// Adds user sessions specific logs from |user_log_dir| into tar archive file
// at |tar_file_path|. Upon completion, it will call CompressArchive() to
// produce |compressed_output_path|.
void AddUserLogsToArchive(const base::FilePath& user_log_dir,
const base::FilePath& tar_file_path,
const base::FilePath& compressed_output_path,
const DebugLogWriter::StoreLogsCallback& callback) {
std::vector<std::string> argv;
argv.push_back(kTarCommand);
argv.push_back("-rvf");
argv.push_back(tar_file_path.value());
argv.push_back(user_log_dir.value());
RunCommand(
argv,
base::Bind(
&CompressArchive, tar_file_path, compressed_output_path, callback));
}
// Appends user logs after system logs are archived into |tar_file_path|.
void OnSystemLogsAdded(const DebugLogWriter::StoreLogsCallback& callback,
const base::FilePath& tar_file_path,
bool succeeded) {
if (!succeeded) {
if (!callback.is_null())
callback.Run(base::FilePath(), false);
return;
}
base::FilePath compressed_output_path =
tar_file_path.AddExtension(FILE_PATH_LITERAL(".gz"));
base::FilePath user_log_dir =
logging::GetSessionLogDir(*base::CommandLine::ForCurrentProcess());
base::PostTaskWithTraits(
FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
base::Bind(&AddUserLogsToArchive, user_log_dir, tar_file_path,
compressed_output_path, callback));
}
void InitializeLogFile(base::File* file,
const base::FilePath& file_path,
uint32_t flags) {
base::FilePath dir = file_path.DirName();
if (!base::DirectoryExists(dir)) {
if (!base::CreateDirectory(dir)) {
LOG(ERROR) << "Can not create " << dir.value();
return;
}
}
file->Initialize(file_path, flags);
}
// Starts logs retrieval process. The output will be stored in file with name
// derived from |file_name_template|.
void StartLogRetrieval(const base::FilePath& file_name_template,
bool should_compress,
const std::string& sequence_token_name,
const DebugLogWriter::StoreLogsCallback& callback) {
base::FilePath file_path =
logging::GenerateTimestampedName(file_name_template, base::Time::Now());
int flags = base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE;
std::unique_ptr<base::File> file(new base::File);
base::File* file_ptr = file.get();
g_sequenced_task_runner.Get()->PostTaskAndReply(
FROM_HERE,
base::Bind(&InitializeLogFile, base::Unretained(file_ptr), file_path,
flags),
base::Bind(&WriteDebugLogToFile, base::Passed(&file), sequence_token_name,
file_path, should_compress, callback));
}
const char kDefaultSequenceName[] = "DebugLogWriter";
} // namespace
// static.
void DebugLogWriter::StoreLogs(const base::FilePath& fileshelf,
bool should_compress,
const StoreLogsCallback& callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(!callback.is_null());
base::FilePath file_path =
fileshelf.Append(should_compress ? FILE_PATH_LITERAL("debug-logs.tgz")
: FILE_PATH_LITERAL("debug-logs.tar"));
StartLogRetrieval(file_path, should_compress, kDefaultSequenceName, callback);
}
// static.
void DebugLogWriter::StoreCombinedLogs(const base::FilePath& fileshelf,
const std::string& sequence_token_name,
const StoreLogsCallback& callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(!callback.is_null());
base::FilePath file_path =
fileshelf.Append(FILE_PATH_LITERAL("combined-logs.tar"));
// Get system logs from /var/log first, then add user-specific stuff.
StartLogRetrieval(file_path,
false,
sequence_token_name,
base::Bind(&OnSystemLogsAdded, callback));
}
} // namespace chromeos