Chromium Code Reviews| 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 #import "client/settings_mac.h" | |
| 16 | |
| 17 #include <fcntl.h> | |
| 18 #include <unistd.h> | |
| 19 #include <uuid/uuid.h> | |
| 20 | |
| 21 #include "base/logging.h" | |
| 22 #include "base/mac/scoped_nsautorelease_pool.h" | |
| 23 #include "base/mac/scoped_nsobject.h" | |
| 24 #include "base/posix/eintr_wrapper.h" | |
| 25 #include "base/strings/sys_string_conversions.h" | |
| 26 #include "util/misc/uuid.h" | |
| 27 | |
| 28 namespace crashpad { | |
| 29 namespace internal { | |
| 30 | |
| 31 namespace { | |
| 32 | |
| 33 NSString* const kClientID = @"ClientID"; | |
| 34 NSString* const kUploadsEnabled = @"UploadsEnabled"; | |
| 35 NSString* const kLastUploadAttemptTime = @"LastUploadAttemptTime"; | |
| 36 | |
| 37 } // namespace | |
| 38 | |
| 39 SettingsMac::SettingsMac(const base::FilePath& file_path) | |
| 40 : file_path_(file_path) { | |
| 41 } | |
| 42 | |
| 43 SettingsMac::~SettingsMac() { | |
| 44 } | |
| 45 | |
| 46 bool SettingsMac::Initialize() { | |
| 47 base::mac::ScopedNSAutoreleasePool pool; | |
| 48 | |
| 49 ScopedFileHandle fd(HANDLE_EINTR( | |
| 50 open(file_path(), | |
| 51 O_CREAT | O_EXCL | O_WRONLY | O_EXLOCK, | |
| 52 0644))); | |
| 53 | |
| 54 // The file was created, so this is a new database that needs to be | |
| 55 // initialized with a client ID. | |
| 56 if (fd.is_valid()) { | |
| 57 return GenerateClientID(fd.get()); | |
| 58 } | |
| 59 | |
| 60 // 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
| |
| 61 fd.reset(HANDLE_EINTR(open(file_path(), O_RDONLY | O_SHLOCK))); | |
| 62 PLOG_IF(ERROR, !fd.is_valid()) << "open " << file_path(); | |
| 63 return fd.is_valid(); | |
| 64 } | |
| 65 | |
| 66 bool SettingsMac::GetClientID(std::string* client_id) { | |
| 67 base::mac::ScopedNSAutoreleasePool pool; | |
| 68 | |
| 69 id value = GetSetting(kClientID, [NSString class]); | |
| 70 if (!value) | |
| 71 return false; | |
|
Mark Mentovai
2015/03/09 14:01:23
If the plist is good but doesn’t contain this key
| |
| 72 | |
| 73 *client_id = base::SysNSStringToUTF8(value); | |
| 74 return true; | |
| 75 } | |
| 76 | |
| 77 bool SettingsMac::GetUploadsEnabled(bool* enabled) { | |
| 78 base::mac::ScopedNSAutoreleasePool pool; | |
| 79 | |
| 80 id value = GetSetting(kUploadsEnabled, [NSValue class]); | |
| 81 if (!value) { | |
| 82 // Return the default value per the interface requirement. | |
| 83 *enabled = false; | |
| 84 return true; | |
| 85 } | |
| 86 | |
| 87 *enabled = [value boolValue]; | |
| 88 return true; | |
| 89 } | |
| 90 | |
| 91 bool SettingsMac::SetUploadsEnabled(bool enabled) { | |
| 92 base::mac::ScopedNSAutoreleasePool pool; | |
| 93 | |
| 94 return SetSetting(kUploadsEnabled, [NSNumber numberWithBool:enabled]); | |
| 95 } | |
| 96 | |
| 97 bool SettingsMac::GetLastUploadAttemptTime(time_t* time) { | |
| 98 base::mac::ScopedNSAutoreleasePool pool; | |
| 99 | |
| 100 id value = GetSetting(kLastUploadAttemptTime, [NSValue class]); | |
| 101 if (!value) { | |
| 102 // Return the default value per the interface requirement. | |
| 103 *time = 0; | |
| 104 return true; | |
| 105 } | |
| 106 | |
| 107 *time = [value doubleValue]; | |
| 108 return true; | |
| 109 } | |
| 110 | |
| 111 bool SettingsMac::SetLastUploadAttemptTime(time_t time) { | |
| 112 base::mac::ScopedNSAutoreleasePool pool; | |
| 113 | |
| 114 return SetSetting(kLastUploadAttemptTime, [NSNumber numberWithDouble:time]); | |
| 115 } | |
| 116 | |
| 117 NSMutableDictionary* SettingsMac::ReadPlist( | |
| 118 FileHandle fd) { | |
| 119 base::scoped_nsobject<NSFileHandle> file_handle( | |
|
Mark Mentovai
2015/03/09 14:01:23
Rewind the fd to the beginning of the file first.
| |
| 120 [[NSFileHandle alloc] initWithFileDescriptor:fd closeOnDealloc:NO]); | |
| 121 NSData* data = nil; | |
| 122 @try { | |
| 123 data = [file_handle readDataToEndOfFile]; | |
| 124 } @catch (NSException* exception) { | |
| 125 LOG(ERROR) << "Failed to read settings: " << [exception description]; | |
| 126 return nil; | |
| 127 } | |
| 128 if (!data) | |
| 129 return nil; | |
| 130 | |
| 131 NSError* error = nil; | |
| 132 NSPropertyListMutabilityOptions options = NSPropertyListMutableContainers; | |
| 133 NSMutableDictionary* plist = | |
| 134 [NSPropertyListSerialization propertyListWithData:data | |
| 135 options:options | |
| 136 format:nullptr | |
| 137 error:&error]; | |
| 138 LOG_IF(ERROR, error != nil) << "Failed to deserialize settings: " | |
| 139 << [error description]; | |
| 140 return plist; | |
| 141 } | |
| 142 | |
| 143 bool SettingsMac::WritePlist(FileHandle fd, NSDictionary* plist) { | |
| 144 NSError* error = nil; | |
| 145 NSPropertyListFormat format = NSPropertyListBinaryFormat_v1_0; | |
| 146 NSData* data = [NSPropertyListSerialization dataWithPropertyList:plist | |
| 147 format:format | |
| 148 options:0 | |
| 149 error:&error]; | |
| 150 LOG_IF(ERROR, error != nil) << "Failed to serialize settings: " | |
| 151 << [error description]; | |
| 152 if (!data) | |
| 153 return false; | |
| 154 | |
| 155 if (HANDLE_EINTR(ftruncate(fd, 0)) != 0) { | |
| 156 PLOG(ERROR) << "ftruncate failed on settings file"; | |
| 157 return false; | |
| 158 } | |
| 159 | |
| 160 if (LoggingSeekFile(fd, 0, SEEK_SET) != 0) { | |
| 161 return false; | |
| 162 } | |
| 163 | |
| 164 base::scoped_nsobject<NSFileHandle> file_handle( | |
| 165 [[NSFileHandle alloc] initWithFileDescriptor:fd closeOnDealloc:NO]); | |
| 166 @try { | |
| 167 [file_handle writeData:data]; | |
| 168 } @catch (NSException* exception) { | |
| 169 LOG(ERROR) << "Failed to write settings: " << [exception description]; | |
| 170 return false; | |
| 171 } | |
| 172 | |
| 173 return true; | |
| 174 } | |
| 175 | |
| 176 id SettingsMac::GetSetting(NSString* key, Class value_class) { | |
| 177 ScopedFileHandle fd(HANDLE_EINTR(open(file_path(), O_RDONLY | O_SHLOCK))); | |
| 178 if (!fd.is_valid()) { | |
| 179 PLOG(ERROR) << "open for get setting"; | |
| 180 return nil; | |
| 181 } | |
| 182 | |
| 183 NSMutableDictionary* plist = ReadPlist(fd.get()); | |
| 184 if (!plist) { | |
| 185 fd.reset(); // Recovery will reopen the file. | |
| 186 plist = RecoverPlist(); | |
| 187 if (!plist) | |
| 188 return nil; | |
| 189 } | |
| 190 | |
| 191 id value = [plist objectForKey:key]; | |
| 192 if (value && ![value isKindOfClass:value_class]) { | |
| 193 LOG(ERROR) << [key UTF8String] << " is not a " | |
| 194 << [NSStringFromClass(value_class) UTF8String]; | |
| 195 return nil; | |
| 196 } | |
| 197 | |
| 198 return value; | |
| 199 } | |
| 200 | |
| 201 bool SettingsMac::SetSetting(NSString* key, NSObject* value) { | |
| 202 ScopedFileHandle fd(HANDLE_EINTR(open(file_path(), O_RDWR | O_EXLOCK))); | |
| 203 if (!fd.is_valid()) { | |
| 204 PLOG(ERROR) << "open for set setting"; | |
| 205 return false; | |
| 206 } | |
| 207 | |
| 208 NSMutableDictionary* plist = ReadPlist(fd.get()); | |
| 209 if (!plist) { | |
| 210 fd.reset(); // Recovery will reopen the file. | |
|
Mark Mentovai
2015/03/09 14:01:23
Kinda unnecessary to do the close-reopen-reread pa
| |
| 211 plist = RecoverPlist(); | |
| 212 if (!plist) | |
| 213 return false; | |
| 214 } | |
| 215 | |
| 216 [plist setObject:value forKey:key]; | |
| 217 | |
| 218 return WritePlist(fd.get(), plist); | |
| 219 } | |
| 220 | |
| 221 bool SettingsMac::GenerateClientID(FileHandle fd) { | |
| 222 uuid_t client_id; | |
| 223 uuid_generate(client_id); | |
| 224 | |
| 225 std::string client_id_string(UUID(client_id).ToString()); | |
| 226 | |
| 227 NSDictionary* plist = | |
| 228 @{ kClientID : base::SysUTF8ToNSString(client_id_string) }; | |
| 229 | |
| 230 return WritePlist(fd, plist); | |
| 231 } | |
| 232 | |
| 233 NSMutableDictionary* SettingsMac::RecoverPlist() { | |
| 234 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
| |
| 235 | |
| 236 ScopedFileHandle fd(HANDLE_EINTR(open(file_path(), O_RDWR | O_EXLOCK))); | |
| 237 if (!fd.is_valid()) { | |
| 238 PLOG(ERROR) << "open for recovery"; | |
| 239 return nil; | |
| 240 } | |
| 241 | |
| 242 NSMutableDictionary* plist = ReadPlist(fd.get()); | |
| 243 id client_id = [plist objectForKey:kClientID]; | |
| 244 if (![client_id isKindOfClass:[NSString class]]) { | |
| 245 LOG(ERROR) << [kClientID UTF8String] | |
| 246 << " is corrupt, recovering settings file"; | |
| 247 if (!GenerateClientID(fd.get())) { | |
| 248 LOG(ERROR) << "Failed to recover plist"; | |
| 249 return nil; | |
| 250 } | |
| 251 | |
| 252 LOG(INFO) << "Recovery complete, re-reading file"; | |
| 253 | |
| 254 return ReadPlist(fd.get()); | |
| 255 } | |
| 256 | |
| 257 LOG(INFO) << "Settings file is OK, no recovery required"; | |
| 258 | |
| 259 // The settings file may have already been recovered. | |
| 260 return plist; | |
| 261 } | |
| 262 | |
| 263 } // namespace internal | |
| 264 } // namespace crashpad | |
| OLD | NEW |