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

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: Address comments 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
« no previous file with comments | « client/crash_report_database.cc ('k') | client/crash_report_database_test.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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 <errno.h>
18 #include <fcntl.h>
19 #import <Foundation/Foundation.h>
20 #include <stdio.h>
21 #include <sys/stat.h>
22 #include <sys/types.h>
23 #include <unistd.h>
24 #include <uuid/uuid.h>
25
26 #include "base/logging.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/file/file_io.h"
33 #include "util/mac/xattr.h"
34
35 namespace crashpad {
36
37 namespace {
38
39 const char kDatabaseDirectoryName[] = "Crashpad";
40
41 const char kWriteDirectory[] = "new";
42 const char kUploadPendingDirectory[] = "pending";
43 const char kCompletedDirectory[] = "completed";
44
45 const char* const kReportDirectories[] = {
46 kWriteDirectory,
47 kUploadPendingDirectory,
48 kCompletedDirectory,
49 };
50
51 const char kCrashReportFileExtension[] = "dmp";
52
53 const char kXattrUUID[] = "uuid";
54 const char kXattrCollectorID[] = "id";
55 const char kXattrCreationTime[] = "creation_time";
56 const char kXattrIsUploaded[] = "uploaded";
57 const char kXattrLastUploadTime[] = "last_upload_time";
58 const char kXattrUploadAttemptCount[] = "upload_count";
59
60 const char kXattrDatabaseInitialized[] = "initialized";
61
62 // Ensures that the node at |path| is a directory, and creates it if it does
63 // not exist. If the |path| points to a file, rather than a directory, or the
64 // directory could not be created, returns false. Otherwise, returns true,
65 // indicating that |path| already was or now is a directory.
66 bool CreateOrEnsureDirectoryExists(const base::FilePath& path) {
67 if (mkdir(path.value().c_str(), 0755) == 0) {
68 return true;
69 } else if (errno == EEXIST) {
70 struct stat st;
71 if (stat(path.value().c_str(), &st) != 0) {
72 PLOG(ERROR) << "stat";
73 return false;
74 }
75 if (S_ISDIR(st.st_mode)) {
76 return true;
77 } else {
78 LOG(ERROR) << "not a directory";
79 return false;
80 }
81 } else {
82 PLOG(ERROR) << "mkdir";
83 return false;
84 }
85 }
86
87 //! \brief A CrashReportDatabase that uses HFS+ extended attributes to store
88 //! report metadata.
89 //!
90 //! The database maintains three directories of reports: `"new"` to hold crash
91 //! reports that are in the process of being written, `"completed"` to hold
92 //! reports that have been written and are awaing upload, and `"uploaded"` to
93 //! hold reports successfully uploaded to a collection server. If the user has
94 //! opted out of report collection, reports will still be written and moved
95 //! to the completed directory, but they just will not be uploaded.
96 //!
97 //! The database stores its metadata in extended filesystem attributes. To
98 //! ensure safe access, the report file is locked using `O_EXLOCK` during all
99 //! extended attribute operations. The lock should be obtained using
100 //! ObtainReportLock().
101 class CrashReportDatabaseMac : public CrashReportDatabase {
102 public:
103 explicit CrashReportDatabaseMac(const base::FilePath& path);
104 virtual ~CrashReportDatabaseMac();
105
106 bool Initialize();
107
108 // CrashReportDatabase:
109 OperationStatus PrepareNewCrashReport(NewReport** report) override;
110 OperationStatus FinishedWritingCrashReport(NewReport* report,
111 UUID* uuid) override;
112 OperationStatus LookUpCrashReport(const UUID& uuid,
113 Report* report) override;
114 OperationStatus GetPendingReports(
115 std::vector<const Report>* reports) override;
116 OperationStatus GetCompletedReports(
117 std::vector<const Report>* reports) override;
118 OperationStatus GetReportForUploading(const UUID& uuid,
119 const Report** report) override;
120 OperationStatus RecordUploadAttempt(const Report* report,
121 bool successful,
122 const std::string& id) override;
123 OperationStatus SkipReportUpload(const UUID& uuid) override;
124
125 private:
126 //! \brief A private extension of the Report class that maintains bookkeeping
127 //! information of the database.
128 struct UploadReport : public Report {
129 //! \brief Stores the flock of the file for the duration of
130 //! GetReportForUploading() and RecordUploadAttempt().
131 int lock_fd;
132 };
133
134 //! \brief Locates a crash report in the database by UUID.
135 //!
136 //! \param[in] uuid The UUID of the crash report to locate.
137 //!
138 //! \return The full path to the report file, or an empty path if it cannot be
139 //! found.
140 base::FilePath LocateCrashReport(const UUID& uuid);
141
142 //! \brief Obtains an exclusive advisory lock on a file.
143 //!
144 //! The flock is used to prevent cross-process concurrent metadata reads or
145 //! writes. While xattrs do not observe the lock, if the lock-then-mutate
146 //! protocol is observed by all clients of the database, it still enforces
147 //! synchronization.
148 //!
149 //! This does not block, and so callers must ensure that the lock is valid
150 //! after calling.
151 //!
152 //! \param[in] path The path of the file to lcok.
153 //!
154 //! \return A scoped lock object. If the result is not valid, an error is
155 //! logged.
156 static base::ScopedFD ObtainReportLock(const base::FilePath& path);
157
158 //! \brief Reads all the database xattrs from a file into a Report. The file
159 //! must be locked with ObtainReportLock.
160 //!
161 //! \param[in] path The path of the report.
162 //! \param[out] report The object into which data will be read.
163 //!
164 //! \return `true` if all the metadata was read successfully, `false`
165 //! otherwise.
166 static bool ReadReportMetadataLocked(const base::FilePath& path,
167 Report* report);
168
169 //! \brief Reads the metadata from all the reports in a database subdirectory.
170 //! Invalid reports are skipped.
171 //!
172 //! \param[in] path The database subdirectory path.
173 //! \param[out] reports An empty vector of reports, which will be filled.
174 //!
175 //! \return The operation status code.
176 static OperationStatus ReportsInDirectory(const base::FilePath& path,
177 std::vector<const Report>* reports);
178
179
180 //! \brief Creates a database xattr name from the short constant name.
181 //!
182 //! \param[in] name The short name of the extended attribute.
183 //!
184 //! \return The long name of the extended attribute.
185 static std::string XattrName(const base::StringPiece& name);
186
187 base::FilePath base_dir_;
188
189 DISALLOW_COPY_AND_ASSIGN(CrashReportDatabaseMac);
190 };
191
192 CrashReportDatabaseMac::CrashReportDatabaseMac(const base::FilePath& path)
193 : CrashReportDatabase(), base_dir_(path) {
194 }
195
196 CrashReportDatabaseMac::~CrashReportDatabaseMac() {}
197
198 bool CrashReportDatabaseMac::Initialize() {
199 // Check if the database already exists.
200 if (!CreateOrEnsureDirectoryExists(base_dir_))
201 return false;
202
203 // Create the three processing directories for the database.
204 for (size_t i = 0; i < arraysize(kReportDirectories); ++i) {
205 if (!CreateOrEnsureDirectoryExists(base_dir_.Append(kReportDirectories[i])))
206 return false;
207 }
208
209 // Write an xattr as the last step, to ensure the filesystem has support for
210 // them. This attribute will never be read.
211 return WriteXattrBool(base_dir_, XattrName(kXattrDatabaseInitialized), true);
212 }
213
214 CrashReportDatabase::OperationStatus
215 CrashReportDatabaseMac::PrepareNewCrashReport(NewReport** out_report) {
216 uuid_t uuid_gen;
217 uuid_generate(uuid_gen);
218 UUID uuid(uuid_gen);
219
220 scoped_ptr<NewReport> report(new NewReport());
221
222 report->path =
223 base_dir_.Append(kWriteDirectory)
224 .Append(uuid.ToString() + "." + kCrashReportFileExtension);
225
226 report->handle = HANDLE_EINTR(open(report->path.value().c_str(),
227 O_CREAT | O_WRONLY | O_EXCL | O_EXLOCK,
228 0600));
229 if (report->handle < 0) {
230 PLOG(ERROR) << "open " << report->path.value();
231 return kFileSystemError;
232 }
233
234 // TODO(rsesek): Potentially use an fsetxattr() here instead.
235 if (!WriteXattr(report->path, XattrName(kXattrUUID), uuid.ToString())) {
236 PLOG_IF(ERROR, IGNORE_EINTR(close(report->handle)) != 0) << "close";
237 return kDatabaseError;
238 }
239
240 *out_report = report.release();
241
242 return kNoError;
243 }
244
245 CrashReportDatabase::OperationStatus
246 CrashReportDatabaseMac::FinishedWritingCrashReport(NewReport* report,
247 UUID* uuid) {
248 // Takes ownership of the |handle| and the O_EXLOCK.
249 base::ScopedFD lock(report->handle);
250
251 // Take ownership of the report.
252 scoped_ptr<NewReport> scoped_report(report);
253
254 // Get the report's UUID to return.
255 std::string uuid_string;
256 if (ReadXattr(report->path, XattrName(kXattrUUID),
257 &uuid_string) != XattrStatus::kOK ||
258 !uuid->InitializeFromString(uuid_string)) {
259 LOG(ERROR) << "Failed to read UUID for crash report "
260 << report->path.value();
261 return kDatabaseError;
262 }
263
264 // Record the creation time of this report.
265 if (!WriteXattrTimeT(report->path, XattrName(kXattrCreationTime),
266 time(nullptr))) {
267 return kDatabaseError;
268 }
269
270 // Move the report to its new location for uploading.
271 base::FilePath new_path =
272 base_dir_.Append(kUploadPendingDirectory).Append(report->path.BaseName());
273 if (rename(report->path.value().c_str(), new_path.value().c_str()) != 0) {
274 PLOG(ERROR) << "rename " << report->path.value() << " to "
275 << new_path.value();
276 return kFileSystemError;
277 }
278
279 return kNoError;
280 }
281
282 CrashReportDatabase::OperationStatus
283 CrashReportDatabaseMac::LookUpCrashReport(const UUID& uuid,
284 CrashReportDatabase::Report* report) {
285 base::FilePath path = LocateCrashReport(uuid);
286 if (path.empty())
287 return kReportNotFound;
288
289 base::ScopedFD lock(ObtainReportLock(path));
290 if (!lock.is_valid())
291 return kBusyError;
292
293 *report = Report();
294 report->file_path = path;
295 if (!ReadReportMetadataLocked(path, report))
296 return kDatabaseError;
297
298 return kNoError;
299 }
300
301 CrashReportDatabase::OperationStatus
302 CrashReportDatabaseMac::GetPendingReports(
303 std::vector<const CrashReportDatabase::Report>* reports) {
304 return ReportsInDirectory(base_dir_.Append(kUploadPendingDirectory), reports);
305 }
306
307 CrashReportDatabase::OperationStatus
308 CrashReportDatabaseMac::GetCompletedReports(
309 std::vector<const CrashReportDatabase::Report>* reports) {
310 return ReportsInDirectory(base_dir_.Append(kCompletedDirectory), reports);
311 }
312
313 CrashReportDatabase::OperationStatus
314 CrashReportDatabaseMac::GetReportForUploading(const UUID& uuid,
315 const Report** report) {
316 base::FilePath report_path = LocateCrashReport(uuid);
317 if (report_path.empty())
318 return kReportNotFound;
319
320 scoped_ptr<UploadReport> upload_report(new UploadReport());
321 upload_report->file_path = report_path;
322
323 base::ScopedFD lock(ObtainReportLock(report_path));
324 if (!lock.is_valid())
325 return kBusyError;
326
327 if (!ReadReportMetadataLocked(report_path, upload_report.get()))
328 return kDatabaseError;
329
330 upload_report->lock_fd = lock.release();
331 *report = upload_report.release();
332 return kNoError;
333 }
334
335 CrashReportDatabase::OperationStatus
336 CrashReportDatabaseMac::RecordUploadAttempt(const Report* report,
337 bool successful,
338 const std::string& id) {
339 DCHECK(report);
340 DCHECK(successful || id.empty());
341
342 base::FilePath report_path = LocateCrashReport(report->uuid);
343 if (report_path.empty())
344 return kReportNotFound;
345
346 scoped_ptr<const UploadReport> upload_report(
347 static_cast<const UploadReport*>(report));
348
349 base::ScopedFD lock(upload_report->lock_fd);
350 if (!lock.is_valid())
351 return kBusyError;
352
353 if (successful) {
354 base::FilePath new_path =
355 base_dir_.Append(kCompletedDirectory).Append(report_path.BaseName());
356 if (rename(report_path.value().c_str(), new_path.value().c_str()) != 0) {
357 PLOG(ERROR) << "rename " << report_path.value() << " to "
358 << new_path.value();
359 return kFileSystemError;
360 }
361 report_path = new_path;
362 }
363
364 if (!WriteXattrBool(report_path, XattrName(kXattrIsUploaded), successful)) {
365 return kDatabaseError;
366 }
367 if (!WriteXattr(report_path, XattrName(kXattrCollectorID), id)) {
368 return kDatabaseError;
369 }
370 if (!WriteXattrTimeT(report_path,
371 XattrName(kXattrLastUploadTime),
372 time(nullptr))) {
373 return kDatabaseError;
374 }
375
376 int upload_attempts = 0;
377 std::string name = XattrName(kXattrUploadAttemptCount);
378 if (ReadXattrInt(report_path, name, &upload_attempts) ==
379 XattrStatus::kOtherError) {
380 return kDatabaseError;
381 }
382 if (!WriteXattrInt(report_path, name, ++upload_attempts)) {
383 return kDatabaseError;
384 }
385
386 return kNoError;
387 }
388
389 CrashReportDatabase::OperationStatus CrashReportDatabaseMac::SkipReportUpload(
390 const UUID& uuid) {
391 base::FilePath report_path = LocateCrashReport(uuid);
392 if (report_path.empty())
393 return kReportNotFound;
394
395 base::ScopedFD lock(ObtainReportLock(report_path));
396 if (!lock.is_valid())
397 return kBusyError;
398
399 base::FilePath new_path =
400 base_dir_.Append(kCompletedDirectory).Append(report_path.BaseName());
401 if (rename(report_path.value().c_str(), new_path.value().c_str()) != 0) {
402 PLOG(ERROR) << "rename " << report_path.value() << " to "
403 << new_path.value();
404 return kFileSystemError;
405 }
406
407 return kNoError;
408 }
409
410 base::FilePath CrashReportDatabaseMac::LocateCrashReport(const UUID& uuid) {
411 const std::string target_uuid = uuid.ToString();
412 for (size_t i = 0; i < arraysize(kReportDirectories); ++i) {
413 base::FilePath path =
414 base_dir_.Append(kReportDirectories[i])
415 .Append(target_uuid + "." + kCrashReportFileExtension);
416
417 // Test if the path exists.
418 struct stat st;
419 if (lstat(path.value().c_str(), &st)) {
420 continue;
421 }
422
423 // Check that the UUID of the report matches.
424 std::string uuid_string;
425 if (ReadXattr(path, XattrName(kXattrUUID),
426 &uuid_string) == XattrStatus::kOK &&
427 uuid_string == target_uuid) {
428 return path;
429 }
430 }
431
432 return base::FilePath();
433 }
434
435 // static
436 base::ScopedFD CrashReportDatabaseMac::ObtainReportLock(
437 const base::FilePath& path) {
438 int fd = HANDLE_EINTR(open(path.value().c_str(),
439 O_RDONLY | O_EXLOCK | O_CLOEXEC | O_NONBLOCK));
440 PLOG_IF(ERROR, fd < 0) << "open lock " << path.value();
441 return base::ScopedFD(fd);
442 }
443
444 // static
445 bool CrashReportDatabaseMac::ReadReportMetadataLocked(
446 const base::FilePath& path, Report* report) {
447 std::string uuid_string;
448 if (ReadXattr(path, XattrName(kXattrUUID),
449 &uuid_string) != XattrStatus::kOK ||
450 !report->uuid.InitializeFromString(uuid_string)) {
451 return false;
452 }
453
454 if (ReadXattrTimeT(path, XattrName(kXattrCreationTime),
455 &report->creation_time) != XattrStatus::kOK) {
456 return false;
457 }
458
459 report->id = std::string();
460 if (ReadXattr(path, XattrName(kXattrCollectorID),
461 &report->id) == XattrStatus::kOtherError) {
462 return false;
463 }
464
465 report->uploaded = false;
466 if (ReadXattrBool(path, XattrName(kXattrIsUploaded),
467 &report->uploaded) == XattrStatus::kOtherError) {
468 return false;
469 }
470
471 report->last_upload_attempt_time = 0;
472 if (ReadXattrTimeT(path, XattrName(kXattrLastUploadTime),
473 &report->last_upload_attempt_time) ==
474 XattrStatus::kOtherError) {
475 return false;
476 }
477
478 report->upload_attempts = 0;
479 if (ReadXattrInt(path, XattrName(kXattrUploadAttemptCount),
480 &report->upload_attempts) == XattrStatus::kOtherError) {
481 return false;
482 }
483
484 return true;
485 }
486
487 // static
488 CrashReportDatabase::OperationStatus
489 CrashReportDatabaseMac::ReportsInDirectory(
490 const base::FilePath& path,
491 std::vector<const CrashReportDatabase::Report>* reports) {
492 DCHECK(reports->empty());
493
494 NSError* error = nil;
495 NSArray* paths = [[NSFileManager defaultManager]
496 contentsOfDirectoryAtPath:base::SysUTF8ToNSString(path.value())
497 error:&error];
498 if (error) {
499 LOG(ERROR) << "Failed to enumerate reports in directory " << path.value()
500 << ": " << [[error description] UTF8String];
501 return kFileSystemError;
502 }
503
504 reports->reserve([paths count]);
505 for (NSString* entry in paths) {
506 base::FilePath report_path = path.Append([entry fileSystemRepresentation]);
507 base::ScopedFD lock(ObtainReportLock(report_path));
508 if (!lock.is_valid())
509 continue;
510
511 Report report;
512 if (!ReadReportMetadataLocked(report_path, &report)) {
513 LOG(WARNING) << "Failed to read report metadata for "
514 << report_path.value();
515 continue;
516 }
517 reports->push_back(report);
518 }
519
520 return kNoError;
521 }
522
523 // static
524 std::string CrashReportDatabaseMac::XattrName(const base::StringPiece& name) {
525 return base::StringPrintf("com.googlecode.crashpad.%s", name.data());
526 }
527
528 } // namespace
529
530 // static
531 scoped_ptr<CrashReportDatabase> CrashReportDatabase::Initialize(
532 const base::FilePath& path) {
533 scoped_ptr<CrashReportDatabaseMac> database_mac(
534 new CrashReportDatabaseMac(path.Append(kDatabaseDirectoryName)));
535 if (!database_mac->Initialize())
536 database_mac.reset();
537
538 return scoped_ptr<CrashReportDatabase>(database_mac.release());
539 }
540
541 } // namespace crashpad
OLDNEW
« no previous file with comments | « client/crash_report_database.cc ('k') | client/crash_report_database_test.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698