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:
OperatingSystemTest()45 OperatingSystemTest() : DiagnosticTest(ASCIIToUTF16("Operating System")) {}
46
GetId()47 virtual int GetId() { return 0; }
48
ExecuteImpl(DiagnosticsModel::Observer * observer)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:
ConflictingDllsTest()74 ConflictingDllsTest() : DiagnosticTest(ASCIIToUTF16("Conflicting modules")) {}
75
GetId()76 virtual int GetId() { return 0; }
77
ExecuteImpl(DiagnosticsModel::Observer * observer)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:
InstallTypeTest()129 InstallTypeTest() : DiagnosticTest(ASCIIToUTF16("Install Type")),
130 user_level_(false) {}
131
GetId()132 virtual int GetId() { return 0; }
133
ExecuteImpl(DiagnosticsModel::Observer * observer)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
system_level() const152 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:
VersionTest()162 VersionTest() : DiagnosticTest(ASCIIToUTF16("Browser Version")) {}
163
GetId()164 virtual int GetId() { return 0; }
165
ExecuteImpl(DiagnosticsModel::Observer * observer)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:
PathTest(const TestPathInfo & path_info)219 explicit PathTest(const TestPathInfo& path_info)
220 : DiagnosticTest(ASCIIToUTF16(path_info.test_name)),
221 path_info_(path_info) {}
222
GetId()223 virtual int GetId() { return 0; }
224
ExecuteImpl(DiagnosticsModel::Observer * observer)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:
DiskSpaceTest()287 DiskSpaceTest() : DiagnosticTest(ASCIIToUTF16("Disk Space")) {}
288
GetId()289 virtual int GetId() { return 0; }
290
ExecuteImpl(DiagnosticsModel::Observer * observer)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:
JSONTest(const FilePath & path,const string16 & name,int64 max_file_size)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
GetId()321 virtual int GetId() { return 0; }
322
ExecuteImpl(DiagnosticsModel::Observer * observer)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
MakeUserDirTest()370 DiagnosticTest* MakeUserDirTest() {
371 return new PathTest(kPathsToTest[0]);
372 }
373
MakeLocalStateFileTest()374 DiagnosticTest* MakeLocalStateFileTest() {
375 return new PathTest(kPathsToTest[1]);
376 }
377
MakeDictonaryDirTest()378 DiagnosticTest* MakeDictonaryDirTest() {
379 return new PathTest(kPathsToTest[2]);
380 }
381
MakeResourcesFileTest()382 DiagnosticTest* MakeResourcesFileTest() {
383 return new PathTest(kPathsToTest[3]);
384 }
385
MakeVersionTest()386 DiagnosticTest* MakeVersionTest() {
387 return new VersionTest();
388 }
389
MakeDiskSpaceTest()390 DiagnosticTest* MakeDiskSpaceTest() {
391 return new DiskSpaceTest();
392 }
393
MakeOperatingSystemTest()394 DiagnosticTest* MakeOperatingSystemTest() {
395 return new OperatingSystemTest();
396 }
397
MakeConflictingDllsTest()398 DiagnosticTest* MakeConflictingDllsTest() {
399 return new ConflictingDllsTest();
400 }
401
MakeInstallTypeTest()402 DiagnosticTest* MakeInstallTypeTest() {
403 return new InstallTypeTest();
404 }
405
MakePreferencesTest()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
MakeBookMarksTest()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
MakeLocalStateTest()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