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