/* * Copyright (C) <2018> Intel Corporation * SPDX-License-Identifier: BSD-3-Clause */ /* * Copyright (C) 2018 Intel Corporation * * Licensed 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "packet.h" #include "protocol.h" #include "log_sys.h" #include "version.h" #include "strutils.h" #define FILE_PATH_LEN_MAX 256 /** * Usercrash works as C/S model: usercrash_s works as usercrash server, which * is to handle events from client in endless loop. Once server receives events * from client, it will create usercrash_0x file under /var/log/usercrashes/ * and send file fd to client. Then server will wait for client filling the * event info completely to the crash file. After client's work has been done, * server will be responsiable to free the crash node and process other events. */ struct crash_node { int crash_fd; int pid; int out_fd; char name[COMM_NAME_LEN]; struct event *crash_event; char crash_path[FILE_PATH_LEN_MAX]; TAILQ_ENTRY(crash_node) entries; }; static const char usercrash_directory[] = "/var/log/usercrashes"; static int usercrash_count = 50; static int next_usercrash; static int kMaxConcurrentDumps = 1; static int num_concurrent_dumps; TAILQ_HEAD(, crash_node) queue_t; static pthread_mutex_t queue_mtx = PTHREAD_MUTEX_INITIALIZER; static void push_back(struct crash_node *crash); static struct crash_node *pop_front(void); /* Forward declare the callbacks so they can be placed in a sensible order */ static void crash_accept_cb(struct evconnlistener *listener, evutil_socket_t sockfd, struct sockaddr*, int, void*); static void crash_request_cb(evutil_socket_t sockfd, short ev, void *arg); static void crash_completed_cb(evutil_socket_t sockfd, short ev, void *arg); static void __attribute__((__unused__)) debuggerd_register_handlers( struct sigaction *action) { sigaction(SIGABRT, action, NULL); sigaction(SIGBUS, action, NULL); sigaction(SIGFPE, action, NULL); sigaction(SIGILL, action, NULL); sigaction(SIGSEGV, action, NULL); #if defined(SIGSTKFLT) sigaction(SIGSTKFLT, action, NULL); #endif sigaction(SIGSYS, action, NULL); sigaction(SIGTRAP, action, NULL); } static void free_crash(struct crash_node *crash) { event_free(crash->crash_event); close(crash->crash_fd); free(crash); } static void push_back(struct crash_node *crash) { pthread_mutex_lock(&queue_mtx); TAILQ_INSERT_TAIL(&queue_t, crash, entries); pthread_mutex_unlock(&queue_mtx); } static struct crash_node *pop_front(void) { struct crash_node *crash = NULL; pthread_mutex_lock(&queue_mtx); if (!TAILQ_EMPTY(&queue_t)) { crash = TAILQ_FIRST(&queue_t); TAILQ_REMOVE(&queue_t, crash, entries); } pthread_mutex_unlock(&queue_mtx); return crash; } static void find_oldest_usercrash(void) { int i; int len; int oldest_usercrash = 0; time_t oldest_time = LONG_MAX; char path[FILE_PATH_LEN_MAX]; struct stat st; memset(path, 0, FILE_PATH_LEN_MAX); for (i = 0; i < usercrash_count; ++i) { len = snprintf(path, sizeof(path), "%s/usercrash_%02d", usercrash_directory, i); if (s_not_expect(len, sizeof(path))) { LOGE("failed to generate path\n"); continue; } if (stat(path, &st) != 0) { if (errno == ENOENT) { oldest_usercrash = i; break; } LOGE("failed to stat %s\n", path); continue; } if (st.st_mtime < oldest_time) { oldest_usercrash = i; oldest_time = st.st_mtime; } } next_usercrash = oldest_usercrash; } static int get_usercrash(struct crash_node *crash) { /** * If kMaxConcurrentDumps is greater than 1, then theoretically the * same filename could be handed out to multiple processes. Unlink and * create the file, instead of using O_TRUNC, to avoid two processes * interleaving their output */ int result; int len; char file_name[FILE_PATH_LEN_MAX]; memset(file_name, 0, FILE_PATH_LEN_MAX); len = snprintf(file_name, sizeof(file_name), "%s/usercrash_%02d", usercrash_directory, next_usercrash); if (s_not_expect(len, sizeof(file_name))) { LOGE("failed to generate file name\n"); return -1; } if (unlink(file_name) != 0 && errno != ENOENT) { LOGE("failed to unlink usercrash at %s\n", file_name); return -1; } result = open(file_name, O_CREAT | O_WRONLY, 0644); if (result == -1) { LOGE("failed to create usercrash at %s\n", file_name); return -1; } next_usercrash = (next_usercrash + 1) % usercrash_count; crash->out_fd = result; strncpy(crash->crash_path, file_name, FILE_PATH_LEN_MAX); crash->crash_path[FILE_PATH_LEN_MAX - 1] = '\0'; return 0; } static void perform_request(struct crash_node *crash) { ssize_t rc; struct crash_packet response = {0}; if (get_usercrash(crash)) { LOGE("server exit for open usercrash file failed\n"); exit(EXIT_FAILURE); } LOGI("Prepare to write the '%s' log to %s\n", crash->name, crash->crash_path); response.packet_type = kPerformDump; rc = send_fd(crash->crash_fd, &response, sizeof(response), crash->out_fd); close(crash->out_fd); if (rc == -1) { LOGE("failed to send response to CrashRequest\n"); goto fail; } else if (rc != sizeof(response)) { LOGE("crash socket write returned short\n"); goto fail; } else { struct timeval timeout = { 100, 0 }; struct event_base *base = event_get_base(crash->crash_event); event_assign(crash->crash_event, base, crash->crash_fd, EV_TIMEOUT | EV_READ, crash_completed_cb, crash); event_add(crash->crash_event, &timeout); } ++num_concurrent_dumps; return; fail: free_crash(crash); } static void dequeue_requests(void) { while (!TAILQ_EMPTY(&queue_t) && num_concurrent_dumps < kMaxConcurrentDumps) { struct crash_node *next_crash = pop_front(); perform_request(next_crash); } } static void crash_accept_cb(struct evconnlistener *listener, evutil_socket_t sockfd, struct sockaddr *sa __attribute__((unused)), int socklen __attribute__((unused)), void *user_data __attribute__((unused))) { struct event_base *base = evconnlistener_get_base(listener); struct crash_node *crash = (struct crash_node *)malloc( sizeof(struct crash_node)); struct timeval timeout = { 1, 0 }; struct event *crash_event = event_new(base, sockfd, EV_TIMEOUT | EV_READ, crash_request_cb, crash); if (!crash) { LOGE("Malloc memory for crash failed.\n"); exit(EXIT_FAILURE); } memset(crash, 0, sizeof(struct crash_node)); crash->crash_fd = sockfd; crash->crash_event = crash_event; event_add(crash_event, &timeout); } static void crash_request_cb(evutil_socket_t sockfd, short ev, void *arg) { ssize_t rc; struct crash_node *crash = arg; struct crash_packet request = {0}; if ((ev & EV_TIMEOUT) != 0) { LOGE("crash request timed out\n"); goto fail; } else if ((ev & EV_READ) == 0) { LOGE("usercrash server received unexpected event "); LOGE("from crash socket\n"); goto fail; } rc = read(sockfd, &request, sizeof(request)); if (rc == -1) { LOGE("failed to read from crash socket\n"); goto fail; } else if (rc != sizeof(request)) { LOGE("crash socket received short read of length %lu ", rc); LOGE("(expected %lu)\n", sizeof(request)); goto fail; } if (request.packet_type != kDumpRequest) { LOGE("unexpected crash packet type, expected kDumpRequest, "); LOGE("received %d\n", request.packet_type); goto fail; } crash->pid = request.pid; strncpy(crash->name, request.name, COMM_NAME_LEN); crash->name[COMM_NAME_LEN - 1] = '\0'; LOGI("received crash request from pid %d, name: %s\n", crash->pid, crash->name); if (num_concurrent_dumps == kMaxConcurrentDumps) { LOGI("enqueueing crash request for pid %d\n", crash->pid); push_back(crash); } else { perform_request(crash); } return; fail: free_crash(crash); } static void crash_completed_cb(evutil_socket_t sockfd, short ev, void *arg) { ssize_t rc; struct crash_node *crash = arg; struct crash_packet request = {0}; --num_concurrent_dumps; if ((ev & EV_TIMEOUT) != 0) { LOGE("error for crash request timed out, error (%s)\n", strerror(errno)); goto out; } else if ((ev & EV_READ) == 0) { LOGE("usercrash server received unexpected event "); LOGE("from crash socket\n"); goto out; } rc = read(sockfd, &request, sizeof(request)); if (rc == -1) { LOGE("failed to read from crash socket\n"); goto out; } else if (rc != sizeof(request)) { LOGE("crash socket received short read of length %lu, ", rc); LOGE("(expected %lu)\n", sizeof(request)); goto out; } if (request.packet_type != kCompletedDump) { LOGE("unexpected crash packet type, expected kCompletedDump, "); LOGE("received %d\n", request.packet_type); goto out; } out: free_crash(crash); /* If there's something queued up, let them proceed */ dequeue_requests(); } static void sig_handler(int sig) { LOGE("received fatal signal: %d\n", sig); exit(EXIT_FAILURE); } static void print_usage(void) { printf("usercrash - tool to dump crash info for the crashes in the "); printf("userspace on sos. It's the server role of usercrash.\n"); printf("[Usage] usercrash_s (root role to run)\n"); printf("[Option]\n"); printf("\t-h: print this usage message\n"); printf("\t-v: print usercrash_s version\n"); printf("[Output] crash log is in %s folder\n", usercrash_directory); } int main(int argc, char *argv[]) { char socket_path[SOCKET_PATH_MAX]; DIR *dir; int fd; int len; evutil_socket_t crash_socket; int opt; struct sigaction action; struct event_base *base; struct evconnlistener *listener; if (argc > 1) { while ((opt = getopt(argc, argv, "vh")) != -1) { switch (opt) { case 'v': printf("version is %d.%d-%s, build by %s@%s\n", UC_MAJOR_VERSION, UC_MINOR_VERSION, UC_BUILD_VERSION, UC_BUILD_USER, UC_BUILD_TIME); break; case 'h': print_usage(); break; default: printf("unknown option\n"); exit(EXIT_FAILURE); } } return 0; } if (getuid() != 0) { LOGE("failed to boot usercrash_s, root is required\n"); exit(EXIT_FAILURE); } dir = opendir(usercrash_directory); if (dir == NULL) { fd = mkdir(usercrash_directory, 0755); if (fd == -1) { LOGE("create log folder failed, error (%s)\n", strerror(errno)); exit(EXIT_FAILURE); } if (chmod(usercrash_directory, 0755) == -1) { LOGE("failed to change usercrash folder priority, "); LOGE("error (%s)\n", strerror(errno)); exit(EXIT_FAILURE); } } else { closedir(dir); } /* Don't try to connect to ourselves if we crash */ memset(&action, 0, sizeof(struct sigaction)); action.sa_handler = sig_handler; debuggerd_register_handlers(&action); find_oldest_usercrash(); len = snprintf(socket_path, sizeof(socket_path), "%s/%s", RESERVED_SOCKET_PREFIX, SOCKET_NAME); if (s_not_expect(len , sizeof(socket_path))) { LOGE("construct socket path error\n"); exit(EXIT_FAILURE); } /** * evutil_socket_t on other platform except WIN32 platform is int * type, but on WIN32 platform evutil_socket_t is intptr_t type. * So, considering compatibility, here need to transfer socket fd to * evutil_socket_t type. */ crash_socket = (evutil_socket_t)create_socket_server(socket_path, SOCK_SEQPACKET); if (crash_socket == -1) { LOGE("failed to get socket from init, error (%s)\n", strerror(errno)); exit(EXIT_FAILURE); } if (chmod(socket_path, 0622) == -1) { LOGE("failed to change usercrashd_crash priority\n"); goto fail; } evutil_make_socket_nonblocking(crash_socket); base = event_base_new(); if (!base) { LOGE("failed to create event_base\n"); goto fail; } TAILQ_INIT(&queue_t); listener = evconnlistener_new(base, crash_accept_cb, NULL, LEV_OPT_CLOSE_ON_FREE, -1, crash_socket); if (!listener) { LOGE("failed to create evconnlistener: %s\n", strerror(errno)); goto fail; } LOGI("usercrash_s successfully initialized\n"); event_base_dispatch(base); evconnlistener_free(listener); event_base_free(base); close(crash_socket); return 0; fail: close(crash_socket); exit(EXIT_FAILURE); }