1 /*
2  * Copyright (C) 2018-2022 Intel Corporation.
3  * SPDX-License-Identifier: BSD-3-Clause
4  */
5 
6 /*
7  * Copyright (C) 2018-2022 Intel Corporation.
8  *
9  * Licensed under the Apache License, Version 2.0 (the "License");
10  * you may not use this file except in compliance with the License.
11  * You may obtain a copy of the License at
12  *
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  *
15  * Unless required by applicable law or agreed to in writing, software
16  * distributed under the License is distributed on an "AS IS" BASIS,
17  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
18  * See the License for the specific language governing permissions and
19  * limitations under the License.
20  */
21 
22 #include <errno.h>
23 #include <stdio.h>
24 #include <fcntl.h>
25 #include <unistd.h>
26 #include <stdlib.h>
27 #include <string.h>
28 
29 #include "crash_dump.h"
30 #include "packet.h"
31 #include "log_sys.h"
32 #include "protocol.h"
33 #include "version.h"
34 
35 /**
36  * Usercrash works as C/S model: usercrash_c works as usercrash client to
37  * collect crash logs and information once crash event occurs. For each time,
38  * usercrash_c receives 3 params from core_dump and sends connect request event
39  * to usercrash_s, then it receives file fd from server to fill crash info into
40  * the file. After this work is done, it will notify server that dump work is
41  * completed.
42  */
43 
44 /**
45  * @sockfd: the socket fd.
46  * set_timeout is used to set timeout for the sockfd, in case client is blocked
47  * when client cannot receive the data from server, or send data to server
48  */
set_timeout(int sockfd)49 static int set_timeout(int sockfd)
50 {
51 	struct timeval timeout = {50, 0};
52 
53 	if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &timeout,
54 				sizeof(timeout)) != 0) {
55 		LOGE("failed to set receive timeout\n");
56 		return -1;
57 	}
58 	if (setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &timeout,
59 				sizeof(timeout)) != 0) {
60 		LOGE("failed to set send timeout\n");
61 		return -1;
62 	}
63 	return 0;
64 }
65 
66 /**
67  * @pid: crash process pid.
68  * @usercrashd_socket: client socket fd pointer, get the value from
69  * usercrashd_connect function.
70  * @output_fd: file fd, receives from server to store dump file.
71  * @name: crash process name
72  * usercrashd_connect is used to connect to server, and get the crash file fd
73  * from server
74  */
usercrashd_connect(int pid,int * usercrashd_socket,int * output_fd,const char * name)75 static int usercrashd_connect(int pid, int *usercrashd_socket,
76 		int *output_fd, const char *name)
77 {
78 	int sockfd;
79 	int tmp_output_fd;
80 	ssize_t rc;
81 	int flags;
82 	struct crash_packet packet = {0};
83 
84 	if (name == NULL) {
85 		LOGE("crash process name is NULL\n");
86 		return -1;
87 	}
88 	sockfd = socket_local_client(SOCKET_NAME, strlen(SOCKET_NAME),
89 			SOCK_SEQPACKET | SOCK_CLOEXEC);
90 	if (sockfd == -1) {
91 		LOGE("failed to connect to usercrashd, error (%s)\n",
92 			strerror(errno));
93 		return -1;
94 	}
95 	packet.packet_type = kDumpRequest;
96 	packet.pid = pid;
97 	strncpy(packet.name, name, COMM_NAME_LEN);
98 	packet.name[COMM_NAME_LEN - 1] = '\0';
99 	if (set_timeout(sockfd)) {
100 		close(sockfd);
101 		return -1;
102 	}
103 	if (write(sockfd, &packet, sizeof(packet)) !=
104 				sizeof(packet)) {
105 		LOGE("failed to write DumpRequest packet, error (%s)\n",
106 			strerror(errno));
107 		close(sockfd);
108 		return -1;
109 	}
110 
111 	rc = recv_fd(sockfd, &packet, sizeof(packet),
112 				&tmp_output_fd);
113 	if (rc == -1) {
114 		LOGE("failed to read response to DumpRequest packet, ");
115 		LOGE("error (%s)\n", strerror(errno));
116 		close(sockfd);
117 		return -1;
118 	} else if (rc != sizeof(packet)) {
119 		LOGE("received DumpRequest response packet of incorrect ");
120 		LOGE("length (expected %lu, got %ld)\n", sizeof(packet), rc);
121 		goto fail;
122 	}
123 	if (packet.packet_type != kPerformDump) {
124 		LOGE("unexpected dump response:%d\n", packet.packet_type);
125 		goto fail;
126 	}
127 
128 	/**
129 	 * Make the fd O_APPEND so that our output is guaranteed to be at the
130 	 * end of a file. (This also makes selinux rules consistent, because
131 	 * selinux distinguishes between writing to a regular fd, and writing
132 	 * to an fd with O_APPEND)
133 	 */
134 	flags = fcntl(tmp_output_fd, F_GETFL);
135 	if (fcntl(tmp_output_fd, F_SETFL, flags | O_APPEND) != 0) {
136 		LOGE("failed to set output fd flags, error (%s)\n",
137 					strerror(errno));
138 		goto fail;
139 	}
140 
141 	*usercrashd_socket = sockfd;
142 	*output_fd = tmp_output_fd;
143 
144 	return 0;
145 fail:
146 	close(sockfd);
147 	close(tmp_output_fd);
148 	return -1;
149 
150 }
151 
152 /**
153  * @usercrashd_socket: client socket fd, used to communicate with server.
154  * usercrashd_notify_completion is used to tell the server it has done the
155  * dump, the server will pop another crash from the queue and execute the dump
156  * process
157  */
usercrashd_notify_completion(int usercrashd_socket)158 static int usercrashd_notify_completion(int usercrashd_socket)
159 {
160 	struct crash_packet packet = {0};
161 
162 	packet.packet_type = kCompletedDump;
163 	if (set_timeout(usercrashd_socket)) {
164 		close(usercrashd_socket);
165 		return -1;
166 	}
167 	if (write(usercrashd_socket, &packet,
168 				sizeof(packet)) != sizeof(packet)) {
169 		return -1;
170 	}
171 	return 0;
172 }
173 
print_usage(void)174 static void print_usage(void)
175 {
176 	printf("usercrash - tool to dump crash info for the crashes in the ");
177 	printf("userspace on sos. It's the client role of usercrash.\n");
178 	printf("[Usage]\n");
179 	printf("\t--coredump, usercrash_c <pid> <comm> <sig> ");
180 	printf("(root role to run)\n");
181 	printf("[Option]\n");
182 	printf("\t-h: print this usage message\n");
183 	printf("\t-v: print usercrash_c version\n");
184 }
185 
main(int argc,char * argv[])186 int main(int argc, char *argv[])
187 {
188 	int pid;
189 	int sig;
190 	int out_fd;
191 	int sock;
192 	int ret;
193 
194 	if (argc > 1) {
195 		if (strcmp(argv[1], "-v") == 0) {
196 			printf("version is %d.%d-%s, build by %s@%s\n",
197 				UC_MAJOR_VERSION, UC_MINOR_VERSION,
198 				UC_BUILD_VERSION, UC_BUILD_USER,
199 				UC_BUILD_TIME);
200 			return 0;
201 		}
202 		if (strcmp(argv[1], "-h") == 0) {
203 			print_usage();
204 			return 0;
205 		}
206 	} else
207 		print_usage();
208 
209 	if (getuid() != 0) {
210 		LOGE("failed to execute usercrash_c, root is required\n");
211 		exit(EXIT_FAILURE);
212 	}
213 
214 	if (argc == 4) {
215 		/* it's from coredump */
216 		pid = (int)strtol(argv[1], NULL, 10);
217 		sig = (int)strtol(argv[3], NULL, 10);
218 		ret = usercrashd_connect(pid, &sock, &out_fd, argv[2]);
219 		if (ret) {
220 			LOGE("usercrashd_connect failed, error (%s)\n",
221 					strerror(errno));
222 			exit(EXIT_FAILURE);
223 		}
224 		crash_dump(pid, sig, out_fd);
225 		close(out_fd);
226 		if (usercrashd_notify_completion(sock)) {
227 			LOGE("failed to notify usercrashd of completion");
228 			close(sock);
229 			exit(EXIT_FAILURE);
230 		}
231 		close(sock);
232 	} else {
233 		print_usage();
234 		return 0;
235 	}
236 
237 	return 0;
238 }
239