recon_diagnostics.cc revision 21d179b334e59e9a3bfcaed4c4430bef1bc5759d
1// Copyright (c) 2010 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 "chrome/common/json_value_serializer.h"
22
23#if defined(OS_WIN)
24#include "base/win/windows_version.h"
25#include "chrome/installer/util/install_util.h"
26#endif
27
28// Reconnaissance diagnostics. These are the first and most critical
29// diagnostic tests. Here we check for the existence of critical files.
30// TODO(cpu): Define if it makes sense to localize strings.
31
32// TODO(cpu): There are a few maxium file sizes hardcoded in this file
33// that have little or no theoretical or experimental ground. Find a way
34// to justify them.
35
36namespace {
37
38class InstallTypeTest;
39InstallTypeTest* g_install_type = 0;
40
41// Check that the flavor of the operating system is supported.
42class OperatingSystemTest : public DiagnosticTest {
43 public:
44  OperatingSystemTest() : DiagnosticTest(ASCIIToUTF16("Operating System")) {}
45
46  virtual int GetId() { return 0; }
47
48  virtual bool ExecuteImpl(DiagnosticsModel::Observer* observer) {
49    int version = 0;
50    int major = 0;
51    int minor = 0;
52#if defined(OS_WIN)
53    version = base::win::GetVersion();
54    if (version < base::win::VERSION_XP) {
55      RecordFailure(ASCIIToUTF16("Windows 2000 or earlier"));
56      return false;
57    }
58    base::win::GetServicePackLevel(&major, &minor);
59    if ((version == base::win::VERSION_XP) && (major < 2)) {
60      RecordFailure(ASCIIToUTF16("XP Service Pack 1 or earlier"));
61      return false;
62    }
63#else
64    // TODO(port): define the OS criteria for linux and mac.
65#endif  // defined(OS_WIN)
66    RecordSuccess(ASCIIToUTF16(StringPrintf("%s %s (%d [%d:%d])",
67        base::SysInfo::OperatingSystemName().c_str(),
68        base::SysInfo::OperatingSystemVersion().c_str(),
69        version, major, minor)));
70    return true;
71  }
72
73 private:
74  DISALLOW_COPY_AND_ASSIGN(OperatingSystemTest);
75};
76
77// Check if it is system install or per-user install.
78class InstallTypeTest : public DiagnosticTest {
79 public:
80  InstallTypeTest() : DiagnosticTest(ASCIIToUTF16("Install Type")),
81                      user_level_(false) {}
82
83  virtual int GetId() { return 0; }
84
85  virtual bool ExecuteImpl(DiagnosticsModel::Observer* observer) {
86#if defined(OS_WIN)
87    FilePath chrome_exe;
88    if (!PathService::Get(base::FILE_EXE, &chrome_exe)) {
89      RecordFailure(ASCIIToUTF16("Path provider failure"));
90      return false;
91    }
92    user_level_ = InstallUtil::IsPerUserInstall(
93        chrome_exe.ToWStringHack().c_str());
94    const char* type = user_level_ ? "User Level" : "System Level";
95    string16 install_type(ASCIIToUTF16(type));
96#else
97    string16 install_type(ASCIIToUTF16("System Level"));
98#endif  // defined(OS_WIN)
99    RecordSuccess(install_type);
100    g_install_type = this;
101    return true;
102  }
103
104  bool system_level() const { return !user_level_; }
105
106 private:
107  bool user_level_;
108  DISALLOW_COPY_AND_ASSIGN(InstallTypeTest);
109};
110
111// Check the version of Chrome.
112class VersionTest : public DiagnosticTest {
113 public:
114  VersionTest() : DiagnosticTest(ASCIIToUTF16("Browser Version")) {}
115
116  virtual int GetId() { return 0; }
117
118  virtual bool ExecuteImpl(DiagnosticsModel::Observer* observer) {
119    chrome::VersionInfo version_info;
120    if (!version_info.is_valid()) {
121      RecordFailure(ASCIIToUTF16("No Version"));
122      return true;
123    }
124    std::string current_version = version_info.Version();
125    if (current_version.empty()) {
126      RecordFailure(ASCIIToUTF16("Empty Version"));
127      return true;
128    }
129    std::string version_modifier = platform_util::GetVersionStringModifier();
130    if (!version_modifier.empty())
131      current_version += " " + version_modifier;
132#if defined(GOOGLE_CHROME_BUILD)
133    current_version += " GCB";
134#endif  // defined(GOOGLE_CHROME_BUILD)
135    RecordSuccess(ASCIIToUTF16(current_version));
136    return true;
137  }
138
139 private:
140  DISALLOW_COPY_AND_ASSIGN(VersionTest);
141};
142
143struct TestPathInfo {
144  const char* test_name;
145  int  path_id;
146  bool is_directory;
147  bool is_optional;
148  bool test_writable;
149  int64 max_size;
150};
151
152const int64 kOneKilo = 1024;
153const int64 kOneMeg = 1024 * kOneKilo;
154
155const TestPathInfo kPathsToTest[] = {
156  {"User data Directory", chrome::DIR_USER_DATA,
157      true, false, true, 850 * kOneMeg},
158  {"Local state file", chrome::FILE_LOCAL_STATE,
159      false, false, true, 500 * kOneKilo},
160  {"Dictionaries Directory", chrome::DIR_APP_DICTIONARIES,
161      true, true, false, 0},
162  {"Inspector Directory", chrome::DIR_INSPECTOR,
163      true, false, false, 0}
164};
165
166// Check that the user's data directory exists and the paths are writeable.
167// If it is a systemwide install some paths are not expected to be writeable.
168// This test depends on |InstallTypeTest| having run succesfuly.
169class PathTest : public DiagnosticTest {
170 public:
171  explicit PathTest(const TestPathInfo& path_info)
172      : DiagnosticTest(ASCIIToUTF16(path_info.test_name)),
173        path_info_(path_info) {}
174
175  virtual int GetId() { return 0; }
176
177  virtual bool ExecuteImpl(DiagnosticsModel::Observer* observer) {
178    if (!g_install_type) {
179      RecordStopFailure(ASCIIToUTF16("dependency failure"));
180      return false;
181    }
182    FilePath dir_or_file;
183    if (!PathService::Get(path_info_.path_id, &dir_or_file)) {
184      RecordStopFailure(ASCIIToUTF16("Path provider failure"));
185      return false;
186    }
187    if (!file_util::PathExists(dir_or_file)) {
188      RecordFailure(ASCIIToUTF16("Path not found"));
189      return true;
190    }
191
192    int64 dir_or_file_size = 0;
193    if (path_info_.is_directory) {
194      dir_or_file_size = file_util::ComputeDirectorySize(dir_or_file);
195    } else {
196      file_util::GetFileSize(dir_or_file, &dir_or_file_size);
197    }
198    if (!dir_or_file_size && !path_info_.is_optional) {
199      RecordFailure(ASCIIToUTF16("Cannot obtain size"));
200      return true;
201    }
202    DataUnits units = GetByteDisplayUnits(dir_or_file_size);
203    string16 printable_size = FormatBytes(dir_or_file_size, units, true);
204
205    if (path_info_.max_size > 0) {
206      if (dir_or_file_size > path_info_.max_size) {
207        RecordFailure(ASCIIToUTF16("Path is too big: ") + printable_size);
208        return true;
209      }
210    }
211    if (g_install_type->system_level() && !path_info_.test_writable) {
212      RecordSuccess(ASCIIToUTF16("Path exists"));
213      return true;
214    }
215    if (!file_util::PathIsWritable(dir_or_file)) {
216      RecordFailure(ASCIIToUTF16("Path is not writable"));
217      return true;
218    }
219    RecordSuccess(ASCIIToUTF16("Path exists and is writable: ")
220                  + printable_size);
221    return true;
222  }
223
224 private:
225  TestPathInfo path_info_;
226  DISALLOW_COPY_AND_ASSIGN(PathTest);
227};
228
229// Check that the disk space in the volume where the user data dir normally
230// lives is not dangerosly low.
231class DiskSpaceTest : public DiagnosticTest {
232 public:
233  DiskSpaceTest() : DiagnosticTest(ASCIIToUTF16("Disk Space")) {}
234
235  virtual int GetId() { return 0; }
236
237  virtual bool ExecuteImpl(DiagnosticsModel::Observer* observer) {
238    FilePath data_dir;
239    if (!PathService::Get(chrome::DIR_USER_DATA, &data_dir))
240      return false;
241    int64 disk_space = base::SysInfo::AmountOfFreeDiskSpace(data_dir);
242    if (disk_space < 0) {
243      RecordFailure(ASCIIToUTF16("Unable to query free space"));
244      return true;
245    }
246    DataUnits units = GetByteDisplayUnits(disk_space);
247    string16 printable_size = FormatBytes(disk_space, units, true);
248    if (disk_space < 80 * kOneMeg) {
249      RecordFailure(ASCIIToUTF16("Low disk space : ") + printable_size);
250      return true;
251    }
252    RecordSuccess(ASCIIToUTF16("Free space : ") + printable_size);
253    return true;
254  }
255
256 private:
257  DISALLOW_COPY_AND_ASSIGN(DiskSpaceTest);
258};
259
260// Checks that a given json file can be correctly parsed.
261class JSONTest : public DiagnosticTest {
262 public:
263  JSONTest(const FilePath& path, const string16 name, int64 max_file_size)
264      : DiagnosticTest(name), path_(path), max_file_size_(max_file_size) {
265  }
266
267  virtual int GetId() { return 0; }
268
269  virtual bool ExecuteImpl(DiagnosticsModel::Observer* observer) {
270    if (!file_util::PathExists(path_)) {
271      RecordFailure(ASCIIToUTF16("File not found"));
272      return true;
273    }
274    int64 file_size;
275    file_util::GetFileSize(path_, &file_size);
276    if (file_size > max_file_size_) {
277      RecordFailure(ASCIIToUTF16("File too big"));
278      return true;
279    }
280    // Being small enough, we can process it in-memory.
281    std::string json_data;
282    if (!file_util::ReadFileToString(path_, &json_data)) {
283      RecordFailure(ASCIIToUTF16(
284          "Could not open file. Possibly locked by other process"));
285      return true;
286    }
287
288    JSONStringValueSerializer json(json_data);
289    int error_code = base::JSONReader::JSON_NO_ERROR;
290    std::string error_message;
291    scoped_ptr<Value> json_root(json.Deserialize(&error_code, &error_message));
292    if (base::JSONReader::JSON_NO_ERROR != error_code) {
293      if (error_message.empty()) {
294        error_message = "Parse error " + base::IntToString(error_code);
295      }
296      RecordFailure(UTF8ToUTF16(error_message));
297      return true;
298    }
299
300    RecordSuccess(ASCIIToUTF16("File parsed OK"));
301    return true;
302  }
303
304 private:
305  FilePath path_;
306  int64 max_file_size_;
307  DISALLOW_COPY_AND_ASSIGN(JSONTest);
308};
309
310}  // namespace
311
312DiagnosticTest* MakeUserDirTest() {
313  return new PathTest(kPathsToTest[0]);
314}
315
316DiagnosticTest* MakeLocalStateFileTest() {
317  return new PathTest(kPathsToTest[1]);
318}
319
320DiagnosticTest* MakeDictonaryDirTest() {
321  return new PathTest(kPathsToTest[2]);
322}
323
324DiagnosticTest* MakeInspectorDirTest() {
325  return new PathTest(kPathsToTest[3]);
326}
327
328DiagnosticTest* MakeVersionTest() {
329  return new VersionTest();
330}
331
332DiagnosticTest* MakeDiskSpaceTest() {
333  return new DiskSpaceTest();
334}
335
336DiagnosticTest* MakeOperatingSystemTest() {
337  return new OperatingSystemTest();
338}
339
340DiagnosticTest* MakeInstallTypeTest() {
341  return new InstallTypeTest();
342}
343
344DiagnosticTest* MakePreferencesTest() {
345  FilePath path = DiagnosticTest::GetUserDefaultProfileDir();
346  path = path.Append(chrome::kPreferencesFilename);
347  return new JSONTest(path, ASCIIToUTF16("Profile JSON"), 100 * kOneKilo);
348}
349
350DiagnosticTest* MakeBookMarksTest() {
351  FilePath path = DiagnosticTest::GetUserDefaultProfileDir();
352  path = path.Append(chrome::kBookmarksFileName);
353  return new JSONTest(path, ASCIIToUTF16("BookMarks JSON"), 2 * kOneMeg);
354}
355
356DiagnosticTest* MakeLocalStateTest() {
357  FilePath path;
358  PathService::Get(chrome::DIR_USER_DATA, &path);
359  path = path.Append(chrome::kLocalStateFilename);
360  return new JSONTest(path, ASCIIToUTF16("Local State JSON"), 50 * kOneKilo);
361}
362