910e62b5创建于 1月15日历史提交
// Copyright 2012 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/diagnostics/recon_diagnostics.h"

#include <stddef.h>
#include <stdint.h>

#include <memory>
#include <string>

#include "base/byte_count.h"
#include "base/files/file_util.h"
#include "base/json/json_reader.h"
#include "base/json/json_string_value_serializer.h"
#include "base/path_service.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/system/sys_info.h"
#include "build/branding_buildflags.h"
#include "build/build_config.h"
#include "chrome/browser/diagnostics/diagnostics_test.h"
#include "chrome/common/channel_info.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_paths.h"
#include "components/bookmarks/common/bookmark_constants.h"
#include "components/version_info/version_info.h"

#if BUILDFLAG(IS_WIN)
#include "chrome/installer/util/install_util.h"
#endif

// Reconnaissance diagnostics. These are the first and most critical
// diagnostic tests. Here we check for the existence of critical files.
// TODO(cpu): Define if it makes sense to localize strings.

// TODO(cpu): There are a few maximum file sizes hard-coded in this file
// that have little or no theoretical or experimental ground. Find a way
// to justify them.

namespace diagnostics {

namespace {

class InstallTypeTest;
InstallTypeTest* g_install_type = nullptr;

// Check that the disk space in the volume where the user data directory
// normally lives is not dangerously low.
class DiskSpaceTest : public DiagnosticsTest {
 public:
  DiskSpaceTest() : DiagnosticsTest(DIAGNOSTICS_DISK_SPACE_TEST) {}

  DiskSpaceTest(const DiskSpaceTest&) = delete;
  DiskSpaceTest& operator=(const DiskSpaceTest&) = delete;

  bool ExecuteImpl(DiagnosticsModel::Observer* observer) override {
    base::FilePath data_dir;
    if (!base::PathService::Get(chrome::DIR_USER_DATA, &data_dir)) {
      return false;
    }
    auto amount_of_free_disk_space =
        base::SysInfo::AmountOfFreeDiskSpace(data_dir);
    if (!amount_of_free_disk_space) {
      RecordFailure(DIAG_RECON_UNABLE_TO_QUERY, "Unable to query free space");
      return true;
    }
    base::ByteCount disk_space = base::ByteCount(*amount_of_free_disk_space);
    std::string printable_size = base::NumberToString(disk_space.InBytes());
    if (disk_space < base::MiB(80)) {
      RecordFailure(DIAG_RECON_LOW_DISK_SPACE,
                    "Low disk space: " + printable_size);
      return true;
    }
    RecordSuccess("Free space: " + printable_size);
    return true;
  }
};

// Check if it is system install or per-user install.
class InstallTypeTest : public DiagnosticsTest {
 public:
  InstallTypeTest() : DiagnosticsTest(DIAGNOSTICS_INSTALL_TYPE_TEST) {}

  InstallTypeTest(const InstallTypeTest&) = delete;
  InstallTypeTest& operator=(const InstallTypeTest&) = delete;

  bool ExecuteImpl(DiagnosticsModel::Observer* observer) override {
#if BUILDFLAG(IS_WIN)
    user_level_ = InstallUtil::IsPerUserInstall();
    const char* type = user_level_ ? "User Level" : "System Level";
    std::string install_type(type);
#else
    std::string install_type("System Level");
#endif  // BUILDFLAG(IS_WIN)
    RecordSuccess(install_type);
    g_install_type = this;
    return true;
  }

  bool system_level() const { return !user_level_; }

 private:
  bool user_level_ = false;
};

// Checks that a given JSON file can be correctly parsed.
class JSONTest : public DiagnosticsTest {
 public:
  enum FileImportance { NON_CRITICAL, CRITICAL };

  JSONTest(const base::FilePath& path,
           DiagnosticsTestId id,
           base::ByteCount max_file_size,
           FileImportance importance)
      : DiagnosticsTest(id),
        path_(path),
        max_file_size_(max_file_size),
        importance_(importance) {}

  JSONTest(const JSONTest&) = delete;
  JSONTest& operator=(const JSONTest&) = delete;

