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