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 : namespace kasko {
31 : namespace testing {
32 :
33 : 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 : void StartWatch(base::FilePathWatcher* watcher,
38 : const base::FilePath& path,
39 E : const base::FilePathWatcher::Callback& callback) {
40 E : LOG(INFO) << "Watching " << path.value();
41 E : if (!watcher->Watch(path, true, callback)) {
42 i : ADD_FAILURE() << "Failed to initiate file path watch.";
43 i : base::MessageLoop::current()->QuitNow();
44 : return;
45 : }
46 E : }
47 : } // namespace
48 :
49 : UploadObserver::UploadObserver(
50 : const base::FilePath& upload_directory,
51 : const base::FilePath& permanent_failure_directory)
52 E : : thread_(upload_directory, permanent_failure_directory) {
53 E : thread_.Start();
54 : // Wait until the file watchers have been initialized.
55 E : thread_.WaitUntilReady();
56 E : }
57 :
58 E : UploadObserver::~UploadObserver() {
59 E : CHECK(thread_.HasBeenJoined());
60 E : }
61 :
62 : void UploadObserver::WaitForUpload(base::FilePath* minidump_path,
63 : std::map<std::string, std::string>* crash_keys,
64 E : bool* upload_success) {
65 E : LOG(INFO) << "Waiting for an upload.";
66 :
67 E : DCHECK(minidump_path);
68 E : DCHECK(crash_keys);
69 E : DCHECK(upload_success);
70 :
71 : // The thread exits once it detects and extracts the data from a crash report.
72 E : thread_.Join();
73 :
74 : // Copy out the data that was extracted by the thread.
75 E : *minidump_path = thread_.minidump_path();
76 E : *crash_keys = thread_.crash_keys();
77 E : *upload_success = thread_.upload_success();
78 :
79 E : LOG(INFO) << "Wait for upload completed. Upload path: "
80 : << thread_.minidump_path().value();
81 E : }
82 :
83 : UploadObserver::UploadObserverThread::UploadObserverThread(
84 : const base::FilePath& upload_directory,
85 : const base::FilePath& permanent_failure_directory)
86 : : base::SimpleThread("UploadObserver thread"),
87 : ready_(false, false),
88 : upload_directory_(upload_directory),
89 E : permanent_failure_directory_(permanent_failure_directory) {
90 E : }
91 :
92 E : UploadObserver::UploadObserverThread::~UploadObserverThread(){
93 E : }
94 :
95 E : void UploadObserver::UploadObserverThread::WaitUntilReady() {
96 E : LOG(INFO) << "Waiting for watch to initiate.";
97 E : ready_.Wait();
98 E : LOG(INFO) << "Watch initiated.";
99 E : }
100 :
101 E : void UploadObserver::UploadObserverThread::Run() {
102 E : base::FilePathWatcher success_watcher;
103 E : base::FilePathWatcher failure_watcher;
104 E : base::MessageLoop watcher_loop(base::MessageLoop::TYPE_IO);
105 :
106 : // Queue up tasks to initialize the watchers on |watcher_loop|.
107 : watcher_loop.PostTask(
108 : FROM_HERE,
109 : base::Bind(&StartWatch, base::Unretained(&success_watcher),
110 : upload_directory_,
111 : base::Bind(&UploadObserverThread::WatchForUpload,
112 E : base::Unretained(this))));
113 : watcher_loop.PostTask(
114 : FROM_HERE,
115 : base::Bind(&StartWatch, base::Unretained(&failure_watcher),
116 : permanent_failure_directory_,
117 : base::Bind(&UploadObserverThread::WatchForPermanentFailure,
118 E : base::Unretained(this))));
119 :
120 : // Queue up a task to notify the main thread after the watchers are
121 : // initialized.
122 : watcher_loop.PostTask(FROM_HERE, base::Bind(&base::WaitableEvent::Signal,
123 E : base::Unretained(&ready_)));
124 :
125 E : 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 E : watcher_loop.Run();
130 :
131 E : LOG(INFO) << "Background thread terminating.";
132 E : }
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 : void UploadObserver::UploadObserverThread::WatchForUpload(
139 : const base::FilePath& path,
140 E : bool error) {
141 E : LOG(INFO) << "Detected potential upload in " << path.value();
142 :
143 E : if (error) {
144 i : ADD_FAILURE() << "Failure in path watching.";
145 i : base::MessageLoop::current()->QuitNow();
146 i : return;
147 : }
148 :
149 E : bool found_minidump = false;
150 E : std::vector<base::FilePath> crash_key_files;
151 E : base::FileEnumerator enumerator(path, true, base::FileEnumerator::FILES);
152 E : for (base::FilePath candidate = enumerator.Next(); !candidate.empty();
153 E : candidate = enumerator.Next()) {
154 E : LOG(INFO) << "Inspecting candidate: " << candidate.value();
155 : if (candidate.BaseName() !=
156 E : base::FilePath(Reporter::kMinidumpUploadFilePart)) {
157 E : crash_key_files.push_back(candidate);
158 E : } else {
159 E : minidump_path_ = candidate;
160 E : found_minidump = true;
161 E : }
162 E : }
163 :
164 E : if (found_minidump) {
165 : // We depend on the fact that the minidump and crash keys appear atomically.
166 E : for (const auto& crash_key_file : crash_key_files) {
167 E : std::string crash_key_value;
168 : bool read_crash_key_result =
169 E : base::ReadFileToString(crash_key_file, &crash_key_value);
170 E : EXPECT_TRUE(read_crash_key_result);
171 : crash_keys_[base::UTF16ToUTF8(crash_key_file.BaseName().value())] =
172 E : crash_key_value;
173 E : }
174 E : upload_success_ = true;
175 E : base::MessageLoop::current()->QuitWhenIdle();
176 E : } else {
177 i : LOG(INFO) << "No minidump file detected.";
178 : }
179 E : }
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 : void UploadObserver::UploadObserverThread::WatchForPermanentFailure(
185 : const base::FilePath& path,
186 E : bool error) {
187 E : LOG(INFO) << "Detected potential permanent failure in " << path.value();
188 E : if (error) {
189 i : ADD_FAILURE() << "Failure in path watching.";
190 i : base::MessageLoop::current()->QuitNow();
191 i : return;
192 : }
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 E : base::FileEnumerator enumerator(path, true, base::FileEnumerator::FILES);
198 E : for (base::FilePath candidate = enumerator.Next(); !candidate.empty();
199 E : candidate = enumerator.Next()) {
200 E : LOG(INFO) << "Inspecting candidate: " << candidate.value();
201 :
202 : // We are scanning for a minidump file.
203 : if (candidate.FinalExtension() !=
204 E : Reporter::kPermanentFailureMinidumpExtension) {
205 i : LOG(INFO) << "Extension " << candidate.FinalExtension()
206 : << " doesn't match "
207 : << Reporter::kPermanentFailureMinidumpExtension;
208 i : continue;
209 : }
210 :
211 : // If we found a minidump file, see if we also find a matching crash keys
212 : // file.
213 : base::FilePath crash_keys_file = candidate.ReplaceExtension(
214 E : Reporter::kPermanentFailureCrashKeysExtension);
215 E : if (!base::PathExists(crash_keys_file)) {
216 E : LOG(INFO) << "Expected crash keys file " << crash_keys_file.value()
217 : << " is missing.";
218 E : continue;
219 : }
220 :
221 : // Copy the data out of the crash keys file.
222 E : std::map<base::string16, base::string16> crash_keys;
223 E : EXPECT_TRUE(ReadCrashKeysFromFile(crash_keys_file, &crash_keys));
224 E : minidump_path_ = candidate;
225 E : for (const auto& entry : crash_keys) {
226 : crash_keys_[base::UTF16ToUTF8(entry.first)] =
227 E : base::UTF16ToUTF8(entry.second);
228 E : }
229 E : upload_success_ = false;
230 E : base::MessageLoop::current()->QuitWhenIdle();
231 E : LOG(INFO) << "Successfully detected a minidump file.";
232 E : return;
233 E : }
234 :
235 E : LOG(INFO) << "No minidump file detected.";
236 E : }
237 :
238 : } // namespace testing
239 : } // namespace kasko
|