File: crash_directory_watcher.cc

package info (click to toggle)
chromium 139.0.7258.127-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 6,122,068 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (160 lines) | stat: -rw-r--r-- 6,388 bytes parent folder | download | duplicates (6)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "remoting/host/crash/crash_directory_watcher.h"

#include <string>

#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/task/single_thread_task_runner.h"

namespace remoting {

namespace {

const base::FilePath::CharType kDumpExtension[] = FILE_PATH_LITERAL("dmp");
const base::FilePath::CharType kJsonExtension[] = FILE_PATH_LITERAL("json");

// Dmp files are of the form: crash_directory/<crash_guid>.dmp
// Metadata files are of the form: crash_directory/<crash_guid>.json
// Upload directory is of the form: crash_directory/<crash_guid>/
bool PrepareFilesForUpload(const base::FilePath& crash_directory,
                           const base::FilePath& crash_guid) {
  base::FilePath minidump_file =
      crash_directory.Append(crash_guid).AddExtension(kDumpExtension);
  if (!base::PathExists(minidump_file)) {
    LOG(WARNING) << "Minidump not found for crash: " << crash_guid;
    return false;
  }

  base::FilePath upload_directory = crash_directory.Append(crash_guid);
  if (base::PathExists(upload_directory)) {
    LOG(ERROR) << "Upload directory already exists for report: " << crash_guid;
    return false;
  }

  // We have a minidump and metadata file for this crash report so move them
  // into a sub-directory so they can be uploaded.
  base::File::Error error;
  if (!base::CreateDirectoryAndGetError(upload_directory, &error)) {
    LOG(ERROR) << "Failed to create directory for crash report: " << crash_guid
               << " due to error: " << error;
    return false;
  }

  base::FilePath metadata_file =
      crash_directory.Append(crash_guid).AddExtension(kJsonExtension);
  if (!base::Move(metadata_file,
                  upload_directory.Append(metadata_file.BaseName()))) {
    LOG(ERROR) << "Failed to move crash json file into upload directory for "
               << "crash: " << crash_guid;
    return false;
  }

  if (!base::Move(minidump_file,
                  upload_directory.Append(minidump_file.BaseName()))) {
    LOG(ERROR) << "Failed to move minidump file into upload directory for "
               << "crash: " << crash_guid;
    return false;
  }

  return true;
}

void DeleteCrashFiles(const base::FilePath& crash_directory,
                      const base::FilePath& crash_guid) {
  base::FilePath minidump_file =
      crash_directory.Append(crash_guid).AddExtension(kDumpExtension);
  if (!base::DeleteFile(minidump_file)) {
    PLOG(ERROR) << "Failed to delete " << minidump_file;
  }
  base::FilePath metadata_file =
      crash_directory.Append(crash_guid).AddExtension(kJsonExtension);
  if (!base::DeleteFile(metadata_file)) {
    PLOG(ERROR) << "Failed to delete " << metadata_file;
  }
}

}  // namespace

CrashDirectoryWatcher::CrashDirectoryWatcher() = default;
CrashDirectoryWatcher::~CrashDirectoryWatcher() = default;

void CrashDirectoryWatcher::Watch(base::FilePath crash_directory,
                                  UploadCallback callback) {
  LOG(INFO) << "Watching for crash minidumps in: " << crash_directory;
  DCHECK(!upload_callback_);
  upload_callback_ = std::move(callback);

  // TODO(joedow): Check |directory_to_watch| to see if there are any pending
  // uploads (i.e. crash_guid directories with a dump file) and run the callback
  // for their IDs.

  // Run a check when Watch() is first called in case there are DMP files left
  // over from a previous run.
  OnFileChangeDetected(crash_directory, false);

  // Unretained is sound as |file_path_watcher_| is owned by this instance and
  // the callback is run synchronously.
  // FilePathWatcher is an old class and isn't well documented (i.e. some of the
  // comments are contradictory and don't match reality). In our case, we are
  // using kNonRecursive which will trigger the callback for any changes in the
  // watched path on Linux and Windows. If this class is reused on other
  // platforms, we will need to confirm what the behavior is on the new
  // platform(s).
  file_path_watcher_.Watch(
      std::move(crash_directory), base::FilePathWatcher::Type::kNonRecursive,
      base::BindRepeating(&CrashDirectoryWatcher::OnFileChangeDetected,
                          base::Unretained(this)));
}

void CrashDirectoryWatcher::OnFileChangeDetected(const base::FilePath& path,
                                                 bool error) {
  if (error) {
    LOG(ERROR) << "Error watching crash directory: " << path;
    // TODO(joedow): Consider terminating the process so the folder to watch can
    // be recreated with the appropriate permissions.
    return;
  }

  // When the process crashes, a dmp file will be written followed by a metadata
  // json file. Thus if the metadata file exists we can assume the dmp is also
  // present and ready for upload.
  // We need to watch for new metadata files in the crash directory but not in
  // any descendants (sub-directories) as this instance will move the dmp and
  // json file to a new directory when they are ready to be uploaded and we
  // don't want to enumerate any files which have already been moved.
  base::FileEnumerator metadata_file_enumerator(
      path, /*recursive=*/false, base::FileEnumerator::FileType::FILES,
      FILE_PATH_LITERAL("*.json"));

  std::vector<base::FilePath> crash_guids;
  base::FilePath metadata_file = metadata_file_enumerator.Next();
  while (!metadata_file.empty()) {
    base::FilePath crash_guid = metadata_file.BaseName().RemoveExtension();
    LOG(INFO) << "Found new crash report: " << crash_guid;
    crash_guids.push_back(std::move(crash_guid));
    metadata_file = metadata_file_enumerator.Next();
  }

  for (const auto& crash_guid : crash_guids) {
    if (PrepareFilesForUpload(path, crash_guid)) {
      LOG(INFO) << "Crash report ready for upload: " << crash_guid;
      upload_callback_.Run(crash_guid);
    } else {
      LOG(ERROR) << "Deleting invalid crash report files for " << crash_guid;
      DeleteCrashFiles(path, crash_guid);
    }
  }

  auto last_error = metadata_file_enumerator.GetError();
  LOG_IF(WARNING, last_error != base::File::FILE_OK)
      << "File enumeration failed: " << last_error;
}

}  // namespace remoting