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/files/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"
22#include "components/bookmarks/common/bookmark_constants.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 hard-coded in this file
35// that have little or no theoretical or experimental ground. Find a way
36// to justify them.
37
38namespace diagnostics {
39
40namespace {
41
42const int64 kOneKilobyte = 1024;
43const int64 kOneMegabyte = 1024 * kOneKilobyte;
44
45class InstallTypeTest;
46InstallTypeTest* g_install_type = 0;
47
48// Check if any conflicting DLLs are loaded.
49class ConflictingDllsTest : public DiagnosticsTest {
50 public:
51  ConflictingDllsTest()
52      : DiagnosticsTest(DIAGNOSTICS_CONFLICTING_DLLS_TEST) {}
53
54  virtual bool ExecuteImpl(DiagnosticsModel::Observer* observer) OVERRIDE {
55#if defined(OS_WIN)
56    EnumerateModulesModel* model = EnumerateModulesModel::GetInstance();
57    model->set_limited_mode(true);
58    model->ScanNow();
59    scoped_ptr<base::ListValue> list(model->GetModuleList());
60    if (!model->confirmed_bad_modules_detected() &&
61        !model->suspected_bad_modules_detected()) {
62      RecordSuccess("No conflicting modules found");
63      return true;
64    }
65
66    std::string failures = "Possibly conflicting modules:";
67    base::DictionaryValue* dictionary;
68    for (size_t i = 0; i < list->GetSize(); ++i) {
69      if (!list->GetDictionary(i, &dictionary))
70        RecordFailure(DIAG_RECON_DICTIONARY_LOOKUP_FAILED,
71                      "Dictionary lookup failed");
72      int status;
73      std::string location;
74      std::string name;
75      if (!dictionary->GetInteger("status", &status))
76        RecordFailure(DIAG_RECON_NO_STATUS_FIELD, "No 'status' field found");
77      if (status < ModuleEnumerator::SUSPECTED_BAD)
78        continue;
79
80      if (!dictionary->GetString("location", &location)) {
81        RecordFailure(DIAG_RECON_NO_LOCATION_FIELD,
82                      "No 'location' field found");
83        return true;
84      }
85      if (!dictionary->GetString("name", &name)) {
86        RecordFailure(DIAG_RECON_NO_NAME_FIELD, "No 'name' field found");
87        return true;
88      }
89
90      failures += "\n" + location + name;
91    }
92    RecordFailure(DIAG_RECON_CONFLICTING_MODULES, failures);
93    return true;
94#else
95    RecordFailure(DIAG_RECON_NOT_IMPLEMENTED, "Not implemented");
96    return true;
97#endif  // defined(OS_WIN)
98  }
99
100 private:
101  DISALLOW_COPY_AND_ASSIGN(ConflictingDllsTest);
102};
103
104// Check that the disk space in the volume where the user data directory
105// normally lives is not dangerously low.
106class DiskSpaceTest : public DiagnosticsTest {
107 public:
108  DiskSpaceTest() : DiagnosticsTest(DIAGNOSTICS_DISK_SPACE_TEST) {}
109
110  virtual bool ExecuteImpl(DiagnosticsModel::Observer* observer) OVERRIDE {
111    base::FilePath data_dir;
112    if (!PathService::Get(chrome::DIR_USER_DATA, &data_dir))
113      return false;
114    int64 disk_space = base::SysInfo::AmountOfFreeDiskSpace(data_dir);
115    if (disk_space < 0) {
116      RecordFailure(DIAG_RECON_UNABLE_TO_QUERY, "Unable to query free space");
117      return true;
118    }
119    std::string printable_size = base::Int64ToString(disk_space);
120    if (disk_space < 80 * kOneMegabyte) {
121      RecordFailure(DIAG_RECON_LOW_DISK_SPACE,
122                    "Low disk space: " + printable_size);
123      return true;
124    }
125    RecordSuccess("Free space: " + printable_size);
126    return true;
127  }
128
129 private:
130  DISALLOW_COPY_AND_ASSIGN(DiskSpaceTest);
131};
132
133// Check if it is system install or per-user install.
134class InstallTypeTest : public DiagnosticsTest {
135 public:
136  InstallTypeTest()
137      : DiagnosticsTest(DIAGNOSTICS_INSTALL_TYPE_TEST), user_level_(false) {}
138
139  virtual bool ExecuteImpl(DiagnosticsModel::Observer* observer) OVERRIDE {
140#if defined(OS_WIN)
141    base::FilePath chrome_exe;
142    if (!PathService::Get(base::FILE_EXE, &chrome_exe)) {
143      RecordFailure(DIAG_RECON_INSTALL_PATH_PROVIDER, "Path provider failure");
144      return false;
145    }
146    user_level_ = InstallUtil::IsPerUserInstall(chrome_exe.value().c_str());
147    const char* type = user_level_ ? "User Level" : "System Level";
148    std::string install_type(type);
149#else
150    std::string install_type("System Level");
151#endif  // defined(OS_WIN)
152    RecordSuccess(install_type);
153    g_install_type = this;
154    return true;
155  }
156
157  bool system_level() const { return !user_level_; }
158
159 private:
160  bool user_level_;
161  DISALLOW_COPY_AND_ASSIGN(InstallTypeTest);
162};
163
164// Checks that a given JSON file can be correctly parsed.
165class JSONTest : public DiagnosticsTest {
166 public:
167  enum FileImportance {
168    NON_CRITICAL,
169    CRITICAL
170  };
171
172  JSONTest(const base::FilePath& path,
173           DiagnosticsTestId id,
174           int64 max_file_size,
175           FileImportance importance)
176      : DiagnosticsTest(id),
177        path_(path),
178        max_file_size_(max_file_size),
179        importance_(importance) {}
180
181  virtual bool ExecuteImpl(DiagnosticsModel::Observer* observer) OVERRIDE {
182    if (!base::PathExists(path_)) {
183      if (importance_ == CRITICAL) {
184        RecordOutcome(DIAG_RECON_FILE_NOT_FOUND,
185                      "File not found",
186                      DiagnosticsModel::TEST_FAIL_CONTINUE);
187      } else {
188        RecordOutcome(DIAG_RECON_FILE_NOT_FOUND_OK,
189                      "File not found (but that is OK)",
190                      DiagnosticsModel::TEST_OK);
191      }
192      return true;
193    }
194    int64 file_size;
195    if (!base::GetFileSize(path_, &file_size)) {
196      RecordFailure(DIAG_RECON_CANNOT_OBTAIN_FILE_SIZE,
197                    "Cannot obtain file size");
198      return true;
199    }
200
201    if (file_size > max_file_size_) {
202      RecordFailure(DIAG_RECON_FILE_TOO_BIG, "File too big");
203      return true;
204    }
205    // Being small enough, we can process it in-memory.
206    std::string json_data;
207    if (!base::ReadFileToString(path_, &json_data)) {
208      RecordFailure(DIAG_RECON_UNABLE_TO_OPEN_FILE,
209                    "Could not open file. Possibly locked by another process");
210      return true;
211    }
212
213    JSONStringValueSerializer json(json_data);
214    int error_code = base::JSONReader::JSON_NO_ERROR;
215    std::string error_message;
216    scoped_ptr<base::Value> json_root(
217        json.Deserialize(&error_code, &error_message));
218    if (base::JSONReader::JSON_NO_ERROR != error_code) {
219      if (error_message.empty()) {
220        error_message = "Parse error " + base::IntToString(error_code);
221      }
222      RecordFailure(DIAG_RECON_PARSE_ERROR, error_message);
223      return true;
224    }
225
226    RecordSuccess("File parsed OK");
227    return true;
228  }
229
230 private:
231  base::FilePath path_;
232  int64 max_file_size_;
233  FileImportance importance_;
234  DISALLOW_COPY_AND_ASSIGN(JSONTest);
235};
236
237// Check that the flavor of the operating system is supported.
238class OperatingSystemTest : public DiagnosticsTest {
239 public:
240  OperatingSystemTest()
241      : DiagnosticsTest(DIAGNOSTICS_OPERATING_SYSTEM_TEST) {}
242
243  virtual bool ExecuteImpl(DiagnosticsModel::Observer* observer) OVERRIDE {
244#if defined(OS_WIN)
245    base::win::Version version = base::win::GetVersion();
246    if ((version < base::win::VERSION_XP) ||
247        ((version == base::win::VERSION_XP) &&
248         (base::win::OSInfo::GetInstance()->service_pack().major < 2))) {
249      RecordFailure(DIAG_RECON_PRE_WINDOW_XP_SP2,
250                    "Must have Windows XP SP2 or later");
251      return false;
252    }
253#else
254// TODO(port): define the OS criteria for Linux and Mac.
255#endif  // defined(OS_WIN)
256    RecordSuccess(
257        base::StringPrintf("%s %s",
258                           base::SysInfo::OperatingSystemName().c_str(),
259                           base::SysInfo::OperatingSystemVersion().c_str()));
260    return true;
261  }
262
263 private:
264  DISALLOW_COPY_AND_ASSIGN(OperatingSystemTest);
265};
266
267struct TestPathInfo {
268  DiagnosticsTestId test_id;
269  int path_id;
270  bool is_directory;
271  bool is_optional;
272  bool test_writable;
273  int64 max_size;
274};
275
276const TestPathInfo kPathsToTest[] = {
277    {DIAGNOSTICS_PATH_DICTIONARIES_TEST, chrome::DIR_APP_DICTIONARIES, true,
278     true, false, 0},
279    {DIAGNOSTICS_PATH_LOCAL_STATE_TEST, chrome::FILE_LOCAL_STATE, false, false,
280     true, 500 * kOneKilobyte},
281    {DIAGNOSTICS_PATH_RESOURCES_TEST, chrome::FILE_RESOURCES_PACK, false, false,
282     false, 0},
283    {DIAGNOSTICS_PATH_USER_DATA_TEST, chrome::DIR_USER_DATA, true, false, true,
284     850 * kOneMegabyte},
285};
286
287// Check that the user's data directory exists and the paths are writable.
288// If it is a system-wide install some paths are not expected to be writable.
289// This test depends on |InstallTypeTest| having run successfully.
290class PathTest : public DiagnosticsTest {
291 public:
292  explicit PathTest(const TestPathInfo& path_info)
293      : DiagnosticsTest(path_info.test_id),
294        path_info_(path_info) {}
295
296  virtual bool ExecuteImpl(DiagnosticsModel::Observer* observer) OVERRIDE {
297    if (!g_install_type) {
298      RecordStopFailure(DIAG_RECON_DEPENDENCY, "Install dependency failure");
299      return false;
300    }
301    base::FilePath dir_or_file;
302    if (!PathService::Get(path_info_.path_id, &dir_or_file)) {
303      RecordStopFailure(DIAG_RECON_PATH_PROVIDER, "Path provider failure");
304      return false;
305    }
306    if (!base::PathExists(dir_or_file)) {
307      RecordFailure(
308          DIAG_RECON_PATH_NOT_FOUND,
309          "Path not found: " +
310              base::UTF16ToUTF8(dir_or_file.LossyDisplayName()));
311      return true;
312    }
313
314    int64 dir_or_file_size = 0;
315    if (path_info_.is_directory) {
316      dir_or_file_size = base::ComputeDirectorySize(dir_or_file);
317    } else {
318      base::GetFileSize(dir_or_file, &dir_or_file_size);
319    }
320    if (!dir_or_file_size && !path_info_.is_optional) {
321      RecordFailure(DIAG_RECON_CANNOT_OBTAIN_SIZE,
322                    "Cannot obtain size for: " +
323                        base::UTF16ToUTF8(dir_or_file.LossyDisplayName()));
324      return true;
325    }
326    std::string printable_size = base::Int64ToString(dir_or_file_size);
327
328    if (path_info_.max_size > 0) {
329      if (dir_or_file_size > path_info_.max_size) {
330        RecordFailure(DIAG_RECON_FILE_TOO_LARGE,
331                      "Path contents too large (" + printable_size + ") for: " +
332                          base::UTF16ToUTF8(dir_or_file.LossyDisplayName()));
333        return true;
334      }
335    }
336    if (g_install_type->system_level() && !path_info_.test_writable) {
337      RecordSuccess("Path exists");
338      return true;
339    }
340    if (!base::PathIsWritable(dir_or_file)) {
341      RecordFailure(DIAG_RECON_NOT_WRITABLE,
342                    "Path is not writable: " +
343                        base::UTF16ToUTF8(dir_or_file.LossyDisplayName()));
344      return true;
345    }
346    RecordSuccess("Path exists and is writable: " + printable_size);
347    return true;
348  }
349
350 private:
351  TestPathInfo path_info_;
352  DISALLOW_COPY_AND_ASSIGN(PathTest);
353};
354
355// Check the version of Chrome.
356class VersionTest : public DiagnosticsTest {
357 public:
358  VersionTest() : DiagnosticsTest(DIAGNOSTICS_VERSION_TEST) {}
359
360  virtual bool ExecuteImpl(DiagnosticsModel::Observer* observer) OVERRIDE {
361    chrome::VersionInfo version_info;
362    if (!version_info.is_valid()) {
363      RecordFailure(DIAG_RECON_NO_VERSION, "No Version");
364      return true;
365    }
366    std::string current_version = version_info.Version();
367    if (current_version.empty()) {
368      RecordFailure(DIAG_RECON_EMPTY_VERSION, "Empty Version");
369      return true;
370    }
371    std::string version_modifier =
372        chrome::VersionInfo::GetVersionStringModifier();
373    if (!version_modifier.empty())
374      current_version += " " + version_modifier;
375#if defined(GOOGLE_CHROME_BUILD)
376    current_version += " GCB";
377#endif  // defined(GOOGLE_CHROME_BUILD)
378    RecordSuccess(current_version);
379    return true;
380  }
381
382 private:
383  DISALLOW_COPY_AND_ASSIGN(VersionTest);
384};
385
386}  // namespace
387
388DiagnosticsTest* MakeConflictingDllsTest() { return new ConflictingDllsTest(); }
389
390DiagnosticsTest* MakeDiskSpaceTest() { return new DiskSpaceTest(); }
391
392DiagnosticsTest* MakeInstallTypeTest() { return new InstallTypeTest(); }
393
394DiagnosticsTest* MakeBookMarksTest() {
395  base::FilePath path = DiagnosticsTest::GetUserDefaultProfileDir();
396  path = path.Append(bookmarks::kBookmarksFileName);
397  return new JSONTest(path,
398                      DIAGNOSTICS_JSON_BOOKMARKS_TEST,
399                      2 * kOneMegabyte,
400                      JSONTest::NON_CRITICAL);
401}
402
403DiagnosticsTest* MakeLocalStateTest() {
404  base::FilePath path;
405  PathService::Get(chrome::DIR_USER_DATA, &path);
406  path = path.Append(chrome::kLocalStateFilename);
407  return new JSONTest(path,
408                      DIAGNOSTICS_JSON_LOCAL_STATE_TEST,
409                      50 * kOneKilobyte,
410                      JSONTest::CRITICAL);
411}
412
413DiagnosticsTest* MakePreferencesTest() {
414  base::FilePath path = DiagnosticsTest::GetUserDefaultProfileDir();
415  path = path.Append(chrome::kPreferencesFilename);
416  return new JSONTest(path,
417                      DIAGNOSTICS_JSON_PREFERENCES_TEST,
418                      100 * kOneKilobyte,
419                      JSONTest::CRITICAL);
420}
421
422
423DiagnosticsTest* MakeOperatingSystemTest() { return new OperatingSystemTest(); }
424
425DiagnosticsTest* MakeDictonaryDirTest() {
426  return new PathTest(kPathsToTest[0]);
427}
428
429DiagnosticsTest* MakeLocalStateFileTest() {
430  return new PathTest(kPathsToTest[1]);
431}
432
433DiagnosticsTest* MakeResourcesFileTest() {
434  return new PathTest(kPathsToTest[2]);
435}
436
437DiagnosticsTest* MakeUserDirTest() { return new PathTest(kPathsToTest[3]); }
438
439DiagnosticsTest* MakeVersionTest() { return new VersionTest(); }
440
441}  // namespace diagnostics
442