  bool ExecuteImpl(DiagnosticsModel::Observer* observer) override {
    if (!base::PathExists(path_)) {
      if (importance_ == CRITICAL) {
        RecordOutcome(DIAG_RECON_FILE_NOT_FOUND, "File not found",
                      DiagnosticsModel::TEST_FAIL_CONTINUE);
      } else {
        RecordOutcome(DIAG_RECON_FILE_NOT_FOUND_OK,
                      "File not found (but that is OK)",
                      DiagnosticsModel::TEST_OK);
      }
      return true;
    }
    std::optional<int64_t> file_size = base::GetFileSize(path_);
    if (!file_size.has_value()) {
      RecordFailure(DIAG_RECON_CANNOT_OBTAIN_FILE_SIZE,
                    "Cannot obtain file size");
      return true;
    }

    if (file_size.value() > max_file_size_.InBytes()) {
      RecordFailure(DIAG_RECON_FILE_TOO_BIG, "File too big");
      return true;
    }
    // Being small enough, we can process it in-memory.
    std::string json_data;
    if (!base::ReadFileToString(path_, &json_data)) {
      RecordFailure(DIAG_RECON_UNABLE_TO_OPEN_FILE,
                    "Could not open file. Possibly locked by another process");
      return true;
    }

    JSONStringValueDeserializer json(json_data);
    int error_code = base::ValueDeserializer::kErrorCodeNoError;
    std::string error_message;
    std::unique_ptr<base::Value> json_root(
        json.Deserialize(&error_code, &error_message));
    if (base::ValueDeserializer::kErrorCodeNoError != error_code) {
      if (error_message.empty()) {
        error_message = "Parse error " + base::NumberToString(error_code);
      }
      RecordFailure(DIAG_RECON_PARSE_ERROR, error_message);
      return true;
    }

    RecordSuccess("File parsed OK");
    return true;
  }

 private:
  base::FilePath path_;
  base::ByteCount max_file_size_;
  FileImportance importance_;
};

// Check that the flavor of the operating system is supported.
class OperatingSystemTest : public DiagnosticsTest {
 public:
  OperatingSystemTest() : DiagnosticsTest(DIAGNOSTICS_OPERATING_SYSTEM_TEST) {}

  OperatingSystemTest(const OperatingSystemTest&) = delete;
  OperatingSystemTest& operator=(const OperatingSystemTest&) = delete;

  bool ExecuteImpl(DiagnosticsModel::Observer* observer) override {
    // TODO(port): define the OS criteria for Linux and Mac.
    RecordSuccess(base::StringPrintf(
        "%s %s", base::SysInfo::OperatingSystemName().c_str(),
        base::SysInfo::OperatingSystemVersion().c_str()));
    return true;
  }
};

struct TestPathInfo {
  DiagnosticsTestId test_id;
  int path_id;
  bool is_directory;
  bool is_optional;
  bool test_writable;
  base::ByteCount max_size;
};

const TestPathInfo kPathsToTest[] = {
    {DIAGNOSTICS_PATH_DICTIONARIES_TEST, chrome::DIR_APP_DICTIONARIES, true,
     true, false, base::ByteCount(0)},
    {DIAGNOSTICS_PATH_LOCAL_STATE_TEST, chrome::FILE_LOCAL_STATE, false, false,
     true, base::KiB(500)},
    {DIAGNOSTICS_PATH_RESOURCES_TEST, chrome::FILE_RESOURCES_PACK, false, false,
     false, base::ByteCount(0)},
    {DIAGNOSTICS_PATH_USER_DATA_TEST, chrome::DIR_USER_DATA, true, false, true,
     base::MiB(850)},
};

// Check that the user's data directory exists and the paths are writable.
class PathTest : public DiagnosticsTest {
 public:
  explicit PathTest(const TestPathInfo& path_info)
      : DiagnosticsTest(path_info.test_id), path_info_(path_info) {}

  PathTest(const PathTest&) = delete;
  PathTest& operator=(const PathTest&) = delete;

  bool ExecuteImpl(DiagnosticsModel::Observer* observer) override {
    if (!g_install_type) {
      RecordStopFailure(DIAG_RECON_DEPENDENCY, "Install dependency failure");
      return false;
    }
    base::FilePath dir_or_file;
    if (!base::PathService::Get(path_info_.path_id, &dir_or_file)) {
      RecordStopFailure(DIAG_RECON_PATH_PROVIDER, "Path provider failure");
      return false;
    }
    if (!base::PathExists(dir_or_file)) {
      RecordFailure(DIAG_RECON_PATH_NOT_FOUND,
                    "Path not found: " +
                        base::UTF16ToUTF8(dir_or_file.LossyDisplayName()));
      return true;
    }

    base::ByteCount dir_or_file_size;
    if (path_info_.is_directory) {
      dir_or_file_size =
          base::ByteCount(base::ComputeDirectorySize(dir_or_file));
    } else {
      dir_or_file_size =
          base::ByteCount(base::GetFileSize(dir_or_file).value_or(0));
    }
    if (dir_or_file_size.is_zero() && !path_info_.is_optional) {
      RecordFailure(DIAG_RECON_CANNOT_OBTAIN_SIZE,
                    "Cannot obtain size for: " +
                        base::UTF16ToUTF8(dir_or_file.LossyDisplayName()));
      return true;
    }
    std::string printable_size =
        base::NumberToString(dir_or_file_size.InBytes());

    if (path_info_.max_size > base::ByteCount(0)) {
      if (dir_or_file_size > path_info_.max_size) {
        RecordFailure(DIAG_RECON_FILE_TOO_LARGE,
                      "Path contents too large (" + printable_size + ") for: " +
                          base::UTF16ToUTF8(dir_or_file.LossyDisplayName()));
        return true;
      }
    }
    if (!path_info_.test_writable) {
      RecordSuccess("Path exists");
      return true;
    }
    if (!base::PathIsWritable(dir_or_file)) {
      RecordFailure(DIAG_RECON_NOT_WRITABLE,
                    "Path is not writable: " +
                        base::UTF16ToUTF8(dir_or_file.LossyDisplayName()));
      return true;
    }
    RecordSuccess("Path exists and is writable: " + printable_size);
    return true;
  }

