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