| /** @file |
| |
| Crash logging helper support |
| |
| @section license License |
| |
| Licensed to the Apache Software Foundation (ASF) under one |
| or more contributor license agreements. See the NOTICE file |
| distributed with this work for additional information |
| regarding copyright ownership. The ASF licenses this file |
| to you under the Apache License, Version 2.0 (the |
| "License"); you may not use this file except in compliance |
| with the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| */ |
| |
| #include "Main.h" |
| #include "tscore/I_Layout.h" |
| #include "tscore/I_Version.h" |
| #include "I_Net.h" |
| #include "tscore/signals.h" |
| #include "tscore/ink_cap.h" |
| |
| // ucontext.h is deprecated on Darwin, and we really only need it on Linux, so only |
| // include it if we are planning to use it. |
| #if defined(__linux__) |
| #include <ucontext.h> |
| #endif |
| |
| extern AppVersionInfo appVersionInfo; |
| |
| static pid_t crash_logger_pid = -1; |
| static int crash_logger_fd = NO_FD; |
| |
| static char * |
| create_logger_path() |
| { |
| RecString name; |
| std::string bindir; |
| ats_scoped_str fullpath; |
| |
| if (RecGetRecordString_Xmalloc("proxy.config.crash_log_helper", &name) != REC_ERR_OKAY) { |
| return nullptr; |
| } |
| |
| // Take an absolute path as it is ... |
| if (name == nullptr || *name == '/') { |
| return name; |
| } |
| |
| // Otherwise locate it relative to $BINDIR. |
| bindir = RecConfigReadBinDir(); |
| fullpath = Layout::relative_to(bindir, name); |
| |
| ats_free(name); |
| return fullpath.release(); |
| } |
| |
| static bool |
| check_logger_path(const char *path) |
| { |
| struct stat sbuf; |
| |
| if (stat(path, &sbuf) != 0) { |
| Error("failed to access crash log helper '%s': %s", path, strerror(errno)); |
| return false; |
| } |
| |
| if (!S_ISREG(sbuf.st_mode) || access(path, X_OK) != 0) { |
| Error("crash log helper '%s' is not executable", path); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void |
| crash_logger_init(const char *user) |
| { |
| ats_scoped_str logger(create_logger_path()); |
| const char *basename; |
| |
| pid_t child; |
| int status; |
| int pipe[2]; |
| |
| // Do nothing the log helper was set to NULL, or we can't find it. |
| if (!logger || !check_logger_path(logger)) { |
| return; |
| } |
| |
| // By this point, we have an absolute path, so we'd better be able to find the basename. |
| basename = strrchr(logger, '/') + 1; |
| |
| socketpair(AF_UNIX, SOCK_STREAM, 0, pipe); |
| |
| child = fork(); |
| switch (child) { |
| case -1: |
| Error("failed to fork crash log helper: %s", strerror(errno)); |
| crash_logger_pid = -1; |
| crash_logger_fd = NO_FD; |
| return; |
| |
| case 0: |
| // Dupe the child socket to stdin. |
| dup2(pipe[1], STDIN_FILENO); |
| close(pipe[0]); |
| close(pipe[1]); |
| |
| // Starting after stderr, keep closing file descriptors until we run out. |
| for (int fd = STDERR_FILENO + 1; fd; ++fd) { |
| if (close(fd) == -1) { |
| break; |
| } |
| } |
| |
| ink_release_assert(execl(logger, basename, "--syslog", "--wait", "--host", TS_BUILD_CANONICAL_HOST, "--user", user, NULL) != |
| -1); |
| return; // not reached. |
| } |
| |
| close(pipe[1]); |
| crash_logger_pid = child; |
| crash_logger_fd = pipe[0]; |
| |
| // Wait for the helper to stop |
| if (waitpid(crash_logger_pid, &status, WUNTRACED) > 0) { |
| Debug("server", "waited on PID %ld, %s", (long)crash_logger_pid, WIFSTOPPED(status) ? "STOPPED" : "???"); |
| |
| if (WIFEXITED(status)) { |
| Warning("crash logger '%s' unexpectedly exited with status %d", (const char *)logger, WEXITSTATUS(status)); |
| close(crash_logger_pid); |
| crash_logger_pid = -1; |
| crash_logger_fd = NO_FD; |
| } |
| } |
| } |
| |
| void |
| crash_logger_invoke(int signo, siginfo_t *info, void *ctx) |
| { |
| if (crash_logger_pid != -1) { |
| int status; |
| |
| // Let the crash logger free ... |
| kill(crash_logger_pid, SIGCONT); |
| |
| #if defined(__linux__) |
| // Write the crashing thread information to the crash logger. While the siginfo_t is blesses by POSIX, the |
| // ucontext_t can contain pointers, so it's highly platform dependent. On Linux with glibc, however, it is |
| // a single memory block that we can just puke out. |
| ATS_UNUSED_RETURN(write(crash_logger_fd, info, sizeof(siginfo_t))); |
| ATS_UNUSED_RETURN(write(crash_logger_fd, static_cast<ucontext_t *>(ctx), sizeof(ucontext_t))); |
| #endif |
| |
| close(crash_logger_fd); |
| crash_logger_fd = NO_FD; |
| |
| // Wait for the logger to finish ... |
| waitpid(crash_logger_pid, &status, 0); |
| } |
| |
| // Log the signal, dump a stack trace and core. |
| signal_format_siginfo(signo, info, appVersionInfo.AppStr); // XXX Add timestamp ... |
| signal_crash_handler(signo, info, ctx); |
| } |