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