blob: 581bb03a9d565722bf7d64b451de29b03cc81290 [file] [log] [blame]
Selim Gurunc4d7c012019-05-13 20:27:22 -07001/*
2 * Copyright (C) 2019 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#define LOG_TAG "car-bugreportd"
18
19#include <android-base/errors.h>
20#include <android-base/file.h>
21#include <android-base/logging.h>
22#include <android-base/macros.h>
23#include <android-base/properties.h>
24#include <android-base/stringprintf.h>
25#include <android-base/strings.h>
26#include <android-base/unique_fd.h>
27#include <cutils/sockets.h>
28#include <errno.h>
29#include <fcntl.h>
Selim Guruna85b7e72019-06-07 11:01:42 -070030#include <ftw.h>
31#include <gui/SurfaceComposerClient.h>
Selim Gurunc4d7c012019-05-13 20:27:22 -070032#include <log/log_main.h>
33#include <private/android_filesystem_config.h>
34#include <stdio.h>
35#include <stdlib.h>
Selim Guruna85b7e72019-06-07 11:01:42 -070036#include <sys/prctl.h>
Selim Gurunc4d7c012019-05-13 20:27:22 -070037#include <sys/stat.h>
38#include <sys/types.h>
39#include <sys/wait.h>
40#include <time.h>
41#include <unistd.h>
Selim Guruna85b7e72019-06-07 11:01:42 -070042#include <ziparchive/zip_writer.h>
Selim Gurunc4d7c012019-05-13 20:27:22 -070043
44#include <chrono>
45#include <string>
46#include <vector>
47
48namespace {
Selim Guruna85b7e72019-06-07 11:01:42 -070049// Directory used for keeping temporary files
50constexpr const char* kTempDirectory = "/data/user_de/0/com.android.shell/temp_bugreport_files";
Selim Gurunc4d7c012019-05-13 20:27:22 -070051// Socket to write the progress information.
52constexpr const char* kCarBrProgressSocket = "car_br_progress_socket";
53// Socket to write the zipped bugreport file.
54constexpr const char* kCarBrOutputSocket = "car_br_output_socket";
Selim Guruna85b7e72019-06-07 11:01:42 -070055// Socket to write the extra bugreport zip file. This zip file contains data that does not exist
56// in bugreport file generated by dumpstate.
57constexpr const char* kCarBrExtraOutputSocket = "car_br_extra_output_socket";
Selim Gurunc4d7c012019-05-13 20:27:22 -070058// The prefix used by bugreportz protocol to indicate bugreport finished successfully.
59constexpr const char* kOkPrefix = "OK:";
60// Number of connect attempts to dumpstate socket
61constexpr const int kMaxDumpstateConnectAttempts = 20;
62// Wait time between connect attempts
63constexpr const int kWaitTimeBetweenConnectAttemptsInSec = 1;
Zhomart Mukhamejanovcd25b852019-09-11 18:35:29 -070064// Wait time for dumpstate. Set a timeout so that if nothing is read in 10 minutes, we'll stop
65// reading and quit. No timeout in dumpstate is longer than 60 seconds, so this gives lots of leeway
66// in case of unforeseen time outs.
67constexpr const int kDumpstateTimeoutInSec = 600;
Selim Guruna85b7e72019-06-07 11:01:42 -070068// The prefix for screenshot filename in the generated zip file.
69constexpr const char* kScreenshotPrefix = "/screenshot";
70
71using android::OK;
72using android::PhysicalDisplayId;
73using android::status_t;
74using android::SurfaceComposerClient;
Selim Gurunc4d7c012019-05-13 20:27:22 -070075
76// Returns a valid socket descriptor or -1 on failure.
77int openSocket(const char* service) {
78 int s = android_get_control_socket(service);
79 if (s < 0) {
80 ALOGE("android_get_control_socket(%s): %s", service, strerror(errno));
81 return -1;
82 }
83 fcntl(s, F_SETFD, FD_CLOEXEC);
84 if (listen(s, 4) < 0) {
85 ALOGE("listen(control socket): %s", strerror(errno));
86 return -1;
87 }
88
89 struct sockaddr addr;
90 socklen_t alen = sizeof(addr);
91 int fd = accept(s, &addr, &alen);
92 if (fd < 0) {
93 ALOGE("accept(control socket): %s", strerror(errno));
94 return -1;
95 }
96 return fd;
97}
98
99// Processes the given dumpstate progress protocol |line| and updates
100// |out_last_nonempty_line| when |line| is non-empty, and |out_zip_path| when
101// the bugreport is finished.
102void processLine(const std::string& line, std::string* out_zip_path,
103 std::string* out_last_nonempty_line) {
104 // The protocol is documented in frameworks/native/cmds/bugreportz/readme.md
105 if (line.empty()) {
106 return;
107 }
108 *out_last_nonempty_line = line;
109 if (line.find(kOkPrefix) != 0) {
110 return;
111 }
112 *out_zip_path = line.substr(strlen(kOkPrefix));
113 return;
114}
115
Selim Guruna85b7e72019-06-07 11:01:42 -0700116// Sends the contents of the zip fileto |outfd|.
117// Returns true if success
118void zipFilesToFd(const std::vector<std::string>& extra_files, int outfd) {
119 // pass fclose as Deleter to close the file when unique_ptr is destroyed.
120 std::unique_ptr<FILE, decltype(fclose)*> outfile = {fdopen(outfd, "wb"), fclose};
121 if (outfile == nullptr) {
122 ALOGE("Failed to open output descriptor");
123 return;
124 }
125 auto writer = std::make_unique<ZipWriter>(outfile.get());
126
127 int error = 0;
128 for (const auto& filepath : extra_files) {
129 const auto name = android::base::Basename(filepath);
130
131 error = writer->StartEntry(name.c_str(), 0);
132 if (error) {
133 ALOGE("Failed to start entry %s", writer->ErrorCodeString(error));
134 return;
135 }
136 android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(filepath.c_str(), O_RDONLY)));
137 if (fd == -1) {
138 return;
139 }
140 while (1) {
141 char buffer[65536];
142
143 ssize_t bytes_read = TEMP_FAILURE_RETRY(read(fd, buffer, sizeof(buffer)));
144 if (bytes_read == 0) {
145 break;
146 }
147 if (bytes_read == -1) {
148 if (errno == EAGAIN) {
149 ALOGE("timed out while reading %s", name.c_str());
150 } else {
151 ALOGE("read terminated abnormally (%s)", strerror(errno));
152 }
153 // fail immediately
154 return;
155 }
156 error = writer->WriteBytes(buffer, bytes_read);
157 if (error) {
158 ALOGE("WriteBytes() failed %s", ZipWriter::ErrorCodeString(error));
159 // fail immediately
160 return;
161 }
162 }
163
164 error = writer->FinishEntry();
165 if (error) {
166 ALOGE("failed to finish entry %s", writer->ErrorCodeString(error));
167 continue;
168 }
169 }
170 error = writer->Finish();
171 if (error) {
172 ALOGE("failed to finish zip writer %s", writer->ErrorCodeString(error));
173 }
174}
175
Selim Gurunc4d7c012019-05-13 20:27:22 -0700176int copyTo(int fd_in, int fd_out, void* buffer, size_t buffer_len) {
177 ssize_t bytes_read = TEMP_FAILURE_RETRY(read(fd_in, buffer, buffer_len));
178 if (bytes_read == 0) {
179 return 0;
180 }
181 if (bytes_read == -1) {
182 // EAGAIN really means time out, so make that clear.
183 if (errno == EAGAIN) {
184 ALOGE("read timed out");
185 } else {
186 ALOGE("read terminated abnormally (%s)", strerror(errno));
187 }
188 return -1;
189 }
190 // copy all bytes to the output socket
191 if (!android::base::WriteFully(fd_out, buffer, bytes_read)) {
192 ALOGE("write failed");
193 return -1;
194 }
195 return bytes_read;
196}
197
198bool copyFile(const std::string& zip_path, int output_socket) {
199 android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(zip_path.c_str(), O_RDONLY)));
200 if (fd == -1) {
Zhomart Mukhamejanovcd25b852019-09-11 18:35:29 -0700201 ALOGE("Failed to open zip file %s.", zip_path.c_str());
Selim Gurunc4d7c012019-05-13 20:27:22 -0700202 return false;
203 }
204 while (1) {
205 char buffer[65536];
206 int bytes_copied = copyTo(fd, output_socket, buffer, sizeof(buffer));
207 if (bytes_copied == 0) {
208 break;
209 }
210 if (bytes_copied == -1) {
Zhomart Mukhamejanovcd25b852019-09-11 18:35:29 -0700211 ALOGE("Failed to copy zip file %s to the output_socket.", zip_path.c_str());
Selim Gurunc4d7c012019-05-13 20:27:22 -0700212 return false;
213 }
214 }
215 return true;
216}
217
218// Triggers a bugreport and waits until it is all collected.
219// returns false if error, true if success
220bool doBugreport(int progress_socket, size_t* out_bytes_written, std::string* zip_path) {
221 // Socket will not be available until service starts.
222 android::base::unique_fd s;
223 for (int i = 0; i < kMaxDumpstateConnectAttempts; i++) {
224 s.reset(socket_local_client("dumpstate", ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_STREAM));
225 if (s != -1) break;
226 sleep(kWaitTimeBetweenConnectAttemptsInSec);
227 }
228
229 if (s == -1) {
230 ALOGE("failed to connect to dumpstatez service");
231 return false;
232 }
233
234 // Set a timeout so that if nothing is read by the timeout, stop reading and quit
235 struct timeval tv = {
236 .tv_sec = kDumpstateTimeoutInSec,
237 .tv_usec = 0,
238 };
239 if (setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) != 0) {
240 ALOGW("Cannot set socket timeout (%s)", strerror(errno));
241 }
242
243 std::string line;
244 std::string last_nonempty_line;
Zhomart Mukhamejanovcd25b852019-09-11 18:35:29 -0700245 char buffer[65536];
Selim Gurunc4d7c012019-05-13 20:27:22 -0700246 while (true) {
Selim Gurunc4d7c012019-05-13 20:27:22 -0700247 ssize_t bytes_read = copyTo(s, progress_socket, buffer, sizeof(buffer));
248 if (bytes_read == 0) {
249 break;
250 }
251 if (bytes_read == -1) {
Zhomart Mukhamejanovcd25b852019-09-11 18:35:29 -0700252 ALOGE("Failed to copy progress to the progress_socket.");
Selim Gurunc4d7c012019-05-13 20:27:22 -0700253 return false;
254 }
255 // Process the buffer line by line. this is needed for the filename.
256 for (int i = 0; i < bytes_read; i++) {
257 char c = buffer[i];
258 if (c == '\n') {
259 processLine(line, zip_path, &last_nonempty_line);
260 line.clear();
261 } else {
262 line.append(1, c);
263 }
264 }
Selim Guruna85b7e72019-06-07 11:01:42 -0700265 *out_bytes_written += bytes_read;
Selim Gurunc4d7c012019-05-13 20:27:22 -0700266 }
267 s.reset();
268 // Process final line, in case it didn't finish with newline.
269 processLine(line, zip_path, &last_nonempty_line);
270 // if doBugReport finished successfully, zip path should be set.
271 if (zip_path->empty()) {
272 ALOGE("no zip file path was found in bugreportz progress data");
273 return false;
274 }
275 return true;
276}
277
Selim Guruna85b7e72019-06-07 11:01:42 -0700278bool waitpid_with_timeout(pid_t pid, int timeout_secs, int* status) {
279 sigset_t child_mask, old_mask;
280 sigemptyset(&child_mask);
281 sigaddset(&child_mask, SIGCHLD);
282
283 if (sigprocmask(SIG_BLOCK, &child_mask, &old_mask) == -1) {
284 ALOGE("*** sigprocmask failed: %s\n", strerror(errno));
285 return false;
286 }
287
288 timespec ts = {.tv_sec = timeout_secs, .tv_nsec = 0};
289 int ret = TEMP_FAILURE_RETRY(sigtimedwait(&child_mask, nullptr, &ts));
290 int saved_errno = errno;
291
292 // Set the signals back the way they were.
293 if (sigprocmask(SIG_SETMASK, &old_mask, nullptr) == -1) {
294 ALOGE("*** sigprocmask failed: %s\n", strerror(errno));
295 if (ret == 0) {
296 return false;
297 }
298 }
299 if (ret == -1) {
300 errno = saved_errno;
301 if (errno == EAGAIN) {
302 errno = ETIMEDOUT;
303 } else {
304 ALOGE("*** sigtimedwait failed: %s\n", strerror(errno));
305 }
306 return false;
307 }
308
309 pid_t child_pid = waitpid(pid, status, WNOHANG);
310 if (child_pid != pid) {
311 if (child_pid != -1) {
312 ALOGE("*** Waiting for pid %d, got pid %d instead\n", pid, child_pid);
313 } else {
314 ALOGE("*** waitpid failed: %s\n", strerror(errno));
315 }
316 return false;
317 }
318 return true;
319}
320
321// Runs the given command. Kills the command if it does not finish by timeout.
322int runCommand(int timeout_secs, const char* file, std::vector<const char*> args) {
323 pid_t pid = fork();
324
325 // handle error case
326 if (pid < 0) {
327 ALOGE("fork failed %s", strerror(errno));
328 return pid;
329 }
330
331 // handle child case
332 if (pid == 0) {
333 /* make sure the child dies when parent dies */
334 prctl(PR_SET_PDEATHSIG, SIGKILL);
335
336 /* just ignore SIGPIPE, will go down with parent's */
337 struct sigaction sigact;
338 memset(&sigact, 0, sizeof(sigact));
339 sigact.sa_handler = SIG_IGN;
340 sigaction(SIGPIPE, &sigact, nullptr);
341
342 execvp(file, (char**)args.data());
343 // execvp's result will be handled after waitpid_with_timeout() below, but
344 // if it failed, it's safer to exit dumpstate.
345 ALOGE("execvp on command %s failed (error: %s)", file, strerror(errno));
346 _exit(EXIT_FAILURE);
347 }
348
349 // handle parent case
350 int status;
351 bool ret = waitpid_with_timeout(pid, timeout_secs, &status);
352
353 if (!ret) {
354 if (errno == ETIMEDOUT) {
355 ALOGE("command %s timed out (killing pid %d)", file, pid);
356 } else {
357 ALOGE("command %s: Error (killing pid %d)\n", file, pid);
358 }
359 kill(pid, SIGTERM);
360 if (!waitpid_with_timeout(pid, 5, nullptr)) {
361 kill(pid, SIGKILL);
362 if (!waitpid_with_timeout(pid, 5, nullptr)) {
363 ALOGE("could not kill command '%s' (pid %d) even with SIGKILL.\n", file, pid);
364 }
365 }
366 return -1;
367 }
368
369 if (WIFSIGNALED(status)) {
370 ALOGE("command '%s' failed: killed by signal %d\n", file, WTERMSIG(status));
371 } else if (WIFEXITED(status) && WEXITSTATUS(status) > 0) {
372 status = WEXITSTATUS(status);
373 ALOGE("command '%s' failed: exit code %d\n", file, status);
374 }
375
376 return status;
377}
378
Zhomart Mukhamejanov2770ccd2019-06-25 14:49:17 -0700379void takeScreenshotForDisplayId(PhysicalDisplayId id, const char* tmp_dir,
Selim Guruna85b7e72019-06-07 11:01:42 -0700380 std::vector<std::string>* extra_files) {
381 std::string id_as_string = std::to_string(id);
382 std::string filename = std::string(tmp_dir) + kScreenshotPrefix + id_as_string + ".png";
383 std::vector<const char*> args { "-p", "-d", id_as_string.c_str(), filename.c_str(), nullptr };
Zhomart Mukhamejanov2770ccd2019-06-25 14:49:17 -0700384 ALOGI("capturing screen for display (%s) as %s", id_as_string.c_str(), filename.c_str());
Selim Guruna85b7e72019-06-07 11:01:42 -0700385 int status = runCommand(10, "/system/bin/screencap", args);
386 if (status == 0) {
387 LOG(INFO) << "Screenshot saved for display:" << id_as_string;
388 }
389 // add the file regardless of the exit status of the screencap util.
390 extra_files->push_back(filename);
391
392 LOG(ERROR) << "Failed to take screenshot for display:" << id_as_string;
393}
394
395void takeScreenshot(const char* tmp_dir, std::vector<std::string>* extra_files) {
396 // Now send the screencaptures
397 std::vector<PhysicalDisplayId> ids = SurfaceComposerClient::getPhysicalDisplayIds();
398
399 for (PhysicalDisplayId display_id : ids) {
400 takeScreenshotForDisplayId(display_id, tmp_dir, extra_files);
401 }
402}
403
404bool recursiveRemoveDir(const std::string& path) {
405 auto callback = [](const char* child, const struct stat*, int file_type, struct FTW*) -> int {
406 if (file_type == FTW_DP) {
407 if (rmdir(child) == -1) {
408 ALOGE("rmdir(%s): %s", child, strerror(errno));
409 return -1;
410 }
411 } else if (file_type == FTW_F) {
412 if (unlink(child) == -1) {
413 ALOGE("unlink(%s): %s", child, strerror(errno));
414 return -1;
415 }
416 }
417 return 0;
418 };
419 // do a file tree walk with a sufficiently large depth.
420 return nftw(path.c_str(), callback, 128, FTW_DEPTH) == 0;
421}
422
423status_t createTempDir(const char* dir) {
424 struct stat sb;
425 if (TEMP_FAILURE_RETRY(stat(dir, &sb)) == 0) {
426 if (!recursiveRemoveDir(dir)) {
427 return -errno;
428 }
429 } else if (errno != ENOENT) {
430 ALOGE("Failed to stat %s ", dir);
431 return -errno;
432 }
433 if (TEMP_FAILURE_RETRY(mkdir(dir, 0700)) == -1) {
434 ALOGE("Failed to mkdir %s", dir);
435 return -errno;
436 }
437 return OK;
438}
439
Selim Gurunc4d7c012019-05-13 20:27:22 -0700440// Removes bugreport
441void cleanupBugreportFile(const std::string& zip_path) {
442 if (unlink(zip_path.c_str()) != 0) {
443 ALOGE("Could not unlink %s (%s)", zip_path.c_str(), strerror(errno));
444 }
445}
446
447} // namespace
448
449int main(void) {
Selim Guruna85b7e72019-06-07 11:01:42 -0700450 ALOGI("Starting bugreport collecting service");
Selim Gurunc4d7c012019-05-13 20:27:22 -0700451
452 auto t0 = std::chrono::steady_clock::now();
453
Selim Guruna85b7e72019-06-07 11:01:42 -0700454 std::vector<std::string> extra_files;
455 if (createTempDir(kTempDirectory) == OK) {
456 // take screenshots of the physical displays as early as possible
457 takeScreenshot(kTempDirectory, &extra_files);
458 }
459
Selim Gurunc4d7c012019-05-13 20:27:22 -0700460 // Start the dumpstatez service.
461 android::base::SetProperty("ctl.start", "car-dumpstatez");
462
463 size_t bytes_written = 0;
464
465 std::string zip_path;
466 int progress_socket = openSocket(kCarBrProgressSocket);
467 if (progress_socket < 0) {
468 // early out. in this case we will not print the final message, but that is ok.
469 android::base::SetProperty("ctl.stop", "car-dumpstatez");
470 return EXIT_FAILURE;
471 }
472 bool ret_val = doBugreport(progress_socket, &bytes_written, &zip_path);
473 close(progress_socket);
474
Zhomart Mukhamejanovcd25b852019-09-11 18:35:29 -0700475 if (ret_val) {
476 int output_socket = openSocket(kCarBrOutputSocket);
477 if (output_socket != -1) {
478 ret_val = copyFile(zip_path, output_socket);
479 close(output_socket);
480 }
Selim Gurunc4d7c012019-05-13 20:27:22 -0700481 }
482
Selim Guruna85b7e72019-06-07 11:01:42 -0700483 int extra_output_socket = openSocket(kCarBrExtraOutputSocket);
484 if (extra_output_socket != -1 && ret_val) {
485 zipFilesToFd(extra_files, extra_output_socket);
486 }
487 if (extra_output_socket != -1) {
488 close(extra_output_socket);
489 }
490
Selim Gurunc4d7c012019-05-13 20:27:22 -0700491 auto delta = std::chrono::duration_cast<std::chrono::duration<double>>(
492 std::chrono::steady_clock::now() - t0)
493 .count();
494
495 std::string result = ret_val ? "success" : "failed";
496 ALOGI("bugreport %s in %.02fs, %zu bytes written", result.c_str(), delta, bytes_written);
497 cleanupBugreportFile(zip_path);
498
Selim Guruna85b7e72019-06-07 11:01:42 -0700499 recursiveRemoveDir(kTempDirectory);
500
Selim Gurunc4d7c012019-05-13 20:27:22 -0700501 // No matter how doBugreport() finished, let's try to explicitly stop
502 // car-dumpstatez in case it stalled.
503 android::base::SetProperty("ctl.stop", "car-dumpstatez");
504
505 return ret_val ? EXIT_SUCCESS : EXIT_FAILURE;
506}