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

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

Powered by Google App Engine
This is Rietveld 408576698