1 // Copyright 2024 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "components/metrics/structured/lib/persistent_proto_internal.h"
6
7 #include <atomic>
8 #include <memory>
9 #include <string>
10 #include <utility>
11
12 #include "base/files/file_util.h"
13 #include "base/files/important_file_writer.h"
14 #include "base/functional/bind.h"
15 #include "base/logging.h"
16 #include "base/rand_util.h"
17 #include "base/task/bind_post_task.h"
18 #include "base/task/sequenced_task_runner.h"
19 #include "base/task/task_traits.h"
20 #include "base/task/thread_pool.h"
21
22 namespace metrics::structured::internal {
23
24 namespace {
25
26 // Attempts to read from |filepath| and returns a string with the file content
27 // if successful.
Read(const base::FilePath & filepath)28 base::expected<std::string, ReadStatus> Read(const base::FilePath& filepath) {
29 if (!base::PathExists(filepath)) {
30 return base::unexpected(ReadStatus::kMissing);
31 }
32
33 std::string proto_str;
34 if (!base::ReadFileToString(filepath, &proto_str)) {
35 return base::unexpected(ReadStatus::kReadError);
36 }
37
38 return base::ok(std::move(proto_str));
39 }
40
41 } // namespace
42
PersistentProtoInternal(const base::FilePath & path,base::TimeDelta write_delay,PersistentProtoInternal::ReadCallback on_read,PersistentProtoInternal::WriteCallback on_write)43 PersistentProtoInternal::PersistentProtoInternal(
44 const base::FilePath& path,
45 base::TimeDelta write_delay,
46 PersistentProtoInternal::ReadCallback on_read,
47 PersistentProtoInternal::WriteCallback on_write)
48 : on_read_(std::move(on_read)),
49 on_write_(std::move(on_write)),
50 task_runner_(base::ThreadPool::CreateSequencedTaskRunner(
51 {base::TaskPriority::BEST_EFFORT, base::MayBlock(),
52 base::TaskShutdownBehavior::BLOCK_SHUTDOWN})),
53 proto_file_(std::make_unique<base::ImportantFileWriter>(
54 path,
55 task_runner_,
56 write_delay,
57 "StructuredMetricsPersistentProto")) {
58 task_runner_->PostTaskAndReplyWithResult(
59 FROM_HERE, base::BindOnce(&Read, proto_file_->path()),
60 base::BindOnce(&PersistentProtoInternal::OnReadComplete,
61 weak_factory_.GetWeakPtr()));
62 }
63
64 PersistentProtoInternal::~PersistentProtoInternal() = default;
65
OnReadComplete(base::expected<std::string,ReadStatus> read_status)66 void PersistentProtoInternal::OnReadComplete(
67 base::expected<std::string, ReadStatus> read_status) {
68 ReadStatus status;
69
70 proto_ = GetProto();
71
72 if (read_status.has_value()) {
73 status = ReadStatus::kOk;
74
75 // Parses the value of |read_status| into |proto_| but attempts to preserve
76 // any existing content. Optional values will be overwritten and repeated
77 // fields will be appended with new values.
78 if (!proto_->MergeFromString(read_status.value())) {
79 status = ReadStatus::kParseError;
80 QueueWrite();
81 }
82 } else {
83 status = read_status.error();
84 }
85
86 // If there was an error, write an empty proto.
87 if (status != ReadStatus::kOk) {
88 QueueWrite();
89 }
90
91 // Purge the read proto if |purge_after_reading_|.
92 if (purge_after_reading_) {
93 proto_->Clear();
94 purge_after_reading_ = false;
95 }
96
97 std::move(on_read_).Run(std::move(status));
98 }
99
QueueWrite()100 void PersistentProtoInternal::QueueWrite() {
101 // Read |updating_path_| to check if we are actively updating the path of this
102 // proto.
103 if (updating_path_.load()) {
104 return;
105 }
106
107 // |proto_| will be null if OnReadComplete() has not finished executing. It is
108 // up to the user to verify that OnReadComplete() has finished with callback
109 // |on_read_| before calling QueueWrite().
110 CHECK(proto_);
111 proto_file_->ScheduleWrite(this);
112 }
113
OnWriteAttempt(bool write_successful)114 void PersistentProtoInternal::OnWriteAttempt(bool write_successful) {
115 if (write_successful) {
116 OnWriteComplete(WriteStatus::kOk);
117 } else {
118 OnWriteComplete(WriteStatus::kWriteError);
119 }
120 }
121
OnWriteComplete(const WriteStatus status)122 void PersistentProtoInternal::OnWriteComplete(const WriteStatus status) {
123 on_write_.Run(status);
124 }
125
Purge()126 void PersistentProtoInternal::Purge() {
127 if (proto_) {
128 proto_->Clear();
129 QueueWrite();
130 } else {
131 purge_after_reading_ = true;
132 }
133 }
134
SerializeData()135 std::optional<std::string> PersistentProtoInternal::SerializeData() {
136 std::string proto_str;
137 if (!proto_->SerializeToString(&proto_str)) {
138 OnWriteComplete(WriteStatus::kSerializationError);
139 return std::nullopt;
140 }
141 proto_file_->RegisterOnNextWriteCallbacks(
142 base::BindOnce(base::IgnoreResult(&base::CreateDirectory),
143 proto_file_->path().DirName()),
144 base::BindPostTask(
145 base::SequencedTaskRunner::GetCurrentDefault(),
146 base::BindOnce(&PersistentProtoInternal::OnWriteAttempt,
147 weak_factory_.GetWeakPtr())));
148 return proto_str;
149 }
150
StartWriteForTesting()151 void PersistentProtoInternal::StartWriteForTesting() {
152 proto_file_->ScheduleWrite(this);
153 proto_file_->DoScheduledWrite();
154 }
155
UpdatePath(const base::FilePath & path,ReadCallback on_read,bool remove_existing)156 void PersistentProtoInternal::UpdatePath(const base::FilePath& path,
157 ReadCallback on_read,
158 bool remove_existing) {
159 updating_path_.store(true);
160
161 // Clean up the state of the current |proto_file_|.
162 if (proto_file_->HasPendingWrite()) {
163 proto_file_->DoScheduledWrite();
164 }
165
166 // If the previous file should be cleaned up then schedule the cleanup on
167 // separate thread.
168 if (remove_existing) {
169 task_runner_->PostTask(FROM_HERE,
170 base::BindOnce(base::IgnoreResult(&base::DeleteFile),
171 proto_file_->path()));
172 }
173
174 // Overwrite the ImportantFileWriter a new one to the new path.
175 proto_file_ = std::make_unique<base::ImportantFileWriter>(
176 path, task_runner_, proto_file_->commit_interval(),
177 "StructuredMetricsPersistentProto");
178
179 on_read_ = std::move(on_read);
180 task_runner_->PostTaskAndReplyWithResult(
181 FROM_HERE, base::BindOnce(&Read, proto_file_->path()),
182 base::BindOnce(&PersistentProtoInternal::OnReadComplete,
183 weak_factory_.GetWeakPtr()));
184
185 updating_path_.store(false);
186
187 // Write the content of the proto back to the path in case it has changed. If
188 // an error occurs while reading |path| then 2 write can occur.
189 QueueWrite();
190 }
191
DeallocProto()192 void PersistentProtoInternal::DeallocProto() {
193 FlushQueuedWrites();
194 proto_ = nullptr;
195 }
196
FlushQueuedWrites()197 void PersistentProtoInternal::FlushQueuedWrites() {
198 if (proto_file_->HasPendingWrite()) {
199 proto_file_->DoScheduledWrite();
200 }
201 }
202
203 } // namespace metrics::structured::internal
204