From 6e656dfd3c992b816dde13963e78a3dd97aa210a Mon Sep 17 00:00:00 2001 From: xiaojin2 Date: Mon, 14 May 2018 14:18:21 +0800 Subject: [PATCH] tools: acrn-crashlog: implementation for the server of usercrash This patch is the implementation patch for the server of usercrash. Usercrash works in C/S model: usercrash_s works as usercrash server, which is to handle events from client in endless loop. Once server receives dump request from client, it will create usercrash_0x file under /var/log/usercrashes/ and send file fd to client. Signed-off-by: xiaojin2 Reviewed-by: Zhang Yanmin Reviewed-by: Liu Chuansheng Reviewed-by: Zhao Yakui Reviewed-by: Geoffroy Van Cutsem Acked-by: Eddie Dong --- tools/acrn-crashlog/usercrash/server.c | 465 ++++++++++++++++++++++++- 1 file changed, 460 insertions(+), 5 deletions(-) diff --git a/tools/acrn-crashlog/usercrash/server.c b/tools/acrn-crashlog/usercrash/server.c index 846878c69..b6b2c63d7 100644 --- a/tools/acrn-crashlog/usercrash/server.c +++ b/tools/acrn-crashlog/usercrash/server.c @@ -3,6 +3,48 @@ * 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" + +#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 @@ -12,11 +54,424 @@ * server will be responsiable to free the crash node and process other events. */ -int main(void) +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) { - //TO BE DONE - //This empty function is to satisfy the dependency of Makefile. - //This is the entry of usercrash_s, the implementation will be filled - //by following patches. + 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 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) { + snprintf(path, sizeof(path), "%s/usercrash_%02d", + usercrash_directory, i); + 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; + char file_name[FILE_PATH_LEN_MAX]; + + memset(file_name, 0, FILE_PATH_LEN_MAX); + snprintf(file_name, sizeof(file_name), "%s/usercrash_%02d", + usercrash_directory, next_usercrash); + + 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 - 1); + + 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 - 1); + + 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; + } + + if (crash->crash_path) { + LOGI("usercrash log written to: %s, ", crash->crash_path); + LOGI("crash process name is: %s\n", crash->name); + } + +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[128]; + DIR *dir; + int fd; + 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("usercrash_s version is 1.0\n"); + 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(); + + /** + * 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)linux_get_control_socket(SOCKET_NAME); + + if (crash_socket == -1) { + LOGE("failed to get socket from init, error (%s)\n", + strerror(errno)); + exit(EXIT_FAILURE); + } + snprintf(socket_path, sizeof(socket_path), "%s/%s", + RESERVED_SOCKET_PREFIX, SOCKET_NAME); + if (chmod(socket_path, 0622) == -1) { + LOGE("failed to change usercrashd_crash priority\n"); + exit(EXIT_FAILURE); + } + + evutil_make_socket_nonblocking(crash_socket); + + base = event_base_new(); + if (!base) { + LOGE("failed to create event_base\n"); + exit(EXIT_FAILURE); + } + + 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)); + exit(EXIT_FAILURE); + } + + LOGI("usercrash_s successfully initialized\n"); + event_base_dispatch(base); + + evconnlistener_free(listener); + event_base_free(base); + return 0; }