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