OLD | NEW |
1 // Copyright (c) 2010 The Chromium OS Authors. All rights reserved. | 1 // 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
| 5 #include <grp.h> // For struct group. |
| 6 #include <pwd.h> // For struct passwd. |
| 7 #include <sys/types.h> // For getpwuid_r and getgrnam_r. |
| 8 |
5 #include <string> | 9 #include <string> |
6 | 10 |
7 #include "base/file_util.h" | 11 #include "base/file_util.h" |
8 #include "base/logging.h" | 12 #include "base/logging.h" |
9 #include "base/string_util.h" | 13 #include "base/string_util.h" |
10 #include "crash-reporter/user_collector.h" | 14 #include "crash-reporter/user_collector.h" |
11 #include "metrics/metrics_library.h" | 15 #include "metrics/metrics_library.h" |
12 | 16 |
13 // This procfs file is used to cause kernel core file writing to | 17 // This procfs file is used to cause kernel core file writing to |
14 // instead pipe the core file into a user space process. See | 18 // instead pipe the core file into a user space process. See |
15 // core(5) man page. | 19 // core(5) man page. |
16 static const char kCorePatternFile[] = "/proc/sys/kernel/core_pattern"; | 20 static const char kCorePatternFile[] = "/proc/sys/kernel/core_pattern"; |
| 21 static const char kCoreToMinidumpConverterPath[] = "/usr/bin/core2md"; |
| 22 static const char kDefaultUserName[] = "chronos"; |
| 23 static const char kLeaveCoreFile[] = "/etc/leave_core"; |
| 24 static const char kSystemCrashPath[] = "/var/spool/crash"; |
| 25 static const char kUserCrashPath[] = "/home/chronos/user/crash"; |
| 26 |
| 27 // Directory mode of the user crash spool directory. |
| 28 static const mode_t kUserCrashPathMode = 0755; |
| 29 |
| 30 // Directory mode of the system crash spool directory. |
| 31 static const mode_t kSystemCrashPathMode = 01755; |
| 32 |
| 33 static const uid_t kRootOwner = 0; |
| 34 static const uid_t kRootGroup = 0; |
| 35 |
| 36 const char *UserCollector::kUserId = "Uid:\t"; |
| 37 const char *UserCollector::kGroupId = "Gid:\t"; |
17 | 38 |
18 UserCollector::UserCollector() | 39 UserCollector::UserCollector() |
19 : core_pattern_file_(kCorePatternFile), | 40 : generate_diagnostics_(false), |
| 41 core_pattern_file_(kCorePatternFile), |
20 count_crash_function_(NULL), | 42 count_crash_function_(NULL), |
21 initialized_(false), | 43 initialized_(false), |
22 is_feedback_allowed_function_(NULL), | 44 is_feedback_allowed_function_(NULL), |
23 logger_(NULL) { | 45 logger_(NULL) { |
24 } | 46 } |
25 | 47 |
26 void UserCollector::Initialize( | 48 void UserCollector::Initialize( |
27 UserCollector::CountCrashFunction count_crash_function, | 49 UserCollector::CountCrashFunction count_crash_function, |
28 const std::string &our_path, | 50 const std::string &our_path, |
29 UserCollector::IsFeedbackAllowedFunction is_feedback_allowed_function, | 51 UserCollector::IsFeedbackAllowedFunction is_feedback_allowed_function, |
30 SystemLogging *logger) { | 52 SystemLogging *logger, |
| 53 bool generate_diagnostics) { |
31 CHECK(count_crash_function != NULL); | 54 CHECK(count_crash_function != NULL); |
32 CHECK(is_feedback_allowed_function != NULL); | 55 CHECK(is_feedback_allowed_function != NULL); |
33 CHECK(logger != NULL); | 56 CHECK(logger != NULL); |
34 | 57 |
35 count_crash_function_ = count_crash_function; | 58 count_crash_function_ = count_crash_function; |
36 our_path_ = our_path; | 59 our_path_ = our_path; |
37 is_feedback_allowed_function_ = is_feedback_allowed_function; | 60 is_feedback_allowed_function_ = is_feedback_allowed_function; |
38 logger_ = logger; | 61 logger_ = logger; |
39 initialized_ = true; | 62 initialized_ = true; |
| 63 generate_diagnostics_ = generate_diagnostics; |
40 } | 64 } |
41 | 65 |
42 UserCollector::~UserCollector() { | 66 UserCollector::~UserCollector() { |
43 } | 67 } |
44 | 68 |
45 std::string UserCollector::GetPattern(bool enabled) const { | 69 std::string UserCollector::GetPattern(bool enabled) const { |
46 if (enabled) { | 70 if (enabled) { |
47 return StringPrintf("|%s --signal=%%s --pid=%%p --exec=%%e", | 71 return StringPrintf("|%s --signal=%%s --pid=%%p", our_path_.c_str()); |
48 our_path_.c_str()); | |
49 } else { | 72 } else { |
50 return "core"; | 73 return "core"; |
51 } | 74 } |
52 } | 75 } |
53 | 76 |
54 bool UserCollector::SetUpInternal(bool enabled) { | 77 bool UserCollector::SetUpInternal(bool enabled) { |
55 CHECK(initialized_); | 78 CHECK(initialized_); |
56 logger_->LogInfo("%s crash handling", enabled ? "Enabling" : "Disabling"); | 79 logger_->LogInfo("%s crash handling", enabled ? "Enabling" : "Disabling"); |
57 std::string pattern = GetPattern(enabled); | 80 std::string pattern = GetPattern(enabled); |
58 if (file_util::WriteFile(FilePath(core_pattern_file_), | 81 if (file_util::WriteFile(FilePath(core_pattern_file_), |
59 pattern.c_str(), | 82 pattern.c_str(), |
60 pattern.length()) != | 83 pattern.length()) != |
61 static_cast<int>(pattern.length())) { | 84 static_cast<int>(pattern.length())) { |
62 logger_->LogError("Unable to write %s", core_pattern_file_.c_str()); | 85 logger_->LogError("Unable to write %s", core_pattern_file_.c_str()); |
63 return false; | 86 return false; |
64 } | 87 } |
65 return true; | 88 return true; |
66 } | 89 } |
67 | 90 |
68 void UserCollector::HandleCrash(int signal, int pid, const std::string &exec) { | 91 FilePath UserCollector::GetProcessPath(pid_t pid) { |
| 92 return FilePath(StringPrintf("/proc/%d", pid)); |
| 93 } |
| 94 |
| 95 bool UserCollector::GetSymlinkTarget(const FilePath &symlink, |
| 96 FilePath *target) { |
| 97 int max_size = 32; |
| 98 scoped_array<char> buffer; |
| 99 while (true) { |
| 100 buffer.reset(new char[max_size + 1]); |
| 101 ssize_t size = readlink(symlink.value().c_str(), buffer.get(), max_size); |
| 102 if (size < 0) { |
| 103 return false; |
| 104 } |
| 105 buffer[size] = 0; |
| 106 if (size == max_size) { |
| 107 // Avoid overflow when doubling. |
| 108 if (max_size * 2 > max_size) { |
| 109 max_size *= 2; |
| 110 continue; |
| 111 } else { |
| 112 return false; |
| 113 } |
| 114 } |
| 115 break; |
| 116 } |
| 117 |
| 118 *target = FilePath(buffer.get()); |
| 119 return true; |
| 120 } |
| 121 |
| 122 bool UserCollector::GetExecutableBaseNameFromPid(uid_t pid, |
| 123 std::string *base_name) { |
| 124 FilePath target; |
| 125 if (!GetSymlinkTarget(GetProcessPath(pid).Append("exe"), &target)) |
| 126 return false; |
| 127 *base_name = target.BaseName().value(); |
| 128 return true; |
| 129 } |
| 130 |
| 131 bool UserCollector::GetIdFromStatus(const char *prefix, |
| 132 IdKind kind, |
| 133 const std::string &status_contents, |
| 134 int *id) { |
| 135 // From fs/proc/array.c:task_state(), this file contains: |
| 136 // \nUid:\t<uid>\t<euid>\t<suid>\t<fsuid>\n |
| 137 std::vector<std::string> status_lines; |
| 138 SplitString(status_contents, '\n', &status_lines); |
| 139 std::vector<std::string>::iterator line_iterator; |
| 140 for (line_iterator = status_lines.begin(); |
| 141 line_iterator != status_lines.end(); |
| 142 ++line_iterator) { |
| 143 if (line_iterator->find(prefix) == 0) |
| 144 break; |
| 145 } |
| 146 if (line_iterator == status_lines.end()) { |
| 147 return false; |
| 148 } |
| 149 std::string id_substring = line_iterator->substr(strlen(prefix), |
| 150 std::string::npos); |
| 151 std::vector<std::string> ids; |
| 152 SplitString(id_substring, '\t', &ids); |
| 153 if (ids.size() != kIdMax || kind < 0 || kind >= kIdMax) { |
| 154 return false; |
| 155 } |
| 156 const char *number = ids[kind].c_str(); |
| 157 char *end_number = NULL; |
| 158 *id = strtol(number, &end_number, 10); |
| 159 if (*end_number != '\0') |
| 160 return false; |
| 161 return true; |
| 162 } |
| 163 |
| 164 bool UserCollector::GetUserInfoFromName(const std::string &name, |
| 165 uid_t *uid, |
| 166 gid_t *gid) { |
| 167 char storage[256]; |
| 168 struct passwd passwd_storage; |
| 169 struct passwd *passwd_result = NULL; |
| 170 |
| 171 if (getpwnam_r(name.c_str(), &passwd_storage, storage, sizeof(storage), |
| 172 &passwd_result) != 0 || passwd_result == NULL) { |
| 173 logger_->LogError("Cannot find user named %s", name.c_str()); |
| 174 return false; |
| 175 } |
| 176 |
| 177 *uid = passwd_result->pw_uid; |
| 178 *gid = passwd_result->pw_gid; |
| 179 return true; |
| 180 } |
| 181 |
| 182 bool UserCollector::CopyOffProcFiles(pid_t pid, |
| 183 const FilePath &container_dir) { |
| 184 if (!file_util::CreateDirectory(container_dir)) { |
| 185 logger_->LogInfo("Could not create %s", container_dir.value().c_str()); |
| 186 return false; |
| 187 } |
| 188 FilePath process_path = GetProcessPath(pid); |
| 189 if (!file_util::PathExists(process_path)) { |
| 190 logger_->LogWarning("Path %s does not exist", |
| 191 process_path.value().c_str()); |
| 192 return false; |
| 193 } |
| 194 static const char *proc_files[] = { |
| 195 "auxv", |
| 196 "cmdline", |
| 197 "environ", |
| 198 "maps", |
| 199 "status" |
| 200 }; |
| 201 for (unsigned i = 0; i < arraysize(proc_files); ++i) { |
| 202 if (!file_util::CopyFile(process_path.Append(proc_files[i]), |
| 203 container_dir.Append(proc_files[i]))) { |
| 204 logger_->LogWarning("Could not copy %s file", proc_files[i]); |
| 205 return false; |
| 206 } |
| 207 } |
| 208 return true; |
| 209 } |
| 210 |
| 211 FilePath UserCollector::GetCrashDirectoryInfo( |
| 212 uid_t process_euid, |
| 213 uid_t default_user_id, |
| 214 gid_t default_user_group, |
| 215 mode_t *mode, |
| 216 uid_t *directory_owner, |
| 217 gid_t *directory_group) { |
| 218 if (process_euid == default_user_id) { |
| 219 *mode = kUserCrashPathMode; |
| 220 *directory_owner = default_user_id; |
| 221 *directory_group = default_user_group; |
| 222 return FilePath(kUserCrashPath); |
| 223 } else { |
| 224 *mode = kSystemCrashPathMode; |
| 225 *directory_owner = kRootOwner; |
| 226 *directory_group = kRootGroup; |
| 227 return FilePath(kSystemCrashPath); |
| 228 } |
| 229 } |
| 230 |
| 231 bool UserCollector::GetCreatedCrashDirectory(pid_t pid, |
| 232 FilePath *crash_file_path) { |
| 233 FilePath process_path = GetProcessPath(pid); |
| 234 std::string status; |
| 235 if (!file_util::ReadFileToString(process_path.Append("status"), |
| 236 &status)) { |
| 237 logger_->LogError("Could not read status file"); |
| 238 return false; |
| 239 } |
| 240 int process_euid; |
| 241 if (!GetIdFromStatus(kUserId, kIdEffective, status, &process_euid)) { |
| 242 logger_->LogError("Could not find euid in status file"); |
| 243 return false; |
| 244 } |
| 245 uid_t default_user_id; |
| 246 gid_t default_user_group; |
| 247 if (!GetUserInfoFromName(kDefaultUserName, |
| 248 &default_user_id, |
| 249 &default_user_group)) { |
| 250 logger_->LogError("Could not find default user info"); |
| 251 return false; |
| 252 } |
| 253 mode_t directory_mode; |
| 254 uid_t directory_owner; |
| 255 gid_t directory_group; |
| 256 *crash_file_path = |
| 257 GetCrashDirectoryInfo(process_euid, |
| 258 default_user_id, |
| 259 default_user_group, |
| 260 &directory_mode, |
| 261 &directory_owner, |
| 262 &directory_group); |
| 263 |
| 264 |
| 265 if (!file_util::PathExists(*crash_file_path)) { |
| 266 // Create the spool directory with the appropriate mode (regardless of |
| 267 // umask) and ownership. |
| 268 mode_t old_mask = umask(0); |
| 269 if (mkdir(crash_file_path->value().c_str(), directory_mode) < 0 || |
| 270 chown(crash_file_path->value().c_str(), |
| 271 directory_owner, |
| 272 directory_group) < 0) { |
| 273 logger_->LogError("Unable to create appropriate crash directory"); |
| 274 return false; |
| 275 } |
| 276 umask(old_mask); |
| 277 } |
| 278 |
| 279 if (!file_util::PathExists(*crash_file_path)) { |
| 280 logger_->LogError("Unable to create crash directory %s", |
| 281 crash_file_path->value().c_str()); |
| 282 return false; |
| 283 } |
| 284 |
| 285 |
| 286 return true; |
| 287 } |
| 288 |
| 289 std::string UserCollector::FormatDumpBasename(const std::string &exec_name, |
| 290 time_t timestamp, |
| 291 pid_t pid) { |
| 292 struct tm tm; |
| 293 localtime_r(×tamp, &tm); |
| 294 return StringPrintf("%s.%04d%02d%02d.%02d%02d%02d.%d", |
| 295 exec_name.c_str(), |
| 296 tm.tm_year + 1900, |
| 297 tm.tm_mon + 1, |
| 298 tm.tm_mday, |
| 299 tm.tm_hour, |
| 300 tm.tm_min, |
| 301 tm.tm_sec, |
| 302 pid); |
| 303 } |
| 304 |
| 305 bool UserCollector::CopyStdinToCoreFile(const FilePath &core_path) { |
| 306 // Copy off all stdin to a core file. |
| 307 FilePath stdin_path("/dev/fd/0"); |
| 308 if (file_util::CopyFile(stdin_path, core_path)) { |
| 309 return true; |
| 310 } |
| 311 |
| 312 logger_->LogError("Could not write core file"); |
| 313 // If the file system was full, make sure we remove any remnants. |
| 314 file_util::Delete(core_path, false); |
| 315 return false; |
| 316 } |
| 317 |
| 318 bool UserCollector::ConvertCoreToMinidump(const FilePath &core_path, |
| 319 const FilePath &procfs_directory, |
| 320 const FilePath &minidump_path, |
| 321 const FilePath &temp_directory) { |
| 322 // TODO(kmixter): Rewrite to use process_util once it's included in |
| 323 // libchrome. |
| 324 FilePath output_path = temp_directory.Append("output"); |
| 325 std::string core2md_command = |
| 326 StringPrintf("\"%s\" \"%s\" \"%s\" \"%s\" > \"%s\" 2>&1", |
| 327 kCoreToMinidumpConverterPath, |
| 328 core_path.value().c_str(), |
| 329 procfs_directory.value().c_str(), |
| 330 minidump_path.value().c_str(), |
| 331 output_path.value().c_str()); |
| 332 int errorlevel = system(core2md_command.c_str()); |
| 333 |
| 334 std::string output; |
| 335 file_util::ReadFileToString(output_path, &output); |
| 336 if (errorlevel != 0) { |
| 337 logger_->LogInfo("Problem during %s [result=%d]: %s", |
| 338 core2md_command.c_str(), |
| 339 errorlevel, |
| 340 output.c_str()); |
| 341 return false; |
| 342 } |
| 343 |
| 344 if (!file_util::PathExists(minidump_path)) { |
| 345 logger_->LogError("Minidump file %s was not created", |
| 346 minidump_path.value().c_str()); |
| 347 return false; |
| 348 } |
| 349 return true; |
| 350 } |
| 351 |
| 352 bool UserCollector::GenerateDiagnostics(pid_t pid, |
| 353 const std::string &exec_name) { |
| 354 FilePath container_dir("/tmp"); |
| 355 container_dir = container_dir.Append( |
| 356 StringPrintf("crash_reporter.%d", pid)); |
| 357 |
| 358 if (!CopyOffProcFiles(pid, container_dir)) { |
| 359 file_util::Delete(container_dir, true); |
| 360 return false; |
| 361 } |
| 362 |
| 363 FilePath spool_path; |
| 364 if (!GetCreatedCrashDirectory(pid, &spool_path)) { |
| 365 file_util::Delete(container_dir, true); |
| 366 return false; |
| 367 } |
| 368 std::string dump_basename = FormatDumpBasename(exec_name, time(NULL), pid); |
| 369 FilePath core_path = spool_path.Append( |
| 370 StringPrintf("%s.core", dump_basename.c_str())); |
| 371 |
| 372 if (!CopyStdinToCoreFile(core_path)) { |
| 373 file_util::Delete(container_dir, true); |
| 374 return false; |
| 375 } |
| 376 |
| 377 FilePath minidump_path = spool_path.Append( |
| 378 StringPrintf("%s.dmp", dump_basename.c_str())); |
| 379 |
| 380 bool conversion_result = true; |
| 381 if (!ConvertCoreToMinidump(core_path, |
| 382 container_dir, // procfs directory |
| 383 minidump_path, |
| 384 container_dir)) { // temporary directory |
| 385 // Note we leave the container directory for inspection. |
| 386 conversion_result = false; |
| 387 } |
| 388 |
| 389 if (conversion_result) { |
| 390 logger_->LogInfo("Stored minidump to %s", minidump_path.value().c_str()); |
| 391 } |
| 392 |
| 393 if (!file_util::PathExists(FilePath(kLeaveCoreFile))) { |
| 394 file_util::Delete(core_path, false); |
| 395 } else { |
| 396 logger_->LogInfo("Leaving core file at %s", core_path.value().c_str()); |
| 397 } |
| 398 |
| 399 return conversion_result; |
| 400 } |
| 401 |
| 402 bool UserCollector::HandleCrash(int signal, int pid, const char *force_exec) { |
69 CHECK(initialized_); | 403 CHECK(initialized_); |
| 404 std::string exec; |
| 405 if (force_exec) { |
| 406 exec.assign(force_exec); |
| 407 } else if (!GetExecutableBaseNameFromPid(pid, &exec)) { |
| 408 // If for some reason we don't have the base name, avoid completely |
| 409 // failing by indicating an unknown name. |
| 410 exec = "unknown"; |
| 411 } |
70 logger_->LogWarning("Received crash notification for %s[%d] sig %d", | 412 logger_->LogWarning("Received crash notification for %s[%d] sig %d", |
71 exec.c_str(), pid, signal); | 413 exec.c_str(), pid, signal); |
72 | 414 |
73 if (is_feedback_allowed_function_()) { | 415 if (is_feedback_allowed_function_()) { |
74 count_crash_function_(); | 416 count_crash_function_(); |
75 } | 417 } |
76 } | 418 |
| 419 if (generate_diagnostics_) { |
| 420 return GenerateDiagnostics(pid, exec); |
| 421 } |
| 422 return true; |
| 423 } |
OLD | NEW |