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