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.
5 #include "chrome/browser/diagnostics/recon_diagnostics.h"
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"
24 #include "base/win/windows_version.h"
25 #include "chrome/browser/enumerate_modules_model_win.h"
26 #include "chrome/installer/util/install_util.h"
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.
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
37 namespace diagnostics
{
41 const int64 kOneKilobyte
= 1024;
42 const int64 kOneMegabyte
= 1024 * kOneKilobyte
;
44 class InstallTypeTest
;
45 InstallTypeTest
* g_install_type
= 0;
47 // Check if any conflicting DLLs are loaded.
48 class ConflictingDllsTest
: public DiagnosticsTest
{
51 : DiagnosticsTest(DIAGNOSTICS_CONFLICTING_DLLS_TEST
) {}
53 virtual bool ExecuteImpl(DiagnosticsModel::Observer
* observer
) OVERRIDE
{
55 EnumerateModulesModel
* model
= EnumerateModulesModel::GetInstance();
56 model
->set_limited_mode(true);
58 scoped_ptr
<base::ListValue
> list(model
->GetModuleList());
59 if (!model
->confirmed_bad_modules_detected() &&
60 !model
->suspected_bad_modules_detected()) {
61 RecordSuccess("No conflicting modules found");
65 std::string failures
= "Possibly conflicting modules:";
66 base::DictionaryValue
* dictionary
;
67 for (size_t i
= 0; i
< list
->GetSize(); ++i
) {
68 if (!list
->GetDictionary(i
, &dictionary
))
69 RecordFailure(DIAG_RECON_DICTIONARY_LOOKUP_FAILED
,
70 "Dictionary lookup failed");
74 if (!dictionary
->GetInteger("status", &status
))
75 RecordFailure(DIAG_RECON_NO_STATUS_FIELD
, "No 'status' field found");
76 if (status
< ModuleEnumerator::SUSPECTED_BAD
)
79 if (!dictionary
->GetString("location", &location
)) {
80 RecordFailure(DIAG_RECON_NO_LOCATION_FIELD
,
81 "No 'location' field found");
84 if (!dictionary
->GetString("name", &name
)) {
85 RecordFailure(DIAG_RECON_NO_NAME_FIELD
, "No 'name' field found");
89 failures
+= "\n" + location
+ name
;
91 RecordFailure(DIAG_RECON_CONFLICTING_MODULES
, failures
);
94 RecordFailure(DIAG_RECON_NOT_IMPLEMENTED
, "Not implemented");
96 #endif // defined(OS_WIN)
100 DISALLOW_COPY_AND_ASSIGN(ConflictingDllsTest
);
103 // Check that the disk space in the volume where the user data directory
104 // normally lives is not dangerously low.
105 class DiskSpaceTest
: public DiagnosticsTest
{
107 DiskSpaceTest() : DiagnosticsTest(DIAGNOSTICS_DISK_SPACE_TEST
) {}
109 virtual bool ExecuteImpl(DiagnosticsModel::Observer
* observer
) OVERRIDE
{
110 base::FilePath data_dir
;
111 if (!PathService::Get(chrome::DIR_USER_DATA
, &data_dir
))
113 int64 disk_space
= base::SysInfo::AmountOfFreeDiskSpace(data_dir
);
114 if (disk_space
< 0) {
115 RecordFailure(DIAG_RECON_UNABLE_TO_QUERY
, "Unable to query free space");
118 std::string printable_size
= base::Int64ToString(disk_space
);
119 if (disk_space
< 80 * kOneMegabyte
) {
120 RecordFailure(DIAG_RECON_LOW_DISK_SPACE
,
121 "Low disk space: " + printable_size
);
124 RecordSuccess("Free space: " + printable_size
);
129 DISALLOW_COPY_AND_ASSIGN(DiskSpaceTest
);
132 // Check if it is system install or per-user install.
133 class InstallTypeTest
: public DiagnosticsTest
{
136 : DiagnosticsTest(DIAGNOSTICS_INSTALL_TYPE_TEST
), user_level_(false) {}
138 virtual bool ExecuteImpl(DiagnosticsModel::Observer
* observer
) OVERRIDE
{
140 base::FilePath chrome_exe
;
141 if (!PathService::Get(base::FILE_EXE
, &chrome_exe
)) {
142 RecordFailure(DIAG_RECON_INSTALL_PATH_PROVIDER
, "Path provider failure");
145 user_level_
= InstallUtil::IsPerUserInstall(chrome_exe
.value().c_str());
146 const char* type
= user_level_
? "User Level" : "System Level";
147 std::string
install_type(type
);
149 std::string
install_type("System Level");
150 #endif // defined(OS_WIN)
151 RecordSuccess(install_type
);
152 g_install_type
= this;
156 bool system_level() const { return !user_level_
; }
160 DISALLOW_COPY_AND_ASSIGN(InstallTypeTest
);
163 // Checks that a given JSON file can be correctly parsed.
164 class JSONTest
: public DiagnosticsTest
{
166 enum FileImportance
{
171 JSONTest(const base::FilePath
& path
,
172 DiagnosticsTestId id
,
174 FileImportance importance
)
175 : DiagnosticsTest(id
),
177 max_file_size_(max_file_size
),
178 importance_(importance
) {}
180 virtual bool ExecuteImpl(DiagnosticsModel::Observer
* observer
) OVERRIDE
{
181 if (!base::PathExists(path_
)) {
182 if (importance_
== CRITICAL
) {
183 RecordOutcome(DIAG_RECON_FILE_NOT_FOUND
,
185 DiagnosticsModel::TEST_FAIL_CONTINUE
);
187 RecordOutcome(DIAG_RECON_FILE_NOT_FOUND_OK
,
188 "File not found (but that is OK)",
189 DiagnosticsModel::TEST_OK
);
194 if (!base::GetFileSize(path_
, &file_size
)) {
195 RecordFailure(DIAG_RECON_CANNOT_OBTAIN_FILE_SIZE
,
196 "Cannot obtain file size");
200 if (file_size
> max_file_size_
) {
201 RecordFailure(DIAG_RECON_FILE_TOO_BIG
, "File too big");
204 // Being small enough, we can process it in-memory.
205 std::string json_data
;
206 if (!base::ReadFileToString(path_
, &json_data
)) {
207 RecordFailure(DIAG_RECON_UNABLE_TO_OPEN_FILE
,
208 "Could not open file. Possibly locked by another process");
212 JSONStringValueSerializer
json(json_data
);
213 int error_code
= base::JSONReader::JSON_NO_ERROR
;
214 std::string error_message
;
215 scoped_ptr
<base::Value
> json_root(
216 json
.Deserialize(&error_code
, &error_message
));
217 if (base::JSONReader::JSON_NO_ERROR
!= error_code
) {
218 if (error_message
.empty()) {
219 error_message
= "Parse error " + base::IntToString(error_code
);
221 RecordFailure(DIAG_RECON_PARSE_ERROR
, error_message
);
225 RecordSuccess("File parsed OK");
230 base::FilePath path_
;
231 int64 max_file_size_
;
232 FileImportance importance_
;
233 DISALLOW_COPY_AND_ASSIGN(JSONTest
);
236 // Check that the flavor of the operating system is supported.
237 class OperatingSystemTest
: public DiagnosticsTest
{
239 OperatingSystemTest()
240 : DiagnosticsTest(DIAGNOSTICS_OPERATING_SYSTEM_TEST
) {}
242 virtual bool ExecuteImpl(DiagnosticsModel::Observer
* observer
) OVERRIDE
{
244 base::win::Version version
= base::win::GetVersion();
245 if ((version
< base::win::VERSION_XP
) ||
246 ((version
== base::win::VERSION_XP
) &&
247 (base::win::OSInfo::GetInstance()->service_pack().major
< 2))) {
248 RecordFailure(DIAG_RECON_PRE_WINDOW_XP_SP2
,
249 "Must have Windows XP SP2 or later");
253 // TODO(port): define the OS criteria for Linux and Mac.
254 #endif // defined(OS_WIN)
256 base::StringPrintf("%s %s",
257 base::SysInfo::OperatingSystemName().c_str(),
258 base::SysInfo::OperatingSystemVersion().c_str()));
263 DISALLOW_COPY_AND_ASSIGN(OperatingSystemTest
);
266 struct TestPathInfo
{
267 DiagnosticsTestId test_id
;
275 const TestPathInfo kPathsToTest
[] = {
276 {DIAGNOSTICS_PATH_DICTIONARIES_TEST
, chrome::DIR_APP_DICTIONARIES
, true,
278 {DIAGNOSTICS_PATH_LOCAL_STATE_TEST
, chrome::FILE_LOCAL_STATE
, false, false,
279 true, 500 * kOneKilobyte
},
280 {DIAGNOSTICS_PATH_RESOURCES_TEST
, chrome::FILE_RESOURCES_PACK
, false, false,
282 {DIAGNOSTICS_PATH_USER_DATA_TEST
, chrome::DIR_USER_DATA
, true, false, true,
286 // Check that the user's data directory exists and the paths are writable.
287 // If it is a system-wide install some paths are not expected to be writable.
288 // This test depends on |InstallTypeTest| having run successfully.
289 class PathTest
: public DiagnosticsTest
{
291 explicit PathTest(const TestPathInfo
& path_info
)
292 : DiagnosticsTest(path_info
.test_id
),
293 path_info_(path_info
) {}
295 virtual bool ExecuteImpl(DiagnosticsModel::Observer
* observer
) OVERRIDE
{
296 if (!g_install_type
) {
297 RecordStopFailure(DIAG_RECON_DEPENDENCY
, "Install dependency failure");
300 base::FilePath dir_or_file
;
301 if (!PathService::Get(path_info_
.path_id
, &dir_or_file
)) {
302 RecordStopFailure(DIAG_RECON_PATH_PROVIDER
, "Path provider failure");
305 if (!base::PathExists(dir_or_file
)) {
307 DIAG_RECON_PATH_NOT_FOUND
,
309 base::UTF16ToUTF8(dir_or_file
.LossyDisplayName()));
313 int64 dir_or_file_size
= 0;
314 if (path_info_
.is_directory
) {
315 dir_or_file_size
= base::ComputeDirectorySize(dir_or_file
);
317 base::GetFileSize(dir_or_file
, &dir_or_file_size
);
319 if (!dir_or_file_size
&& !path_info_
.is_optional
) {
320 RecordFailure(DIAG_RECON_CANNOT_OBTAIN_SIZE
,
321 "Cannot obtain size for: " +
322 base::UTF16ToUTF8(dir_or_file
.LossyDisplayName()));
325 std::string printable_size
= base::Int64ToString(dir_or_file_size
);
327 if (path_info_
.max_size
> 0) {
328 if (dir_or_file_size
> path_info_
.max_size
) {
329 RecordFailure(DIAG_RECON_FILE_TOO_LARGE
,
330 "Path contents too large (" + printable_size
+ ") for: " +
331 base::UTF16ToUTF8(dir_or_file
.LossyDisplayName()));
335 if (g_install_type
->system_level() && !path_info_
.test_writable
) {
336 RecordSuccess("Path exists");
339 if (!base::PathIsWritable(dir_or_file
)) {
340 RecordFailure(DIAG_RECON_NOT_WRITABLE
,
341 "Path is not writable: " +
342 base::UTF16ToUTF8(dir_or_file
.LossyDisplayName()));
345 RecordSuccess("Path exists and is writable: " + printable_size
);
350 TestPathInfo path_info_
;
351 DISALLOW_COPY_AND_ASSIGN(PathTest
);
354 // Check the version of Chrome.
355 class VersionTest
: public DiagnosticsTest
{
357 VersionTest() : DiagnosticsTest(DIAGNOSTICS_VERSION_TEST
) {}
359 virtual bool ExecuteImpl(DiagnosticsModel::Observer
* observer
) OVERRIDE
{
360 chrome::VersionInfo version_info
;
361 if (!version_info
.is_valid()) {
362 RecordFailure(DIAG_RECON_NO_VERSION
, "No Version");
365 std::string current_version
= version_info
.Version();
366 if (current_version
.empty()) {
367 RecordFailure(DIAG_RECON_EMPTY_VERSION
, "Empty Version");
370 std::string version_modifier
=
371 chrome::VersionInfo::GetVersionStringModifier();
372 if (!version_modifier
.empty())
373 current_version
+= " " + version_modifier
;
374 #if defined(GOOGLE_CHROME_BUILD)
375 current_version
+= " GCB";
376 #endif // defined(GOOGLE_CHROME_BUILD)
377 RecordSuccess(current_version
);
382 DISALLOW_COPY_AND_ASSIGN(VersionTest
);
387 DiagnosticsTest
* MakeConflictingDllsTest() { return new ConflictingDllsTest(); }
389 DiagnosticsTest
* MakeDiskSpaceTest() { return new DiskSpaceTest(); }
391 DiagnosticsTest
* MakeInstallTypeTest() { return new InstallTypeTest(); }
393 DiagnosticsTest
* MakeBookMarksTest() {
394 base::FilePath path
= DiagnosticsTest::GetUserDefaultProfileDir();
395 path
= path
.Append(chrome::kBookmarksFileName
);
396 return new JSONTest(path
,
397 DIAGNOSTICS_JSON_BOOKMARKS_TEST
,
399 JSONTest::NON_CRITICAL
);
402 DiagnosticsTest
* MakeLocalStateTest() {
404 PathService::Get(chrome::DIR_USER_DATA
, &path
);
405 path
= path
.Append(chrome::kLocalStateFilename
);
406 return new JSONTest(path
,
407 DIAGNOSTICS_JSON_LOCAL_STATE_TEST
,
412 DiagnosticsTest
* MakePreferencesTest() {
413 base::FilePath path
= DiagnosticsTest::GetUserDefaultProfileDir();
414 path
= path
.Append(chrome::kPreferencesFilename
);
415 return new JSONTest(path
,
416 DIAGNOSTICS_JSON_PREFERENCES_TEST
,
422 DiagnosticsTest
* MakeOperatingSystemTest() { return new OperatingSystemTest(); }
424 DiagnosticsTest
* MakeDictonaryDirTest() {
425 return new PathTest(kPathsToTest
[0]);
428 DiagnosticsTest
* MakeLocalStateFileTest() {
429 return new PathTest(kPathsToTest
[1]);
432 DiagnosticsTest
* MakeResourcesFileTest() {
433 return new PathTest(kPathsToTest
[2]);
436 DiagnosticsTest
* MakeUserDirTest() { return new PathTest(kPathsToTest
[3]); }
438 DiagnosticsTest
* MakeVersionTest() { return new VersionTest(); }
440 } // namespace diagnostics