| // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/diagnostics/recon_diagnostics.h" |
| |
| #include <string> |
| |
| #include "base/file_util.h" |
| #include "base/json/json_reader.h" |
| #include "base/string_util.h" |
| #include "base/string_number_conversions.h" |
| #include "base/utf_string_conversions.h" |
| #include "base/sys_info.h" |
| #include "base/path_service.h" |
| #include "chrome/browser/diagnostics/diagnostics_test.h" |
| #include "chrome/browser/platform_util.h" |
| #include "chrome/common/chrome_constants.h" |
| #include "chrome/common/chrome_paths.h" |
| #include "chrome/common/chrome_version_info.h" |
| #include "content/common/json_value_serializer.h" |
| |
| #if defined(OS_WIN) |
| #include "base/win/windows_version.h" |
| #include "chrome/browser/enumerate_modules_model_win.h" |
| #include "chrome/installer/util/install_util.h" |
| #endif |
| |
| // Reconnaissance diagnostics. These are the first and most critical |
| // diagnostic tests. Here we check for the existence of critical files. |
| // TODO(cpu): Define if it makes sense to localize strings. |
| |
| // TODO(cpu): There are a few maximum file sizes hardcoded in this file |
| // that have little or no theoretical or experimental ground. Find a way |
| // to justify them. |
| |
| namespace { |
| |
| class InstallTypeTest; |
| InstallTypeTest* g_install_type = 0; |
| |
| // Check that the flavor of the operating system is supported. |
| class OperatingSystemTest : public DiagnosticTest { |
| public: |
| OperatingSystemTest() : DiagnosticTest(ASCIIToUTF16("Operating System")) {} |
| |
| virtual int GetId() { return 0; } |
| |
| virtual bool ExecuteImpl(DiagnosticsModel::Observer* observer) { |
| #if defined(OS_WIN) |
| base::win::Version version = base::win::GetVersion(); |
| if ((version < base::win::VERSION_XP) || |
| ((version == base::win::VERSION_XP) && |
| (base::win::OSInfo::GetInstance()->service_pack().major < 2))) { |
| RecordFailure(ASCIIToUTF16("Must have Windows XP SP2 or later")); |
| return false; |
| } |
| #else |
| // TODO(port): define the OS criteria for Linux and Mac. |
| #endif // defined(OS_WIN) |
| RecordSuccess(ASCIIToUTF16(StringPrintf("%s %s", |
| base::SysInfo::OperatingSystemName().c_str(), |
| base::SysInfo::OperatingSystemVersion().c_str()))); |
| return true; |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(OperatingSystemTest); |
| }; |
| |
| // Check if any conflicting DLLs are loaded. |
| class ConflictingDllsTest : public DiagnosticTest { |
| public: |
| ConflictingDllsTest() : DiagnosticTest(ASCIIToUTF16("Conflicting modules")) {} |
| |
| virtual int GetId() { return 0; } |
| |
| virtual bool ExecuteImpl(DiagnosticsModel::Observer* observer) { |
| #if defined(OS_WIN) |
| EnumerateModulesModel* model = EnumerateModulesModel::GetInstance(); |
| model->set_limited_mode(true); |
| model->ScanNow(); |
| ListValue* list = model->GetModuleList(); |
| if (!model->confirmed_bad_modules_detected() && |
| !model->suspected_bad_modules_detected()) { |
| RecordSuccess(ASCIIToUTF16("No conflicting modules found")); |
| return true; |
| } |
| |
| string16 failures = ASCIIToUTF16("Possibly conflicting modules:"); |
| DictionaryValue* dictionary; |
| for (size_t i = 0; i < list->GetSize(); ++i) { |
| if (!list->GetDictionary(i, &dictionary)) |
| RecordFailure(ASCIIToUTF16("Dictionary lookup failed")); |
| int status; |
| string16 location; |
| string16 name; |
| if (!dictionary->GetInteger("status", &status)) |
| RecordFailure(ASCIIToUTF16("No 'status' field found")); |
| if (status < ModuleEnumerator::SUSPECTED_BAD) |
| continue; |
| |
| if (!dictionary->GetString("location", &location)) { |
| RecordFailure(ASCIIToUTF16("No 'location' field found")); |
| return true; |
| } |
| if (!dictionary->GetString("name", &name)) { |
| RecordFailure(ASCIIToUTF16("No 'name' field found")); |
| return true; |
| } |
| |
| failures += ASCIIToUTF16("\n") + location + name; |
| } |
| RecordFailure(failures); |
| return true; |
| #else |
| RecordFailure(ASCIIToUTF16("Not implemented")); |
| return true; |
| #endif // defined(OS_WIN) |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(ConflictingDllsTest); |
| }; |
| |
| // Check if it is system install or per-user install. |
| class InstallTypeTest : public DiagnosticTest { |
| public: |
| InstallTypeTest() : DiagnosticTest(ASCIIToUTF16("Install Type")), |
| user_level_(false) {} |
| |
| virtual int GetId() { return 0; } |
| |
| virtual bool ExecuteImpl(DiagnosticsModel::Observer* observer) { |
| #if defined(OS_WIN) |
| FilePath chrome_exe; |
| if (!PathService::Get(base::FILE_EXE, &chrome_exe)) { |
| RecordFailure(ASCIIToUTF16("Path provider failure")); |
| return false; |
| } |
| user_level_ = InstallUtil::IsPerUserInstall(chrome_exe.value().c_str()); |
| const char* type = user_level_ ? "User Level" : "System Level"; |
| string16 install_type(ASCIIToUTF16(type)); |
| #else |
| string16 install_type(ASCIIToUTF16("System Level")); |
| #endif // defined(OS_WIN) |
| RecordSuccess(install_type); |
| g_install_type = this; |
| return true; |
| } |
| |
| bool system_level() const { return !user_level_; } |
| |
| private: |
| bool user_level_; |
| DISALLOW_COPY_AND_ASSIGN(InstallTypeTest); |
| }; |
| |
| // Check the version of Chrome. |
| class VersionTest : public DiagnosticTest { |
| public: |
| VersionTest() : DiagnosticTest(ASCIIToUTF16("Browser Version")) {} |
| |
| virtual int GetId() { return 0; } |
| |
| virtual bool ExecuteImpl(DiagnosticsModel::Observer* observer) { |
| chrome::VersionInfo version_info; |
| if (!version_info.is_valid()) { |
| RecordFailure(ASCIIToUTF16("No Version")); |
| return true; |
| } |
| std::string current_version = version_info.Version(); |
| if (current_version.empty()) { |
| RecordFailure(ASCIIToUTF16("Empty Version")); |
| return true; |
| } |
| std::string version_modifier = platform_util::GetVersionStringModifier(); |
| if (!version_modifier.empty()) |
| current_version += " " + version_modifier; |
| #if defined(GOOGLE_CHROME_BUILD) |
| current_version += " GCB"; |
| #endif // defined(GOOGLE_CHROME_BUILD) |
| RecordSuccess(ASCIIToUTF16(current_version)); |
| return true; |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(VersionTest); |
| }; |
| |
| struct TestPathInfo { |
| const char* test_name; |
| int path_id; |
| bool is_directory; |
| bool is_optional; |
| bool test_writable; |
| int64 max_size; |
| }; |
| |
| const int64 kOneKilo = 1024; |
| const int64 kOneMeg = 1024 * kOneKilo; |
| |
| const TestPathInfo kPathsToTest[] = { |
| {"User data Directory", chrome::DIR_USER_DATA, |
| true, false, true, 850 * kOneMeg}, |
| {"Local state file", chrome::FILE_LOCAL_STATE, |
| false, false, true, 500 * kOneKilo}, |
| {"Dictionaries Directory", chrome::DIR_APP_DICTIONARIES, |
| true, true, false, 0}, |
| {"Resources file", chrome::FILE_RESOURCES_PACK, |
| false, false, false, 0} |
| }; |
| |
| // Check that the user's data directory exists and the paths are writable. |
| // If it is a systemwide install some paths are not expected to be writable. |
| // This test depends on |InstallTypeTest| having run successfully. |
| class PathTest : public DiagnosticTest { |
| public: |
| explicit PathTest(const TestPathInfo& path_info) |
| : DiagnosticTest(ASCIIToUTF16(path_info.test_name)), |
| path_info_(path_info) {} |
| |
| virtual int GetId() { return 0; } |
| |
| virtual bool ExecuteImpl(DiagnosticsModel::Observer* observer) { |
| if (!g_install_type) { |
| RecordStopFailure(ASCIIToUTF16("dependency failure")); |
| return false; |
| } |
| FilePath dir_or_file; |
| if (!PathService::Get(path_info_.path_id, &dir_or_file)) { |
| RecordStopFailure(ASCIIToUTF16("Path provider failure")); |
| return false; |
| } |
| if (!file_util::PathExists(dir_or_file)) { |
| RecordFailure(ASCIIToUTF16("Path not found: ") + |
| dir_or_file.LossyDisplayName()); |
| return true; |
| } |
| |
| int64 dir_or_file_size = 0; |
| if (path_info_.is_directory) { |
| dir_or_file_size = file_util::ComputeDirectorySize(dir_or_file); |
| } else { |
| file_util::GetFileSize(dir_or_file, &dir_or_file_size); |
| } |
| if (!dir_or_file_size && !path_info_.is_optional) { |
| RecordFailure(ASCIIToUTF16("Cannot obtain size for: ") + |
| dir_or_file.LossyDisplayName()); |
| return true; |
| } |
| DataUnits units = GetByteDisplayUnits(dir_or_file_size); |
| string16 printable_size = FormatBytes(dir_or_file_size, units, true); |
| |
| if (path_info_.max_size > 0) { |
| if (dir_or_file_size > path_info_.max_size) { |
| RecordFailure(ASCIIToUTF16("Path contents too large (") + |
| printable_size + |
| ASCIIToUTF16(") for: ") + |
| dir_or_file.LossyDisplayName()); |
| return true; |
| } |
| } |
| if (g_install_type->system_level() && !path_info_.test_writable) { |
| RecordSuccess(ASCIIToUTF16("Path exists")); |
| return true; |
| } |
| if (!file_util::PathIsWritable(dir_or_file)) { |
| RecordFailure(ASCIIToUTF16("Path is not writable: ") + |
| dir_or_file.LossyDisplayName()); |
| return true; |
| } |
| RecordSuccess(ASCIIToUTF16("Path exists and is writable: ") |
| + printable_size); |
| return true; |
| } |
| |
| private: |
| TestPathInfo path_info_; |
| DISALLOW_COPY_AND_ASSIGN(PathTest); |
| }; |
| |
| // Check that the disk space in the volume where the user data dir normally |
| // lives is not dangerously low. |
| class DiskSpaceTest : public DiagnosticTest { |
| public: |
| DiskSpaceTest() : DiagnosticTest(ASCIIToUTF16("Disk Space")) {} |
| |
| virtual int GetId() { return 0; } |
| |
| virtual bool ExecuteImpl(DiagnosticsModel::Observer* observer) { |
| FilePath data_dir; |
| if (!PathService::Get(chrome::DIR_USER_DATA, &data_dir)) |
| return false; |
| int64 disk_space = base::SysInfo::AmountOfFreeDiskSpace(data_dir); |
| if (disk_space < 0) { |
| RecordFailure(ASCIIToUTF16("Unable to query free space")); |
| return true; |
| } |
| DataUnits units = GetByteDisplayUnits(disk_space); |
| string16 printable_size = FormatBytes(disk_space, units, true); |
| if (disk_space < 80 * kOneMeg) { |
| RecordFailure(ASCIIToUTF16("Low disk space : ") + printable_size); |
| return true; |
| } |
| RecordSuccess(ASCIIToUTF16("Free space : ") + printable_size); |
| return true; |
| } |
| |
| private: |
| DISALLOW_COPY_AND_ASSIGN(DiskSpaceTest); |
| }; |
| |
| // Checks that a given json file can be correctly parsed. |
| class JSONTest : public DiagnosticTest { |
| public: |
| JSONTest(const FilePath& path, const string16& name, int64 max_file_size) |
| : DiagnosticTest(name), path_(path), max_file_size_(max_file_size) { |
| } |
| |
| virtual int GetId() { return 0; } |
| |
| virtual bool ExecuteImpl(DiagnosticsModel::Observer* observer) { |
| if (!file_util::PathExists(path_)) { |
| RecordFailure(ASCIIToUTF16("File not found")); |
| return true; |
| } |
| int64 file_size; |
| if (!file_util::GetFileSize(path_, &file_size)) { |
| RecordFailure(ASCIIToUTF16("Cannot obtain file size")); |
| return true; |
| } |
| |
| if (file_size > max_file_size_) { |
| RecordFailure(ASCIIToUTF16("File too big")); |
| return true; |
| } |
| // Being small enough, we can process it in-memory. |
| std::string json_data; |
| if (!file_util::ReadFileToString(path_, &json_data)) { |
| RecordFailure(ASCIIToUTF16( |
| "Could not open file. Possibly locked by other process")); |
| return true; |
| } |
| |
| JSONStringValueSerializer json(json_data); |
| int error_code = base::JSONReader::JSON_NO_ERROR; |
| std::string error_message; |
| scoped_ptr<Value> json_root(json.Deserialize(&error_code, &error_message)); |
| if (base::JSONReader::JSON_NO_ERROR != error_code) { |
| if (error_message.empty()) { |
| error_message = "Parse error " + base::IntToString(error_code); |
| } |
| RecordFailure(UTF8ToUTF16(error_message)); |
| return true; |
| } |
| |
| RecordSuccess(ASCIIToUTF16("File parsed OK")); |
| return true; |
| } |
| |
| private: |
| FilePath path_; |
| int64 max_file_size_; |
| DISALLOW_COPY_AND_ASSIGN(JSONTest); |
| }; |
| |
| } // namespace |
| |
| DiagnosticTest* MakeUserDirTest() { |
| return new PathTest(kPathsToTest[0]); |
| } |
| |
| DiagnosticTest* MakeLocalStateFileTest() { |
| return new PathTest(kPathsToTest[1]); |
| } |
| |
| DiagnosticTest* MakeDictonaryDirTest() { |
| return new PathTest(kPathsToTest[2]); |
| } |
| |
| DiagnosticTest* MakeResourcesFileTest() { |
| return new PathTest(kPathsToTest[3]); |
| } |
| |
| DiagnosticTest* MakeVersionTest() { |
| return new VersionTest(); |
| } |
| |
| DiagnosticTest* MakeDiskSpaceTest() { |
| return new DiskSpaceTest(); |
| } |
| |
| DiagnosticTest* MakeOperatingSystemTest() { |
| return new OperatingSystemTest(); |
| } |
| |
| DiagnosticTest* MakeConflictingDllsTest() { |
| return new ConflictingDllsTest(); |
| } |
| |
| DiagnosticTest* MakeInstallTypeTest() { |
| return new InstallTypeTest(); |
| } |
| |
| DiagnosticTest* MakePreferencesTest() { |
| FilePath path = DiagnosticTest::GetUserDefaultProfileDir(); |
| path = path.Append(chrome::kPreferencesFilename); |
| return new JSONTest(path, ASCIIToUTF16("Profile JSON"), 100 * kOneKilo); |
| } |
| |
| DiagnosticTest* MakeBookMarksTest() { |
| FilePath path = DiagnosticTest::GetUserDefaultProfileDir(); |
| path = path.Append(chrome::kBookmarksFileName); |
| return new JSONTest(path, ASCIIToUTF16("BookMarks JSON"), 2 * kOneMeg); |
| } |
| |
| DiagnosticTest* MakeLocalStateTest() { |
| FilePath path; |
| PathService::Get(chrome::DIR_USER_DATA, &path); |
| path = path.Append(chrome::kLocalStateFilename); |
| return new JSONTest(path, ASCIIToUTF16("Local State JSON"), 50 * kOneKilo); |
| } |