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