Coverage for /Syzygy/kasko/report_repository.cc

CoverageLines executed / instrumented / missingexe / inst / missLanguageGroup
86.8%1311510.C++source

Line-by-line coverage:

   1    :  // Copyright 2014 Google Inc. 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    :  // -----------------
  16    :  // Repository Format
  17    :  // -----------------
  18    :  //
  19    :  // This file implements a repository for crash reports that are pending upload.
  20    :  // The repository has a single root directory and creates several subdirectories
  21    :  // beneath it:
  22    :  //
  23    :  // <root>/Incoming
  24    :  // <root>/Retry
  25    :  // <root>/Retry 2
  26    :  //
  27    :  // Reports are stored in the repository by creating a minidump file and passing
  28    :  // its path, along with a dictionary of crash keys to StoreReport. The minidump
  29    :  // will be moved into Incoming and its crash keys serialized alongside it. The
  30    :  // minidump will be given a .dmp extension (if it doesn't already have one) and
  31    :  // the crash keys will be in a file having the same basename and a '.kys'
  32    :  // extension.
  33    :  //
  34    :  // After a successful upload, the minidump and crash keys files are deleted.
  35    :  // After a failed upload, a report in "Incoming" will be moved to "Retry", a
  36    :  // report in "Retry" to "Retry 2", and a report from "Retry 2" will be processed
  37    :  // using the configured PermanentFailureHandler.
  38    :  //
  39    :  // When the repository receives or attempts to upload a report the report file
  40    :  // timestamps are updated. While files in "Incoming" are always eligible for
  41    :  // upload, those in "Retry" and "Retry 2" are eligible when their last-modified
  42    :  // date is older than the configured retry interval.
  43    :  //
  44    :  // Orphaned report files (minidumps without crash keys and vice-versa) may be
  45    :  // detected during upload attempts. When receiving new minidumps, we first write
  46    :  // the crash keys to "Incoming" before moving the minidump file in. As a result,
  47    :  // an orphaned minidump file is always an error condition and will be deleted
  48    :  // immediately upon detection. An orphaned crash keys file may occur normally in
  49    :  // the interval before the minidump file is moved. These files are only deleted
  50    :  // when their timestamp is more than a day in the past.
  51    :  
  52    :  #include "syzygy/kasko/report_repository.h"
  53    :  
  54    :  #include "base/file_util.h"
  55    :  #include "base/logging.h"
  56    :  #include "base/files/file_enumerator.h"
  57    :  #include "syzygy/kasko/crash_keys_serialization.h"
  58    :  
  59    :  namespace kasko {
  60    :  
  61    :  namespace {
  62    :  
  63    :  // The extension used when serializing crash keys.
  64    :  const base::char16 kCrashKeysFileExtension[] = L".kys";
  65    :  // The extension used to identify minidump files.
  66    :  const base::char16 kDumpFileExtension[] = L".dmp";
  67    :  // The subdirectory where new reports (minidumps and crash keys) are initially
  68    :  // stored.
  69    :  const base::char16 kIncomingReportsSubdir[] = L"Incoming";
  70    :  // The subdirectory where reports that have failed once are stored.
  71    :  const base::char16 kFailedOnceSubdir[] = L"Retry";
  72    :  // The subdirectory where reports that have failed twice are stored.
  73    :  const base::char16 kFailedTwiceSubdir[] = L"Retry 2";
  74    :  
  75    :  // Deletes a path non-recursively and logs an error in case of failure.
  76    :  // @param path The path to delete.
  77    :  // @returns true if the operation succeeds.
  78  E :  bool LoggedDeleteFile(const base::FilePath& path) {
  79  E :    if (base::DeleteFile(path, false))
  80  E :      return true;
  81  i :    LOG(ERROR) << "Failed to delete " << path.value();
  82  i :    return false;
  83  E :  }
  84    :  
  85    :  // Takes ownership of a FilePath. The owned path will be deleted when the
  86    :  // ScopedReportFile is destroyed.
  87    :  class ScopedReportFile {
  88    :   public:
  89  E :    explicit ScopedReportFile(const base::FilePath& path) : path_(path) {}
  90    :  
  91  E :    ~ScopedReportFile() {
  92  E :      if (!path_.empty())
  93  E :        LoggedDeleteFile(path_);
  94  E :    }
  95    :  
  96    :    // Provides access to the owned value.
  97    :    // @returns the owned path.
  98  E :    base::FilePath Get() const { return path_; }
  99    :  
 100    :    // Releases ownership of the owned path.
 101    :    // @returns the owned path.
 102  E :    base::FilePath Take() {
 103  E :      base::FilePath temp = path_;
 104  E :      path_ = base::FilePath();
 105  E :      return temp;
 106  E :    }
 107    :  
 108    :    // Moves the file pointed to by the owned path, and updates the owned path
 109    :    // to the new path.
 110    :    // @param new_path The full destination path.
 111    :    // @returns true if the operation succeeds.
 112  E :    bool Move(const base::FilePath& new_path) {
 113  E :      if (base::Move(path_, new_path)) {
 114  E :        path_ = new_path;
 115  E :        return true;
 116    :      }
 117  i :      LOG(ERROR) << "Failed to move " << path_.value() << " to "
 118    :                 << new_path.value();
 119  i :      return false;
 120  E :    }
 121    :  
 122    :    // Sets the last-modified timestamp of the file pointed to by the owned path.
 123    :    // @param value The desired timestamp.
 124    :    // @returns true if the operation succeeds.
 125  E :    bool UpdateTimestamp(const base::Time& value) {
 126  E :      if (path_.empty())
 127  i :        return false;
 128  E :      if (base::TouchFile(path_, value, value))
 129  E :        return true;
 130  i :      LOG(ERROR) << "Failed to update timestamp for " << path_.value();
 131  i :      return false;
 132  E :    }
 133    :  
 134    :   private:
 135    :    base::FilePath path_;
 136    :  
 137    :    DISALLOW_COPY_AND_ASSIGN(ScopedReportFile);
 138    :  };
 139    :  
 140    :  // Returns the crash keys file path corresponding to the supplied minidump file
 141    :  // path.
 142    :  // @param minidump_path The path to a minidump file.
 143    :  // @returns The path where the corresponding crash keys file should be stored.
 144    :  base::FilePath GetCrashKeysFileForDumpFile(
 145  E :      const base::FilePath& minidump_path) {
 146  E :    return minidump_path.ReplaceExtension(kCrashKeysFileExtension);
 147  E :  }
 148    :  
 149    :  // Returns the minidump file path corresponding to the supplied crash keys file
 150    :  // path.
 151    :  // @param crash_keys_path The path to a crash keys file.
 152    :  // @returns The path where the corresponding minidump file should be stored.
 153    :  base::FilePath GetDumpFileForCrashKeysFile(
 154  E :      const base::FilePath& crash_keys_path) {
 155  E :    return crash_keys_path.ReplaceExtension(kDumpFileExtension);
 156  E :  }
 157    :  
 158    :  // Returns a minidump that is eligible for upload from the given directory, if
 159    :  // any are.
 160    :  // @param directory The directory to scan.
 161    :  // @param maximum_timestamp_for_retries The cutoff for the most most recent
 162    :  //     upload attempt of eligible minidumps. If null, there is no cutoff.
 163    :  // @returns The path to a minidump that is eligible for upload, if any.
 164    :  base::FilePath GetPendingReportFromDirectory(
 165    :      const base::FilePath& directory,
 166  E :      const base::Time& maximum_timestamp_for_retries) {
 167    :    base::FileEnumerator file_enumerator(
 168    :        directory, false, base::FileEnumerator::FILES,
 169  E :        base::string16(L"*") + kDumpFileExtension);
 170    :    // Visit all files in this directory until we find an eligible one.
 171  E :    for (base::FilePath candidate = file_enumerator.Next(); !candidate.empty();
 172  E :         candidate = file_enumerator.Next()) {
 173    :      // Skip dumps with missing crash keys.
 174  E :      if (!base::PathExists(GetCrashKeysFileForDumpFile(candidate))) {
 175  E :        LOG(ERROR) << "Deleting a minidump file with missing crash keys: "
 176    :                   << candidate.value();
 177  E :        LoggedDeleteFile(candidate);
 178  E :        continue;
 179    :      }
 180  E :      if (maximum_timestamp_for_retries.is_null())
 181  E :        return candidate;
 182    :  
 183    :      // Check if this file is eligible for retry.
 184  E :      base::FileEnumerator::FileInfo file_info = file_enumerator.GetInfo();
 185  E :      if (file_info.GetLastModifiedTime() <= maximum_timestamp_for_retries)
 186  E :        return candidate;
 187  E :    }
 188  E :    return base::FilePath();
 189  E :  }
 190    :  
 191    :  void CleanOrphanedCrashKeysFiles(
 192    :      const base::FilePath& repository_path,
 193  E :      const base::Time& now) {
 194  E :    base::Time one_day_ago(now - base::TimeDelta::FromDays(1));
 195    :    const base::char16* subdirs[] = {
 196  E :        kIncomingReportsSubdir, kFailedOnceSubdir, kFailedTwiceSubdir};
 197    :  
 198  E :    for (size_t i = 0; i < arraysize(subdirs); ++i) {
 199    :      base::FileEnumerator file_enumerator(
 200    :          repository_path.Append(subdirs[i]), false, base::FileEnumerator::FILES,
 201  E :          base::string16(L"*") + kCrashKeysFileExtension);
 202  E :      for (base::FilePath candidate = file_enumerator.Next(); !candidate.empty();
 203  E :           candidate = file_enumerator.Next()) {
 204  E :        if (base::PathExists(GetDumpFileForCrashKeysFile(candidate)))
 205  E :          continue;
 206    :  
 207    :        // We write crash keys files before moving dump files, so there is a brief
 208    :        // period where an orphan might be expected. Only delete orphans that are
 209    :        // more than a day old.
 210  E :        if (file_enumerator.GetInfo().GetLastModifiedTime() >= one_day_ago)
 211  E :          continue;
 212    :  
 213  E :        LOG(ERROR) << "Deleting a crash keys file with missing minidump: "
 214    :                   << candidate.value();
 215  E :        LoggedDeleteFile(candidate);
 216  E :      }
 217  E :    }
 218  E :  }
 219    :  
 220    :  // Returns a minidump that is eligible for upload, if any are.
 221    :  // @param repository_path The directory where this repository stores reports.
 222    :  // @param now The current time.
 223    :  // @param retry_interval The minimum interval between upload attempts for a
 224    :  //     given report.
 225    :  // @returns A pair of mindump path (empty if none) and failure destination
 226    :  //     (empty if the next failure is permanent).
 227    :  std::pair<base::FilePath, base::FilePath> GetPendingReport(
 228    :      const base::FilePath& repository_path,
 229    :      const base::Time& now,
 230  E :      const base::TimeDelta& retry_interval) {
 231    :    struct {
 232    :      const base::char16* subdir;
 233    :      const base::char16* failure_subdir;
 234    :      base::Time retry_cutoff;
 235    :    } directories[] = {
 236    :        {kIncomingReportsSubdir, kFailedOnceSubdir, base::Time()},
 237    :        {kFailedOnceSubdir, kFailedTwiceSubdir, now - retry_interval},
 238  E :        {kFailedTwiceSubdir, nullptr, now - retry_interval}};
 239    :  
 240  E :    for (size_t i = 0; i < arraysize(directories); ++i) {
 241    :      base::FilePath result = GetPendingReportFromDirectory(
 242    :          repository_path.Append(directories[i].subdir),
 243  E :          directories[i].retry_cutoff);
 244  E :      if (result.empty())
 245  E :        continue;
 246  E :      if (!directories[i].failure_subdir)
 247  E :        return std::make_pair(result, base::FilePath());
 248    :      return std::make_pair(
 249  E :          result, repository_path.Append(directories[i].failure_subdir));
 250  i :    }
 251  E :    return std::pair<base::FilePath, base::FilePath>();
 252  E :  }
 253    :  
 254    :  // Handles a non-permanent failure by moving the report files to a new queue.
 255    :  // @param minidump_file The minidump file. This method calls Take() on success.
 256    :  // @param crash_keys_file The crash keys file. This method calls Take() on
 257    :  //     success.
 258    :  // @param destination_directory The directory where the files should be moved
 259    :  //     to.
 260    :  void HandleNonpermanentFailure(ScopedReportFile* minidump_file,
 261    :                                 ScopedReportFile* crash_keys_file,
 262  E :                                 const base::FilePath& destination_directory) {
 263  E :    if (!base::CreateDirectory(destination_directory)) {
 264  i :      LOG(ERROR) << "Failed to create destination directory "
 265    :                 << destination_directory.value();
 266  i :      return;
 267    :    }
 268    :    if (!minidump_file->Move(
 269  E :            destination_directory.Append(minidump_file->Get().BaseName()))) {
 270  i :      return;
 271    :    }
 272    :    if (!crash_keys_file->Move(
 273  E :            destination_directory.Append(crash_keys_file->Get().BaseName()))) {
 274  i :      return;
 275    :    }
 276  E :    minidump_file->Take();
 277  E :    crash_keys_file->Take();
 278  E :  }
 279    :  
 280    :  // Handles a permanent failure by invoking the PermanentFailureHandler. Ensures
 281    :  // that the report files are removed from the repository.
 282    :  // @param minidump_path The path to the minidump file.
 283    :  // @param crash_keys_path The path to the crash keys file.
 284    :  // @param permanent_failure_handler The PermanentFailureHandler to invoke.
 285    :  void HandlePermanentFailure(const base::FilePath& minidump_path,
 286    :                              const base::FilePath& crash_keys_path,
 287    :                              const ReportRepository::PermanentFailureHandler&
 288  E :                                  permanent_failure_handler) {
 289  E :    permanent_failure_handler.Run(minidump_path, crash_keys_path);
 290    :  
 291    :    // In case the handler didn't delete the files, we will.
 292  E :    if (base::PathExists(minidump_path))
 293  E :      LoggedDeleteFile(minidump_path);
 294  E :    if (base::PathExists(crash_keys_path))
 295  E :      LoggedDeleteFile(crash_keys_path);
 296  E :  }
 297    :  
 298    :  }  // namespace
 299    :  
 300    :  ReportRepository::ReportRepository(
 301    :      const base::FilePath& repository_path,
 302    :      const base::TimeDelta& retry_interval,
 303    :      const TimeSource& time_source,
 304    :      const Uploader& uploader,
 305    :      const PermanentFailureHandler& permanent_failure_handler)
 306    :      : repository_path_(repository_path),
 307    :        retry_interval_(retry_interval),
 308    :        time_source_(time_source),
 309    :        uploader_(uploader),
 310  E :        permanent_failure_handler_(permanent_failure_handler) {
 311  E :  }
 312    :  
 313  E :  ReportRepository::~ReportRepository() {
 314  E :  }
 315    :  
 316    :  void ReportRepository::StoreReport(
 317    :      const base::FilePath& minidump_path,
 318  E :      const std::map<base::string16, base::string16>& crash_keys) {
 319  E :    ScopedReportFile minidump_file(minidump_path);
 320    :  
 321    :    base::FilePath destination_directory(
 322  E :        repository_path_.Append(kIncomingReportsSubdir));
 323  E :    if (!base::CreateDirectory(destination_directory)) {
 324  i :      LOG(ERROR) << "Failed to create target directory "
 325    :                 << destination_directory.value();
 326  i :      return;
 327    :    }
 328    :  
 329    :    // Choose the location and extension where the minidump will be stored.
 330    :    base::FilePath minidump_target_path = destination_directory.Append(
 331  E :        minidump_path.BaseName().ReplaceExtension(kDumpFileExtension));
 332    :    base::FilePath crash_keys_path =
 333  E :        GetCrashKeysFileForDumpFile(minidump_target_path);
 334    :  
 335  E :    if (!WriteCrashKeysToFile(crash_keys_path, crash_keys))
 336  i :      return;
 337  E :    ScopedReportFile crash_keys_file(crash_keys_path);
 338    :  
 339  E :    if (!minidump_file.Move(minidump_target_path))
 340  i :      return;
 341    :  
 342  E :    base::Time now = time_source_.Run();
 343  E :    if (!minidump_file.UpdateTimestamp(now))
 344  i :      return;
 345  E :    if (!crash_keys_file.UpdateTimestamp(now))
 346  i :      return;
 347    :  
 348    :    // Prevent the files from being deleted.
 349  E :    minidump_file.Take();
 350  E :    crash_keys_file.Take();
 351  E :  }
 352    :  
 353  E :  bool ReportRepository::UploadPendingReport() {
 354  E :    base::Time now = time_source_.Run();
 355    :  
 356    :    // Do a bit of opportunistic cleanup.
 357  E :    CleanOrphanedCrashKeysFiles(repository_path_, now);
 358    :  
 359    :    std::pair<base::FilePath, base::FilePath> entry =
 360  E :        GetPendingReport(repository_path_, now, retry_interval_);
 361  E :    ScopedReportFile minidump_file(entry.first);
 362  E :    base::FilePath failure_destination = entry.second;
 363    :  
 364  E :    if (minidump_file.Get().empty())
 365  E :      return true;  // Successful no-op.
 366    :  
 367    :    ScopedReportFile crash_keys_file(
 368  E :        GetCrashKeysFileForDumpFile(minidump_file.Get()));
 369    :  
 370    :    // Renew the file timestamps before attempting upload. If we are unable to do
 371    :    // this, make no upload attempt (since that would potentially lead to a hot
 372    :    // loop of upload attempts).
 373  E :    if (!minidump_file.UpdateTimestamp(now))
 374  i :      return false;
 375  E :    if (!crash_keys_file.UpdateTimestamp(now))
 376  i :      return false;
 377    :  
 378    :    // Attempt the upload.
 379  E :    std::map<base::string16, base::string16> crash_keys;
 380  E :    if (ReadCrashKeysFromFile(crash_keys_file.Get(), &crash_keys)) {
 381  E :      if (uploader_.Run(minidump_file.Get(), crash_keys))
 382  E :        return true;
 383    :    }
 384    :  
 385    :    // We failed.
 386  E :    if (!failure_destination.empty()) {
 387    :      HandleNonpermanentFailure(&minidump_file, &crash_keys_file,
 388  E :                                failure_destination);
 389  E :    } else {
 390    :      HandlePermanentFailure(minidump_file.Take(), crash_keys_file.Take(),
 391  E :                             permanent_failure_handler_);
 392    :    }
 393  E :    return false;
 394  E :  }
 395    :  
 396  E :  bool ReportRepository::HasPendingReports() {
 397    :    return !GetPendingReport(repository_path_, time_source_.Run(),
 398  E :                             retry_interval_).first.empty();
 399  E :  }
 400    :  
 401    :  }  // namespace kasko

Coverage information generated Thu Mar 26 16:15:41 2015.