1 // Copyright 2011 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 #ifdef UNSAFE_BUFFERS_BUILD
6 // TODO(crbug.com/40284755): Remove this and spanify to fix the errors.
7 #pragma allow_unsafe_buffers
8 #endif
9
10 #include "base/files/important_file_writer.h"
11
12 #include <stddef.h>
13 #include <stdint.h>
14 #include <stdio.h>
15
16 #include <algorithm>
17 #include <string>
18 #include <string_view>
19 #include <utility>
20
21 #include "base/check_op.h"
22 #include "base/critical_closure.h"
23 #include "base/debug/alias.h"
24 #include "base/files/file.h"
25 #include "base/files/file_path.h"
26 #include "base/files/file_util.h"
27 #include "base/files/important_file_writer_cleaner.h"
28 #include "base/functional/bind.h"
29 #include "base/functional/callback_helpers.h"
30 #include "base/logging.h"
31 #include "base/metrics/histogram_functions.h"
32 #include "base/notreached.h"
33 #include "base/numerics/safe_conversions.h"
34 #include "base/strings/string_number_conversions.h"
35 #include "base/strings/string_util.h"
36 #include "base/task/sequenced_task_runner.h"
37 #include "base/task/task_runner.h"
38 #include "base/threading/platform_thread.h"
39 #include "base/threading/scoped_thread_priority.h"
40 #include "base/threading/thread.h"
41 #include "base/time/time.h"
42 #include "build/build_config.h"
43 #include "build/chromeos_buildflags.h"
44
45 namespace base {
46
47 namespace {
48
49 constexpr auto kDefaultCommitInterval = Seconds(10);
50
51 #if BUILDFLAG(IS_WIN)
52 // This is how many times we will retry ReplaceFile on Windows.
53 constexpr int kReplaceRetries = 5;
54
55 // This is the result code recorded to ImportantFile.FileReplaceRetryCount if
56 // ReplaceFile still fails. It should stay constant even if we change
57 // kReplaceRetries.
58 constexpr int kReplaceRetryFailure = 10;
59 static_assert(kReplaceRetryFailure > kReplaceRetries, "No overlap allowed");
60
61 constexpr auto kReplacePauseInterval = Milliseconds(100);
62
63 // Alternate representation of ReplaceFile results, recorded to
64 // ImportantFile.FileReplaceResult.
65 // These values are persisted to logs. Entries should not be renumbered and
66 // numeric values should never be reused.
67 enum class ReplaceResult {
68 // ReplaceFile succeeded on the first try.
69 kSuccessWithoutRetry = 0,
70 // ReplaceFile succeeded after one or more retries.
71 kSuccessWithRetry = 1,
72 // ReplaceFile never succeeded, even after retries.
73 kFailure = 2,
74 kMaxValue = kFailure
75 };
76
UmaHistogramRetryCountWithSuffix(std::string_view histogram_suffix,int retry_count,bool success)77 void UmaHistogramRetryCountWithSuffix(std::string_view histogram_suffix,
78 int retry_count,
79 bool success) {
80 constexpr char kCountHistogramName[] = "ImportantFile.FileReplaceRetryCount2";
81 constexpr char kResultHistogramName[] = "ImportantFile.FileReplaceResult";
82 CHECK_LE(retry_count, kReplaceRetries);
83 auto result = success
84 ? (retry_count > 0 ? ReplaceResult::kSuccessWithRetry
85 : ReplaceResult::kSuccessWithoutRetry)
86 : ReplaceResult::kFailure;
87
88 // Log with the given suffix and the aggregated ".All" suffix.
89 if (histogram_suffix.empty()) {
90 UmaHistogramEnumeration(kResultHistogramName, result);
91 } else {
92 UmaHistogramEnumeration(
93 base::JoinString({kResultHistogramName, histogram_suffix}, "."),
94 result);
95 }
96 UmaHistogramEnumeration(base::JoinString({kResultHistogramName, "All"}, "."),
97 result);
98 if (retry_count > 0) {
99 if (histogram_suffix.empty()) {
100 UmaHistogramExactLinear(kCountHistogramName, retry_count,
101 kReplaceRetries + 1);
102 } else {
103 UmaHistogramExactLinear(
104 base::JoinString({kCountHistogramName, histogram_suffix}, "."),
105 retry_count, kReplaceRetries + 1);
106 }
107 UmaHistogramExactLinear(base::JoinString({kCountHistogramName, "All"}, "."),
108 retry_count, kReplaceRetries + 1);
109 }
110 }
111 #endif
112
UmaHistogramTimesWithSuffix(const char * histogram_name,std::string_view histogram_suffix,base::TimeDelta sample)113 void UmaHistogramTimesWithSuffix(const char* histogram_name,
114 std::string_view histogram_suffix,
115 base::TimeDelta sample) {
116 DCHECK(histogram_name);
117 // Log with the given suffix and the aggregated ".All" suffix.
118 if (histogram_suffix.empty()) {
119 UmaHistogramTimes(histogram_name, sample);
120 } else {
121 UmaHistogramTimes(base::JoinString({histogram_name, histogram_suffix}, "."),
122 sample);
123 }
124 UmaHistogramTimes(base::JoinString({histogram_name, "All"}, "."), sample);
125 }
126
127 // Deletes the file named |tmp_file_path| (which may be open as |tmp_file|),
128 // retrying on the same sequence after some delay in case of error. It is sadly
129 // common that third-party software on Windows may open the temp file and map it
130 // into its own address space, which prevents others from marking it for
131 // deletion (even if opening it for deletion was possible). |attempt| is the
132 // number of failed previous attempts to the delete the file (defaults to 0).
DeleteTmpFileWithRetry(File tmp_file,const FilePath & tmp_file_path,int attempt=0)133 void DeleteTmpFileWithRetry(File tmp_file,
134 const FilePath& tmp_file_path,
135 int attempt = 0) {
136 #if BUILDFLAG(IS_WIN)
137 // Mark the file for deletion when it is closed and then close it implicitly.
138 if (tmp_file.IsValid()) {
139 if (tmp_file.DeleteOnClose(true))
140 return;
141 // The file was opened with exclusive r/w access, so failures are primarily
142 // due to I/O errors or other phenomena out of the process's control. Go
143 // ahead and close the file. The call to DeleteFile below will basically
144 // repeat the above, but maybe it will somehow succeed.
145 tmp_file.Close();
146 }
147 #endif
148
149 // Retry every 250ms for up to two seconds. Metrics indicate that this is a
150 // reasonable number of retries -- the failures after all attempts generally
151 // point to access denied. The ImportantFileWriterCleaner should clean these
152 // up in the next process.
153 constexpr int kMaxDeleteAttempts = 8;
154 constexpr TimeDelta kDeleteFileRetryDelay = Milliseconds(250);
155
156 if (!DeleteFile(tmp_file_path) && ++attempt < kMaxDeleteAttempts &&
157 SequencedTaskRunner::HasCurrentDefault()) {
158 SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
159 FROM_HERE,
160 BindOnce(&DeleteTmpFileWithRetry, base::File(), tmp_file_path, attempt),
161 kDeleteFileRetryDelay);
162 }
163 }
164
165 } // namespace
166
167 // static
WriteFileAtomically(const FilePath & path,std::string_view data,std::string_view histogram_suffix)168 bool ImportantFileWriter::WriteFileAtomically(
169 const FilePath& path,
170 std::string_view data,
171 std::string_view histogram_suffix) {
172 // Calling the impl by way of the public WriteFileAtomically, so
173 // |from_instance| is false.
174 return WriteFileAtomicallyImpl(path, data, histogram_suffix,
175 /*from_instance=*/false,
176 BindRepeating(&ReplaceFile));
177 }
178
179 // static
ProduceAndWriteStringToFileAtomically(const FilePath & path,BackgroundDataProducerCallback data_producer_for_background_sequence,OnceClosure before_write_callback,OnceCallback<void (bool success)> after_write_callback,ReplaceFileCallback replace_file_callback,const std::string & histogram_suffix)180 void ImportantFileWriter::ProduceAndWriteStringToFileAtomically(
181 const FilePath& path,
182 BackgroundDataProducerCallback data_producer_for_background_sequence,
183 OnceClosure before_write_callback,
184 OnceCallback<void(bool success)> after_write_callback,
185 ReplaceFileCallback replace_file_callback,
186 const std::string& histogram_suffix) {
187 // Produce the actual data string on the background sequence.
188 std::optional<std::string> data =
189 std::move(data_producer_for_background_sequence).Run();
190 if (!data) {
191 DLOG(WARNING) << "Failed to serialize data to be saved in " << path.value();
192 return;
193 }
194
195 if (!before_write_callback.is_null())
196 std::move(before_write_callback).Run();
197
198 // Calling the impl by way of the private
199 // ProduceAndWriteStringToFileAtomically, which originated from an
200 // ImportantFileWriter instance, so |from_instance| is true.
201 const bool result = WriteFileAtomicallyImpl(path, *data, histogram_suffix,
202 /*from_instance=*/true,
203 std::move(replace_file_callback));
204
205 if (!after_write_callback.is_null())
206 std::move(after_write_callback).Run(result);
207 }
208
209 // static
WriteFileAtomicallyImpl(const FilePath & path,std::string_view data,std::string_view histogram_suffix,bool from_instance,ReplaceFileCallback replace_file_callback)210 bool ImportantFileWriter::WriteFileAtomicallyImpl(
211 const FilePath& path,
212 std::string_view data,
213 std::string_view histogram_suffix,
214 bool from_instance,
215 ReplaceFileCallback replace_file_callback) {
216 const TimeTicks write_start = TimeTicks::Now();
217 if (!from_instance)
218 ImportantFileWriterCleaner::AddDirectory(path.DirName());
219
220 #if BUILDFLAG(IS_WIN) && DCHECK_IS_ON()
221 // In https://crbug.com/920174, we have cases where CreateTemporaryFileInDir
222 // hits a DCHECK because creation fails with no indication why. Pull the path
223 // onto the stack so that we can see if it is malformed in some odd way.
224 wchar_t path_copy[MAX_PATH];
225 base::wcslcpy(path_copy, path.value().c_str(), std::size(path_copy));
226 base::debug::Alias(path_copy);
227 #endif // BUILDFLAG(IS_WIN) && DCHECK_IS_ON()
228
229 #if BUILDFLAG(IS_CHROMEOS_ASH)
230 // On Chrome OS, chrome gets killed when it cannot finish shutdown quickly,
231 // and this function seems to be one of the slowest shutdown steps.
232 // Include some info to the report for investigation. crbug.com/418627
233 // TODO(hashimoto): Remove this.
234 struct {
235 size_t data_size;
236 char path[128];
237 } file_info;
238 file_info.data_size = data.size();
239 strlcpy(file_info.path, path.value().c_str(), std::size(file_info.path));
240 debug::Alias(&file_info);
241 #endif
242
243 // Write the data to a temp file then rename to avoid data loss if we crash
244 // while writing the file. Ensure that the temp file is on the same volume
245 // as target file, so it can be moved in one step, and that the temp file
246 // is securely created.
247 FilePath tmp_file_path;
248 File tmp_file =
249 CreateAndOpenTemporaryFileInDir(path.DirName(), &tmp_file_path);
250 if (!tmp_file.IsValid()) {
251 DPLOG(WARNING) << "Failed to create temporary file to update " << path;
252 return false;
253 }
254
255 // Don't write all of the data at once because this can lead to kernel
256 // address-space exhaustion on 32-bit Windows (see https://crbug.com/1001022
257 // for details).
258 constexpr ptrdiff_t kMaxWriteAmount = 8 * 1024 * 1024;
259 int bytes_written = 0;
260 for (const char *scan = data.data(), *const end = scan + data.length();
261 scan < end; scan += bytes_written) {
262 const int write_amount =
263 static_cast<int>(std::min(kMaxWriteAmount, end - scan));
264 bytes_written = tmp_file.WriteAtCurrentPos(scan, write_amount);
265 if (bytes_written != write_amount) {
266 DPLOG(WARNING) << "Failed to write " << write_amount << " bytes to temp "
267 << "file to update " << path
268 << " (bytes_written=" << bytes_written << ")";
269 DeleteTmpFileWithRetry(std::move(tmp_file), tmp_file_path);
270 return false;
271 }
272 }
273
274 if (!tmp_file.Flush()) {
275 DPLOG(WARNING) << "Failed to flush temp file to update " << path;
276 DeleteTmpFileWithRetry(std::move(tmp_file), tmp_file_path);
277 return false;
278 }
279
280 File::Error replace_file_error = File::FILE_OK;
281 bool result;
282
283 // The file must be closed for ReplaceFile to do its job, which opens up a
284 // race with other software that may open the temp file (e.g., an A/V scanner
285 // doing its job without oplocks). Boost a background thread's priority on
286 // Windows and close as late as possible to improve the chances that the other
287 // software will lose the race.
288 #if BUILDFLAG(IS_WIN)
289 DWORD last_error;
290 int retry_count = 0;
291 {
292 ScopedBoostPriority scoped_boost_priority(ThreadType::kDisplayCritical);
293 tmp_file.Close();
294 result =
295 replace_file_callback.Run(tmp_file_path, path, &replace_file_error);
296 // Save and restore the last error code so that it's not polluted by the
297 // thread priority change.
298 last_error = ::GetLastError();
299 for (/**/; !result && retry_count < kReplaceRetries; ++retry_count) {
300 // The race condition between closing the temporary file and moving it
301 // gets hit on a regular basis on some systems
302 // (https://crbug.com/1099284), so we retry a few times before giving up.
303 PlatformThread::Sleep(kReplacePauseInterval);
304 result =
305 replace_file_callback.Run(tmp_file_path, path, &replace_file_error);
306 last_error = ::GetLastError();
307 }
308 }
309
310 // Log how many times we had to retry the ReplaceFile operation before it
311 // succeeded.
312 UmaHistogramRetryCountWithSuffix(histogram_suffix, retry_count, result);
313
314 // Log to an unsuffixed histogram as well. If we never succeeded then return a
315 // special value.
316 if (!result) {
317 retry_count = kReplaceRetryFailure;
318 }
319 UmaHistogramExactLinear("ImportantFile.FileReplaceRetryCount", retry_count,
320 kReplaceRetryFailure);
321 #else
322 tmp_file.Close();
323 result = replace_file_callback.Run(tmp_file_path, path, &replace_file_error);
324 #endif // BUILDFLAG(IS_WIN)
325
326 if (!result) {
327 #if BUILDFLAG(IS_WIN)
328 // Restore the error code from ReplaceFile so that it will be available for
329 // the log message, otherwise failures in SetCurrentThreadType may be
330 // reported instead.
331 ::SetLastError(last_error);
332 #endif
333 DPLOG(WARNING) << "Failed to replace " << path << " with " << tmp_file_path;
334 DeleteTmpFileWithRetry(File(), tmp_file_path);
335 }
336
337 const TimeDelta write_duration = TimeTicks::Now() - write_start;
338 UmaHistogramTimesWithSuffix("ImportantFile.WriteDuration", histogram_suffix,
339 write_duration);
340
341 return result;
342 }
343
ImportantFileWriter(const FilePath & path,scoped_refptr<SequencedTaskRunner> task_runner,std::string_view histogram_suffix)344 ImportantFileWriter::ImportantFileWriter(
345 const FilePath& path,
346 scoped_refptr<SequencedTaskRunner> task_runner,
347 std::string_view histogram_suffix)
348 : ImportantFileWriter(path,
349 std::move(task_runner),
350 kDefaultCommitInterval,
351 histogram_suffix) {}
352
ImportantFileWriter(const FilePath & path,scoped_refptr<SequencedTaskRunner> task_runner,TimeDelta interval,std::string_view histogram_suffix)353 ImportantFileWriter::ImportantFileWriter(
354 const FilePath& path,
355 scoped_refptr<SequencedTaskRunner> task_runner,
356 TimeDelta interval,
357 std::string_view histogram_suffix)
358 : path_(path),
359 task_runner_(std::move(task_runner)),
360 commit_interval_(interval),
361 histogram_suffix_(histogram_suffix),
362 replace_file_callback_(BindRepeating(&ReplaceFile)) {
363 DCHECK(task_runner_);
364 ImportantFileWriterCleaner::AddDirectory(path.DirName());
365 }
366
~ImportantFileWriter()367 ImportantFileWriter::~ImportantFileWriter() {
368 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
369 // We're usually a member variable of some other object, which also tends
370 // to be our serializer. It may not be safe to call back to the parent object
371 // being destructed.
372 DCHECK(!HasPendingWrite());
373 }
374
HasPendingWrite() const375 bool ImportantFileWriter::HasPendingWrite() const {
376 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
377 return timer().IsRunning();
378 }
379
WriteNow(std::string data)380 void ImportantFileWriter::WriteNow(std::string data) {
381 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
382 CHECK(IsValueInRangeForNumericType<int32_t>(data.length()));
383
384 WriteNowWithBackgroundDataProducer(base::BindOnce(
385 [](std::string data) { return std::make_optional(std::move(data)); },
386 std::move(data)));
387 }
388
SetReplaceFileCallbackForTesting(ReplaceFileCallback callback)389 void ImportantFileWriter::SetReplaceFileCallbackForTesting(
390 ReplaceFileCallback callback) {
391 replace_file_callback_ = std::move(callback);
392 }
393
WriteNowWithBackgroundDataProducer(BackgroundDataProducerCallback background_data_producer)394 void ImportantFileWriter::WriteNowWithBackgroundDataProducer(
395 BackgroundDataProducerCallback background_data_producer) {
396 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
397
398 auto split_task =
399 SplitOnceCallback(BindOnce(&ProduceAndWriteStringToFileAtomically, path_,
400 std::move(background_data_producer),
401 std::move(before_next_write_callback_),
402 std::move(after_next_write_callback_),
403 replace_file_callback_, histogram_suffix_));
404
405 if (!task_runner_->PostTask(
406 FROM_HERE, MakeCriticalClosure("ImportantFileWriter::WriteNow",
407 std::move(split_task.first),
408 /*is_immediate=*/true))) {
409 // Posting the task to background message loop is not expected
410 // to fail.
411 NOTREACHED();
412 }
413 ClearPendingWrite();
414 }
415
ScheduleWrite(DataSerializer * serializer)416 void ImportantFileWriter::ScheduleWrite(DataSerializer* serializer) {
417 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
418
419 DCHECK(serializer);
420 serializer_.emplace<DataSerializer*>(serializer);
421
422 if (!timer().IsRunning()) {
423 timer().Start(
424 FROM_HERE, commit_interval_,
425 BindOnce(&ImportantFileWriter::DoScheduledWrite, Unretained(this)));
426 }
427 }
428
ScheduleWriteWithBackgroundDataSerializer(BackgroundDataSerializer * serializer)429 void ImportantFileWriter::ScheduleWriteWithBackgroundDataSerializer(
430 BackgroundDataSerializer* serializer) {
431 DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
432
433 DCHECK(serializer);
434 serializer_.emplace<BackgroundDataSerializer*>(serializer);
435
436 if (!timer().IsRunning()) {
437 timer().Start(
438 FROM_HERE, commit_interval_,
439 BindOnce(&ImportantFileWriter::DoScheduledWrite, Unretained(this)));
440 }
441 }
442
DoScheduledWrite()443 void ImportantFileWriter::DoScheduledWrite() {
444 // One of the serializers should be set.
445 DCHECK(!absl::holds_alternative<absl::monostate>(serializer_));
446
447 const TimeTicks serialization_start = TimeTicks::Now();
448 BackgroundDataProducerCallback data_producer_for_background_sequence;
449
450 if (absl::holds_alternative<DataSerializer*>(serializer_)) {
451 std::optional<std::string> data;
452 data = absl::get<DataSerializer*>(serializer_)->SerializeData();
453 if (!data) {
454 DLOG(WARNING) << "Failed to serialize data to be saved in "
455 << path_.value();
456 ClearPendingWrite();
457 return;
458 }
459
460 previous_data_size_ = data->size();
461 data_producer_for_background_sequence = base::BindOnce(
462 [](std::string data) { return std::make_optional(std::move(data)); },
463 std::move(data).value());
464 } else {
465 data_producer_for_background_sequence =
466 absl::get<BackgroundDataSerializer*>(serializer_)
467 ->GetSerializedDataProducerForBackgroundSequence();
468
469 DCHECK(data_producer_for_background_sequence);
470 }
471
472 const TimeDelta serialization_duration =
473 TimeTicks::Now() - serialization_start;
474
475 UmaHistogramTimesWithSuffix("ImportantFile.SerializationDuration",
476 histogram_suffix_, serialization_duration);
477
478 WriteNowWithBackgroundDataProducer(
479 std::move(data_producer_for_background_sequence));
480 DCHECK(!HasPendingWrite());
481 }
482
RegisterOnNextWriteCallbacks(OnceClosure before_next_write_callback,OnceCallback<void (bool success)> after_next_write_callback)483 void ImportantFileWriter::RegisterOnNextWriteCallbacks(
484 OnceClosure before_next_write_callback,
485 OnceCallback<void(bool success)> after_next_write_callback) {
486 before_next_write_callback_ = std::move(before_next_write_callback);
487 after_next_write_callback_ = std::move(after_next_write_callback);
488 }
489
ClearPendingWrite()490 void ImportantFileWriter::ClearPendingWrite() {
491 timer().Stop();
492 serializer_.emplace<absl::monostate>();
493 }
494
SetTimerForTesting(OneShotTimer * timer_override)495 void ImportantFileWriter::SetTimerForTesting(OneShotTimer* timer_override) {
496 timer_override_ = timer_override;
497 }
498
499 } // namespace base
500