Index: client/settings.cc |
diff --git a/client/settings.cc b/client/settings.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..736f5e9744d609204d5af98d327d7b5d736107ac |
--- /dev/null |
+++ b/client/settings.cc |
@@ -0,0 +1,237 @@ |
+// 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. |
+ |
+#include "client/settings.h" |
+ |
+#include <fcntl.h> |
+#include <unistd.h> |
+#include <uuid/uuid.h> |
+ |
+#include "base/logging.h" |
+#include "base/posix/eintr_wrapper.h" |
+ |
+namespace crashpad { |
+ |
+struct ALIGNAS(4) Settings::Data { |
Mark Mentovai
2015/03/09 22:00:57
#include "base/compiler_specific.h"
|
+ static const uint16_t kSettingsVersion = 1; |
+ |
+ Data() : version(kSettingsVersion), |
+ options(), |
Mark Mentovai
2015/03/09 22:00:57
Explicit 0 for uint32_t.
|
+ last_upload_attempt_time(0), |
+ client_id() {} |
+ |
+ enum Options : uint32_t { |
+ kUploadsEnabled = 1 << 1, |
Mark Mentovai
2015/03/09 22:00:58
1 << 0
|
+ }; |
+ |
+ uint32_t version; |
+ uint32_t options; |
+ uint64_t last_upload_attempt_time; |
Mark Mentovai
2015/03/09 22:00:58
// time_t
|
+ UUID client_id; |
+}; |
+ |
+Settings::Settings(const base::FilePath& file_path) |
+ : file_path_(file_path) { |
+} |
+ |
+Settings::~Settings() { |
+} |
+ |
+bool Settings::Initialize() { |
+ ScopedFileHandle handle(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 (handle.is_valid()) { |
+ return InitializeSettings(handle.get()); |
+ } |
+ |
+ // The file wasn't created, try opening it for a write operation. If the file |
+ // needs to be recovered, writing is necessary. This also ensures that the |
+ // file has write permissions. |
Mark Mentovai
2015/03/09 22:00:58
process has permission to write the file.
|
+ handle.reset(OpenForReadingAndWriting().release()); |
+ if (!handle.is_valid()) { |
+ return false; |
+ } |
+ |
+ // Ensure that the file is valid. If it fails to read, re-initialize the |
+ // settings. |
+ Data settings; |
+ if (!ReadSettings(handle.get(), &settings)) { |
+ return RecoverSettings(handle.get(), &settings); |
+ } |
+ |
+ return true; |
+} |
+ |
+bool Settings::GetClientID(UUID* client_id) { |
+ Data settings; |
+ if (!OpenAndReadSettings(&settings)) { |
+ return false; |
+ } |
+ |
+ *client_id = settings.client_id; |
+ return true; |
+} |
+ |
+bool Settings::GetUploadsEnabled(bool* enabled) { |
+ Data settings; |
+ if (!OpenAndReadSettings(&settings)) { |
+ return false; |
+ } |
+ |
+ *enabled = (settings.options & Data::Options::kUploadsEnabled) != 0; |
+ return true; |
+} |
+ |
+bool Settings::SetUploadsEnabled(bool enabled) { |
+ Data settings; |
+ ScopedFileHandle handle = OpenForWritingAndReadSettings(&settings); |
+ if (!handle.is_valid()) |
+ return false; |
+ |
+ if (enabled) |
+ settings.options |= Data::Options::kUploadsEnabled; |
+ else |
+ settings.options &= ~Data::Options::kUploadsEnabled; |
+ |
+ return WriteSettings(handle.get(), settings); |
+} |
+ |
+bool Settings::GetLastUploadAttemptTime(time_t* time) { |
+ Data settings; |
+ if (!OpenAndReadSettings(&settings)) |
+ return false; |
+ |
+ *time = settings.last_upload_attempt_time; |
Mark Mentovai
2015/03/09 22:00:57
saturated_cast<time_t>()…
Perhaps with a LOG(WARNI
|
+ return true; |
+} |
+ |
+bool Settings::SetLastUploadAttemptTime(time_t time) { |
+ Data settings; |
+ ScopedFileHandle handle = OpenForWritingAndReadSettings(&settings); |
+ if (!handle.is_valid()) |
+ return false; |
+ |
+ settings.last_upload_attempt_time = time; |
Mark Mentovai
2015/03/09 22:00:57
satured_cast<uint64_t>(), again perhaps with a war
|
+ |
+ return WriteSettings(handle.get(), settings); |
+} |
+ |
+ScopedFileHandle Settings::OpenForReading() { |
+ ScopedFileHandle handle(HANDLE_EINTR(open(file_path(), O_RDONLY | O_SHLOCK))); |
+ PLOG_IF(ERROR, !handle.is_valid()) << "open for reading"; |
+ return handle.Pass(); |
+} |
+ |
+ScopedFileHandle Settings::OpenForReadingAndWriting() { |
+ ScopedFileHandle handle(HANDLE_EINTR( |
+ open(file_path(), O_RDWR | O_EXLOCK | O_CREAT))); |
+ PLOG_IF(ERROR, !handle.is_valid()) << "open for writing"; |
+ return handle.Pass(); |
+} |
+ |
+bool Settings::OpenAndReadSettings(Data* out_data) { |
+ ScopedFileHandle handle = OpenForReading(); |
+ if (!handle.is_valid()) |
+ return false; |
+ |
+ if (ReadSettings(handle.get(), out_data)) |
+ return true; |
+ |
+ // The settings file is corrupt, so reinitialize it. |
+ handle.reset(); |
+ |
+ // The settings failed to be read, so re-initialize them. |
+ return RecoverSettings(kInvalidFileHandle, out_data); |
+} |
+ |
+ScopedFileHandle Settings::OpenForWritingAndReadSettings(Data* out_data) { |
+ ScopedFileHandle handle = OpenForReadingAndWriting(); |
+ if (!handle.is_valid()) |
+ return ScopedFileHandle(); |
+ |
+ if (!ReadSettings(handle.get(), out_data)) { |
+ if (!RecoverSettings(handle.get(), out_data)) |
+ return ScopedFileHandle(); |
+ } |
+ |
+ return handle.Pass(); |
+} |
+ |
+bool Settings::ReadSettings(FileHandle handle, Data* out_data) { |
+ if (LoggingSeekFile(handle, 0, SEEK_SET) != 0) |
+ return false; |
+ |
+ if (!LoggingReadFile(handle, out_data, sizeof(*out_data))) |
+ return false; |
+ |
+ if (out_data->version != Data::kSettingsVersion) { |
+ LOG(ERROR) << "Settings version is not " << Data::kSettingsVersion; |
+ return false; |
+ } |
+ |
+ return true; |
+} |
+ |
+bool Settings::WriteSettings(FileHandle handle, const Data& data) { |
+ if (LoggingSeekFile(handle, 0, SEEK_SET) != 0) |
+ return false; |
+ |
+ if (HANDLE_EINTR(ftruncate(handle, 0)) != 0) { |
+ PLOG(ERROR) << "ftruncate settings file"; |
+ return false; |
+ } |
+ |
+ return LoggingWriteFile(handle, &data, sizeof(Data)); |
+} |
+ |
+bool Settings::RecoverSettings(FileHandle handle, Data* out_data) { |
+ LOG(INFO) << "Recovering settings file " << file_path(); |
Mark Mentovai
2015/03/09 22:00:57
Move this to after the next conditional, since thi
|
+ |
+ ScopedFileHandle scoped_handle; |
+ if (handle == kInvalidFileHandle) { |
+ scoped_handle.reset(OpenForReadingAndWriting().release()); |
+ handle = scoped_handle.get(); |
+ |
+ // Test if the file has already been recovered. |
Mark Mentovai
2015/03/09 22:00:57
The critical thing here is that the test happens u
|
+ if (ReadSettings(handle, out_data)) |
+ return true; |
+ } |
+ |
+ if (handle == kInvalidFileHandle) { |
+ LOG(ERROR) << "Invalid file handle"; |
+ return false; |
+ } |
+ |
+ if (!InitializeSettings(handle)) |
+ return false; |
+ |
+ return ReadSettings(handle, out_data); |
+} |
+ |
+bool Settings::InitializeSettings(FileHandle handle) { |
+ uuid_t uuid; |
+ uuid_generate(uuid); |
+ |
+ Data settings; |
+ settings.client_id.InitializeFromBytes(uuid); |
+ |
+ return WriteSettings(handle, settings); |
+} |
+ |
+} // namespace crashpad |