1 : // Copyright 2015 Google Inc. All Rights Reserved.
2 : //
3 : // Licensed under the Apache License, Version 2.0 (the "License");
4 : // you may not use this file except in compliance with the License.
5 : // You may obtain a copy of the License at
6 : //
7 : // http://www.apache.org/licenses/LICENSE-2.0
8 : //
9 : // Unless required by applicable law or agreed to in writing, software
10 : // distributed under the License is distributed on an "AS IS" BASIS,
11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 : // See the License for the specific language governing permissions and
13 : // limitations under the License.
14 :
15 : #include "syzygy/kasko/testing/upload_observer.h"
16 :
17 : #include "base/bind.h"
18 : #include "base/bind_helpers.h"
19 : #include "base/location.h"
20 : #include "base/logging.h"
21 : #include "base/files/file_enumerator.h"
22 : #include "base/files/file_path_watcher.h"
23 : #include "base/files/file_util.h"
24 : #include "base/message_loop/message_loop.h"
25 : #include "base/strings/utf_string_conversions.h"
26 : #include "gtest/gtest.h"
27 : #include "syzygy/kasko/crash_keys_serialization.h"
28 : #include "syzygy/kasko/reporter.h"
29 :
30 m : namespace kasko {
31 m : namespace testing {
32 :
33 m : namespace {
34 : // Starts watching |path| using |watcher|. Must be invoked inside the IO message
35 : // loop. |callback| will be invoked when a change to |path| or its contents is
36 : // detected.
37 m : void StartWatch(base::FilePathWatcher* watcher,
38 m : const base::FilePath& path,
39 m : const base::FilePathWatcher::Callback& callback) {
40 m : LOG(INFO) << "Watching " << path.value();
41 m : if (!watcher->Watch(path, true, callback)) {
42 m : ADD_FAILURE() << "Failed to initiate file path watch.";
43 m : base::MessageLoop::current()->QuitNow();
44 m : return;
45 m : }
46 m : }
47 m : } // namespace
48 :
49 m : UploadObserver::UploadObserver(
50 m : const base::FilePath& upload_directory,
51 m : const base::FilePath& permanent_failure_directory)
52 m : : thread_(upload_directory, permanent_failure_directory) {
53 m : thread_.Start();
54 : // Wait until the file watchers have been initialized.
55 m : thread_.WaitUntilReady();
56 m : }
57 :
58 m : UploadObserver::~UploadObserver() {
59 m : CHECK(thread_.HasBeenJoined());
60 m : }
61 :
62 m : void UploadObserver::WaitForUpload(base::FilePath* minidump_path,
63 m : std::map<std::string, std::string>* crash_keys,
64 m : bool* upload_success) {
65 m : LOG(INFO) << "Waiting for an upload.";
66 :
67 m : DCHECK(minidump_path);
68 m : DCHECK(crash_keys);
69 m : DCHECK(upload_success);
70 :
71 : // The thread exits once it detects and extracts the data from a crash report.
72 m : thread_.Join();
73 :
74 : // Copy out the data that was extracted by the thread.
75 m : *minidump_path = thread_.minidump_path();
76 m : *crash_keys = thread_.crash_keys();
77 m : *upload_success = thread_.upload_success();
78 :
79 m : LOG(INFO) << "Wait for upload completed. Upload path: "
80 m : << thread_.minidump_path().value();
81 m : }
82 :
83 m : UploadObserver::UploadObserverThread::UploadObserverThread(
84 m : const base::FilePath& upload_directory,
85 m : const base::FilePath& permanent_failure_directory)
86 m : : base::SimpleThread("UploadObserver thread"),
87 m : ready_(false, false),
88 m : upload_directory_(upload_directory),
89 m : permanent_failure_directory_(permanent_failure_directory) {
90 m : }
91 :
92 m : UploadObserver::UploadObserverThread::~UploadObserverThread(){
93 m : }
94 :
95 m : void UploadObserver::UploadObserverThread::WaitUntilReady() {
96 m : LOG(INFO) << "Waiting for watch to initiate.";
97 m : ready_.Wait();
98 m : LOG(INFO) << "Watch initiated.";
99 m : }
100 :
101 m : void UploadObserver::UploadObserverThread::Run() {
102 m : base::FilePathWatcher success_watcher;
103 m : base::FilePathWatcher failure_watcher;
104 m : base::MessageLoop watcher_loop(base::MessageLoop::TYPE_IO);
105 :
106 : // Queue up tasks to initialize the watchers on |watcher_loop|.
107 m : watcher_loop.PostTask(
108 m : FROM_HERE,
109 m : base::Bind(&StartWatch, base::Unretained(&success_watcher),
110 m : upload_directory_,
111 m : base::Bind(&UploadObserverThread::WatchForUpload,
112 m : base::Unretained(this))));
113 m : watcher_loop.PostTask(
114 m : FROM_HERE,
115 m : base::Bind(&StartWatch, base::Unretained(&failure_watcher),
116 m : permanent_failure_directory_,
117 m : base::Bind(&UploadObserverThread::WatchForPermanentFailure,
118 m : base::Unretained(this))));
119 :
120 : // Queue up a task to notify the main thread after the watchers are
121 : // initialized.
122 m : watcher_loop.PostTask(FROM_HERE, base::Bind(&base::WaitableEvent::Signal,
123 m : base::Unretained(&ready_)));
124 :
125 m : LOG(INFO) << "Running background thread.";
126 :
127 : // Run the loop. This will block until one of the watcher callbacks detects
128 : // and extracts the data from a crash report.
129 m : watcher_loop.Run();
130 :
131 m : LOG(INFO) << "Background thread terminating.";
132 m : }
133 :
134 : // Observes changes to the test server's 'incoming' directory. Notifications do
135 : // not specify the individual file changed; for each notification we must scan
136 : // for new minidump files. Once one is found, we store the minidump path and
137 : // crash keys and then quit the current message loop.
138 m : void UploadObserver::UploadObserverThread::WatchForUpload(
139 m : const base::FilePath& path,
140 m : bool error) {
141 m : LOG(INFO) << "Detected potential upload in " << path.value();
142 :
143 m : if (error) {
144 m : ADD_FAILURE() << "Failure in path watching.";
145 m : base::MessageLoop::current()->QuitNow();
146 m : return;
147 m : }
148 :
149 m : bool found_minidump = false;
150 m : std::vector<base::FilePath> crash_key_files;
151 m : base::FileEnumerator enumerator(path, true, base::FileEnumerator::FILES);
152 m : for (base::FilePath candidate = enumerator.Next(); !candidate.empty();
153 m : candidate = enumerator.Next()) {
154 m : LOG(INFO) << "Inspecting candidate: " << candidate.value();
155 m : if (candidate.BaseName() !=
156 m : base::FilePath(Reporter::kMinidumpUploadFilePart)) {
157 m : crash_key_files.push_back(candidate);
158 m : } else {
159 m : minidump_path_ = candidate;
160 m : found_minidump = true;
161 m : }
162 m : }
163 :
164 m : if (found_minidump) {
165 : // We depend on the fact that the minidump and crash keys appear atomically.
166 m : for (const auto& crash_key_file : crash_key_files) {
167 m : std::string crash_key_value;
168 m : bool read_crash_key_result =
169 m : base::ReadFileToString(crash_key_file, &crash_key_value);
170 m : EXPECT_TRUE(read_crash_key_result);
171 m : crash_keys_[base::UTF16ToUTF8(crash_key_file.BaseName().value())] =
172 m : crash_key_value;
173 m : }
174 m : upload_success_ = true;
175 m : base::MessageLoop::current()->QuitWhenIdle();
176 m : } else {
177 m : LOG(INFO) << "No minidump file detected.";
178 m : }
179 m : }
180 :
181 : // Observes changes to the permanent failure destination. Once a complete report
182 : // is found, we store the minidump path and crash keys and then quit the current
183 : // message loop.
184 m : void UploadObserver::UploadObserverThread::WatchForPermanentFailure(
185 m : const base::FilePath& path,
186 m : bool error) {
187 m : LOG(INFO) << "Detected potential permanent failure in " << path.value();
188 m : if (error) {
189 m : ADD_FAILURE() << "Failure in path watching.";
190 m : base::MessageLoop::current()->QuitNow();
191 m : return;
192 m : }
193 :
194 : // We are notified when the directory changes. It's possible only one of the
195 : // minidump file or crash keys file is present, in which case we will wait for
196 : // a subsequent notification for the other file.
197 m : base::FileEnumerator enumerator(path, true, base::FileEnumerator::FILES);
198 m : for (base::FilePath candidate = enumerator.Next(); !candidate.empty();
199 m : candidate = enumerator.Next()) {
200 m : LOG(INFO) << "Inspecting candidate: " << candidate.value();
201 :
202 : // We are scanning for a minidump file.
203 m : if (candidate.FinalExtension() !=
204 m : Reporter::kPermanentFailureMinidumpExtension) {
205 m : LOG(INFO) << "Extension " << candidate.FinalExtension()
206 m : << " doesn't match "
207 m : << Reporter::kPermanentFailureMinidumpExtension;
208 m : continue;
209 m : }
210 :
211 : // If we found a minidump file, see if we also find a matching crash keys
212 : // file.
213 m : base::FilePath crash_keys_file = candidate.ReplaceExtension(
214 m : Reporter::kPermanentFailureCrashKeysExtension);
215 m : if (!base::PathExists(crash_keys_file)) {
216 m : LOG(INFO) << "Expected crash keys file " << crash_keys_file.value()
217 m : << " is missing.";
218 m : continue;
219 m : }
220 :
221 : // Copy the data out of the crash keys file.
222 m : std::map<base::string16, base::string16> crash_keys;
223 m : EXPECT_TRUE(ReadCrashKeysFromFile(crash_keys_file, &crash_keys));
224 m : minidump_path_ = candidate;
225 m : for (const auto& entry : crash_keys) {
226 m : crash_keys_[base::UTF16ToUTF8(entry.first)] =
227 m : base::UTF16ToUTF8(entry.second);
228 m : }
229 m : upload_success_ = false;
230 m : base::MessageLoop::current()->QuitWhenIdle();
231 m : LOG(INFO) << "Successfully detected a minidump file.";
232 m : return;
233 m : }
234 :
235 m : LOG(INFO) << "No minidump file detected.";
236 m : }
237 :
238 m : } // namespace testing
239 m : } // namespace kasko
|