Index: client/settings_mac.mm |
diff --git a/client/settings_mac.mm b/client/settings_mac.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..840001268362ea1f4b7857a093b3a458691d8746 |
--- /dev/null |
+++ b/client/settings_mac.mm |
@@ -0,0 +1,264 @@ |
+// Copyright 2015 The Crashpad Authors. All rights reserved. |
+// |
+// Licensed under the Apache License, Version 2.0 (the "License"); |
+// you may not use this file except in compliance with the License. |
+// You may obtain a copy of the License at |
+// |
+// http://www.apache.org/licenses/LICENSE-2.0 |
+// |
+// Unless required by applicable law or agreed to in writing, software |
+// distributed under the License is distributed on an "AS IS" BASIS, |
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
+// See the License for the specific language governing permissions and |
+// limitations under the License. |
+ |
+#import "client/settings_mac.h" |
+ |
+#include <fcntl.h> |
+#include <unistd.h> |
+#include <uuid/uuid.h> |
+ |
+#include "base/logging.h" |
+#include "base/mac/scoped_nsautorelease_pool.h" |
+#include "base/mac/scoped_nsobject.h" |
+#include "base/posix/eintr_wrapper.h" |
+#include "base/strings/sys_string_conversions.h" |
+#include "util/misc/uuid.h" |
+ |
+namespace crashpad { |
+namespace internal { |
+ |
+namespace { |
+ |
+NSString* const kClientID = @"ClientID"; |
+NSString* const kUploadsEnabled = @"UploadsEnabled"; |
+NSString* const kLastUploadAttemptTime = @"LastUploadAttemptTime"; |
+ |
+} // namespace |
+ |
+SettingsMac::SettingsMac(const base::FilePath& file_path) |
+ : file_path_(file_path) { |
+} |
+ |
+SettingsMac::~SettingsMac() { |
+} |
+ |
+bool SettingsMac::Initialize() { |
+ base::mac::ScopedNSAutoreleasePool pool; |
+ |
+ ScopedFileHandle fd(HANDLE_EINTR( |
+ open(file_path(), |
+ O_CREAT | O_EXCL | O_WRONLY | O_EXLOCK, |
+ 0644))); |
+ |
+ // The file was created, so this is a new database that needs to be |
+ // initialized with a client ID. |
+ if (fd.is_valid()) { |
+ return GenerateClientID(fd.get()); |
+ } |
+ |
+ // The file wasn't opened successfully, try opening it without creating. |
Mark Mentovai
2015/03/09 14:01:23
“Wasn’t opened successfully” glosses over the fact
|
+ fd.reset(HANDLE_EINTR(open(file_path(), O_RDONLY | O_SHLOCK))); |
+ PLOG_IF(ERROR, !fd.is_valid()) << "open " << file_path(); |
+ return fd.is_valid(); |
+} |
+ |
+bool SettingsMac::GetClientID(std::string* client_id) { |
+ base::mac::ScopedNSAutoreleasePool pool; |
+ |
+ id value = GetSetting(kClientID, [NSString class]); |
+ if (!value) |
+ return false; |
Mark Mentovai
2015/03/09 14:01:23
If the plist is good but doesn’t contain this key
|
+ |
+ *client_id = base::SysNSStringToUTF8(value); |
+ return true; |
+} |
+ |
+bool SettingsMac::GetUploadsEnabled(bool* enabled) { |
+ base::mac::ScopedNSAutoreleasePool pool; |
+ |
+ id value = GetSetting(kUploadsEnabled, [NSValue class]); |
+ if (!value) { |
+ // Return the default value per the interface requirement. |
+ *enabled = false; |
+ return true; |
+ } |
+ |
+ *enabled = [value boolValue]; |
+ return true; |
+} |
+ |
+bool SettingsMac::SetUploadsEnabled(bool enabled) { |
+ base::mac::ScopedNSAutoreleasePool pool; |
+ |
+ return SetSetting(kUploadsEnabled, [NSNumber numberWithBool:enabled]); |
+} |
+ |
+bool SettingsMac::GetLastUploadAttemptTime(time_t* time) { |
+ base::mac::ScopedNSAutoreleasePool pool; |
+ |
+ id value = GetSetting(kLastUploadAttemptTime, [NSValue class]); |
+ if (!value) { |
+ // Return the default value per the interface requirement. |
+ *time = 0; |
+ return true; |
+ } |
+ |
+ *time = [value doubleValue]; |
+ return true; |
+} |
+ |
+bool SettingsMac::SetLastUploadAttemptTime(time_t time) { |
+ base::mac::ScopedNSAutoreleasePool pool; |
+ |
+ return SetSetting(kLastUploadAttemptTime, [NSNumber numberWithDouble:time]); |
+} |
+ |
+NSMutableDictionary* SettingsMac::ReadPlist( |
+ FileHandle fd) { |
+ base::scoped_nsobject<NSFileHandle> file_handle( |
Mark Mentovai
2015/03/09 14:01:23
Rewind the fd to the beginning of the file first.
|
+ [[NSFileHandle alloc] initWithFileDescriptor:fd closeOnDealloc:NO]); |
+ NSData* data = nil; |
+ @try { |
+ data = [file_handle readDataToEndOfFile]; |
+ } @catch (NSException* exception) { |
+ LOG(ERROR) << "Failed to read settings: " << [exception description]; |
+ return nil; |
+ } |
+ if (!data) |
+ return nil; |
+ |
+ NSError* error = nil; |
+ NSPropertyListMutabilityOptions options = NSPropertyListMutableContainers; |
+ NSMutableDictionary* plist = |
+ [NSPropertyListSerialization propertyListWithData:data |
+ options:options |
+ format:nullptr |
+ error:&error]; |
+ LOG_IF(ERROR, error != nil) << "Failed to deserialize settings: " |
+ << [error description]; |
+ return plist; |
+} |
+ |
+bool SettingsMac::WritePlist(FileHandle fd, NSDictionary* plist) { |
+ NSError* error = nil; |
+ NSPropertyListFormat format = NSPropertyListBinaryFormat_v1_0; |
+ NSData* data = [NSPropertyListSerialization dataWithPropertyList:plist |
+ format:format |
+ options:0 |
+ error:&error]; |
+ LOG_IF(ERROR, error != nil) << "Failed to serialize settings: " |
+ << [error description]; |
+ if (!data) |
+ return false; |
+ |
+ if (HANDLE_EINTR(ftruncate(fd, 0)) != 0) { |
+ PLOG(ERROR) << "ftruncate failed on settings file"; |
+ return false; |
+ } |
+ |
+ if (LoggingSeekFile(fd, 0, SEEK_SET) != 0) { |
+ return false; |
+ } |
+ |
+ base::scoped_nsobject<NSFileHandle> file_handle( |
+ [[NSFileHandle alloc] initWithFileDescriptor:fd closeOnDealloc:NO]); |
+ @try { |
+ [file_handle writeData:data]; |
+ } @catch (NSException* exception) { |
+ LOG(ERROR) << "Failed to write settings: " << [exception description]; |
+ return false; |
+ } |
+ |
+ return true; |
+} |
+ |
+id SettingsMac::GetSetting(NSString* key, Class value_class) { |
+ ScopedFileHandle fd(HANDLE_EINTR(open(file_path(), O_RDONLY | O_SHLOCK))); |
+ if (!fd.is_valid()) { |
+ PLOG(ERROR) << "open for get setting"; |
+ return nil; |
+ } |
+ |
+ NSMutableDictionary* plist = ReadPlist(fd.get()); |
+ if (!plist) { |
+ fd.reset(); // Recovery will reopen the file. |
+ plist = RecoverPlist(); |
+ if (!plist) |
+ return nil; |
+ } |
+ |
+ id value = [plist objectForKey:key]; |
+ if (value && ![value isKindOfClass:value_class]) { |
+ LOG(ERROR) << [key UTF8String] << " is not a " |
+ << [NSStringFromClass(value_class) UTF8String]; |
+ return nil; |
+ } |
+ |
+ return value; |
+} |
+ |
+bool SettingsMac::SetSetting(NSString* key, NSObject* value) { |
+ ScopedFileHandle fd(HANDLE_EINTR(open(file_path(), O_RDWR | O_EXLOCK))); |
+ if (!fd.is_valid()) { |
+ PLOG(ERROR) << "open for set setting"; |
+ return false; |
+ } |
+ |
+ NSMutableDictionary* plist = ReadPlist(fd.get()); |
+ if (!plist) { |
+ fd.reset(); // Recovery will reopen the file. |
Mark Mentovai
2015/03/09 14:01:23
Kinda unnecessary to do the close-reopen-reread pa
|
+ plist = RecoverPlist(); |
+ if (!plist) |
+ return false; |
+ } |
+ |
+ [plist setObject:value forKey:key]; |
+ |
+ return WritePlist(fd.get(), plist); |
+} |
+ |
+bool SettingsMac::GenerateClientID(FileHandle fd) { |
+ uuid_t client_id; |
+ uuid_generate(client_id); |
+ |
+ std::string client_id_string(UUID(client_id).ToString()); |
+ |
+ NSDictionary* plist = |
+ @{ kClientID : base::SysUTF8ToNSString(client_id_string) }; |
+ |
+ return WritePlist(fd, plist); |
+} |
+ |
+NSMutableDictionary* SettingsMac::RecoverPlist() { |
+ LOG(INFO) << "Attempting to recover settings file " << file_path(); |
Mark Mentovai
2015/03/09 14:01:23
I don’t know if we need the LOG(INFO)s. Maybe just
|
+ |
+ ScopedFileHandle fd(HANDLE_EINTR(open(file_path(), O_RDWR | O_EXLOCK))); |
+ if (!fd.is_valid()) { |
+ PLOG(ERROR) << "open for recovery"; |
+ return nil; |
+ } |
+ |
+ NSMutableDictionary* plist = ReadPlist(fd.get()); |
+ id client_id = [plist objectForKey:kClientID]; |
+ if (![client_id isKindOfClass:[NSString class]]) { |
+ LOG(ERROR) << [kClientID UTF8String] |
+ << " is corrupt, recovering settings file"; |
+ if (!GenerateClientID(fd.get())) { |
+ LOG(ERROR) << "Failed to recover plist"; |
+ return nil; |
+ } |
+ |
+ LOG(INFO) << "Recovery complete, re-reading file"; |
+ |
+ return ReadPlist(fd.get()); |
+ } |
+ |
+ LOG(INFO) << "Settings file is OK, no recovery required"; |
+ |
+ // The settings file may have already been recovered. |
+ return plist; |
+} |
+ |
+} // namespace internal |
+} // namespace crashpad |