 private:
  TestPathInfo path_info_;
};

// Check the version of Chrome.
class VersionTest : public DiagnosticsTest {
 public:
  VersionTest() : DiagnosticsTest(DIAGNOSTICS_VERSION_TEST) {}

  VersionTest(const VersionTest&) = delete;
  VersionTest& operator=(const VersionTest&) = delete;

  bool ExecuteImpl(DiagnosticsModel::Observer* observer) override {
    std::string current_version(version_info::GetVersionNumber());
    if (current_version.empty()) {
      RecordFailure(DIAG_RECON_EMPTY_VERSION, "Empty Version");
      return true;
    }
    std::string version_modifier =
        chrome::GetChannelName(chrome::WithExtendedStable(true));
    if (!version_modifier.empty()) {
      current_version += " " + version_modifier;
    }
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
    current_version += " GCB";
#endif  // BUILDFLAG(GOOGLE_CHROME_BRANDING)
    RecordSuccess(current_version);
    return true;
  }
};

}  // namespace

std::unique_ptr<DiagnosticsTest> MakeDiskSpaceTest() {
  return std::make_unique<DiskSpaceTest>();
}

std::unique_ptr<DiagnosticsTest> MakeInstallTypeTest() {
  return std::make_unique<InstallTypeTest>();
}

std::unique_ptr<DiagnosticsTest> MakeLocalOrSyncableBookmarksTest() {
  base::FilePath path = DiagnosticsTest::GetUserDefaultProfileDir();
  path = path.Append(bookmarks::kLocalOrSyncableBookmarksFileName);
  return std::make_unique<JSONTest>(path, DIAGNOSTICS_JSON_BOOKMARKS_TEST,
                                    base::MiB(2), JSONTest::NON_CRITICAL);
}

std::unique_ptr<DiagnosticsTest> MakeAccountBookmarksTest() {
  base::FilePath path = DiagnosticsTest::GetUserDefaultProfileDir();
  path = path.Append(bookmarks::kAccountBookmarksFileName);
  return std::make_unique<JSONTest>(path, DIAGNOSTICS_JSON_BOOKMARKS_TEST,
                                    base::MiB(2), JSONTest::NON_CRITICAL);
}

std::unique_ptr<DiagnosticsTest> MakeLocalStateTest() {
  base::FilePath path;
  base::PathService::Get(chrome::DIR_USER_DATA, &path);
  path = path.Append(chrome::kLocalStateFilename);
  return std::make_unique<JSONTest>(path, DIAGNOSTICS_JSON_LOCAL_STATE_TEST,
                                    base::KiB(50), JSONTest::CRITICAL);
}

std::unique_ptr<DiagnosticsTest> MakePreferencesTest() {
  base::FilePath path = DiagnosticsTest::GetUserDefaultProfileDir();
  path = path.Append(chrome::kPreferencesFilename);
  return std::make_unique<JSONTest>(path, DIAGNOSTICS_JSON_PREFERENCES_TEST,
                                    base::KiB(100), JSONTest::CRITICAL);
}

std::unique_ptr<DiagnosticsTest> MakeOperatingSystemTest() {
  return std::make_unique<OperatingSystemTest>();
}

std::unique_ptr<DiagnosticsTest> MakeDictionaryDirTest() {
  return std::make_unique<PathTest>(kPathsToTest[0]);
}

std::unique_ptr<DiagnosticsTest> MakeLocalStateFileTest() {
  return std::make_unique<PathTest>(kPathsToTest[1]);
}

std::unique_ptr<DiagnosticsTest> MakeResourcesFileTest() {
  return std::make_unique<PathTest>(kPathsToTest[2]);
}

std::unique_ptr<DiagnosticsTest> MakeUserDirTest() {
  return std::make_unique<PathTest>(kPathsToTest[3]);
}

std::unique_ptr<DiagnosticsTest> MakeVersionTest() {
  return std::make_unique<VersionTest>();
}

}  // namespace diagnostics