diff --git a/tools/acrn-crashlog/usercrash/client.c b/tools/acrn-crashlog/usercrash/client.c index 7756dfda1..5089c2aa0 100644 --- a/tools/acrn-crashlog/usercrash/client.c +++ b/tools/acrn-crashlog/usercrash/client.c @@ -3,6 +3,34 @@ * 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 "crash_dump.h" +#include "packet.h" +#include "log_sys.h" +#include "protocol.h" + /** * Usercrash works as C/S model: usercrash_c works as usercrash client to * collect crash logs and information once crash event occurs. For each time, @@ -12,11 +40,188 @@ * completed. */ -int main(void) +/** + * @sockfd: the socket fd. + * set_timeout is used to set timeout for the sockfd, in case client is blocked + * when client cannot receive the data from server, or send data to server + */ +static int set_timeout(int sockfd) { - //TO BE DONE - //This empty function is to satisfy the dependency of Makefile. - //This is the entry of usercrash_c, the implementation will be - //filled by following patches. + struct timeval timeout = {50, 0}; + + if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &timeout, + sizeof(timeout)) != 0) { + LOGE("failed to set receive timeout\n"); + return -1; + } + if (setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &timeout, + sizeof(timeout)) != 0) { + LOGE("failed to set send timeout\n"); + return -1; + } + return 0; +} + +/** + * @pid: crash process pid. + * @usercrashd_socket: client socket fd pointer, get the value from + * usercrashd_connect function. + * @output_fd: file fd, receives from server to store dump file. + * @name: crash process name + * usercrashd_connect is used to connect to server, and get the crash file fd + * from server + */ +bool usercrashd_connect(int pid, int *usercrashd_socket, + int *output_fd, char *name) +{ + int sockfd; + int tmp_output_fd; + ssize_t rc; + int flags; + struct crash_packet packet = {0}; + + sockfd = socket_local_client(SOCKET_NAME, SOCK_SEQPACKET); + if (sockfd == -1) { + LOGE("failed to connect to usercrashd, error (%s)\n", + strerror(errno)); + return false; + } + packet.packet_type = kDumpRequest; + packet.pid = pid; + strncpy(packet.name, name, COMM_NAME_LEN - 1); + if (set_timeout(sockfd)) { + close(sockfd); + return -1; + } + if (write(sockfd, &packet, sizeof(packet)) != + sizeof(packet)) { + LOGE("failed to write DumpRequest packet, error (%s)\n", + strerror(errno)); + close(sockfd); + return false; + } + + rc = recv_fd(sockfd, &packet, sizeof(packet), + &tmp_output_fd); + if (rc == -1) { + LOGE("failed to read response to DumpRequest packet, "); + LOGE("error (%s)\n", strerror(errno)); + close(sockfd); + return false; + } else if (rc != sizeof(packet)) { + LOGE("received DumpRequest response packet of incorrect "); + LOGE("length (expected %lu, got %ld)\n", sizeof(packet), rc); + goto fail; + } + if (packet.packet_type != kPerformDump) { + LOGE("unexpected dump response:%d\n", packet.packet_type); + goto fail; + } + + /** + * Make the fd O_APPEND so that our output is guaranteed to be at the + * end of a file. (This also makes selinux rules consistent, because + * selinux distinguishes between writing to a regular fd, and writing + * to an fd with O_APPEND) + */ + flags = fcntl(tmp_output_fd, F_GETFL); + if (fcntl(tmp_output_fd, F_SETFL, flags | O_APPEND) != 0) { + LOGE("failed to set output fd flags, error (%s)\n", + strerror(errno)); + goto fail; + } + + *usercrashd_socket = sockfd; + *output_fd = tmp_output_fd; + + return true; +fail: + close(sockfd); + close(tmp_output_fd); + return false; + +} + +/** + * @usercrashd_socket: client socket fd, used to communicate with server. + * usercrashd_notify_completion is used to tell the server it has done the + * dump, the server will pop another crash from the queue and execute the dump + * process + */ +bool usercrashd_notify_completion(int usercrashd_socket) +{ + struct crash_packet packet = {0}; + + packet.packet_type = kCompletedDump; + if (set_timeout(usercrashd_socket)) { + close(usercrashd_socket); + return -1; + } + if (write(usercrashd_socket, &packet, + sizeof(packet)) != sizeof(packet)) { + return false; + } + return true; +} + +static void print_usage(void) +{ + printf("It's the client role of usercrash.\n"); + printf("[Usage]\n"); + printf("\t--coredump, usercrash_c "); + printf("(root role to run)\n"); + printf("[Option]\n"); + printf("\t-h: print this usage message\n"); + printf("\t-v: print usercrash_c version\n"); +} + +int main(int argc, char *argv[]) +{ + int pid; + int sig; + int out_fd; + int sock; + int ret; + + if (argc > 1) { + if (strcmp(argv[1], "-v") == 0) { + printf("usercrash_c version is 1.0\n"); + return 0; + } + if (strcmp(argv[1], "-h") == 0) { + print_usage(); + return 0; + } + } else + print_usage(); + + if (getuid() != 0) { + LOGE("failed to execute usercrash_c, root is required\n"); + exit(EXIT_FAILURE); + } + + if (argc == 4) { + /* it's from coredump */ + pid = atoi(argv[1]); + sig = atoi(argv[3]); + ret = usercrashd_connect(pid, &sock, &out_fd, argv[2]); + if (!ret) { + LOGE("usercrashd_connect failed, error (%s)\n", + strerror(errno)); + exit(EXIT_FAILURE); + } + crash_dump(pid, sig, out_fd); + close(out_fd); + if (!usercrashd_notify_completion(sock)) { + LOGE("failed to notify usercrashd of completion"); + close(sock); + exit(EXIT_FAILURE); + } + close(sock); + } else { + print_usage(); + return 0; + } + return 0; }