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