Coverage for /Syzygy/kasko/report_repository.cc

CoverageLines executed / instrumented / missingexe / inst / missLanguageGroup
100.0%1431430.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/logging.h"
  55    :  #include "base/files/file_enumerator.h"
  56    :  #include "base/files/file_util.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 :    bool result = base::DeleteFile(path, false);
  80  E :    LOG_IF(ERROR, !result) << "Failed to delete " << path.value();
  81  E :    return result;
  82  E :  }
  83    :  
  84    :  // Takes ownership of a FilePath. The owned path will be deleted when the
  85    :  // ScopedReportFile is destroyed.
  86    :  class ScopedReportFile {
  87    :   public:
  88  E :    explicit ScopedReportFile(const base::FilePath& path) : path_(path) {}
  89    :  
  90  E :    ~ScopedReportFile() {
  91  E :      if (!path_.empty())
  92  E :        LoggedDeleteFile(path_);
  93  E :    }
  94    :  
  95    :    // Provides access to the owned value.
  96    :    // @returns the owned path.
  97  E :    base::FilePath Get() const { return path_; }
  98    :  
  99    :    // Releases ownership of the owned path.
 100    :    // @returns the owned path.
 101  E :    base::FilePath Take() {
 102  E :      base::FilePath temp = path_;
 103  E :      path_ = base::FilePath();
 104  E :      return temp;
 105  E :    }
 106    :  
 107    :    // Moves the file pointed to by the owned path, and updates the owned path
 108    :    // to the new path.
 109    :    // @param new_path The full destination path.
 110    :    // @returns true if the operation succeeds.
 111  E :    bool Move(const base::FilePath& new_path) {
 112  E :      bool result = base::Move(path_, new_path);
 113  E :      LOG_IF(ERROR, !result) << "Failed to move " << path_.value() << " to "
 114    :                             << new_path.value();
 115  E :      if (result)
 116  E :        path_ = new_path;
 117  E :      return result;
 118  E :    }
 119    :  
 120    :    // Sets the last-modified timestamp of the file pointed to by the owned path.
 121    :    // @param value The desired timestamp.
 122    :    // @returns true if the operation succeeds.
 123  E :    bool UpdateTimestamp(const base::Time& value) {
 124  E :      bool result = false;
 125  E :      if (!path_.empty()) {
 126  E :        result = base::TouchFile(path_, value, value);
 127  E :        LOG_IF(ERROR, !result) << "Failed to update timestamp for "
 128    :                               << path_.value();
 129    :      }
 130  E :      return result;
 131  E :    }
 132    :  
 133    :   private:
 134    :    base::FilePath path_;
 135    :  
 136    :    DISALLOW_COPY_AND_ASSIGN(ScopedReportFile);
 137    :  };
 138    :  
 139    :  // Returns the crash keys file path corresponding to the supplied minidump file
 140    :  // path.
 141    :  // @param minidump_path The path to a minidump file.
 142    :  // @returns The path where the corresponding crash keys file should be stored.
 143    :  base::FilePath GetCrashKeysFileForDumpFile(
 144  E :      const base::FilePath& minidump_path) {
 145  E :    return minidump_path.ReplaceExtension(kCrashKeysFileExtension);
 146  E :  }
 147    :  
 148    :  // Returns the minidump file path corresponding to the supplied crash keys file
 149    :  // path.
 150    :  // @param crash_keys_path The path to a crash keys file.
 151    :  // @returns The path where the corresponding minidump file should be stored.
 152    :  base::FilePath GetDumpFileForCrashKeysFile(
 153  E :      const base::FilePath& crash_keys_path) {
 154  E :    return crash_keys_path.ReplaceExtension(kDumpFileExtension);
 155  E :  }
 156    :  
 157    :  // Returns a minidump that is eligible for upload from the given directory, if
 158    :  // any are.
 159    :  // @param directory The directory to scan.
 160    :  // @param maximum_timestamp_for_retries The cutoff for the most most recent
 161    :  //     upload attempt of eligible minidumps. If null, there is no cutoff.
 162    :  // @returns The path to a minidump that is eligible for upload, if any.
 163    :  base::FilePath GetPendingReportFromDirectory(
 164    :      const base::FilePath& directory,
 165  E :      const base::Time& maximum_timestamp_for_retries) {
 166    :    base::FileEnumerator file_enumerator(
 167    :        directory, false, base::FileEnumerator::FILES,
 168  E :        base::string16(L"*") + kDumpFileExtension);
 169    :    // Visit all files in this directory until we find an eligible one.
 170  E :    for (base::FilePath candidate = file_enumerator.Next(); !candidate.empty();
 171  E :         candidate = file_enumerator.Next()) {
 172    :      // Skip dumps with missing crash keys.
 173  E :      if (!base::PathExists(GetCrashKeysFileForDumpFile(candidate))) {
 174  E :        LOG(ERROR) << "Deleting a minidump file with missing crash keys: "
 175    :                   << candidate.value();
 176  E :        LoggedDeleteFile(candidate);
 177  E :        continue;
 178    :      }
 179  E :      if (maximum_timestamp_for_retries.is_null())
 180  E :        return candidate;
 181    :  
 182    :      // Check if this file is eligible for retry.
 183  E :      base::FileEnumerator::FileInfo file_info = file_enumerator.GetInfo();
 184  E :      if (file_info.GetLastModifiedTime() <= maximum_timestamp_for_retries)
 185  E :        return candidate;
 186  E :    }
 187  E :    return base::FilePath();
 188  E :  }
 189    :  
 190    :  void CleanOrphanedCrashKeysFiles(
 191    :      const base::FilePath& repository_path,
 192  E :      const base::Time& now) {
 193  E :    base::Time one_day_ago(now - base::TimeDelta::FromDays(1));
 194    :    const base::char16* subdirs[] = {
 195  E :        kIncomingReportsSubdir, kFailedOnceSubdir, kFailedTwiceSubdir};
 196    :  
 197  E :    for (size_t i = 0; i < arraysize(subdirs); ++i) {
 198    :      base::FileEnumerator file_enumerator(
 199    :          repository_path.Append(subdirs[i]), false, base::FileEnumerator::FILES,
 200  E :          base::string16(L"*") + kCrashKeysFileExtension);
 201  E :      for (base::FilePath candidate = file_enumerator.Next(); !candidate.empty();
 202  E :           candidate = file_enumerator.Next()) {
 203  E :        if (base::PathExists(GetDumpFileForCrashKeysFile(candidate)))
 204  E :          continue;
 205    :  
 206    :        // We write crash keys files before moving dump files, so there is a brief
 207    :        // period where an orphan might be expected. Only delete orphans that are
 208    :        // more than a day old.
 209  E :        if (file_enumerator.GetInfo().GetLastModifiedTime() >= one_day_ago)
 210  E :          continue;
 211    :  
 212  E :        LOG(ERROR) << "Deleting a crash keys file with missing minidump: "
 213    :                   << candidate.value();
 214  E :        LoggedDeleteFile(candidate);
 215  E :      }
 216  E :    }
 217  E :  }
 218    :  
 219    :  // Returns a minidump that is eligible for upload, if any are.
 220    :  // @param repository_path The directory where this repository stores reports.
 221    :  // @param now The current time.
 222    :  // @param retry_interval The minimum interval between upload attempts for a
 223    :  //     given report.
 224    :  // @returns A pair of mindump path (empty if none) and failure destination
 225    :  //     (empty if the next failure is permanent).
 226    :  std::pair<base::FilePath, base::FilePath> GetPendingReport(
 227    :      const base::FilePath& repository_path,
 228    :      const base::Time& now,
 229  E :      const base::TimeDelta& retry_interval) {
 230    :    struct {
 231    :      const base::char16* subdir;
 232    :      const base::char16* failure_subdir;
 233    :      base::Time retry_cutoff;
 234    :    } directories[] = {
 235    :        {kIncomingReportsSubdir, kFailedOnceSubdir, base::Time()},
 236    :        {kFailedOnceSubdir, kFailedTwiceSubdir, now - retry_interval},
 237  E :        {kFailedTwiceSubdir, nullptr, now - retry_interval}};
 238    :  
 239  E :    for (size_t i = 0; i < arraysize(directories); ++i) {
 240    :      base::FilePath result = GetPendingReportFromDirectory(
 241    :          repository_path.Append(directories[i].subdir),
 242  E :          directories[i].retry_cutoff);
 243  E :      if (!result.empty()) {
 244  E :        if (!directories[i].failure_subdir)
 245  E :          return std::make_pair(result, base::FilePath());
 246    :        return std::make_pair(
 247  E :            result, repository_path.Append(directories[i].failure_subdir));
 248    :      }
 249  E :    }
 250  E :    return std::pair<base::FilePath, base::FilePath>();
 251  E :  }
 252    :  
 253    :  // Handles a non-permanent failure by moving the report files to a new queue.
 254    :  // @param minidump_file The minidump file. This method calls Take() on success.
 255    :  // @param crash_keys_file The crash keys file. This method calls Take() on
 256    :  //     success.
 257    :  // @param destination_directory The directory where the files should be moved
 258    :  //     to.
 259    :  void HandleNonpermanentFailure(ScopedReportFile* minidump_file,
 260    :                                 ScopedReportFile* crash_keys_file,
 261  E :                                 const base::FilePath& destination_directory) {
 262  E :    bool result = base::CreateDirectory(destination_directory);
 263  E :    LOG_IF(ERROR, !result) << "Failed to create destination directory "
 264    :                           << destination_directory.value();
 265  E :    if (result) {
 266    :      if (minidump_file->Move(
 267  E :              destination_directory.Append(minidump_file->Get().BaseName()))) {
 268    :        if (crash_keys_file->Move(destination_directory.Append(
 269  E :                crash_keys_file->Get().BaseName()))) {
 270  E :          minidump_file->Take();
 271  E :          crash_keys_file->Take();
 272    :        }
 273    :      }
 274    :    }
 275  E :  }
 276    :  
 277    :  // Handles a permanent failure by invoking the PermanentFailureHandler. Ensures
 278    :  // that the report files are removed from the repository.
 279    :  // @param minidump_path The path to the minidump file.
 280    :  // @param crash_keys_path The path to the crash keys file.
 281    :  // @param permanent_failure_handler The PermanentFailureHandler to invoke.
 282    :  void HandlePermanentFailure(const base::FilePath& minidump_path,
 283    :                              const base::FilePath& crash_keys_path,
 284    :                              const ReportRepository::PermanentFailureHandler&
 285  E :                                  permanent_failure_handler) {
 286  E :    permanent_failure_handler.Run(minidump_path, crash_keys_path);
 287    :  
 288    :    // In case the handler didn't delete the files, we will.
 289  E :    if (base::PathExists(minidump_path))
 290  E :      LoggedDeleteFile(minidump_path);
 291  E :    if (base::PathExists(crash_keys_path))
 292  E :      LoggedDeleteFile(crash_keys_path);
 293  E :  }
 294    :  
 295    :  }  // namespace
 296    :  
 297    :  ReportRepository::ReportRepository(
 298    :      const base::FilePath& repository_path,
 299    :      const base::TimeDelta& retry_interval,
 300    :      const TimeSource& time_source,
 301    :      const Uploader& uploader,
 302    :      const PermanentFailureHandler& permanent_failure_handler)
 303    :      : repository_path_(repository_path),
 304    :        retry_interval_(retry_interval),
 305    :        time_source_(time_source),
 306    :        uploader_(uploader),
 307  E :        permanent_failure_handler_(permanent_failure_handler) {
 308  E :  }
 309    :  
 310  E :  ReportRepository::~ReportRepository() {
 311  E :  }
 312    :  
 313    :  void ReportRepository::StoreReport(
 314    :      const base::FilePath& minidump_path,
 315  E :      const std::map<base::string16, base::string16>& crash_keys) {
 316  E :    ScopedReportFile minidump_file(minidump_path);
 317    :  
 318    :    base::FilePath destination_directory(
 319  E :        repository_path_.Append(kIncomingReportsSubdir));
 320  E :    bool result = base::CreateDirectory(destination_directory);
 321  E :    LOG_IF(ERROR, !result) << "Failed to create destination directory "
 322    :                           << destination_directory.value();
 323  E :    if (result) {
 324    :      // Choose the location and extension where the minidump will be stored.
 325    :      base::FilePath minidump_target_path = destination_directory.Append(
 326  E :          minidump_path.BaseName().ReplaceExtension(kDumpFileExtension));
 327    :      base::FilePath crash_keys_path =
 328  E :          GetCrashKeysFileForDumpFile(minidump_target_path);
 329    :  
 330  E :      if (WriteCrashKeysToFile(crash_keys_path, crash_keys)) {
 331  E :        ScopedReportFile crash_keys_file(crash_keys_path);
 332    :  
 333  E :        if (minidump_file.Move(minidump_target_path)) {
 334  E :          base::Time now = time_source_.Run();
 335  E :          if (minidump_file.UpdateTimestamp(now)) {
 336  E :            if (crash_keys_file.UpdateTimestamp(now)) {
 337    :              // Prevent the files from being deleted.
 338  E :              minidump_file.Take();
 339  E :              crash_keys_file.Take();
 340    :            }
 341    :          }
 342    :        }
 343  E :      }
 344  E :    }
 345  E :  }
 346    :  
 347  E :  bool ReportRepository::UploadPendingReport() {
 348  E :    base::Time now = time_source_.Run();
 349    :  
 350    :    // Do a bit of opportunistic cleanup.
 351  E :    CleanOrphanedCrashKeysFiles(repository_path_, now);
 352    :  
 353    :    std::pair<base::FilePath, base::FilePath> entry =
 354  E :        GetPendingReport(repository_path_, now, retry_interval_);
 355  E :    ScopedReportFile minidump_file(entry.first);
 356  E :    base::FilePath failure_destination = entry.second;
 357    :  
 358  E :    if (minidump_file.Get().empty())
 359  E :      return true;  // Successful no-op.
 360    :  
 361    :    ScopedReportFile crash_keys_file(
 362  E :        GetCrashKeysFileForDumpFile(minidump_file.Get()));
 363    :  
 364    :    // Renew the file timestamps before attempting upload. If we are unable to do
 365    :    // this, make no upload attempt (since that would potentially lead to a hot
 366    :    // loop of upload attempts).
 367  E :    if (minidump_file.UpdateTimestamp(now)) {
 368  E :      if (crash_keys_file.UpdateTimestamp(now)) {
 369    :        // Attempt the upload.
 370  E :        std::map<base::string16, base::string16> crash_keys;
 371  E :        if (ReadCrashKeysFromFile(crash_keys_file.Get(), &crash_keys)) {
 372  E :          if (uploader_.Run(minidump_file.Get(), crash_keys))
 373  E :            return true;
 374    :        }
 375    :  
 376    :        // We failed.
 377  E :        if (!failure_destination.empty()) {
 378    :          HandleNonpermanentFailure(&minidump_file, &crash_keys_file,
 379  E :                                    failure_destination);
 380  E :        } else {
 381    :          HandlePermanentFailure(minidump_file.Take(), crash_keys_file.Take(),
 382  E :                                 permanent_failure_handler_);
 383    :        }
 384  E :      }
 385    :    }
 386    :  
 387  E :    return false;
 388  E :  }
 389    :  
 390  E :  bool ReportRepository::HasPendingReports() {
 391    :    return !GetPendingReport(repository_path_, time_source_.Run(),
 392  E :                             retry_interval_).first.empty();
 393  E :  }
 394    :  
 395    :  }  // namespace kasko

Coverage information generated Thu Jan 14 17:40:38 2016.