/** @file A brief file description @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 "traffic_crashlog.h" #include "tscore/ink_args.h" #include "tscore/ink_cap.h" #include "tscore/I_Version.h" #include "tscore/I_Layout.h" #include "tscore/ink_syslog.h" #include "records/I_RecProcess.h" #include "RecordsConfig.h" #include "tscore/BaseLogFile.h" #include "tscore/runroot.h" static int syslog_mode = false; static int debug_mode = false; static int wait_mode = false; static char *host_triplet = nullptr; static int target_pid = getppid(); static char *user = nullptr; // If pid_t is not sizeof(int), we will have to jiggle argument parsing. extern char __pid_size_static_assert[sizeof(pid_t) == sizeof(int) ? 0 : -1]; static AppVersionInfo appVersionInfo; static const ArgumentDescription argument_descriptions[] = { {"target", '-', "Target process ID", "I", &target_pid, nullptr, nullptr}, {"host", '-', "Host triplet for the process being logged", "S*", &host_triplet, nullptr, nullptr}, {"wait", '-', "Stop until signalled at startup", "F", &wait_mode, nullptr, nullptr}, {"syslog", '-', "Syslog after writing a crash log", "F", &syslog_mode, nullptr, nullptr}, {"debug", '-', "Enable debugging mode", "F", &debug_mode, nullptr, nullptr}, {"user", '-', "Username used to set privileges", "S*", &user, nullptr, nullptr}, HELP_ARGUMENT_DESCRIPTION(), VERSION_ARGUMENT_DESCRIPTION(), RUNROOT_ARGUMENT_DESCRIPTION()}; static struct tm timestamp() { time_t now = time(nullptr); struct tm tm; localtime_r(&now, &tm); return tm; } static char * crashlog_name() { char filename[64]; struct tm now = timestamp(); std::string logdir(RecConfigReadLogDir()); ats_scoped_str pathname; strftime(filename, sizeof(filename), "crash-%Y-%m-%d-%H%M%S.log", &now); pathname = Layout::relative_to(logdir, filename); return pathname.release(); } static FILE * crashlog_open(const char *path) { int fd; fd = open(path, O_WRONLY | O_TRUNC | O_CREAT, 0400); return (fd == -1) ? nullptr : fdopen(fd, "w"); } static unsigned max_passwd_size() { #if defined(_SC_GETPW_R_SIZE_MAX) long val = sysconf(_SC_GETPW_R_SIZE_MAX); if (val > 0) { return static_cast(val); } #endif return 4096; } static void change_privileges() { struct passwd *pwd; struct passwd pbuf; char buf[max_passwd_size()]; if (getpwnam_r(user, &pbuf, buf, sizeof(buf), &pwd) != 0) { Error("missing password database entry for username '%s': %s", user, strerror(errno)); return; } if (pwd == nullptr) { // Password entry not found ... Error("missing password database entry for '%s'", user); return; } if (setegid(pwd->pw_gid) != 0) { Error("setegid(%d) failed: %s", pwd->pw_gid, strerror(errno)); return; } if (setreuid(pwd->pw_uid, 0) != 0) { Error("setreuid(%d, %d) failed: %s", pwd->pw_uid, 0, strerror(errno)); return; } } int main(int /* argc ATS_UNUSED */, const char **argv) { FILE *fp; char *logname; TSMgmtError mgmterr; crashlog_target target; pid_t parent = getppid(); diags = new Diags("traffic_crashlog", "" /* tags */, "" /* actions */, new BaseLogFile("stderr")); appVersionInfo.setup(PACKAGE_NAME, "traffic_crashlog", PACKAGE_VERSION, __DATE__, __TIME__, BUILD_MACHINE, BUILD_PERSON, ""); // Process command line arguments and dump into variables process_args(&appVersionInfo, argument_descriptions, countof(argument_descriptions), argv); // XXX This is a hack. traffic_manager starts traffic_server with the euid of the admin user. We are still // privileged, but won't be able to open files in /proc or ptrace the target. This really should be fixed // in traffic_manager. if (getuid() == 0) { change_privileges(); } if (wait_mode) { EnableDeathSignal(SIGKILL); kill(getpid(), SIGSTOP); } // If our parent changed, then we were woken after traffic_server exited. There's no point trying to // emit a crashlog because traffic_server is gone. if (getppid() != parent) { return 0; } runroot_handler(argv); Layout::create(); RecProcessInit(RECM_STAND_ALONE, nullptr /* diags */); LibRecordsConfigInit(); if (syslog_mode) { RecString name; int facility = -1; if (REC_ReadConfigStringAlloc(name, "proxy.config.syslog_facility") == REC_ERR_OKAY) { facility = facility_string_to_int(name); ats_free(name); } if (facility < 0) { facility = LOG_DAEMON; } openlog(appVersionInfo.AppStr, LOG_PID | LOG_NDELAY | LOG_NOWAIT, facility); diags->config.outputs[DL_Debug].to_syslog = true; diags->config.outputs[DL_Status].to_syslog = true; diags->config.outputs[DL_Note].to_syslog = true; diags->config.outputs[DL_Warning].to_syslog = true; diags->config.outputs[DL_Error].to_syslog = true; diags->config.outputs[DL_Fatal].to_syslog = true; diags->config.outputs[DL_Alert].to_syslog = true; diags->config.outputs[DL_Emergency].to_syslog = true; } Note("crashlog started, target=%ld, debug=%s syslog=%s, uid=%ld euid=%ld", static_cast(target_pid), debug_mode ? "true" : "false", syslog_mode ? "true" : "false", (long)getuid(), (long)geteuid()); mgmterr = TSInit(nullptr, (TSInitOptionT)(TS_MGMT_OPT_NO_EVENTS | TS_MGMT_OPT_NO_SOCK_TESTS)); if (mgmterr != TS_ERR_OKAY) { char *msg = TSGetErrorMessage(mgmterr); Warning("failed to initialize management API: %s", msg); TSfree(msg); } ink_zero(target); target.pid = static_cast(target_pid); target.timestamp = timestamp(); if (host_triplet && strncmp(host_triplet, "x86_64-unknown-linux", sizeof("x86_64-unknown-linux") - 1) == 0) { ssize_t nbytes; target.flags |= CRASHLOG_HAVE_THREADINFO; nbytes = read(STDIN_FILENO, &target.siginfo, sizeof(target.siginfo)); if (nbytes < static_cast(sizeof(target.siginfo))) { Warning("received %zd of %zu expected signal info bytes", nbytes, sizeof(target.siginfo)); target.flags &= ~CRASHLOG_HAVE_THREADINFO; } nbytes = read(STDIN_FILENO, &target.ucontext, sizeof(target.ucontext)); if (nbytes < static_cast(sizeof(target.ucontext))) { Warning("received %zd of %zu expected thread context bytes", nbytes, sizeof(target.ucontext)); target.flags &= ~CRASHLOG_HAVE_THREADINFO; } } logname = crashlog_name(); fp = debug_mode ? stdout : crashlog_open(logname); if (fp == nullptr) { Error("failed to create '%s': %s", logname, strerror(errno)); ats_free(logname); return 1; } Note("logging to %p", fp); crashlog_write_procname(fp, target); crashlog_write_exename(fp, target); fprintf(fp, LABELFMT "Traffic Server %s\n", "Version:", PACKAGE_VERSION); crashlog_write_uname(fp, target); crashlog_write_datime(fp, target); fprintf(fp, "\n"); crashlog_write_siginfo(fp, target); fprintf(fp, "\n"); crashlog_write_registers(fp, target); fprintf(fp, "\n"); crashlog_write_backtrace(fp, target); fprintf(fp, "\n"); crashlog_write_procstatus(fp, target); fprintf(fp, "\n"); crashlog_write_proclimits(fp, target); fprintf(fp, "\n"); crashlog_write_regions(fp, target); fprintf(fp, "\n"); crashlog_write_records(fp, target); Error("wrote crash log to %s", logname); ats_free(logname); fflush(fp); fclose(fp); return 0; }