Chromium Code Reviews| 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 |