Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(397)

Side by Side Diff: client/crash_report_database_mac.mm

Issue 842513002: Create CrashReportDatabase interface, a test, and a Mac implementation. (Closed) Base URL: https://chromium.googlesource.com/crashpad/crashpad@master
Patch Set: Created 5 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2015 The Crashpad Authors. 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 "client/crash_report_database.h"
16
17 #include <fcntl.h>
18 #import <Foundation/Foundation.h>
19 #include <stdio.h>
20 #include <sys/errno.h>
21 #include <sys/stat.h>
22 #include <sys/types.h>
23 #include <unistd.h>
24
25 #include "base/logging.h"
26 #include "base/rand_util.h"
27 #include "base/posix/eintr_wrapper.h"
28 #include "base/scoped_generic.h"
29 #include "base/strings/string_piece.h"
30 #include "base/strings/stringprintf.h"
31 #include "base/strings/sys_string_conversions.h"
32 #include "util/mac/xattr.h"
33
34 namespace crashpad {
35
36 namespace {
37
38 const char kDatabaseDirectoryName[] = "Crashpad Database";
39
40 const char kWriteDirectory[] = "In Progress";
41
42 const char kUploadPendingDirectory[] = "Reports";
43
44 const char kUploadedDirectory[] = "Uploaded";
45
46 const char kPlaceholderReportFileExtension[] = "placeholder";
47 const char kCrashReportFileExtension[] = "dmp";
48
49 const char kXattrUUID[] = "uuid";
50 const char kXattrCollectorID[] = "id";
51 const char kXattrIsUploaded[] = "uploaded";
52 const char kXattrLastUploadTime[] = "last_upload_time";
53 const char kXattrUploadAttemptCount[] = "upload_count";
54
55 const char kXattrDatabaseInitialized[] = "initialized";
56
57 struct ScopedFileLockTraits {
58 static int InvalidValue() {
59 return 0;
60 }
61
62 static void Free(int fd) {
63 PCHECK(IGNORE_EINTR(close(fd)) == 0) << "close";
64 }
65 };
66
67 using ScopedFileLock = base::ScopedGeneric<int, ScopedFileLockTraits>;
68
69 void GenerateUUID(UUID* uuid) {
70 uint8_t bytes[sizeof(*uuid)];
71 base::RandBytes(bytes, sizeof(bytes));
72 uuid->InitializeFromBytes(bytes);
73 }
74
75 class CrashReportDatabaseMac : public CrashReportDatabase {
76 public:
77 explicit CrashReportDatabaseMac(const base::FilePath& path);
78 virtual ~CrashReportDatabaseMac();
79
80 bool Initialize();
81
82 // CrashReportDatabase:
83 OperationStatus PrepareNewCrashReport(Report* report) override;
84 OperationStatus FinishedWritingCrashReport(const UUID& uuid) override;
85 OperationStatus LookUpCrashReport(const UUID& uuid,
86 Report* report) override;
87 OperationStatus GetNotUploadedReports(
88 std::vector<const Report>* reports) override;
89 OperationStatus GetUploadedReports(
90 std::vector<const Report>* reports) override;
91 OperationStatus RecordUploadAttempt(const UUID& uuid,
92 bool successful,
93 const std::string& id) override;
94
95 private:
96 // Locates a crash report by |uuid| in the database and returns the full path
97 // to the report file, or an empty path if it cannot be found.
98 base::FilePath LocateCrashReport(const UUID& uuid);
99
100 // Obtains an advisory lock on the file at |path|. The flock is used to
101 // prevent cross-process concurrent metadata reads or writes. While xattrs
102 // do not observe the lock, if the lock-then-mutate protocol is observed by
103 // all clients of the database, it still enforces synchronization.
104 ScopedFileLock ObtainReportLock(const base::FilePath& path);
105
106 // Reads all the database xattrs from a file at |path| into the |report|. The
107 // flock must be obtained before calling this.
108 bool ReadReportMetadataLocked(const base::FilePath& path,
109 Report* report);
110
111 // Reads the metadata from all the reports in a database subdirectory.
112 // Invalid reports are skipped.
113 OperationStatus ReportsInDirectory(const base::FilePath& path,
114 std::vector<const Report>* reports);
115
116
117 // Creates a database xattr name from the short constant name.
118 static std::string XattrName(const base::StringPiece& name);
119
120 base::FilePath base_dir_;
121
122 DISALLOW_COPY_AND_ASSIGN(CrashReportDatabaseMac);
123 };
124
125 CrashReportDatabaseMac::CrashReportDatabaseMac(const base::FilePath& path)
126 : CrashReportDatabase(), base_dir_(path) {
127 }
128
129 CrashReportDatabaseMac::~CrashReportDatabaseMac() {}
130
131 bool CrashReportDatabaseMac::Initialize() {
132 const std::string is_initialized_flag = XattrName(kXattrDatabaseInitialized);
133 NSFileManager* file_manager = [NSFileManager defaultManager];
134
135 // Check if the database already exists.
136 BOOL is_dir = NO;
137 if ([file_manager fileExistsAtPath:base::SysUTF8ToNSString(base_dir_.value())
138 isDirectory:&is_dir]) {
139 if (!is_dir) {
140 LOG(ERROR) << "Database exists at " << base_dir_.value()
141 << " but is not a directory";
142 return false;
143 }
144
145 // The |base_dir_| path exists and is a directory, make sure it is marked
146 // as initialized. This will also ensure the filesystem has xattr support.
147 bool is_initialized = false;
148 if (ReadXattrBool(base_dir_, is_initialized_flag, &is_initialized) &&
149 is_initialized) {
150 return true;
151 }
152 } else {
153 // The database directory does not exist, so create it.
154 if (mkdir(base_dir_.value().c_str(), S_IRWXU | S_IRWXG)) {
155 PLOG(ERROR) << "mkdir " << base_dir_.value();
156 return false;
157 }
158 }
159
160 // Create the three processing directories for the database.
161 const char* paths[] = {
162 kWriteDirectory,
163 kUploadPendingDirectory,
164 kUploadedDirectory
165 };
166 for (size_t i = 0; i < arraysize(paths); ++i) {
167 base::FilePath path = base_dir_.Append(paths[i]);
168 if (mkdir(path.value().c_str(), S_IRWXU | S_IRWXG)) {
169 PLOG(ERROR) << "mkdir " << path.value();
170 return false;
171 }
172 }
173
174 return WriteXattrBool(base_dir_, is_initialized_flag, true);
175 }
176
177 CrashReportDatabase::OperationStatus
178 CrashReportDatabaseMac::PrepareNewCrashReport(
179 CrashReportDatabase::Report* report) {
180 *report = Report();
181 GenerateUUID(&report->uuid);
182
183 base::FilePath write_dir = base_dir_.Append(kWriteDirectory);
184
185 // Create a placeholder file that will be used to store a record of this
186 // operation. The crash report file is written by a client of this class.
187 base::FilePath placeholder = write_dir.Append(
188 report->uuid.ToString() + "." + kPlaceholderReportFileExtension);
189 NSFileManager* manager = [NSFileManager defaultManager];
190 if (![manager createFileAtPath:base::SysUTF8ToNSString(placeholder.value())
191 contents:[NSData data]
192 attributes:nil]) {
193 LOG(ERROR) << "Failed to create report placeholder file "
194 << placeholder.value();
195 return kFileSystemError;
196 }
197
198 report->file_path = write_dir.Append(
199 report->uuid.ToString() + "." + kCrashReportFileExtension);
200
201 return kNoError;
202 }
203
204 CrashReportDatabase::OperationStatus
205 CrashReportDatabaseMac::FinishedWritingCrashReport(const UUID& uuid) {
206 NSFileManager* manager = [NSFileManager defaultManager];
207 base::FilePath write_dir = base_dir_.Append(kWriteDirectory);
208
209 // Look up the placeholder file to ensure this UUID was prepared.
210 base::FilePath placeholder = write_dir.Append(
211 uuid.ToString() + "." + kPlaceholderReportFileExtension);
212 if (![manager fileExistsAtPath:base::SysUTF8ToNSString(placeholder.value())
213 isDirectory:nullptr]) {
214 LOG(ERROR) << "Failed to find placeholder report " << placeholder.value();
215 return kReportNotFound;
216 }
217
218 // Delete the placeholder.
219 if (unlink(placeholder.value().c_str())) {
220 PLOG(ERROR) << "unlink " << placeholder.value();
221 // While not a good sign, continue processing if possible.
222 }
223
224 // Now make sure the crash report was actually written.
225 base::FilePath report_path = write_dir.Append(
226 uuid.ToString() + "." + kCrashReportFileExtension);
227 if (![manager fileExistsAtPath:base::SysUTF8ToNSString(report_path.value())
228 isDirectory:nullptr]) {
229 LOG(ERROR) << "Failed to find crash report " << report_path.value();
230 return kReportNotFound;
231 }
232
233 ScopedFileLock lock(ObtainReportLock(report_path));
234
235 // Record the UUID of this crash report.
236 if (!WriteXattr(report_path, XattrName(kXattrUUID), uuid.ToString())) {
237 return kDatabaseError;
238 }
239
240 // Move the report to its new location for uploading.
241 base::FilePath new_path =
242 base_dir_.Append(kUploadPendingDirectory).Append(report_path.BaseName());
243 if (rename(report_path.value().c_str(), new_path.value().c_str())) {
244 PLOG(ERROR) << "rename " << report_path.value() << " to "
245 << new_path.value();
246 return kFileSystemError;
247 }
248
249 return kNoError;
250 }
251
252 CrashReportDatabase::OperationStatus
253 CrashReportDatabaseMac::LookUpCrashReport(const UUID& uuid,
254 CrashReportDatabase::Report* report) {
255 base::FilePath path = LocateCrashReport(uuid);
256 if (path.empty())
257 return kReportNotFound;
258
259 ScopedFileLock lock(ObtainReportLock(path));
260
261 *report = Report();
262 report->file_path = path;
263 if (!ReadReportMetadataLocked(path, report)) {
264 LOG(ERROR) << "Failed to look up crash report " << uuid.ToString();
265 return kDatabaseError;
266 }
267 return kNoError;
268 }
269
270 CrashReportDatabase::OperationStatus
271 CrashReportDatabaseMac::GetNotUploadedReports(
272 std::vector<const CrashReportDatabase::Report>* reports) {
273 return ReportsInDirectory(base_dir_.Append(kUploadPendingDirectory), reports);
274 }
275
276 CrashReportDatabase::OperationStatus
277 CrashReportDatabaseMac::GetUploadedReports(
278 std::vector<const CrashReportDatabase::Report>* reports) {
279 return ReportsInDirectory(base_dir_.Append(kUploadedDirectory), reports);
280 }
281
282 CrashReportDatabase::OperationStatus
283 CrashReportDatabaseMac::RecordUploadAttempt(const UUID& uuid,
284 bool successful,
285 const std::string& id) {
286 DCHECK(successful || id.empty());
287
288 base::FilePath report_path = LocateCrashReport(uuid);
289 if (report_path.empty())
290 return kReportNotFound;
291
292 ScopedFileLock lock(ObtainReportLock(report_path));
293
294 if (successful) {
295 base::FilePath new_path =
296 base_dir_.Append(kUploadedDirectory).Append(report_path.BaseName());
297 if (rename(report_path.value().c_str(), new_path.value().c_str())) {
298 PLOG(ERROR) << "rename " << report_path.value() << " to "
299 << new_path.value();
300 return kFileSystemError;
301 }
302 report_path = new_path;
303 lock.reset(); // Reset before re-taking.
304 lock.reset(ObtainReportLock(report_path).release());
305 }
306
307 if (!WriteXattrBool(report_path, XattrName(kXattrIsUploaded), successful)) {
308 return kDatabaseError;
309 }
310 if (!WriteXattr(report_path, XattrName(kXattrCollectorID), id)) {
311 return kDatabaseError;
312 }
313
314 int upload_attempts = 0;
315 std::string name = XattrName(kXattrUploadAttemptCount);
316 if (HasXattr(report_path, name) &&
317 !ReadXattrInt(report_path, name, &upload_attempts)) {
318 return kDatabaseError;
319 }
320 if (!WriteXattrInt(report_path, name, ++upload_attempts)) {
321 return kDatabaseError;
322 }
323
324 if (!WriteXattrTimeT(report_path,
325 XattrName(kXattrLastUploadTime),
326 time(nullptr))) {
327 return kDatabaseError;
328 }
329
330 return kNoError;
331 }
332
333 base::FilePath CrashReportDatabaseMac::LocateCrashReport(const UUID& uuid) {
334 const std::string target_uuid = uuid.ToString();
335 NSString* path_ns_string = base::SysUTF8ToNSString(base_dir_.value());
336 NSString* dump_extension =
337 [NSString stringWithUTF8String:kCrashReportFileExtension];
338 NSDirectoryEnumerator* dir_it =
339 [[NSFileManager defaultManager] enumeratorAtPath:path_ns_string];
340 NSString* file = nil;
341 while ((file = [dir_it nextObject])) {
342 if (![[file pathExtension] isEqualToString:dump_extension])
343 continue;
344
345 base::FilePath full_path =
346 base_dir_.Append([file fileSystemRepresentation]);
347
348 ScopedFileLock lock(ObtainReportLock(full_path));
349 std::string uuid_string;
350 if (ReadXattr(full_path, XattrName(kXattrUUID), &uuid_string) &&
351 uuid_string == target_uuid) {
352 return full_path;
353 }
354 }
355 return base::FilePath();
356 }
357
358 ScopedFileLock CrashReportDatabaseMac::ObtainReportLock(
359 const base::FilePath& path) {
360 int fd = HANDLE_EINTR(open(path.value().c_str(),
361 O_RDONLY | O_NONBLOCK | O_EXLOCK | O_CLOEXEC));
362 PCHECK(fd >= 0) << "open lock " << path.value();
363 return ScopedFileLock(fd);
364 }
365
366 bool CrashReportDatabaseMac::ReadReportMetadataLocked(
367 const base::FilePath& path, Report* report) {
368 std::string uuid_string;
369 if (!ReadXattr(path, XattrName(kXattrUUID), &uuid_string) ||
370 !report->uuid.InitializeFromString(uuid_string)) {
371 return false;
372 }
373 if (!HasXattr(path, XattrName(kXattrCollectorID))) {
374 report->id = std::string();
375 } else if (!ReadXattr(path, XattrName(kXattrCollectorID), &report->id)) {
376 return false;
377 }
378 if (!HasXattr(path, XattrName(kXattrIsUploaded))) {
379 report->uploaded = false;
380 } else if (!ReadXattrBool(path, XattrName(kXattrIsUploaded),
381 &report->uploaded)) {
382 return false;
383 }
384 if (!HasXattr(path, XattrName(kXattrLastUploadTime))) {
385 report->last_upload_attempt_time = 0;
386 } else if (!ReadXattrTimeT(path, XattrName(kXattrLastUploadTime),
387 &report->last_upload_attempt_time)) {
388 return false;
389 }
390 if (!HasXattr(path, XattrName(kXattrUploadAttemptCount))) {
391 report->upload_attempts = 0;
392 } else if (!ReadXattrInt(path, XattrName(kXattrUploadAttemptCount),
393 &report->upload_attempts)) {
394 return false;
395 }
396
397 return true;
398 }
399
400 CrashReportDatabase::OperationStatus
401 CrashReportDatabaseMac::ReportsInDirectory(
402 const base::FilePath& path,
403 std::vector<const CrashReportDatabase::Report>* reports) {
404 DCHECK(reports->empty());
405
406 NSError* error = nil;
407 NSArray* paths = [[NSFileManager defaultManager]
408 contentsOfDirectoryAtPath:base::SysUTF8ToNSString(path.value())
409 error:&error];
410 if (error) {
411 LOG(ERROR) << "Failed to enumerate reports in directory " << path.value()
412 << ": " << [[error description] UTF8String];
413 return kFileSystemError;
414 }
415
416 reports->reserve([paths count]);
417 for (NSString* entry in paths) {
418 base::FilePath report_path = path.Append([entry fileSystemRepresentation]);
419 ScopedFileLock lock(ObtainReportLock(report_path));
420 Report report;
421 if (!ReadReportMetadataLocked(report_path, &report)) {
422 LOG(ERROR) << "Failed to read report metadata for "
423 << report_path.value();
424 continue;
425 }
426 reports->push_back(report);
427 }
428
429 return kNoError;
430 }
431
432 std::string CrashReportDatabaseMac::XattrName(const base::StringPiece& name) {
433 return base::StringPrintf("com.google.crashpad.%s", name.data());
434 }
435
436 } // namespace
437
438 scoped_ptr<CrashReportDatabase> CrashReportDatabase::Initialize(
439 const base::FilePath& path) {
440 scoped_ptr<CrashReportDatabase> database;
441
442 NSFileManager* file_manager = [NSFileManager defaultManager];
443 BOOL is_dir = NO;
444 if ([file_manager fileExistsAtPath:base::SysUTF8ToNSString(path.value())
445 isDirectory:&is_dir] &&
446 !is_dir) {
447 LOG(ERROR) << "File exists at " << path.value()
448 << " but is not a directory";
449 return database;
450 }
451
452 scoped_ptr<CrashReportDatabaseMac> database_mac(
453 new CrashReportDatabaseMac(path.Append(kDatabaseDirectoryName)));
454 if (!database_mac->Initialize())
455 database_mac.reset();
456
457 database.reset(database_mac.release());
458 return database;
459 }
460
461 } // namespace crashpad
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698