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/chromeos/system_logs/debug_daemon_log_source.h"
6 
7 #include <stddef.h>
8 
9 #include <utility>
10 
11 #include "base/bind.h"
12 #include "base/callback_helpers.h"
13 #include "base/files/file_util.h"
14 #include "base/logging.h"
15 #include "base/memory/weak_ptr.h"
16 #include "base/no_destructor.h"
17 #include "base/stl_util.h"
18 #include "base/strings/string_number_conversions.h"
19 #include "base/strings/string_util.h"
20 #include "base/task/post_task.h"
21 #include "base/task/thread_pool.h"
22 #include "chrome/browser/chromeos/profiles/profile_helper.h"
23 #include "chrome/common/chrome_switches.h"
24 #include "chromeos/cryptohome/cryptohome_parameters.h"
25 #include "chromeos/dbus/dbus_thread_manager.h"
26 #include "chromeos/dbus/debug_daemon/debug_daemon_client.h"
27 #include "components/feedback/feedback_util.h"
28 #include "components/user_manager/user.h"
29 #include "components/user_manager/user_manager.h"
30 #include "content/public/browser/browser_thread.h"
31 
32 namespace system_logs {
33 
34 namespace {
35 
36 constexpr char kNotAvailable[] = "<not available>";
37 constexpr char kRoutesKeyName[] = "routes";
38 constexpr char kLogTruncated[] = "<earlier logs truncated>\n";
39 
40 // List of user log files that Chrome reads directly as these logs are generated
41 // by Chrome itself.
42 constexpr struct UserLogs {
43   // A string key used as a title for this log in feedback reports.
44   const char* log_key;
45 
46   // The log file's path relative to the user's profile directory.
47   const char* log_file_relative_path;
48 } kUserLogs[] = {
49     {"chrome_user_log", "log/chrome"},
50     {"chrome_user_log.PREVIOUS", "log/chrome.PREVIOUS"},
51     {"libassistant_user_log", "log/libassistant.log"},
52     {"login-times", "login-times"},
53     {"logout-times", "logout-times"},
54 };
55 
56 // List of debugd entries to exclude from the results.
57 constexpr std::array<const char*, 2> kExcludeList = {
58     // Shill device and service properties are retrieved by ShillLogSource.
59     // TODO(https://crbug.com/967800): Modify debugd to omit these for
60     // feedback report gathering and remove these entries.
61     "network-devices",
62     "network-services",
63 };
64 
65 // Buffer size for user logs in bytes. Given that maximum feedback report size
66 // is ~7M and that majority of log files are under 1M, we set a per-file limit
67 // of 1MiB.
68 const int64_t kMaxLogSize = 1024 * 1024;
69 
70 }  // namespace
71 
72 // Reads the contents of the user log files listed in |kUserLogs| and adds them
73 // to the |response| parameter.
ReadUserLogFiles(const std::vector<base::FilePath> & profile_dirs,SystemLogsResponse * response)74 void ReadUserLogFiles(const std::vector<base::FilePath>& profile_dirs,
75                       SystemLogsResponse* response) {
76   for (size_t i = 0; i < profile_dirs.size(); ++i) {
77     std::string profile_prefix = "Profile[" + base::NumberToString(i) + "] ";
78     for (const auto& log : kUserLogs) {
79       std::string value;
80       const bool read_success = feedback_util::ReadEndOfFile(
81           profile_dirs[i].Append(log.log_file_relative_path), kMaxLogSize,
82           &value);
83 
84       if (read_success && value.length() == kMaxLogSize) {
85         value.replace(0, strlen(kLogTruncated), kLogTruncated);
86 
87         LOG(WARNING) << "Large log file was likely truncated: "
88                      << log.log_file_relative_path;
89       }
90 
91       response->emplace(
92           profile_prefix + log.log_key,
93           (read_success && !value.empty()) ? std::move(value) : kNotAvailable);
94     }
95   }
96 }
97 
DebugDaemonLogSource(bool scrub)98 DebugDaemonLogSource::DebugDaemonLogSource(bool scrub)
99     : SystemLogsSource("DebugDemon"),
100       response_(new SystemLogsResponse()),
101       num_pending_requests_(0),
102       scrub_(scrub) {}
103 
~DebugDaemonLogSource()104 DebugDaemonLogSource::~DebugDaemonLogSource() {}
105 
Fetch(SysLogsSourceCallback callback)106 void DebugDaemonLogSource::Fetch(SysLogsSourceCallback callback) {
107   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
108   DCHECK(!callback.is_null());
109   DCHECK(callback_.is_null());
110 
111   callback_ = std::move(callback);
112   chromeos::DebugDaemonClient* client =
113       chromeos::DBusThreadManager::Get()->GetDebugDaemonClient();
114 
115   client->GetRoutes(true,   // Numeric
116                     false,  // No IPv6
117                     base::BindOnce(&DebugDaemonLogSource::OnGetRoutes,
118                                    weak_ptr_factory_.GetWeakPtr()));
119   ++num_pending_requests_;
120 
121   if (scrub_) {
122     const user_manager::User* user =
123         user_manager::UserManager::Get()->GetActiveUser();
124     client->GetScrubbedBigLogs(
125         cryptohome::CreateAccountIdentifierFromAccountId(
126             user ? user->GetAccountId() : EmptyAccountId()),
127         base::BindOnce(&DebugDaemonLogSource::OnGetLogs,
128                        weak_ptr_factory_.GetWeakPtr()));
129   } else {
130     client->GetAllLogs(base::BindOnce(&DebugDaemonLogSource::OnGetLogs,
131                                       weak_ptr_factory_.GetWeakPtr()));
132   }
133   ++num_pending_requests_;
134 }
135 
OnGetRoutes(base::Optional<std::vector<std::string>> routes)136 void DebugDaemonLogSource::OnGetRoutes(
137     base::Optional<std::vector<std::string>> routes) {
138   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
139 
140   (*response_)[kRoutesKeyName] = routes.has_value()
141                                      ? base::JoinString(routes.value(), "\n")
142                                      : kNotAvailable;
143   RequestCompleted();
144 }
145 
OnGetOneLog(std::string key,base::Optional<std::string> status)146 void DebugDaemonLogSource::OnGetOneLog(std::string key,
147                                        base::Optional<std::string> status) {
148   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
149 
150   (*response_)[std::move(key)] = std::move(status).value_or(kNotAvailable);
151   RequestCompleted();
152 }
153 
OnGetLogs(bool,const KeyValueMap & logs)154 void DebugDaemonLogSource::OnGetLogs(bool /* succeeded */,
155                                      const KeyValueMap& logs) {
156   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
157 
158   // We ignore 'succeeded' for this callback - we want to display as much of the
159   // debug info as we can even if we failed partway through parsing, and if we
160   // couldn't fetch any of it, none of the fields will even appear.
161   for (const auto& log : logs) {
162     if (base::Contains(kExcludeList, log.first))
163       continue;
164     response_->insert(log);
165   }
166   RequestCompleted();
167 }
168 
GetLoggedInUsersLogFiles()169 void DebugDaemonLogSource::GetLoggedInUsersLogFiles() {
170   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
171 
172   // List all logged-in users' profile directories.
173   std::vector<base::FilePath> profile_dirs;
174   const user_manager::UserList& users =
175       user_manager::UserManager::Get()->GetLoggedInUsers();
176   for (const auto* user : users) {
177     if (user->username_hash().empty())
178       continue;
179 
180     profile_dirs.emplace_back(
181         chromeos::ProfileHelper::GetProfilePathByUserIdHash(
182             user->username_hash()));
183   }
184 
185   auto response = std::make_unique<SystemLogsResponse>();
186   SystemLogsResponse* response_ptr = response.get();
187   base::ThreadPool::PostTaskAndReply(
188       FROM_HERE, {base::MayBlock(), base::TaskPriority::BEST_EFFORT},
189       base::BindOnce(&ReadUserLogFiles, profile_dirs, response_ptr),
190       base::BindOnce(&DebugDaemonLogSource::MergeUserLogFilesResponse,
191                      weak_ptr_factory_.GetWeakPtr(), std::move(response)));
192 }
193 
MergeUserLogFilesResponse(std::unique_ptr<SystemLogsResponse> response)194 void DebugDaemonLogSource::MergeUserLogFilesResponse(
195     std::unique_ptr<SystemLogsResponse> response) {
196   for (auto& pair : *response)
197     response_->emplace(pair.first, std::move(pair.second));
198 
199   auto response_to_return = std::make_unique<SystemLogsResponse>();
200   std::swap(response_to_return, response_);
201   DCHECK(!callback_.is_null());
202   std::move(callback_).Run(std::move(response_to_return));
203 }
204 
RequestCompleted()205 void DebugDaemonLogSource::RequestCompleted() {
206   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
207   DCHECK(!callback_.is_null());
208 
209   --num_pending_requests_;
210   if (num_pending_requests_ > 0)
211     return;
212 
213   // When all other logs are collected, fetch the user logs, because any errors
214   // fetching the other logs is reported in the user logs.
215   GetLoggedInUsersLogFiles();
216 }
217 
218 }  // namespace system_logs
219