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