blob: e91f70a12056975a50212506ac67dd2cb807198b [file] [log] [blame]
Ken Mixter03403162010-08-18 15:23:16 -07001// Copyright (c) 2010 The Chromium OS 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 "crash-reporter/crash_collector.h"
6
Ken Mixter04ec10f2010-08-26 16:02:02 -07007#include <dirent.h>
Ken Mixter03403162010-08-18 15:23:16 -07008#include <pwd.h> // For struct passwd.
9#include <sys/types.h> // for mode_t.
10
Ken Mixteree849c52010-09-30 15:30:10 -070011#include <set>
12
Ken Mixter03403162010-08-18 15:23:16 -070013#include "base/file_util.h"
14#include "base/logging.h"
15#include "base/string_util.h"
16#include "crash-reporter/system_logging.h"
17
18static const char kDefaultUserName[] = "chronos";
Ken Mixteree849c52010-09-30 15:30:10 -070019static const char kLsbRelease[] = "/etc/lsb-release";
Ken Mixter03403162010-08-18 15:23:16 -070020static const char kSystemCrashPath[] = "/var/spool/crash";
21static const char kUserCrashPath[] = "/home/chronos/user/crash";
22
23// Directory mode of the user crash spool directory.
24static const mode_t kUserCrashPathMode = 0755;
25
26// Directory mode of the system crash spool directory.
27static const mode_t kSystemCrashPathMode = 01755;
28
29static const uid_t kRootOwner = 0;
30static const uid_t kRootGroup = 0;
31
Ken Mixterda5db7a2010-09-17 13:50:42 -070032// Maximum crash reports per crash spool directory. Note that this is
33// a separate maximum from the maximum rate at which we upload these
34// diagnostics. The higher this rate is, the more space we allow for
35// core files, minidumps, and kcrash logs, and equivalently the more
36// processor and I/O bandwidth we dedicate to handling these crashes when
37// many occur at once. Also note that if core files are configured to
38// be left on the file system, we stop adding crashes when either the
39// number of core files or minidumps reaches this number.
40const int CrashCollector::kMaxCrashDirectorySize = 32;
Ken Mixter04ec10f2010-08-26 16:02:02 -070041
Ken Mixter03403162010-08-18 15:23:16 -070042CrashCollector::CrashCollector() : forced_crash_directory_(NULL) {
43}
44
45CrashCollector::~CrashCollector() {
46}
47
48void CrashCollector::Initialize(
49 CrashCollector::CountCrashFunction count_crash_function,
50 CrashCollector::IsFeedbackAllowedFunction is_feedback_allowed_function,
51 SystemLogging *logger) {
52 CHECK(count_crash_function != NULL);
53 CHECK(is_feedback_allowed_function != NULL);
54 CHECK(logger != NULL);
55
56 count_crash_function_ = count_crash_function;
57 is_feedback_allowed_function_ = is_feedback_allowed_function;
58 logger_ = logger;
59}
60
Ken Mixteree849c52010-09-30 15:30:10 -070061std::string CrashCollector::Sanitize(const std::string &name) {
62 std::string result = name;
63 for (size_t i = 0; i < name.size(); ++i) {
64 if (!isalnum(result[i]) && result[i] != '_')
65 result[i] = '_';
66 }
67 return result;
68}
69
Ken Mixter03403162010-08-18 15:23:16 -070070std::string CrashCollector::FormatDumpBasename(const std::string &exec_name,
71 time_t timestamp,
72 pid_t pid) {
73 struct tm tm;
74 localtime_r(&timestamp, &tm);
Ken Mixteree849c52010-09-30 15:30:10 -070075 std::string sanitized_exec_name = Sanitize(exec_name);
Ken Mixter03403162010-08-18 15:23:16 -070076 return StringPrintf("%s.%04d%02d%02d.%02d%02d%02d.%d",
Ken Mixteree849c52010-09-30 15:30:10 -070077 sanitized_exec_name.c_str(),
Ken Mixter03403162010-08-18 15:23:16 -070078 tm.tm_year + 1900,
79 tm.tm_mon + 1,
80 tm.tm_mday,
81 tm.tm_hour,
82 tm.tm_min,
83 tm.tm_sec,
84 pid);
85}
86
87FilePath CrashCollector::GetCrashDirectoryInfo(
88 uid_t process_euid,
89 uid_t default_user_id,
90 gid_t default_user_group,
91 mode_t *mode,
92 uid_t *directory_owner,
93 gid_t *directory_group) {
94 if (process_euid == default_user_id) {
95 *mode = kUserCrashPathMode;
96 *directory_owner = default_user_id;
97 *directory_group = default_user_group;
98 return FilePath(kUserCrashPath);
99 } else {
100 *mode = kSystemCrashPathMode;
101 *directory_owner = kRootOwner;
102 *directory_group = kRootGroup;
103 return FilePath(kSystemCrashPath);
104 }
105}
106
107bool CrashCollector::GetUserInfoFromName(const std::string &name,
108 uid_t *uid,
109 gid_t *gid) {
110 char storage[256];
111 struct passwd passwd_storage;
112 struct passwd *passwd_result = NULL;
113
114 if (getpwnam_r(name.c_str(), &passwd_storage, storage, sizeof(storage),
115 &passwd_result) != 0 || passwd_result == NULL) {
116 logger_->LogError("Cannot find user named %s", name.c_str());
117 return false;
118 }
119
120 *uid = passwd_result->pw_uid;
121 *gid = passwd_result->pw_gid;
122 return true;
123}
124
125bool CrashCollector::GetCreatedCrashDirectoryByEuid(uid_t euid,
126 FilePath *crash_directory) {
127 uid_t default_user_id;
128 gid_t default_user_group;
129
130 // For testing.
131 if (forced_crash_directory_ != NULL) {
132 *crash_directory = FilePath(forced_crash_directory_);
133 return true;
134 }
135
136 if (!GetUserInfoFromName(kDefaultUserName,
137 &default_user_id,
138 &default_user_group)) {
139 logger_->LogError("Could not find default user info");
140 return false;
141 }
142 mode_t directory_mode;
143 uid_t directory_owner;
144 gid_t directory_group;
145 *crash_directory =
146 GetCrashDirectoryInfo(euid,
147 default_user_id,
148 default_user_group,
149 &directory_mode,
150 &directory_owner,
151 &directory_group);
152
153 if (!file_util::PathExists(*crash_directory)) {
154 // Create the spool directory with the appropriate mode (regardless of
155 // umask) and ownership.
156 mode_t old_mask = umask(0);
157 if (mkdir(crash_directory->value().c_str(), directory_mode) < 0 ||
158 chown(crash_directory->value().c_str(),
159 directory_owner,
160 directory_group) < 0) {
161 logger_->LogError("Unable to create appropriate crash directory");
162 return false;
163 }
164 umask(old_mask);
165 }
166
167 if (!file_util::PathExists(*crash_directory)) {
168 logger_->LogError("Unable to create crash directory %s",
169 crash_directory->value().c_str());
170 return false;
171 }
172
Ken Mixter04ec10f2010-08-26 16:02:02 -0700173 if (!CheckHasCapacity(*crash_directory)) {
174 return false;
175 }
176
Ken Mixter03403162010-08-18 15:23:16 -0700177 return true;
178}
Ken Mixter04ec10f2010-08-26 16:02:02 -0700179
180// Return true if the given crash directory has not already reached
181// maximum capacity.
182bool CrashCollector::CheckHasCapacity(const FilePath &crash_directory) {
183 DIR* dir = opendir(crash_directory.value().c_str());
184 if (!dir) {
185 return false;
186 }
187 struct dirent ent_buf;
188 struct dirent* ent;
Ken Mixter04ec10f2010-08-26 16:02:02 -0700189 bool full = false;
Ken Mixteree849c52010-09-30 15:30:10 -0700190 std::set<std::string> basenames;
Ken Mixter04ec10f2010-08-26 16:02:02 -0700191 while (readdir_r(dir, &ent_buf, &ent) == 0 && ent != NULL) {
192 if ((strcmp(ent->d_name, ".") == 0) ||
193 (strcmp(ent->d_name, "..") == 0))
194 continue;
195
Ken Mixteree849c52010-09-30 15:30:10 -0700196 std::string filename(ent->d_name);
197 size_t last_dot = filename.rfind(".");
198 std::string basename;
199 // If there is a valid looking extension, use the base part of the
200 // name. If the only dot is the first byte (aka a dot file), treat
201 // it as unique to avoid allowing a directory full of dot files
202 // from accumulating.
203 if (last_dot != std::string::npos && last_dot != 0)
204 basename = filename.substr(0, last_dot);
205 else
206 basename = filename;
207 basenames.insert(basename);
Ken Mixter04ec10f2010-08-26 16:02:02 -0700208
Ken Mixteree849c52010-09-30 15:30:10 -0700209 if (basenames.size() >= static_cast<size_t>(kMaxCrashDirectorySize)) {
Ken Mixter04ec10f2010-08-26 16:02:02 -0700210 logger_->LogWarning(
211 "Crash directory %s already full with %d pending reports",
212 crash_directory.value().c_str(),
213 kMaxCrashDirectorySize);
214 full = true;
215 break;
216 }
217 }
218 closedir(dir);
219 return !full;
220}
Ken Mixteree849c52010-09-30 15:30:10 -0700221
222bool CrashCollector::ReadKeyValueFile(
223 const FilePath &path,
224 const char separator,
225 std::map<std::string, std::string> *dictionary) {
226 std::string contents;
227 if (!file_util::ReadFileToString(path, &contents)) {
228 return false;
229 }
230 typedef std::vector<std::string> StringVector;
231 StringVector lines;
232 SplitString(contents, '\n', &lines);
233 bool any_errors = false;
234 for (StringVector::iterator line = lines.begin(); line != lines.end();
235 ++line) {
236 // Allow empty strings.
237 if (line->empty())
238 continue;
239 StringVector sides;
240 SplitString(*line, separator, &sides);
241 if (sides.size() != 2) {
242 any_errors = true;
243 continue;
244 }
245 dictionary->insert(std::pair<std::string, std::string>(sides[0], sides[1]));
246 }
247 return !any_errors;
248}
249
250void CrashCollector::WriteCrashMetaData(const FilePath &meta_path,
251 const std::string &exec_name) {
252 std::map<std::string, std::string> contents;
253 if (!ReadKeyValueFile(FilePath(std::string(kLsbRelease)), '=', &contents)) {
254 logger_->LogError("Problem parsing %s", kLsbRelease);
255 // Even though there was some failure, take as much as we could read.
256 }
257 std::string version("unknown");
258 std::map<std::string, std::string>::iterator i;
259 if ((i = contents.find("CHROMEOS_RELEASE_VERSION")) != contents.end()) {
260 version = i->second;
261 }
262 std::string meta_data = StringPrintf("exec_name=%s\n"
263 "ver=%s\n"
264 "done=1\n",
265 exec_name.c_str(),
266 version.c_str());
267 if (!file_util::WriteFile(meta_path, meta_data.c_str(), meta_data.size())) {
268 logger_->LogError("Unable to write %s", meta_path.value().c_str());
269 }
270}