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

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: 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 <fcntl.h>
18 #import <Foundation/Foundation.h>
19 #include <stdio.h>
20 #include <sys/errno.h>
Mark Mentovai 2015/01/08 22:38:10 Just <errno.h>
Robert Sesek 2015/01/13 16:18:23 Done.
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 Database";
39
40 const char kWriteDirectory[] = "In Progress";
Mark Mentovai 2015/01/08 22:38:10 If I saw these three directories side-by-side on t
Robert Sesek 2015/01/13 16:18:23 This seems self-contradictory to say that it shoul
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 class CrashReportDatabaseMac : public CrashReportDatabase {
Mark Mentovai 2015/01/08 22:38:10 Either this file or this class should have some co
Robert Sesek 2015/01/13 16:18:23 Done.
70 public:
71 explicit CrashReportDatabaseMac(const base::FilePath& path);
72 virtual ~CrashReportDatabaseMac();
73
74 bool Initialize();
75
76 // CrashReportDatabase:
77 OperationStatus PrepareNewCrashReport(Report* report) override;
78 OperationStatus FinishedWritingCrashReport(const UUID& uuid) override;
79 OperationStatus LookUpCrashReport(const UUID& uuid,
80 Report* report) override;
81 OperationStatus GetNotUploadedReports(
82 std::vector<const Report>* reports) override;
83 OperationStatus GetUploadedReports(
84 std::vector<const Report>* reports) override;
85 OperationStatus RecordUploadAttempt(const UUID& uuid,
86 bool successful,
87 const std::string& id) override;
88
89 private:
90 // Locates a crash report by |uuid| in the database and returns the full path
91 // to the report file, or an empty path if it cannot be found.
92 base::FilePath LocateCrashReport(const UUID& uuid);
93
94 // Obtains an advisory lock on the file at |path|. The flock is used to
95 // prevent cross-process concurrent metadata reads or writes. While xattrs
96 // do not observe the lock, if the lock-then-mutate protocol is observed by
97 // all clients of the database, it still enforces synchronization.
98 ScopedFileLock ObtainReportLock(const base::FilePath& path);
99
100 // Reads all the database xattrs from a file at |path| into the |report|. The
101 // flock must be obtained before calling this.
102 bool ReadReportMetadataLocked(const base::FilePath& path,
103 Report* report);
104
105 // Reads the metadata from all the reports in a database subdirectory.
106 // Invalid reports are skipped.
107 OperationStatus ReportsInDirectory(const base::FilePath& path,
108 std::vector<const Report>* reports);
109
110
111 // Creates a database xattr name from the short constant name.
112 static std::string XattrName(const base::StringPiece& name);
113
114 base::FilePath base_dir_;
115
116 DISALLOW_COPY_AND_ASSIGN(CrashReportDatabaseMac);
117 };
118
119 CrashReportDatabaseMac::CrashReportDatabaseMac(const base::FilePath& path)
120 : CrashReportDatabase(), base_dir_(path) {
121 }
122
123 CrashReportDatabaseMac::~CrashReportDatabaseMac() {}
124
125 bool CrashReportDatabaseMac::Initialize() {
126 NSFileManager* file_manager = [NSFileManager defaultManager];
127
128 // Check if the database already exists.
129 BOOL is_dir = NO;
130 if ([file_manager fileExistsAtPath:base::SysUTF8ToNSString(base_dir_.value())
131 isDirectory:&is_dir]) {
132 if (!is_dir) {
133 LOG(ERROR) << "Database exists at " << base_dir_.value()
134 << " but is not a directory";
135 return false;
136 }
137 } else {
138 // The database directory does not exist, so create it.
139 if (mkdir(base_dir_.value().c_str(), S_IRWXU | S_IRWXG)) {
Mark Mentovai 2015/01/08 22:38:10 Why are you giving group (but not other) permissio
Robert Sesek 2015/01/13 16:18:23 Done.
140 PLOG(ERROR) << "mkdir " << base_dir_.value();
141 return false;
142 }
143 }
144
145 // Create the three processing directories for the database.
146 const char* paths[] = {
147 kWriteDirectory,
148 kUploadPendingDirectory,
149 kUploadedDirectory
150 };
151 for (size_t i = 0; i < arraysize(paths); ++i) {
152 base::FilePath path = base_dir_.Append(paths[i]);
153 BOOL is_dir = NO;
154 if ([file_manager fileExistsAtPath:base::SysUTF8ToNSString(path.value())
Mark Mentovai 2015/01/08 22:38:10 You’ve got this pattern twice now, maybe you want
Robert Sesek 2015/01/13 16:18:23 Done.
155 isDirectory:&is_dir] &&
156 is_dir) {
157 continue;
158 }
159
160 if (mkdir(path.value().c_str(), S_IRWXU | S_IRWXG)) {
161 PLOG(ERROR) << "mkdir " << path.value();
162 return false;
163 }
164 }
165
166 // Write an xattr as the last step, to ensure the filesystem has support for
167 // them. This attribute will never be read.
Mark Mentovai 2015/01/08 22:38:10 Smart!
168 return WriteXattrBool(base_dir_, XattrName(kXattrDatabaseInitialized), true);
169 }
170
171 CrashReportDatabase::OperationStatus
172 CrashReportDatabaseMac::PrepareNewCrashReport(
173 CrashReportDatabase::Report* report) {
174 *report = Report();
175 uuid_t uuid;
176 uuid_generate(uuid);
177 report->uuid.InitializeFromBytes(uuid);
178
179 base::FilePath write_dir = base_dir_.Append(kWriteDirectory);
180
181 // Create a placeholder file that will be used to store a record of this
182 // operation. The crash report file is written by a client of this class.
183 base::FilePath placeholder = write_dir.Append(
184 report->uuid.ToString() + "." + kPlaceholderReportFileExtension);
185 NSFileManager* manager = [NSFileManager defaultManager];
186 if (![manager createFileAtPath:base::SysUTF8ToNSString(placeholder.value())
Mark Mentovai 2015/01/08 22:38:10 This doesn’t have O_EXCL semantics, but it probabl
Mark Mentovai 2015/01/08 22:38:10 Honestly, I was kinda hoping that the on-disk file
Robert Sesek 2015/01/13 16:18:23 I'm not sure the PID is useful. Timestamp definite
187 contents:[NSData data]
188 attributes:nil]) {
189 LOG(ERROR) << "Failed to create report placeholder file "
190 << placeholder.value();
191 return kFileSystemError;
192 }
193
194 report->file_path = write_dir.Append(
Mark Mentovai 2015/01/08 22:38:10 Why is there a separate placeholder file? Why don’
Robert Sesek 2015/01/13 16:18:23 I'm glad you suggested returning the FD. I went ba
195 report->uuid.ToString() + "." + kCrashReportFileExtension);
196
197 return kNoError;
198 }
199
200 CrashReportDatabase::OperationStatus
201 CrashReportDatabaseMac::FinishedWritingCrashReport(const UUID& uuid) {
202 NSFileManager* manager = [NSFileManager defaultManager];
203 base::FilePath write_dir = base_dir_.Append(kWriteDirectory);
204
205 // Look up the placeholder file to ensure this UUID was prepared.
206 base::FilePath placeholder = write_dir.Append(
207 uuid.ToString() + "." + kPlaceholderReportFileExtension);
208 if (![manager fileExistsAtPath:base::SysUTF8ToNSString(placeholder.value())
209 isDirectory:nullptr]) {
210 LOG(ERROR) << "Failed to find placeholder report " << placeholder.value();
211 return kReportNotFound;
212 }
213
214 // Delete the placeholder.
215 if (unlink(placeholder.value().c_str())) {
216 PLOG(ERROR) << "unlink " << placeholder.value();
Mark Mentovai 2015/01/08 22:38:09 For things that you’re tolerating, WARNING is prob
217 // While not a good sign, continue processing if possible.
218 }
219
220 // Now make sure the crash report was actually written.
221 base::FilePath report_path = write_dir.Append(
222 uuid.ToString() + "." + kCrashReportFileExtension);
223 if (![manager fileExistsAtPath:base::SysUTF8ToNSString(report_path.value())
Mark Mentovai 2015/01/08 22:38:10 I don’t know if you need any of these file-exists
Robert Sesek 2015/01/13 16:18:22 Done.
224 isDirectory:nullptr]) {
225 LOG(ERROR) << "Failed to find crash report " << report_path.value();
226 return kReportNotFound;
227 }
228
229 ScopedFileLock lock(ObtainReportLock(report_path));
230
231 // Record the UUID of this crash report.
232 if (!WriteXattr(report_path, XattrName(kXattrUUID), uuid.ToString())) {
233 return kDatabaseError;
234 }
235
236 // Move the report to its new location for uploading.
237 base::FilePath new_path =
238 base_dir_.Append(kUploadPendingDirectory).Append(report_path.BaseName());
239 if (rename(report_path.value().c_str(), new_path.value().c_str())) {
240 PLOG(ERROR) << "rename " << report_path.value() << " to "
241 << new_path.value();
242 return kFileSystemError;
243 }
244
245 return kNoError;
246 }
247
248 CrashReportDatabase::OperationStatus
249 CrashReportDatabaseMac::LookUpCrashReport(const UUID& uuid,
250 CrashReportDatabase::Report* report) {
251 base::FilePath path = LocateCrashReport(uuid);
252 if (path.empty())
253 return kReportNotFound;
254
255 ScopedFileLock lock(ObtainReportLock(path));
256
257 *report = Report();
258 report->file_path = path;
259 if (!ReadReportMetadataLocked(path, report))
260 return kDatabaseError;
Mark Mentovai 2015/01/08 22:38:10 The interface documentation said that this return
Robert Sesek 2015/01/13 16:18:22 ReadXattr and friends, do, though. Do you want mor
261
262 return kNoError;
263 }
264
265 CrashReportDatabase::OperationStatus
266 CrashReportDatabaseMac::GetNotUploadedReports(
267 std::vector<const CrashReportDatabase::Report>* reports) {
268 return ReportsInDirectory(base_dir_.Append(kUploadPendingDirectory), reports);
269 }
270
271 CrashReportDatabase::OperationStatus
272 CrashReportDatabaseMac::GetUploadedReports(
273 std::vector<const CrashReportDatabase::Report>* reports) {
274 return ReportsInDirectory(base_dir_.Append(kUploadedDirectory), reports);
275 }
276
277 CrashReportDatabase::OperationStatus
278 CrashReportDatabaseMac::RecordUploadAttempt(const UUID& uuid,
279 bool successful,
280 const std::string& id) {
281 DCHECK(successful || id.empty());
282
283 base::FilePath report_path = LocateCrashReport(uuid);
284 if (report_path.empty())
285 return kReportNotFound;
286
287 ScopedFileLock lock(ObtainReportLock(report_path));
Mark Mentovai 2015/01/08 22:38:10 The file needs to stay locked while the uploader i
Robert Sesek 2015/01/13 16:18:23 Done. Now the caller has a two-phase call like wit
288
289 if (successful) {
290 base::FilePath new_path =
291 base_dir_.Append(kUploadedDirectory).Append(report_path.BaseName());
292 if (rename(report_path.value().c_str(), new_path.value().c_str())) {
293 PLOG(ERROR) << "rename " << report_path.value() << " to "
294 << new_path.value();
295 return kFileSystemError;
296 }
297 report_path = new_path;
298 }
299
300 if (!WriteXattrBool(report_path, XattrName(kXattrIsUploaded), successful)) {
301 return kDatabaseError;
302 }
303 if (!WriteXattr(report_path, XattrName(kXattrCollectorID), id)) {
304 return kDatabaseError;
305 }
306
307 int upload_attempts = 0;
308 std::string name = XattrName(kXattrUploadAttemptCount);
309 if (HasXattr(report_path, name) &&
310 !ReadXattrInt(report_path, name, &upload_attempts)) {
311 return kDatabaseError;
312 }
313 if (!WriteXattrInt(report_path, name, ++upload_attempts)) {
314 return kDatabaseError;
315 }
316
317 if (!WriteXattrTimeT(report_path,
Mark Mentovai 2015/01/08 22:38:10 I’d set the time_t before doing the upload_attempt
Robert Sesek 2015/01/13 16:18:23 Done.
318 XattrName(kXattrLastUploadTime),
319 time(nullptr))) {
320 return kDatabaseError;
321 }
322
323 return kNoError;
324 }
325
326 base::FilePath CrashReportDatabaseMac::LocateCrashReport(const UUID& uuid) {
327 const std::string target_uuid = uuid.ToString();
328 NSString* path_ns_string = base::SysUTF8ToNSString(base_dir_.value());
329 NSString* dump_extension =
330 [NSString stringWithUTF8String:kCrashReportFileExtension];
331 NSDirectoryEnumerator* dir_it =
332 [[NSFileManager defaultManager] enumeratorAtPath:path_ns_string];
Mark Mentovai 2015/01/08 22:38:09 Directory enumeration is kind of heavy. Why isn’t
Robert Sesek 2015/01/13 16:18:23 Done.
333 NSString* file = nil;
334 while ((file = [dir_it nextObject])) {
335 if (![[file pathExtension] isEqualToString:dump_extension])
336 continue;
337
338 base::FilePath full_path =
339 base_dir_.Append([file fileSystemRepresentation]);
340
341 ScopedFileLock lock(ObtainReportLock(full_path));
342 std::string uuid_string;
343 if (ReadXattr(full_path, XattrName(kXattrUUID), &uuid_string) &&
344 uuid_string == target_uuid) {
345 return full_path;
346 }
347 }
348 return base::FilePath();
349 }
350
351 ScopedFileLock CrashReportDatabaseMac::ObtainReportLock(
352 const base::FilePath& path) {
353 int fd = HANDLE_EINTR(open(path.value().c_str(),
354 O_RDONLY | O_EXLOCK | O_CLOEXEC));
355 PCHECK(fd >= 0) << "open lock " << path.value();
Mark Mentovai 2015/01/08 22:38:10 CHECK is a little harsh here: 1. The filesystem m
Robert Sesek 2015/01/13 16:18:23 1) Callers now check the result of ObtainReportLoc
356 return ScopedFileLock(fd);
357 }
358
359 bool CrashReportDatabaseMac::ReadReportMetadataLocked(
Mark Mentovai 2015/01/08 22:38:09 This one can be static.
Robert Sesek 2015/01/13 16:18:23 Done.
360 const base::FilePath& path, Report* report) {
361 std::string uuid_string;
362 if (!ReadXattr(path, XattrName(kXattrUUID), &uuid_string) ||
363 !report->uuid.InitializeFromString(uuid_string)) {
364 return false;
365 }
366 if (!HasXattr(path, XattrName(kXattrCollectorID))) {
Mark Mentovai 2015/01/08 22:38:10 HasXattr(), ReadXattr(), HasXattr(), ReadXattr(),
Robert Sesek 2015/01/13 16:18:23 Done.
367 report->id = std::string();
368 } else if (!ReadXattr(path, XattrName(kXattrCollectorID), &report->id)) {
369 return false;
370 }
371 if (!HasXattr(path, XattrName(kXattrIsUploaded))) {
372 report->uploaded = false;
373 } else if (!ReadXattrBool(path, XattrName(kXattrIsUploaded),
374 &report->uploaded)) {
375 return false;
376 }
377 if (!HasXattr(path, XattrName(kXattrLastUploadTime))) {
378 report->last_upload_attempt_time = 0;
379 } else if (!ReadXattrTimeT(path, XattrName(kXattrLastUploadTime),
380 &report->last_upload_attempt_time)) {
381 return false;
382 }
383 if (!HasXattr(path, XattrName(kXattrUploadAttemptCount))) {
384 report->upload_attempts = 0;
385 } else if (!ReadXattrInt(path, XattrName(kXattrUploadAttemptCount),
386 &report->upload_attempts)) {
387 return false;
388 }
389
390 return true;
391 }
392
393 CrashReportDatabase::OperationStatus
394 CrashReportDatabaseMac::ReportsInDirectory(
Mark Mentovai 2015/01/08 22:38:09 This one can be static too.
Robert Sesek 2015/01/13 16:18:22 Done.
395 const base::FilePath& path,
396 std::vector<const CrashReportDatabase::Report>* reports) {
397 DCHECK(reports->empty());
398
399 NSError* error = nil;
400 NSArray* paths = [[NSFileManager defaultManager]
401 contentsOfDirectoryAtPath:base::SysUTF8ToNSString(path.value())
402 error:&error];
Mark Mentovai 2015/01/08 22:38:10 Not really sure that this is any better than opend
Robert Sesek 2015/01/13 16:18:23 I don't have to manage the DIR* or handle dot-dirs
403 if (error) {
404 LOG(ERROR) << "Failed to enumerate reports in directory " << path.value()
405 << ": " << [[error description] UTF8String];
406 return kFileSystemError;
407 }
408
409 reports->reserve([paths count]);
410 for (NSString* entry in paths) {
411 base::FilePath report_path = path.Append([entry fileSystemRepresentation]);
412 ScopedFileLock lock(ObtainReportLock(report_path));
413 Report report;
414 if (!ReadReportMetadataLocked(report_path, &report)) {
415 LOG(ERROR) << "Failed to read report metadata for "
Mark Mentovai 2015/01/08 22:38:09 Since you continue on past this, it’s more of a WA
Robert Sesek 2015/01/13 16:18:23 Done.
416 << report_path.value();
417 continue;
418 }
419 reports->push_back(report);
420 }
421
422 return kNoError;
423 }
424
425 // static
426 std::string CrashReportDatabaseMac::XattrName(const base::StringPiece& name) {
427 return base::StringPrintf("com.google.crashpad.%s", name.data());
Mark Mentovai 2015/01/08 22:38:10 org.googlecode
Mark Mentovai 2015/01/09 14:26:09 I wrote:
Robert Sesek 2015/01/13 16:18:23 Done.
428 }
429
430 } // namespace
431
432 // static
433 scoped_ptr<CrashReportDatabase> CrashReportDatabase::Initialize(
434 const base::FilePath& path) {
435 scoped_ptr<CrashReportDatabase> database;
436
437 NSFileManager* file_manager = [NSFileManager defaultManager];
Mark Mentovai 2015/01/08 22:38:10 I don’t think you need this check, because CrashRe
Robert Sesek 2015/01/13 16:18:23 Done.
438 BOOL is_dir = NO;
439 if ([file_manager fileExistsAtPath:base::SysUTF8ToNSString(path.value())
440 isDirectory:&is_dir] &&
441 !is_dir) {
442 LOG(ERROR) << "File exists at " << path.value()
443 << " but is not a directory";
444 return database;
445 }
446
447 scoped_ptr<CrashReportDatabaseMac> database_mac(
448 new CrashReportDatabaseMac(path.Append(kDatabaseDirectoryName)));
449 if (!database_mac->Initialize())
450 database_mac.reset();
451
452 database.reset(database_mac.release());
453 return database;
454 }
455
456 } // namespace crashpad
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698