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