Coverage for /Syzygy/kasko/report_repository.cc

CoverageLines executed / instrumented / missingexe / inst / missLanguageGroup
0.0%00227.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  m :  namespace kasko {
  60    :  
  61  m :  namespace {
  62    :  
  63    :  // The extension used when serializing crash keys.
  64  m :  const base::char16 kCrashKeysFileExtension[] = L".kys";
  65    :  // The extension used to identify minidump files.
  66  m :  const base::char16 kDumpFileExtension[] = L".dmp";
  67    :  // The subdirectory where new reports (minidumps and crash keys) are initially
  68    :  // stored.
  69  m :  const base::char16 kIncomingReportsSubdir[] = L"Incoming";
  70    :  // The subdirectory where reports that have failed once are stored.
  71  m :  const base::char16 kFailedOnceSubdir[] = L"Retry";
  72    :  // The subdirectory where reports that have failed twice are stored.
  73  m :  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  m :  bool LoggedDeleteFile(const base::FilePath& path) {
  79  m :    bool result = base::DeleteFile(path, false);
  80  m :    LOG_IF(ERROR, !result) << "Failed to delete " << path.value();
  81  m :    return result;
  82  m :  }
  83    :  
  84    :  // Takes ownership of a FilePath. The owned path will be deleted when the
  85    :  // ScopedReportFile is destroyed.
  86  m :  class ScopedReportFile {
  87  m :   public:
  88  m :    explicit ScopedReportFile(const base::FilePath& path) : path_(path) {}
  89    :  
  90  m :    ~ScopedReportFile() {
  91  m :      if (!path_.empty())
  92  m :        LoggedDeleteFile(path_);
  93  m :    }
  94    :  
  95    :    // Provides access to the owned value.
  96    :    // @returns the owned path.
  97  m :    base::FilePath Get() const { return path_; }
  98    :  
  99    :    // Releases ownership of the owned path.
 100    :    // @returns the owned path.
 101  m :    base::FilePath Take() {
 102  m :      base::FilePath temp = path_;
 103  m :      path_ = base::FilePath();
 104  m :      return temp;
 105  m :    }
 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  m :    bool Move(const base::FilePath& new_path) {
 112  m :      bool result = base::Move(path_, new_path);
 113  m :      LOG_IF(ERROR, !result) << "Failed to move " << path_.value() << " to "
 114  m :                             << new_path.value();
 115  m :      if (result)
 116  m :        path_ = new_path;
 117  m :      return result;
 118  m :    }
 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  m :    bool UpdateTimestamp(const base::Time& value) {
 124  m :      bool result = false;
 125  m :      if (!path_.empty()) {
 126  m :        result = base::TouchFile(path_, value, value);
 127  m :        LOG_IF(ERROR, !result) << "Failed to update timestamp for "
 128  m :                               << path_.value();
 129  m :      }
 130  m :      return result;
 131  m :    }
 132    :  
 133  m :   private:
 134  m :    base::FilePath path_;
 135    :  
 136  m :    DISALLOW_COPY_AND_ASSIGN(ScopedReportFile);
 137  m :  };
 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  m :  base::FilePath GetCrashKeysFileForDumpFile(
 144  m :      const base::FilePath& minidump_path) {
 145  m :    return minidump_path.ReplaceExtension(kCrashKeysFileExtension);
 146  m :  }
 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  m :  base::FilePath GetDumpFileForCrashKeysFile(
 153  m :      const base::FilePath& crash_keys_path) {
 154  m :    return crash_keys_path.ReplaceExtension(kDumpFileExtension);
 155  m :  }
 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  m :  base::FilePath GetPendingReportFromDirectory(
 164  m :      const base::FilePath& directory,
 165  m :      const base::Time& maximum_timestamp_for_retries) {
 166  m :    base::FileEnumerator file_enumerator(
 167  m :        directory, false, base::FileEnumerator::FILES,
 168  m :        base::string16(L"*") + kDumpFileExtension);
 169    :    // Visit all files in this directory until we find an eligible one.
 170  m :    for (base::FilePath candidate = file_enumerator.Next(); !candidate.empty();
 171  m :         candidate = file_enumerator.Next()) {
 172    :      // Skip dumps with missing crash keys.
 173  m :      if (!base::PathExists(GetCrashKeysFileForDumpFile(candidate))) {
 174  m :        LOG(ERROR) << "Deleting a minidump file with missing crash keys: "
 175  m :                   << candidate.value();
 176  m :        LoggedDeleteFile(candidate);
 177  m :        continue;
 178  m :      }
 179  m :      if (maximum_timestamp_for_retries.is_null())
 180  m :        return candidate;
 181    :  
 182    :      // Check if this file is eligible for retry.
 183  m :      base::FileEnumerator::FileInfo file_info = file_enumerator.GetInfo();
 184  m :      if (file_info.GetLastModifiedTime() <= maximum_timestamp_for_retries)
 185  m :        return candidate;
 186  m :    }
 187  m :    return base::FilePath();
 188  m :  }
 189    :  
 190  m :  void CleanOrphanedCrashKeysFiles(
 191  m :      const base::FilePath& repository_path,
 192  m :      const base::Time& now) {
 193  m :    base::Time one_day_ago(now - base::TimeDelta::FromDays(1));
 194  m :    const base::char16* subdirs[] = {
 195  m :        kIncomingReportsSubdir, kFailedOnceSubdir, kFailedTwiceSubdir};
 196    :  
 197  m :    for (size_t i = 0; i < arraysize(subdirs); ++i) {
 198  m :      base::FileEnumerator file_enumerator(
 199  m :          repository_path.Append(subdirs[i]), false, base::FileEnumerator::FILES,
 200  m :          base::string16(L"*") + kCrashKeysFileExtension);
 201  m :      for (base::FilePath candidate = file_enumerator.Next(); !candidate.empty();
 202  m :           candidate = file_enumerator.Next()) {
 203  m :        if (base::PathExists(GetDumpFileForCrashKeysFile(candidate)))
 204  m :          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  m :        if (file_enumerator.GetInfo().GetLastModifiedTime() >= one_day_ago)
 210  m :          continue;
 211    :  
 212  m :        LOG(ERROR) << "Deleting a crash keys file with missing minidump: "
 213  m :                   << candidate.value();
 214  m :        LoggedDeleteFile(candidate);
 215  m :      }
 216  m :    }
 217  m :  }
 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  m :  std::pair<base::FilePath, base::FilePath> GetPendingReport(
 227  m :      const base::FilePath& repository_path,
 228  m :      const base::Time& now,
 229  m :      const base::TimeDelta& retry_interval) {
 230  m :    struct {
 231  m :      const base::char16* subdir;
 232  m :      const base::char16* failure_subdir;
 233  m :      base::Time retry_cutoff;
 234  m :    } directories[] = {
 235  m :        {kIncomingReportsSubdir, kFailedOnceSubdir, base::Time()},
 236  m :        {kFailedOnceSubdir, kFailedTwiceSubdir, now - retry_interval},
 237  m :        {kFailedTwiceSubdir, nullptr, now - retry_interval}};
 238    :  
 239  m :    for (size_t i = 0; i < arraysize(directories); ++i) {
 240  m :      base::FilePath result = GetPendingReportFromDirectory(
 241  m :          repository_path.Append(directories[i].subdir),
 242  m :          directories[i].retry_cutoff);
 243  m :      if (!result.empty()) {
 244  m :        if (!directories[i].failure_subdir)
 245  m :          return std::make_pair(result, base::FilePath());
 246  m :        return std::make_pair(
 247  m :            result, repository_path.Append(directories[i].failure_subdir));
 248  m :      }
 249  m :    }
 250  m :    return std::pair<base::FilePath, base::FilePath>();
 251  m :  }
 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  m :  void HandleNonpermanentFailure(ScopedReportFile* minidump_file,
 260  m :                                 ScopedReportFile* crash_keys_file,
 261  m :                                 const base::FilePath& destination_directory) {
 262  m :    bool result = base::CreateDirectory(destination_directory);
 263  m :    LOG_IF(ERROR, !result) << "Failed to create destination directory "
 264  m :                           << destination_directory.value();
 265  m :    if (result) {
 266  m :      if (minidump_file->Move(
 267  m :              destination_directory.Append(minidump_file->Get().BaseName()))) {
 268  m :        if (crash_keys_file->Move(destination_directory.Append(
 269  m :                crash_keys_file->Get().BaseName()))) {
 270  m :          minidump_file->Take();
 271  m :          crash_keys_file->Take();
 272  m :        }
 273  m :      }
 274  m :    }
 275  m :  }
 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  m :  void HandlePermanentFailure(const base::FilePath& minidump_path,
 283  m :                              const base::FilePath& crash_keys_path,
 284  m :                              const ReportRepository::PermanentFailureHandler&
 285  m :                                  permanent_failure_handler) {
 286  m :    permanent_failure_handler.Run(minidump_path, crash_keys_path);
 287    :  
 288    :    // In case the handler didn't delete the files, we will.
 289  m :    if (base::PathExists(minidump_path))
 290  m :      LoggedDeleteFile(minidump_path);
 291  m :    if (base::PathExists(crash_keys_path))
 292  m :      LoggedDeleteFile(crash_keys_path);
 293  m :  }
 294    :  
 295  m :  }  // namespace
 296    :  
 297  m :  ReportRepository::ReportRepository(
 298  m :      const base::FilePath& repository_path,
 299  m :      const base::TimeDelta& retry_interval,
 300  m :      const TimeSource& time_source,
 301  m :      const Uploader& uploader,
 302  m :      const PermanentFailureHandler& permanent_failure_handler)
 303  m :      : repository_path_(repository_path),
 304  m :        retry_interval_(retry_interval),
 305  m :        time_source_(time_source),
 306  m :        uploader_(uploader),
 307  m :        permanent_failure_handler_(permanent_failure_handler) {
 308  m :  }
 309    :  
 310  m :  ReportRepository::~ReportRepository() {
 311  m :  }
 312    :  
 313  m :  void ReportRepository::StoreReport(
 314  m :      const base::FilePath& minidump_path,
 315  m :      const std::map<base::string16, base::string16>& crash_keys) {
 316  m :    ScopedReportFile minidump_file(minidump_path);
 317    :  
 318  m :    base::FilePath destination_directory(
 319  m :        repository_path_.Append(kIncomingReportsSubdir));
 320  m :    bool result = base::CreateDirectory(destination_directory);
 321  m :    LOG_IF(ERROR, !result) << "Failed to create destination directory "
 322  m :                           << destination_directory.value();
 323  m :    if (result) {
 324    :      // Choose the location and extension where the minidump will be stored.
 325  m :      base::FilePath minidump_target_path = destination_directory.Append(
 326  m :          minidump_path.BaseName().ReplaceExtension(kDumpFileExtension));
 327  m :      base::FilePath crash_keys_path =
 328  m :          GetCrashKeysFileForDumpFile(minidump_target_path);
 329    :  
 330  m :      if (WriteCrashKeysToFile(crash_keys_path, crash_keys)) {
 331  m :        ScopedReportFile crash_keys_file(crash_keys_path);
 332    :  
 333  m :        if (minidump_file.Move(minidump_target_path)) {
 334  m :          base::Time now = time_source_.Run();
 335  m :          if (minidump_file.UpdateTimestamp(now)) {
 336  m :            if (crash_keys_file.UpdateTimestamp(now)) {
 337    :              // Prevent the files from being deleted.
 338  m :              minidump_file.Take();
 339  m :              crash_keys_file.Take();
 340  m :            }
 341  m :          }
 342  m :        }
 343  m :      }
 344  m :    }
 345  m :  }
 346    :  
 347  m :  bool ReportRepository::UploadPendingReport() {
 348  m :    base::Time now = time_source_.Run();
 349    :  
 350    :    // Do a bit of opportunistic cleanup.
 351  m :    CleanOrphanedCrashKeysFiles(repository_path_, now);
 352    :  
 353  m :    std::pair<base::FilePath, base::FilePath> entry =
 354  m :        GetPendingReport(repository_path_, now, retry_interval_);
 355  m :    ScopedReportFile minidump_file(entry.first);
 356  m :    base::FilePath failure_destination = entry.second;
 357    :  
 358  m :    if (minidump_file.Get().empty())
 359  m :      return true;  // Successful no-op.
 360    :  
 361  m :    ScopedReportFile crash_keys_file(
 362  m :        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  m :    if (minidump_file.UpdateTimestamp(now)) {
 368  m :      if (crash_keys_file.UpdateTimestamp(now)) {
 369    :        // Attempt the upload.
 370  m :        std::map<base::string16, base::string16> crash_keys;
 371  m :        if (ReadCrashKeysFromFile(crash_keys_file.Get(), &crash_keys)) {
 372  m :          if (uploader_.Run(minidump_file.Get(), crash_keys))
 373  m :            return true;
 374  m :        }
 375    :  
 376    :        // We failed.
 377  m :        if (!failure_destination.empty()) {
 378  m :          HandleNonpermanentFailure(&minidump_file, &crash_keys_file,
 379  m :                                    failure_destination);
 380  m :        } else {
 381  m :          HandlePermanentFailure(minidump_file.Take(), crash_keys_file.Take(),
 382  m :                                 permanent_failure_handler_);
 383  m :        }
 384  m :      }
 385  m :    }
 386    :  
 387  m :    return false;
 388  m :  }
 389    :  
 390  m :  bool ReportRepository::HasPendingReports() {
 391  m :    return !GetPendingReport(repository_path_, time_source_.Run(),
 392  m :                             retry_interval_).first.empty();
 393  m :  }
 394    :  
 395  m :  }  // namespace kasko

Coverage information generated Fri Jul 29 11:00:21 2016.