OLD | NEW |
---|---|
(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 | |
OLD | NEW |