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