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
37namespace {
38
39class InstallTypeTest;
40InstallTypeTest* g_install_type = 0;
41
42// Check that the flavor of the operating system is supported.
43class OperatingSystemTest : public DiagnosticTest {
44 public:
45  OperatingSystemTest() : DiagnosticTest(ASCIIToUTF16("Operating System")) {}
46
47  virtual int GetId() { return 0; }
48
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.
72class ConflictingDllsTest : public DiagnosticTest {
73 public:
74  ConflictingDllsTest() : DiagnosticTest(ASCIIToUTF16("Conflicting modules")) {}
75
76  virtual int GetId() { return 0; }
77
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.
127class InstallTypeTest : public DiagnosticTest {
128 public:
129  InstallTypeTest() : DiagnosticTest(ASCIIToUTF16("Install Type")),
130                      user_level_(false) {}
131
132  virtual int GetId() { return 0; }
133
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
152  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.
160class VersionTest : public DiagnosticTest {
161 public:
162  VersionTest() : DiagnosticTest(ASCIIToUTF16("Browser Version")) {}
163
164  virtual int GetId() { return 0; }
165
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
191struct 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
200const int64 kOneKilo = 1024;
201const int64 kOneMeg = 1024 * kOneKilo;
202
203const 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.
217class PathTest : public DiagnosticTest {
218 public:
219  explicit PathTest(const TestPathInfo& path_info)
220      : DiagnosticTest(ASCIIToUTF16(path_info.test_name)),
221        path_info_(path_info) {}
222
223  virtual int GetId() { return 0; }
224
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.
285class DiskSpaceTest : public DiagnosticTest {
286 public:
287  DiskSpaceTest() : DiagnosticTest(ASCIIToUTF16("Disk Space")) {}
288
289  virtual int GetId() { return 0; }
290
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.
315class JSONTest : public DiagnosticTest {
316 public:
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
321  virtual int GetId() { return 0; }
322
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
370DiagnosticTest* MakeUserDirTest() {
371  return new PathTest(kPathsToTest[0]);
372}
373
374DiagnosticTest* MakeLocalStateFileTest() {
375  return new PathTest(kPathsToTest[1]);
376}
377
378DiagnosticTest* MakeDictonaryDirTest() {
379  return new PathTest(kPathsToTest[2]);
380}
381
382DiagnosticTest* MakeResourcesFileTest() {
383  return new PathTest(kPathsToTest[3]);
384}
385
386DiagnosticTest* MakeVersionTest() {
387  return new VersionTest();
388}
389
390DiagnosticTest* MakeDiskSpaceTest() {
391  return new DiskSpaceTest();
392}
393
394DiagnosticTest* MakeOperatingSystemTest() {
395  return new OperatingSystemTest();
396}
397
398DiagnosticTest* MakeConflictingDllsTest() {
399  return new ConflictingDllsTest();
400}
401
402DiagnosticTest* MakeInstallTypeTest() {
403  return new InstallTypeTest();
404}
405
406DiagnosticTest* MakePreferencesTest() {
407  FilePath path = DiagnosticTest::GetUserDefaultProfileDir();
408  path = path.Append(chrome::kPreferencesFilename);
409  return new JSONTest(path, ASCIIToUTF16("Profile JSON"), 100 * kOneKilo);
410}
411
412DiagnosticTest* MakeBookMarksTest() {
413  FilePath path = DiagnosticTest::GetUserDefaultProfileDir();
414  path = path.Append(chrome::kBookmarksFileName);
415  return new JSONTest(path, ASCIIToUTF16("BookMarks JSON"), 2 * kOneMeg);
416}
417
418DiagnosticTest* 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