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