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