#include "remoting/host/crash/crash_directory_watcher.h"
#include <string>
#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/task/single_thread_task_runner.h"
namespace remoting {
namespace {
const base::FilePath::CharType kDumpExtension[] = FILE_PATH_LITERAL("dmp");
const base::FilePath::CharType kJsonExtension[] = FILE_PATH_LITERAL("json");
bool PrepareFilesForUpload(const base::FilePath& crash_directory,
const base::FilePath& crash_guid) {
base::FilePath minidump_file =
crash_directory.Append(crash_guid).AddExtension(kDumpExtension);
if (!base::PathExists(minidump_file)) {
LOG(WARNING) << "Minidump not found for crash: " << crash_guid;
return false;
}
base::FilePath upload_directory = crash_directory.Append(crash_guid);
if (base::PathExists(upload_directory)) {
LOG(ERROR) << "Upload directory already exists for report: " << crash_guid;
return false;
}
base::File::Error error;
if (!base::CreateDirectoryAndGetError(upload_directory, &error)) {
LOG(ERROR) << "Failed to create directory for crash report: " << crash_guid
<< " due to error: " << error;
return false;
}
base::FilePath metadata_file =
crash_directory.Append(crash_guid).AddExtension(kJsonExtension);
if (!base::Move(metadata_file,
upload_directory.Append(metadata_file.BaseName()))) {
LOG(ERROR) << "Failed to move crash json file into upload directory for "
<< "crash: " << crash_guid;
return false;
}
if (!base::Move(minidump_file,
upload_directory.Append(minidump_file.BaseName()))) {
LOG(ERROR) << "Failed to move minidump file into upload directory for "
<< "crash: " << crash_guid;
return false;
}
return true;
}
void DeleteCrashFiles(const base::FilePath& crash_directory,
const base::FilePath& crash_guid) {
base::FilePath minidump_file =
crash_directory.Append(crash_guid).AddExtension(kDumpExtension);
if (!base::DeleteFile(minidump_file)) {
PLOG(ERROR) << "Failed to delete " << minidump_file;
}
base::FilePath metadata_file =
crash_directory.Append(crash_guid).AddExtension(kJsonExtension);
if (!base::DeleteFile(metadata_file)) {
PLOG(ERROR) << "Failed to delete " << metadata_file;
}
}
}
CrashDirectoryWatcher::CrashDirectoryWatcher() = default;
CrashDirectoryWatcher::~CrashDirectoryWatcher() = default;
void CrashDirectoryWatcher::Watch(base::FilePath crash_directory,
UploadCallback callback) {
LOG(INFO) << "Watching for crash minidumps in: " << crash_directory;
DCHECK(!upload_callback_);
upload_callback_ = std::move(callback);
OnFileChangeDetected(crash_directory, false);
file_path_watcher_.Watch(
std::move(crash_directory), base::FilePathWatcher::Type::kNonRecursive,
base::BindRepeating(&CrashDirectoryWatcher::OnFileChangeDetected,
base::Unretained(this)));
}
void CrashDirectoryWatcher::OnFileChangeDetected(const base::FilePath& path,
bool error) {
if (error) {
LOG(ERROR) << "Error watching crash directory: " << path;
return;
}
base::FileEnumerator metadata_file_enumerator(
path, false, base::FileEnumerator::FileType::FILES,
FILE_PATH_LITERAL("*.json"));
std::vector<base::FilePath> crash_guids;
base::FilePath metadata_file = metadata_file_enumerator.Next();
while (!metadata_file.empty()) {
base::FilePath crash_guid = metadata_file.BaseName().RemoveExtension();
LOG(INFO) << "Found new crash report: " << crash_guid;
crash_guids.push_back(std::move(crash_guid));
metadata_file = metadata_file_enumerator.Next();
}
for (const auto& crash_guid : crash_guids) {
if (PrepareFilesForUpload(path, crash_guid)) {
LOG(INFO) << "Crash report ready for upload: " << crash_guid;
upload_callback_.Run(crash_guid);
} else {
LOG(ERROR) << "Deleting invalid crash report files for " << crash_guid;
DeleteCrashFiles(path, crash_guid);
}
}
auto last_error = metadata_file_enumerator.GetError();
LOG_IF(WARNING, last_error != base::File::FILE_OK)
<< "File enumeration failed: " << last_error;
}
}