blob: ebb2d5f9349ddae8915d334f4508297b3f5c6a25 [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;
64// Wait time for dumpstate. No timeout in dumpstate is longer than 60 seconds. Choose
65// a value that is twice longer.
66constexpr const int kDumpstateTimeoutInSec = 120;
Selim Guruna85b7e72019-06-07 11:01:42 -070067// The prefix for screenshot filename in the generated zip file.
68constexpr const char* kScreenshotPrefix = "/screenshot";
69
70using android::OK;
71using android::PhysicalDisplayId;
72using android::status_t;
73using android::SurfaceComposerClient;
Selim Gurunc4d7c012019-05-13 20:27:22 -070074
75// Returns a valid socket descriptor or -1 on failure.
76int openSocket(const char* service) {
77 int s = android_get_control_socket(service);
78 if (s < 0) {
79 ALOGE("android_get_control_socket(%s): %s", service, strerror(errno));
80 return -1;
81 }
82 fcntl(s, F_SETFD, FD_CLOEXEC);
83 if (listen(s, 4) < 0) {
84 ALOGE("listen(control socket): %s", strerror(errno));
85 return -1;
86 }
87
88 struct sockaddr addr;
89 socklen_t alen = sizeof(addr);
90 int fd = accept(s, &addr, &alen);
91 if (fd < 0) {
92 ALOGE("accept(control socket): %s", strerror(errno));
93 return -1;
94 }
95 return fd;
96}
97
98// Processes the given dumpstate progress protocol |line| and updates
99// |out_last_nonempty_line| when |line| is non-empty, and |out_zip_path| when
100// the bugreport is finished.
101void processLine(const std::string& line, std::string* out_zip_path,
102 std::string* out_last_nonempty_line) {
103 // The protocol is documented in frameworks/native/cmds/bugreportz/readme.md
104 if (line.empty()) {
105 return;
106 }
107 *out_last_nonempty_line = line;
108 if (line.find(kOkPrefix) != 0) {
109 return;
110 }
111 *out_zip_path = line.substr(strlen(kOkPrefix));
112 return;
113}
114
Selim Guruna85b7e72019-06-07 11:01:42 -0700115// Sends the contents of the zip fileto |outfd|.
116// Returns true if success
117void zipFilesToFd(const std::vector<std::string>& extra_files, int outfd) {
118 // pass fclose as Deleter to close the file when unique_ptr is destroyed.
119 std::unique_ptr<FILE, decltype(fclose)*> outfile = {fdopen(outfd, "wb"), fclose};
120 if (outfile == nullptr) {
121 ALOGE("Failed to open output descriptor");
122 return;
123 }
124 auto writer = std::make_unique<ZipWriter>(outfile.get());
125
126 int error = 0;
127 for (const auto& filepath : extra_files) {
128 const auto name = android::base::Basename(filepath);
129
130 error = writer->StartEntry(name.c_str(), 0);
131 if (error) {
132 ALOGE("Failed to start entry %s", writer->ErrorCodeString(error));
133 return;
134 }
135 android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(filepath.c_str(), O_RDONLY)));
136 if (fd == -1) {
137 return;
138 }
139 while (1) {
140 char buffer[65536];
141
142 ssize_t bytes_read = TEMP_FAILURE_RETRY(read(fd, buffer, sizeof(buffer)));
143 if (bytes_read == 0) {
144 break;
145 }
146 if (bytes_read == -1) {
147 if (errno == EAGAIN) {
148 ALOGE("timed out while reading %s", name.c_str());
149 } else {
150 ALOGE("read terminated abnormally (%s)", strerror(errno));
151 }
152 // fail immediately
153 return;
154 }
155 error = writer->WriteBytes(buffer, bytes_read);
156 if (error) {
157 ALOGE("WriteBytes() failed %s", ZipWriter::ErrorCodeString(error));
158 // fail immediately
159 return;
160 }
161 }
162
163 error = writer->FinishEntry();
164 if (error) {
165 ALOGE("failed to finish entry %s", writer->ErrorCodeString(error));
166 continue;
167 }
168 }
169 error = writer->Finish();
170 if (error) {
171 ALOGE("failed to finish zip writer %s", writer->ErrorCodeString(error));
172 }
173}
174
Selim Gurunc4d7c012019-05-13 20:27:22 -0700175int copyTo(int fd_in, int fd_out, void* buffer, size_t buffer_len) {
176 ssize_t bytes_read = TEMP_FAILURE_RETRY(read(fd_in, buffer, buffer_len));
177 if (bytes_read == 0) {
178 return 0;
179 }
180 if (bytes_read == -1) {
181 // EAGAIN really means time out, so make that clear.
182 if (errno == EAGAIN) {
183 ALOGE("read timed out");
184 } else {
185 ALOGE("read terminated abnormally (%s)", strerror(errno));
186 }
187 return -1;
188 }
189 // copy all bytes to the output socket
190 if (!android::base::WriteFully(fd_out, buffer, bytes_read)) {
191 ALOGE("write failed");
192 return -1;
193 }
194 return bytes_read;
195}
196
197bool copyFile(const std::string& zip_path, int output_socket) {
198 android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(zip_path.c_str(), O_RDONLY)));
199 if (fd == -1) {
200 return false;
201 }
202 while (1) {
203 char buffer[65536];
204 int bytes_copied = copyTo(fd, output_socket, buffer, sizeof(buffer));
205 if (bytes_copied == 0) {
206 break;
207 }
208 if (bytes_copied == -1) {
209 return false;
210 }
211 }
212 return true;
213}
214
215// Triggers a bugreport and waits until it is all collected.
216// returns false if error, true if success
217bool doBugreport(int progress_socket, size_t* out_bytes_written, std::string* zip_path) {
218 // Socket will not be available until service starts.
219 android::base::unique_fd s;
220 for (int i = 0; i < kMaxDumpstateConnectAttempts; i++) {
221 s.reset(socket_local_client("dumpstate", ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_STREAM));
222 if (s != -1) break;
223 sleep(kWaitTimeBetweenConnectAttemptsInSec);
224 }
225
226 if (s == -1) {
227 ALOGE("failed to connect to dumpstatez service");
228 return false;
229 }
230
231 // Set a timeout so that if nothing is read by the timeout, stop reading and quit
232 struct timeval tv = {
233 .tv_sec = kDumpstateTimeoutInSec,
234 .tv_usec = 0,
235 };
236 if (setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) != 0) {
237 ALOGW("Cannot set socket timeout (%s)", strerror(errno));
238 }
239
240 std::string line;
241 std::string last_nonempty_line;
242 while (true) {
243 char buffer[65536];
244 ssize_t bytes_read = copyTo(s, progress_socket, buffer, sizeof(buffer));
245 if (bytes_read == 0) {
246 break;
247 }
248 if (bytes_read == -1) {
249 return false;
250 }
251 // Process the buffer line by line. this is needed for the filename.
252 for (int i = 0; i < bytes_read; i++) {
253 char c = buffer[i];
254 if (c == '\n') {
255 processLine(line, zip_path, &last_nonempty_line);
256 line.clear();
257 } else {
258 line.append(1, c);
259 }
260 }
Selim Guruna85b7e72019-06-07 11:01:42 -0700261 *out_bytes_written += bytes_read;
Selim Gurunc4d7c012019-05-13 20:27:22 -0700262 }
263 s.reset();
264 // Process final line, in case it didn't finish with newline.
265 processLine(line, zip_path, &last_nonempty_line);
266 // if doBugReport finished successfully, zip path should be set.
267 if (zip_path->empty()) {
268 ALOGE("no zip file path was found in bugreportz progress data");
269 return false;
270 }
271 return true;
272}
273
Selim Guruna85b7e72019-06-07 11:01:42 -0700274bool waitpid_with_timeout(pid_t pid, int timeout_secs, int* status) {
275 sigset_t child_mask, old_mask;
276 sigemptyset(&child_mask);
277 sigaddset(&child_mask, SIGCHLD);
278
279 if (sigprocmask(SIG_BLOCK, &child_mask, &old_mask) == -1) {
280 ALOGE("*** sigprocmask failed: %s\n", strerror(errno));
281 return false;
282 }
283
284 timespec ts = {.tv_sec = timeout_secs, .tv_nsec = 0};
285 int ret = TEMP_FAILURE_RETRY(sigtimedwait(&child_mask, nullptr, &ts));
286 int saved_errno = errno;
287
288 // Set the signals back the way they were.
289 if (sigprocmask(SIG_SETMASK, &old_mask, nullptr) == -1) {
290 ALOGE("*** sigprocmask failed: %s\n", strerror(errno));
291 if (ret == 0) {
292 return false;
293 }
294 }
295 if (ret == -1) {
296 errno = saved_errno;
297 if (errno == EAGAIN) {
298 errno = ETIMEDOUT;
299 } else {
300 ALOGE("*** sigtimedwait failed: %s\n", strerror(errno));
301 }
302 return false;
303 }
304
305 pid_t child_pid = waitpid(pid, status, WNOHANG);
306 if (child_pid != pid) {
307 if (child_pid != -1) {
308 ALOGE("*** Waiting for pid %d, got pid %d instead\n", pid, child_pid);
309 } else {
310 ALOGE("*** waitpid failed: %s\n", strerror(errno));
311 }
312 return false;
313 }
314 return true;
315}
316
317// Runs the given command. Kills the command if it does not finish by timeout.
318int runCommand(int timeout_secs, const char* file, std::vector<const char*> args) {
319 pid_t pid = fork();
320
321 // handle error case
322 if (pid < 0) {
323 ALOGE("fork failed %s", strerror(errno));
324 return pid;
325 }
326
327 // handle child case
328 if (pid == 0) {
329 /* make sure the child dies when parent dies */
330 prctl(PR_SET_PDEATHSIG, SIGKILL);
331
332 /* just ignore SIGPIPE, will go down with parent's */
333 struct sigaction sigact;
334 memset(&sigact, 0, sizeof(sigact));
335 sigact.sa_handler = SIG_IGN;
336 sigaction(SIGPIPE, &sigact, nullptr);
337
338 execvp(file, (char**)args.data());
339 // execvp's result will be handled after waitpid_with_timeout() below, but
340 // if it failed, it's safer to exit dumpstate.
341 ALOGE("execvp on command %s failed (error: %s)", file, strerror(errno));
342 _exit(EXIT_FAILURE);
343 }
344
345 // handle parent case
346 int status;
347 bool ret = waitpid_with_timeout(pid, timeout_secs, &status);
348
349 if (!ret) {
350 if (errno == ETIMEDOUT) {
351 ALOGE("command %s timed out (killing pid %d)", file, pid);
352 } else {
353 ALOGE("command %s: Error (killing pid %d)\n", file, pid);
354 }
355 kill(pid, SIGTERM);
356 if (!waitpid_with_timeout(pid, 5, nullptr)) {
357 kill(pid, SIGKILL);
358 if (!waitpid_with_timeout(pid, 5, nullptr)) {
359 ALOGE("could not kill command '%s' (pid %d) even with SIGKILL.\n", file, pid);
360 }
361 }
362 return -1;
363 }
364
365 if (WIFSIGNALED(status)) {
366 ALOGE("command '%s' failed: killed by signal %d\n", file, WTERMSIG(status));
367 } else if (WIFEXITED(status) && WEXITSTATUS(status) > 0) {
368 status = WEXITSTATUS(status);
369 ALOGE("command '%s' failed: exit code %d\n", file, status);
370 }
371
372 return status;
373}
374
Zhomart Mukhamejanov2770ccd2019-06-25 14:49:17 -0700375void takeScreenshotForDisplayId(PhysicalDisplayId id, const char* tmp_dir,
Selim Guruna85b7e72019-06-07 11:01:42 -0700376 std::vector<std::string>* extra_files) {
377 std::string id_as_string = std::to_string(id);
378 std::string filename = std::string(tmp_dir) + kScreenshotPrefix + id_as_string + ".png";
379 std::vector<const char*> args { "-p", "-d", id_as_string.c_str(), filename.c_str(), nullptr };
Zhomart Mukhamejanov2770ccd2019-06-25 14:49:17 -0700380 ALOGI("capturing screen for display (%s) as %s", id_as_string.c_str(), filename.c_str());
Selim Guruna85b7e72019-06-07 11:01:42 -0700381 int status = runCommand(10, "/system/bin/screencap", args);
382 if (status == 0) {
383 LOG(INFO) << "Screenshot saved for display:" << id_as_string;
384 }
385 // add the file regardless of the exit status of the screencap util.
386 extra_files->push_back(filename);
387
388 LOG(ERROR) << "Failed to take screenshot for display:" << id_as_string;
389}
390
391void takeScreenshot(const char* tmp_dir, std::vector<std::string>* extra_files) {
392 // Now send the screencaptures
393 std::vector<PhysicalDisplayId> ids = SurfaceComposerClient::getPhysicalDisplayIds();
394
395 for (PhysicalDisplayId display_id : ids) {
396 takeScreenshotForDisplayId(display_id, tmp_dir, extra_files);
397 }
398}
399
400bool recursiveRemoveDir(const std::string& path) {
401 auto callback = [](const char* child, const struct stat*, int file_type, struct FTW*) -> int {
402 if (file_type == FTW_DP) {
403 if (rmdir(child) == -1) {
404 ALOGE("rmdir(%s): %s", child, strerror(errno));
405 return -1;
406 }
407 } else if (file_type == FTW_F) {
408 if (unlink(child) == -1) {
409 ALOGE("unlink(%s): %s", child, strerror(errno));
410 return -1;
411 }
412 }
413 return 0;
414 };
415 // do a file tree walk with a sufficiently large depth.
416 return nftw(path.c_str(), callback, 128, FTW_DEPTH) == 0;
417}
418
419status_t createTempDir(const char* dir) {
420 struct stat sb;
421 if (TEMP_FAILURE_RETRY(stat(dir, &sb)) == 0) {
422 if (!recursiveRemoveDir(dir)) {
423 return -errno;
424 }
425 } else if (errno != ENOENT) {
426 ALOGE("Failed to stat %s ", dir);
427 return -errno;
428 }
429 if (TEMP_FAILURE_RETRY(mkdir(dir, 0700)) == -1) {
430 ALOGE("Failed to mkdir %s", dir);
431 return -errno;
432 }
433 return OK;
434}
435
Selim Gurunc4d7c012019-05-13 20:27:22 -0700436// Removes bugreport
437void cleanupBugreportFile(const std::string& zip_path) {
438 if (unlink(zip_path.c_str()) != 0) {
439 ALOGE("Could not unlink %s (%s)", zip_path.c_str(), strerror(errno));
440 }
441}
442
443} // namespace
444
445int main(void) {
Selim Guruna85b7e72019-06-07 11:01:42 -0700446 ALOGI("Starting bugreport collecting service");
Selim Gurunc4d7c012019-05-13 20:27:22 -0700447
448 auto t0 = std::chrono::steady_clock::now();
449
Selim Guruna85b7e72019-06-07 11:01:42 -0700450 std::vector<std::string> extra_files;
451 if (createTempDir(kTempDirectory) == OK) {
452 // take screenshots of the physical displays as early as possible
453 takeScreenshot(kTempDirectory, &extra_files);
454 }
455
Selim Gurunc4d7c012019-05-13 20:27:22 -0700456 // Start the dumpstatez service.
457 android::base::SetProperty("ctl.start", "car-dumpstatez");
458
459 size_t bytes_written = 0;
460
461 std::string zip_path;
462 int progress_socket = openSocket(kCarBrProgressSocket);
463 if (progress_socket < 0) {
464 // early out. in this case we will not print the final message, but that is ok.
465 android::base::SetProperty("ctl.stop", "car-dumpstatez");
466 return EXIT_FAILURE;
467 }
468 bool ret_val = doBugreport(progress_socket, &bytes_written, &zip_path);
469 close(progress_socket);
470
471 int output_socket = openSocket(kCarBrOutputSocket);
472 if (output_socket != -1 && ret_val) {
473 ret_val = copyFile(zip_path, output_socket);
474 }
475 if (output_socket != -1) {
476 close(output_socket);
477 }
478
Selim Guruna85b7e72019-06-07 11:01:42 -0700479 int extra_output_socket = openSocket(kCarBrExtraOutputSocket);
480 if (extra_output_socket != -1 && ret_val) {
481 zipFilesToFd(extra_files, extra_output_socket);
482 }
483 if (extra_output_socket != -1) {
484 close(extra_output_socket);
485 }
486
Selim Gurunc4d7c012019-05-13 20:27:22 -0700487 auto delta = std::chrono::duration_cast<std::chrono::duration<double>>(
488 std::chrono::steady_clock::now() - t0)
489 .count();
490
491 std::string result = ret_val ? "success" : "failed";
492 ALOGI("bugreport %s in %.02fs, %zu bytes written", result.c_str(), delta, bytes_written);
493 cleanupBugreportFile(zip_path);
494
Selim Guruna85b7e72019-06-07 11:01:42 -0700495 recursiveRemoveDir(kTempDirectory);
496
Selim Gurunc4d7c012019-05-13 20:27:22 -0700497 // No matter how doBugreport() finished, let's try to explicitly stop
498 // car-dumpstatez in case it stalled.
499 android::base::SetProperty("ctl.stop", "car-dumpstatez");
500
501 return ret_val ? EXIT_SUCCESS : EXIT_FAILURE;
502}