Home | History | Annotate | Download | only in diagnostics
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/diagnostics/recon_diagnostics.h"
      6 
      7 #include <string>
      8 
      9 #include "base/files/file_util.h"
     10 #include "base/json/json_reader.h"
     11 #include "base/json/json_string_value_serializer.h"
     12 #include "base/path_service.h"
     13 #include "base/strings/string_number_conversions.h"
     14 #include "base/strings/string_util.h"
     15 #include "base/strings/stringprintf.h"
     16 #include "base/strings/utf_string_conversions.h"
     17 #include "base/sys_info.h"
     18 #include "chrome/browser/diagnostics/diagnostics_test.h"
     19 #include "chrome/common/chrome_constants.h"
     20 #include "chrome/common/chrome_paths.h"
     21 #include "chrome/common/chrome_version_info.h"
     22 #include "components/bookmarks/common/bookmark_constants.h"
     23 
     24 #if defined(OS_WIN)
     25 #include "base/win/windows_version.h"
     26 #include "chrome/browser/enumerate_modules_model_win.h"
     27 #include "chrome/installer/util/install_util.h"
     28 #endif
     29 
     30 // Reconnaissance diagnostics. These are the first and most critical
     31 // diagnostic tests. Here we check for the existence of critical files.
     32 // TODO(cpu): Define if it makes sense to localize strings.
     33 
     34 // TODO(cpu): There are a few maximum file sizes hard-coded in this file
     35 // that have little or no theoretical or experimental ground. Find a way
     36 // to justify them.
     37 
     38 namespace diagnostics {
     39 
     40 namespace {
     41 
     42 const int64 kOneKilobyte = 1024;
     43 const int64 kOneMegabyte = 1024 * kOneKilobyte;
     44 
     45 class InstallTypeTest;
     46 InstallTypeTest* g_install_type = 0;
     47 
     48 // Check if any conflicting DLLs are loaded.
     49 class ConflictingDllsTest : public DiagnosticsTest {
     50  public:
     51   ConflictingDllsTest()
     52       : DiagnosticsTest(DIAGNOSTICS_CONFLICTING_DLLS_TEST) {}
     53 
     54   virtual bool ExecuteImpl(DiagnosticsModel::Observer* observer) OVERRIDE {
     55 #if defined(OS_WIN)
     56     EnumerateModulesModel* model = EnumerateModulesModel::GetInstance();
     57     model->set_limited_mode(true);
     58     model->ScanNow();
     59     scoped_ptr<base::ListValue> list(model->GetModuleList());
     60     if (!model->confirmed_bad_modules_detected() &&
     61         !model->suspected_bad_modules_detected()) {
     62       RecordSuccess("No conflicting modules found");
     63       return true;
     64     }
     65 
     66     std::string failures = "Possibly conflicting modules:";
     67     base::DictionaryValue* dictionary;
     68     for (size_t i = 0; i < list->GetSize(); ++i) {
     69       if (!list->GetDictionary(i, &dictionary))
     70         RecordFailure(DIAG_RECON_DICTIONARY_LOOKUP_FAILED,
     71                       "Dictionary lookup failed");
     72       int status;
     73       std::string location;
     74       std::string name;
     75       if (!dictionary->GetInteger("status", &status))
     76         RecordFailure(DIAG_RECON_NO_STATUS_FIELD, "No 'status' field found");
     77       if (status < ModuleEnumerator::SUSPECTED_BAD)
     78         continue;
     79 
     80       if (!dictionary->GetString("location", &location)) {
     81         RecordFailure(DIAG_RECON_NO_LOCATION_FIELD,
     82                       "No 'location' field found");
     83         return true;
     84       }
     85       if (!dictionary->GetString("name", &name)) {
     86         RecordFailure(DIAG_RECON_NO_NAME_FIELD, "No 'name' field found");
     87         return true;
     88       }
     89 
     90       failures += "\n" + location + name;
     91     }
     92     RecordFailure(DIAG_RECON_CONFLICTING_MODULES, failures);
     93     return true;
     94 #else
     95     RecordFailure(DIAG_RECON_NOT_IMPLEMENTED, "Not implemented");
     96     return true;
     97 #endif  // defined(OS_WIN)
     98   }
     99 
    100  private:
    101   DISALLOW_COPY_AND_ASSIGN(ConflictingDllsTest);
    102 };
    103 
    104 // Check that the disk space in the volume where the user data directory
    105 // normally lives is not dangerously low.
    106 class DiskSpaceTest : public DiagnosticsTest {
    107  public:
    108   DiskSpaceTest() : DiagnosticsTest(DIAGNOSTICS_DISK_SPACE_TEST) {}
    109 
    110   virtual bool ExecuteImpl(DiagnosticsModel::Observer* observer) OVERRIDE {
    111     base::FilePath data_dir;
    112     if (!PathService::Get(chrome::DIR_USER_DATA, &data_dir))
    113       return false;
    114     int64 disk_space = base::SysInfo::AmountOfFreeDiskSpace(data_dir);
    115     if (disk_space < 0) {
    116       RecordFailure(DIAG_RECON_UNABLE_TO_QUERY, "Unable to query free space");
    117       return true;
    118     }
    119     std::string printable_size = base::Int64ToString(disk_space);
    120     if (disk_space < 80 * kOneMegabyte) {
    121       RecordFailure(DIAG_RECON_LOW_DISK_SPACE,
    122                     "Low disk space: " + printable_size);
    123       return true;
    124     }
    125     RecordSuccess("Free space: " + printable_size);
    126     return true;
    127   }
    128 
    129  private:
    130   DISALLOW_COPY_AND_ASSIGN(DiskSpaceTest);
    131 };
    132 
    133 // Check if it is system install or per-user install.
    134 class InstallTypeTest : public DiagnosticsTest {
    135  public:
    136   InstallTypeTest()
    137       : DiagnosticsTest(DIAGNOSTICS_INSTALL_TYPE_TEST), user_level_(false) {}
    138 
    139   virtual bool ExecuteImpl(DiagnosticsModel::Observer* observer) OVERRIDE {
    140 #if defined(OS_WIN)
    141     base::FilePath chrome_exe;
    142     if (!PathService::Get(base::FILE_EXE, &chrome_exe)) {
    143       RecordFailure(DIAG_RECON_INSTALL_PATH_PROVIDER, "Path provider failure");
    144       return false;
    145     }
    146     user_level_ = InstallUtil::IsPerUserInstall(chrome_exe.value().c_str());
    147     const char* type = user_level_ ? "User Level" : "System Level";
    148     std::string install_type(type);
    149 #else
    150     std::string install_type("System Level");
    151 #endif  // defined(OS_WIN)
    152     RecordSuccess(install_type);
    153     g_install_type = this;
    154     return true;
    155   }
    156 
    157   bool system_level() const { return !user_level_; }
    158 
    159  private:
    160   bool user_level_;
    161   DISALLOW_COPY_AND_ASSIGN(InstallTypeTest);
    162 };
    163 
    164 // Checks that a given JSON file can be correctly parsed.
    165 class JSONTest : public DiagnosticsTest {
    166  public:
    167   enum FileImportance {
    168     NON_CRITICAL,
    169     CRITICAL
    170   };
    171 
    172   JSONTest(const base::FilePath& path,
    173            DiagnosticsTestId id,
    174            int64 max_file_size,
    175            FileImportance importance)
    176       : DiagnosticsTest(id),
    177         path_(path),
    178         max_file_size_(max_file_size),
    179         importance_(importance) {}
    180 
    181   virtual bool ExecuteImpl(DiagnosticsModel::Observer* observer) OVERRIDE {
    182     if (!base::PathExists(path_)) {
    183       if (importance_ == CRITICAL) {
    184         RecordOutcome(DIAG_RECON_FILE_NOT_FOUND,
    185                       "File not found",
    186                       DiagnosticsModel::TEST_FAIL_CONTINUE);
    187       } else {
    188         RecordOutcome(DIAG_RECON_FILE_NOT_FOUND_OK,
    189                       "File not found (but that is OK)",
    190                       DiagnosticsModel::TEST_OK);
    191       }
    192       return true;
    193     }
    194     int64 file_size;
    195     if (!base::GetFileSize(path_, &file_size)) {
    196       RecordFailure(DIAG_RECON_CANNOT_OBTAIN_FILE_SIZE,
    197                     "Cannot obtain file size");
    198       return true;
    199     }
    200 
    201     if (file_size > max_file_size_) {
    202       RecordFailure(DIAG_RECON_FILE_TOO_BIG, "File too big");
    203       return true;
    204     }
    205     // Being small enough, we can process it in-memory.
    206     std::string json_data;
    207     if (!base::ReadFileToString(path_, &json_data)) {
    208       RecordFailure(DIAG_RECON_UNABLE_TO_OPEN_FILE,
    209                     "Could not open file. Possibly locked by another process");
    210       return true;
    211     }
    212 
    213     JSONStringValueSerializer json(json_data);
    214     int error_code = base::JSONReader::JSON_NO_ERROR;
    215     std::string error_message;
    216     scoped_ptr<base::Value> json_root(
    217         json.Deserialize(&error_code, &error_message));
    218     if (base::JSONReader::JSON_NO_ERROR != error_code) {
    219       if (error_message.empty()) {
    220         error_message = "Parse error " + base::IntToString(error_code);
    221       }
    222       RecordFailure(DIAG_RECON_PARSE_ERROR, error_message);
    223       return true;
    224     }
    225 
    226     RecordSuccess("File parsed OK");
    227     return true;
    228   }
    229 
    230  private:
    231   base::FilePath path_;
    232   int64 max_file_size_;
    233   FileImportance importance_;
    234   DISALLOW_COPY_AND_ASSIGN(JSONTest);
    235 };
    236 
    237 // Check that the flavor of the operating system is supported.
    238 class OperatingSystemTest : public DiagnosticsTest {
    239  public:
    240   OperatingSystemTest()
    241       : DiagnosticsTest(DIAGNOSTICS_OPERATING_SYSTEM_TEST) {}
    242 
    243   virtual bool ExecuteImpl(DiagnosticsModel::Observer* observer) OVERRIDE {
    244 #if defined(OS_WIN)
    245     base::win::Version version = base::win::GetVersion();
    246     if ((version < base::win::VERSION_XP) ||
    247         ((version == base::win::VERSION_XP) &&
    248          (base::win::OSInfo::GetInstance()->service_pack().major < 2))) {
    249       RecordFailure(DIAG_RECON_PRE_WINDOW_XP_SP2,
    250                     "Must have Windows XP SP2 or later");
    251       return false;
    252     }
    253 #else
    254 // TODO(port): define the OS criteria for Linux and Mac.
    255 #endif  // defined(OS_WIN)
    256     RecordSuccess(
    257         base::StringPrintf("%s %s",
    258                            base::SysInfo::OperatingSystemName().c_str(),
    259                            base::SysInfo::OperatingSystemVersion().c_str()));
    260     return true;
    261   }
    262 
    263  private:
    264   DISALLOW_COPY_AND_ASSIGN(OperatingSystemTest);
    265 };
    266 
    267 struct TestPathInfo {
    268   DiagnosticsTestId test_id;
    269   int path_id;
    270   bool is_directory;
    271   bool is_optional;
    272   bool test_writable;
    273   int64 max_size;
    274 };
    275 
    276 const TestPathInfo kPathsToTest[] = {
    277     {DIAGNOSTICS_PATH_DICTIONARIES_TEST, chrome::DIR_APP_DICTIONARIES, true,
    278      true, false, 0},
    279     {DIAGNOSTICS_PATH_LOCAL_STATE_TEST, chrome::FILE_LOCAL_STATE, false, false,
    280      true, 500 * kOneKilobyte},
    281     {DIAGNOSTICS_PATH_RESOURCES_TEST, chrome::FILE_RESOURCES_PACK, false, false,
    282      false, 0},
    283     {DIAGNOSTICS_PATH_USER_DATA_TEST, chrome::DIR_USER_DATA, true, false, true,
    284      850 * kOneMegabyte},
    285 };
    286 
    287 // Check that the user's data directory exists and the paths are writable.
    288 // If it is a system-wide install some paths are not expected to be writable.
    289 // This test depends on |InstallTypeTest| having run successfully.
    290 class PathTest : public DiagnosticsTest {
    291  public:
    292   explicit PathTest(const TestPathInfo& path_info)
    293       : DiagnosticsTest(path_info.test_id),
    294         path_info_(path_info) {}
    295 
    296   virtual bool ExecuteImpl(DiagnosticsModel::Observer* observer) OVERRIDE {
    297     if (!g_install_type) {
    298       RecordStopFailure(DIAG_RECON_DEPENDENCY, "Install dependency failure");
    299       return false;
    300     }
    301     base::FilePath dir_or_file;
    302     if (!PathService::Get(path_info_.path_id, &dir_or_file)) {
    303       RecordStopFailure(DIAG_RECON_PATH_PROVIDER, "Path provider failure");
    304       return false;
    305     }
    306     if (!base::PathExists(dir_or_file)) {
    307       RecordFailure(
    308           DIAG_RECON_PATH_NOT_FOUND,
    309           "Path not found: " +
    310               base::UTF16ToUTF8(dir_or_file.LossyDisplayName()));
    311       return true;
    312     }
    313 
    314     int64 dir_or_file_size = 0;
    315     if (path_info_.is_directory) {
    316       dir_or_file_size = base::ComputeDirectorySize(dir_or_file);
    317     } else {
    318       base::GetFileSize(dir_or_file, &dir_or_file_size);
    319     }
    320     if (!dir_or_file_size && !path_info_.is_optional) {
    321       RecordFailure(DIAG_RECON_CANNOT_OBTAIN_SIZE,
    322                     "Cannot obtain size for: " +
    323                         base::UTF16ToUTF8(dir_or_file.LossyDisplayName()));
    324       return true;
    325     }
    326     std::string printable_size = base::Int64ToString(dir_or_file_size);
    327 
    328     if (path_info_.max_size > 0) {
    329       if (dir_or_file_size > path_info_.max_size) {
    330         RecordFailure(DIAG_RECON_FILE_TOO_LARGE,
    331                       "Path contents too large (" + printable_size + ") for: " +
    332                           base::UTF16ToUTF8(dir_or_file.LossyDisplayName()));
    333         return true;
    334       }
    335     }
    336     if (g_install_type->system_level() && !path_info_.test_writable) {
    337       RecordSuccess("Path exists");
    338       return true;
    339     }
    340     if (!base::PathIsWritable(dir_or_file)) {
    341       RecordFailure(DIAG_RECON_NOT_WRITABLE,
    342                     "Path is not writable: " +
    343                         base::UTF16ToUTF8(dir_or_file.LossyDisplayName()));
    344       return true;
    345     }
    346     RecordSuccess("Path exists and is writable: " + printable_size);
    347     return true;
    348   }
    349 
    350  private:
    351   TestPathInfo path_info_;
    352   DISALLOW_COPY_AND_ASSIGN(PathTest);
    353 };
    354 
    355 // Check the version of Chrome.
    356 class VersionTest : public DiagnosticsTest {
    357  public:
    358   VersionTest() : DiagnosticsTest(DIAGNOSTICS_VERSION_TEST) {}
    359 
    360   virtual bool ExecuteImpl(DiagnosticsModel::Observer* observer) OVERRIDE {
    361     chrome::VersionInfo version_info;
    362     if (!version_info.is_valid()) {
    363       RecordFailure(DIAG_RECON_NO_VERSION, "No Version");
    364       return true;
    365     }
    366     std::string current_version = version_info.Version();
    367     if (current_version.empty()) {
    368       RecordFailure(DIAG_RECON_EMPTY_VERSION, "Empty Version");
    369       return true;
    370     }
    371     std::string version_modifier =
    372         chrome::VersionInfo::GetVersionStringModifier();
    373     if (!version_modifier.empty())
    374       current_version += " " + version_modifier;
    375 #if defined(GOOGLE_CHROME_BUILD)
    376     current_version += " GCB";
    377 #endif  // defined(GOOGLE_CHROME_BUILD)
    378     RecordSuccess(current_version);
    379     return true;
    380   }
    381 
    382  private:
    383   DISALLOW_COPY_AND_ASSIGN(VersionTest);
    384 };
    385 
    386 }  // namespace
    387 
    388 DiagnosticsTest* MakeConflictingDllsTest() { return new ConflictingDllsTest(); }
    389 
    390 DiagnosticsTest* MakeDiskSpaceTest() { return new DiskSpaceTest(); }
    391 
    392 DiagnosticsTest* MakeInstallTypeTest() { return new InstallTypeTest(); }
    393 
    394 DiagnosticsTest* MakeBookMarksTest() {
    395   base::FilePath path = DiagnosticsTest::GetUserDefaultProfileDir();
    396   path = path.Append(bookmarks::kBookmarksFileName);
    397   return new JSONTest(path,
    398                       DIAGNOSTICS_JSON_BOOKMARKS_TEST,
    399                       2 * kOneMegabyte,
    400                       JSONTest::NON_CRITICAL);
    401 }
    402 
    403 DiagnosticsTest* MakeLocalStateTest() {
    404   base::FilePath path;
    405   PathService::Get(chrome::DIR_USER_DATA, &path);
    406   path = path.Append(chrome::kLocalStateFilename);
    407   return new JSONTest(path,
    408                       DIAGNOSTICS_JSON_LOCAL_STATE_TEST,
    409                       50 * kOneKilobyte,
    410                       JSONTest::CRITICAL);
    411 }
    412 
    413 DiagnosticsTest* MakePreferencesTest() {
    414   base::FilePath path = DiagnosticsTest::GetUserDefaultProfileDir();
    415   path = path.Append(chrome::kPreferencesFilename);
    416   return new JSONTest(path,
    417                       DIAGNOSTICS_JSON_PREFERENCES_TEST,
    418                       100 * kOneKilobyte,
    419                       JSONTest::CRITICAL);
    420 }
    421 
    422 
    423 DiagnosticsTest* MakeOperatingSystemTest() { return new OperatingSystemTest(); }
    424 
    425 DiagnosticsTest* MakeDictonaryDirTest() {
    426   return new PathTest(kPathsToTest[0]);
    427 }
    428 
    429 DiagnosticsTest* MakeLocalStateFileTest() {
    430   return new PathTest(kPathsToTest[1]);
    431 }
    432 
    433 DiagnosticsTest* MakeResourcesFileTest() {
    434   return new PathTest(kPathsToTest[2]);
    435 }
    436 
    437 DiagnosticsTest* MakeUserDirTest() { return new PathTest(kPathsToTest[3]); }
    438 
    439 DiagnosticsTest* MakeVersionTest() { return new VersionTest(); }
    440 
    441 }  // namespace diagnostics
    442