1 // Copyright 2016 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 "chromecast/crash/linux/minidump_uploader.h"
6
7 #include <errno.h>
8 #include <regex.h>
9 #include <stdlib.h>
10 #include <string.h>
11
12 #include <fstream>
13 #include <list>
14 #include <memory>
15 #include <sstream>
16 #include <vector>
17
18 #include "base/bind.h"
19 #include "base/files/file_util.h"
20 #include "base/logging.h"
21 #include "base/path_service.h"
22 #include "base/single_thread_task_runner.h"
23 #include "base/strings/string_split.h"
24 #include "base/threading/thread_task_runner_handle.h"
25 #include "chromecast/base/cast_paths.h"
26 #include "chromecast/base/pref_names.h"
27 #include "chromecast/base/version.h"
28 #include "chromecast/crash/build_info.h"
29 #include "chromecast/crash/cast_crashdump_uploader.h"
30 #include "chromecast/crash/linux/dump_info.h"
31 #include "chromecast/public/cast_sys_info.h"
32 #include "components/metrics/metrics_pref_names.h"
33 #include "components/prefs/pref_registry_simple.h"
34 #include "components/prefs/pref_service.h"
35 #include "components/prefs/pref_service_factory.h"
36
37 namespace chromecast {
38
39 namespace {
40
41 const char kProductName[] = "Eureka";
42
43 const char kCrashServerProduction[] = "https://clients2.google.com/cr/report";
44
45 const char kVirtualChannel[] = "virtual-channel";
46
47 const char kLatestUiVersion[] = "latest-ui-version";
48
49 typedef std::vector<std::unique_ptr<DumpInfo>> DumpList;
50
CreatePrefService()51 std::unique_ptr<PrefService> CreatePrefService() {
52 base::FilePath prefs_path;
53 CHECK(base::PathService::Get(chromecast::FILE_CAST_CONFIG, &prefs_path));
54 DVLOG(1) << "Loading prefs from " << prefs_path.value();
55
56 PrefRegistrySimple* registry = new PrefRegistrySimple;
57 registry->RegisterBooleanPref(prefs::kOptInStats, true);
58 registry->RegisterStringPref(::metrics::prefs::kMetricsClientID, "");
59 registry->RegisterStringPref(kVirtualChannel, "");
60 registry->RegisterForeignPref(kLatestUiVersion);
61
62 PrefServiceFactory prefServiceFactory;
63 prefServiceFactory.SetUserPrefsFile(
64 prefs_path, base::ThreadTaskRunnerHandle::Get().get());
65 return prefServiceFactory.Create(registry);
66 }
67
IsDumpObsolete(const DumpInfo & dump)68 bool IsDumpObsolete(const DumpInfo& dump) {
69 return dump.params().cast_release_version.empty() ||
70 dump.params().cast_build_number.empty();
71 }
72
73 } // namespace
74
MinidumpUploader(CastSysInfo * sys_info,const std::string & server_url,CastCrashdumpUploader * const uploader,PrefServiceGeneratorCallback callback)75 MinidumpUploader::MinidumpUploader(CastSysInfo* sys_info,
76 const std::string& server_url,
77 CastCrashdumpUploader* const uploader,
78 PrefServiceGeneratorCallback callback)
79 : release_channel_(sys_info->GetSystemReleaseChannel()),
80 product_name_(sys_info->GetProductName()),
81 device_model_(sys_info->GetDeviceModel()),
82 board_name_(sys_info->GetBoardName()),
83 board_revision_(sys_info->GetBoardRevision()),
84 manufacturer_(sys_info->GetManufacturer()),
85 system_version_(sys_info->GetSystemBuildNumber()),
86 upload_location_(!server_url.empty() ? server_url
87 : kCrashServerProduction),
88 reboot_scheduled_(false),
89 filestate_initialized_(false),
90 uploader_(uploader),
91 pref_service_generator_(std::move(callback)) {}
92
MinidumpUploader(CastSysInfo * sys_info,const std::string & server_url)93 MinidumpUploader::MinidumpUploader(CastSysInfo* sys_info,
94 const std::string& server_url)
95 : MinidumpUploader(sys_info,
96 server_url,
97 nullptr,
98 base::BindRepeating(&CreatePrefService)) {}
99
~MinidumpUploader()100 MinidumpUploader::~MinidumpUploader() {}
101
UploadAllMinidumps()102 bool MinidumpUploader::UploadAllMinidumps() {
103 // Create the lockfile if it doesn't exist.
104 if (!filestate_initialized_)
105 filestate_initialized_ = InitializeFileState();
106
107 if (HasDumps())
108 return AcquireLockAndDoWork();
109
110 return true;
111 }
112
DoWork()113 bool MinidumpUploader::DoWork() {
114 // Read the file stream line by line into a list. As each file is uploaded,
115 // it is subsequently deleted from the list. If the file cannot be found
116 // (which is a possible scenario if the file uploaded previously, but the
117 // device powered off before the log could be updated), it is also deleted.
118 // Whenever an upload fails (due to lost connection), the remaining entries
119 // on the list will overwrite the log file. This way, the log file reflects
120 // the state of the un-uploaded dumps as best as it can.
121 // Note: it is also possible that a dump previously uploaded exists in the
122 // list, *and* can also be found. This might happen if the device powered
123 // off before the dump can be deleted and the log updated. This is
124 // unpreventable.
125
126 DumpList dumps(GetDumps());
127
128 int num_uploaded = 0;
129
130 std::unique_ptr<PrefService> pref_service = pref_service_generator_.Run();
131 const std::string& client_id(
132 pref_service->GetString(::metrics::prefs::kMetricsClientID));
133 std::string virtual_channel(pref_service->GetString(kVirtualChannel));
134 if (virtual_channel.empty()) {
135 virtual_channel = release_channel_;
136 }
137 bool opt_in_stats = pref_service->GetBoolean(prefs::kOptInStats);
138 // Handle each dump and consume it out of the structure.
139 while (dumps.size()) {
140 const DumpInfo& dump = *(dumps.front());
141 const base::FilePath dump_path(dump.crashed_process_dump());
142 base::FilePath log_path(dump.logfile());
143
144 bool ignore_and_erase_dump = false;
145 if (!opt_in_stats) {
146 LOG(INFO) << "OptInStats is false, removing crash dump";
147 ignore_and_erase_dump = true;
148 } else if (IsDumpObsolete(dump)) {
149 NOTREACHED();
150 LOG(INFO) << "DumpInfo belongs to older version, removing crash dump";
151 ignore_and_erase_dump = true;
152 }
153
154 // Ratelimiting persists across reboots.
155 if (reboot_scheduled_) {
156 LOG(INFO) << "Already rate limited with a reboot scheduled, removing "
157 "crash dump";
158 ignore_and_erase_dump = true;
159 } else if (CanUploadDump()) {
160 // Record dump for ratelimiting
161 IncrementNumDumpsInCurrentPeriod();
162 } else {
163 LOG(INFO) << "Can't upload dump due to rate limit, will reboot";
164 ResetRateLimitPeriod();
165 ignore_and_erase_dump = true;
166 reboot_scheduled_ = true;
167 }
168
169 if (ignore_and_erase_dump) {
170 base::DeleteFile(dump_path);
171 base::DeleteFile(log_path);
172 dumps.erase(dumps.begin());
173 continue;
174 }
175
176 LOG(INFO) << "OptInStats is true, uploading crash dump";
177
178 int64_t size;
179 if (!dump_path.empty() && !base::GetFileSize(dump_path, &size)) {
180 // either the file does not exist, or there was an error logging its
181 // path, or settings its permission; regardless, we can't upload it.
182 dumps.erase(dumps.begin());
183 continue;
184 }
185
186 std::stringstream comment;
187 if (log_path.empty()) {
188 comment << "Log file not specified. ";
189 } else if (!base::GetFileSize(log_path, &size)) {
190 comment << "Can't get size of " << log_path.value() << ": "
191 << strerror(errno);
192 // if we can't find the log file, don't upload the log
193 log_path.clear();
194 } else {
195 comment << "Log size is " << size << ". ";
196 }
197
198 std::stringstream uptime_stream;
199 uptime_stream << dump.params().process_uptime;
200
201 // attempt to upload
202 LOG(INFO) << "Uploading crash to " << upload_location_;
203 CastCrashdumpData crashdump_data;
204 crashdump_data.product = kProductName;
205 crashdump_data.version = GetVersionString();
206 crashdump_data.guid = client_id;
207 crashdump_data.ptime = uptime_stream.str();
208 crashdump_data.comments = comment.str();
209 crashdump_data.minidump_pathname = dump_path.value();
210 crashdump_data.crash_server = upload_location_;
211
212 // Depending on if a testing CastCrashdumpUploader object has been set,
213 // assign |g| as a reference to the correct object.
214 CastCrashdumpUploader vanilla(crashdump_data);
215 CastCrashdumpUploader& g = (uploader_ ? *uploader_ : vanilla);
216
217 if (!log_path.empty() && !g.AddAttachment("log_file", log_path.value())) {
218 LOG(ERROR) << "Could not attach log file " << log_path.value();
219 // Don't fail to upload just because of this.
220 comment << "Could not attach log file " << log_path.value() << ". ";
221 }
222
223 // Dump some Android properties directly into product data.
224 g.SetParameter("ro.revision", board_revision_);
225 g.SetParameter("ro.product.release.track", release_channel_);
226 g.SetParameter("ro.hardware", board_name_);
227 g.SetParameter("ro.product.name", product_name_);
228 g.SetParameter("device", product_name_);
229 g.SetParameter("ro.product.model", device_model_);
230 g.SetParameter("ro.product.manufacturer", manufacturer_);
231 g.SetParameter("ro.system.version", system_version_);
232 g.SetParameter("release.virtual-channel", virtual_channel);
233 g.SetParameter("ro.build.type", GetBuildVariant());
234 if (pref_service->HasPrefPath(kLatestUiVersion)) {
235 g.SetParameter("ui.version",
236 pref_service->GetString(kLatestUiVersion));
237 }
238 // Add app state information
239 if (!dump.params().previous_app_name.empty()) {
240 g.SetParameter("previous_app", dump.params().previous_app_name);
241 }
242 if (!dump.params().current_app_name.empty()) {
243 g.SetParameter("current_app", dump.params().current_app_name);
244 }
245 if (!dump.params().last_app_name.empty()) {
246 g.SetParameter("last_app", dump.params().last_app_name);
247 }
248 if (!dump.params().reason.empty()) {
249 g.SetParameter("reason", dump.params().reason);
250 }
251 if (!dump.params().stadia_session_id.empty()) {
252 g.SetParameter("stadia_session_id", dump.params().stadia_session_id);
253 }
254 if (!dump.params().extra_info.empty()) {
255 std::vector<std::string> pairs = base::SplitString(dump.params().extra_info,
256 " ",
257 base::TRIM_WHITESPACE,
258 base::SPLIT_WANT_NONEMPTY
259 );
260 for (const auto& pair : pairs) {
261 std::vector<std::string> key_value =
262 base::SplitString(pair, "=", base::TRIM_WHITESPACE,
263 base::SPLIT_WANT_NONEMPTY);
264 if (key_value.size() == 2) {
265 g.SetParameter(key_value[0], key_value[1]);
266 }
267 }
268 }
269
270 std::string response;
271 if (!g.Upload(&response)) {
272 // We have failed to upload this file.
273 // Save our state by flushing our dumps to the lockfile
274 // We'll come back around later and try again.
275 LOG(ERROR) << "Upload report failed. response: " << response;
276 // The increment will happen when it retries the upload.
277 DecrementNumDumpsInCurrentPeriod();
278 SetCurrentDumps(dumps);
279 return true;
280 }
281
282 LOG(INFO) << "Uploaded report id " << response;
283 // upload succeeded, so delete the entry
284 dumps.erase(dumps.begin());
285 // delete the dump if it exists in /data/minidumps.
286 // (We may use a fake dump file which should not be deleted.)
287 if (!dump_path.empty() && dump_path.DirName() == dump_path_ &&
288 !base::DeleteFile(dump_path)) {
289 LOG(WARNING) << "remove dump " << dump_path.value() << " failed"
290 << strerror(errno);
291 }
292 // delete the log if exists
293 if (!log_path.empty() && !base::DeleteFile(log_path)) {
294 LOG(WARNING) << "remove log " << log_path.value() << " failed"
295 << strerror(errno);
296 }
297 ++num_uploaded;
298 }
299
300 // This will simply empty the log file.
301 // Entries should either be skipped/deleted or processed/deleted.
302 SetCurrentDumps(dumps);
303
304 // If we reach here, then the log file should be empty, and there should
305 // be no more dumps to upload. However, it is possible that there are
306 // lingering files (for example, if the dump was written, but the log
307 // updating failed). Since we have no entries on these files, we cannot
308 // upload them. Therefore we should delete them. This is also a good way
309 // to make sure system resources aren't being drained.
310
311 int num_deleted = GetNumDumps(true /* delete_all_dumps */);
312 if (num_deleted > 0) {
313 LOG(WARNING) << num_deleted << " lingering dump files deleted.";
314 }
315
316 LOG(INFO) << num_uploaded << " dumps were uploaded.";
317 return true;
318 }
319
320 } // namespace chromecast
321