Home | History | Annotate | Download | only in files
      1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
      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 "base/files/important_file_writer.h"
      6 
      7 #include <stddef.h>
      8 #include <stdint.h>
      9 #include <stdio.h>
     10 #include <string>
     11 #include <utility>
     12 
     13 #include "base/bind.h"
     14 #include "base/critical_closure.h"
     15 #include "base/debug/alias.h"
     16 #include "base/files/file.h"
     17 #include "base/files/file_path.h"
     18 #include "base/files/file_util.h"
     19 #include "base/logging.h"
     20 #include "base/macros.h"
     21 #include "base/metrics/histogram.h"
     22 #include "base/numerics/safe_conversions.h"
     23 #include "base/strings/string_number_conversions.h"
     24 #include "base/strings/string_util.h"
     25 #include "base/task_runner.h"
     26 #include "base/task_runner_util.h"
     27 #include "base/threading/thread.h"
     28 #include "base/time/time.h"
     29 #include "build/build_config.h"
     30 
     31 namespace base {
     32 
     33 namespace {
     34 
     35 const int kDefaultCommitIntervalMs = 10000;
     36 
     37 // This enum is used to define the buckets for an enumerated UMA histogram.
     38 // Hence,
     39 //   (a) existing enumerated constants should never be deleted or reordered, and
     40 //   (b) new constants should only be appended at the end of the enumeration.
     41 enum TempFileFailure {
     42   FAILED_CREATING,
     43   FAILED_OPENING,
     44   FAILED_CLOSING,  // Unused.
     45   FAILED_WRITING,
     46   FAILED_RENAMING,
     47   FAILED_FLUSHING,
     48   TEMP_FILE_FAILURE_MAX
     49 };
     50 
     51 void LogFailure(const FilePath& path, TempFileFailure failure_code,
     52                 StringPiece message) {
     53   UMA_HISTOGRAM_ENUMERATION("ImportantFile.TempFileFailures", failure_code,
     54                             TEMP_FILE_FAILURE_MAX);
     55   DPLOG(WARNING) << "temp file failure: " << path.value() << " : " << message;
     56 }
     57 
     58 // Helper function to call WriteFileAtomically() with a
     59 // std::unique_ptr<std::string>.
     60 bool WriteScopedStringToFileAtomically(const FilePath& path,
     61                                        std::unique_ptr<std::string> data) {
     62   return ImportantFileWriter::WriteFileAtomically(path, *data);
     63 }
     64 
     65 }  // namespace
     66 
     67 // static
     68 bool ImportantFileWriter::WriteFileAtomically(const FilePath& path,
     69                                               StringPiece data) {
     70 #if defined(OS_CHROMEOS)
     71   // On Chrome OS, chrome gets killed when it cannot finish shutdown quickly,
     72   // and this function seems to be one of the slowest shutdown steps.
     73   // Include some info to the report for investigation. crbug.com/418627
     74   // TODO(hashimoto): Remove this.
     75   struct {
     76     size_t data_size;
     77     char path[128];
     78   } file_info;
     79   file_info.data_size = data.size();
     80   strlcpy(file_info.path, path.value().c_str(), arraysize(file_info.path));
     81   debug::Alias(&file_info);
     82 #endif
     83 
     84   // Write the data to a temp file then rename to avoid data loss if we crash
     85   // while writing the file. Ensure that the temp file is on the same volume
     86   // as target file, so it can be moved in one step, and that the temp file
     87   // is securely created.
     88   FilePath tmp_file_path;
     89   if (!CreateTemporaryFileInDir(path.DirName(), &tmp_file_path)) {
     90     LogFailure(path, FAILED_CREATING, "could not create temporary file");
     91     return false;
     92   }
     93 
     94   File tmp_file(tmp_file_path, File::FLAG_OPEN | File::FLAG_WRITE);
     95   if (!tmp_file.IsValid()) {
     96     LogFailure(path, FAILED_OPENING, "could not open temporary file");
     97     return false;
     98   }
     99 
    100   // If this fails in the wild, something really bad is going on.
    101   const int data_length = checked_cast<int32_t>(data.length());
    102   int bytes_written = tmp_file.Write(0, data.data(), data_length);
    103   bool flush_success = tmp_file.Flush();
    104   tmp_file.Close();
    105 
    106   if (bytes_written < data_length) {
    107     LogFailure(path, FAILED_WRITING, "error writing, bytes_written=" +
    108                IntToString(bytes_written));
    109     DeleteFile(tmp_file_path, false);
    110     return false;
    111   }
    112 
    113   if (!flush_success) {
    114     LogFailure(path, FAILED_FLUSHING, "error flushing");
    115     DeleteFile(tmp_file_path, false);
    116     return false;
    117   }
    118 
    119   if (!ReplaceFile(tmp_file_path, path, nullptr)) {
    120     LogFailure(path, FAILED_RENAMING, "could not rename temporary file");
    121     DeleteFile(tmp_file_path, false);
    122     return false;
    123   }
    124 
    125   return true;
    126 }
    127 
    128 ImportantFileWriter::ImportantFileWriter(
    129     const FilePath& path,
    130     scoped_refptr<SequencedTaskRunner> task_runner)
    131     : ImportantFileWriter(
    132           path,
    133           std::move(task_runner),
    134           TimeDelta::FromMilliseconds(kDefaultCommitIntervalMs)) {}
    135 
    136 ImportantFileWriter::ImportantFileWriter(
    137     const FilePath& path,
    138     scoped_refptr<SequencedTaskRunner> task_runner,
    139     TimeDelta interval)
    140     : path_(path),
    141       task_runner_(std::move(task_runner)),
    142       serializer_(nullptr),
    143       commit_interval_(interval),
    144       weak_factory_(this) {
    145   DCHECK(CalledOnValidThread());
    146   DCHECK(task_runner_);
    147 }
    148 
    149 ImportantFileWriter::~ImportantFileWriter() {
    150   // We're usually a member variable of some other object, which also tends
    151   // to be our serializer. It may not be safe to call back to the parent object
    152   // being destructed.
    153   DCHECK(!HasPendingWrite());
    154 }
    155 
    156 bool ImportantFileWriter::HasPendingWrite() const {
    157   DCHECK(CalledOnValidThread());
    158   return timer_.IsRunning();
    159 }
    160 
    161 void ImportantFileWriter::WriteNow(std::unique_ptr<std::string> data) {
    162   DCHECK(CalledOnValidThread());
    163   if (!IsValueInRangeForNumericType<int32_t>(data->length())) {
    164     NOTREACHED();
    165     return;
    166   }
    167 
    168   if (HasPendingWrite())
    169     timer_.Stop();
    170 
    171   auto task = Bind(&WriteScopedStringToFileAtomically, path_, Passed(&data));
    172   if (!PostWriteTask(task)) {
    173     // Posting the task to background message loop is not expected
    174     // to fail, but if it does, avoid losing data and just hit the disk
    175     // on the current thread.
    176     NOTREACHED();
    177 
    178     task.Run();
    179   }
    180 }
    181 
    182 void ImportantFileWriter::ScheduleWrite(DataSerializer* serializer) {
    183   DCHECK(CalledOnValidThread());
    184 
    185   DCHECK(serializer);
    186   serializer_ = serializer;
    187 
    188   if (!timer_.IsRunning()) {
    189     timer_.Start(FROM_HERE, commit_interval_, this,
    190                  &ImportantFileWriter::DoScheduledWrite);
    191   }
    192 }
    193 
    194 void ImportantFileWriter::DoScheduledWrite() {
    195   DCHECK(serializer_);
    196   std::unique_ptr<std::string> data(new std::string);
    197   if (serializer_->SerializeData(data.get())) {
    198     WriteNow(std::move(data));
    199   } else {
    200     DLOG(WARNING) << "failed to serialize data to be saved in "
    201                   << path_.value();
    202   }
    203   serializer_ = nullptr;
    204 }
    205 
    206 void ImportantFileWriter::RegisterOnNextSuccessfulWriteCallback(
    207     const Closure& on_next_successful_write) {
    208   DCHECK(on_next_successful_write_.is_null());
    209   on_next_successful_write_ = on_next_successful_write;
    210 }
    211 
    212 bool ImportantFileWriter::PostWriteTask(const Callback<bool()>& task) {
    213   // TODO(gab): This code could always use PostTaskAndReplyWithResult and let
    214   // ForwardSuccessfulWrite() no-op if |on_next_successful_write_| is null, but
    215   // PostTaskAndReply causes memory leaks in tests (crbug.com/371974) and
    216   // suppressing all of those is unrealistic hence we avoid most of them by
    217   // using PostTask() in the typical scenario below.
    218   if (!on_next_successful_write_.is_null()) {
    219     return PostTaskAndReplyWithResult(
    220         task_runner_.get(),
    221         FROM_HERE,
    222         MakeCriticalClosure(task),
    223         Bind(&ImportantFileWriter::ForwardSuccessfulWrite,
    224              weak_factory_.GetWeakPtr()));
    225   }
    226   return task_runner_->PostTask(
    227       FROM_HERE,
    228       MakeCriticalClosure(Bind(IgnoreResult(task))));
    229 }
    230 
    231 void ImportantFileWriter::ForwardSuccessfulWrite(bool result) {
    232   DCHECK(CalledOnValidThread());
    233   if (result && !on_next_successful_write_.is_null()) {
    234     on_next_successful_write_.Run();
    235     on_next_successful_write_.Reset();
    236   }
    237 }
    238 
    239 }  // namespace base
    240