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 |