Selim Gurun | c4d7c01 | 2019-05-13 20:27:22 -0700 | [diff] [blame] | 1 | /* |
| 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 Gurun | a85b7e7 | 2019-06-07 11:01:42 -0700 | [diff] [blame] | 30 | #include <ftw.h> |
| 31 | #include <gui/SurfaceComposerClient.h> |
Selim Gurun | c4d7c01 | 2019-05-13 20:27:22 -0700 | [diff] [blame] | 32 | #include <log/log_main.h> |
| 33 | #include <private/android_filesystem_config.h> |
| 34 | #include <stdio.h> |
| 35 | #include <stdlib.h> |
Selim Gurun | a85b7e7 | 2019-06-07 11:01:42 -0700 | [diff] [blame] | 36 | #include <sys/prctl.h> |
Selim Gurun | c4d7c01 | 2019-05-13 20:27:22 -0700 | [diff] [blame] | 37 | #include <sys/stat.h> |
| 38 | #include <sys/types.h> |
| 39 | #include <sys/wait.h> |
| 40 | #include <time.h> |
| 41 | #include <unistd.h> |
Selim Gurun | a85b7e7 | 2019-06-07 11:01:42 -0700 | [diff] [blame] | 42 | #include <ziparchive/zip_writer.h> |
Selim Gurun | c4d7c01 | 2019-05-13 20:27:22 -0700 | [diff] [blame] | 43 | |
| 44 | #include <chrono> |
| 45 | #include <string> |
| 46 | #include <vector> |
| 47 | |
| 48 | namespace { |
Selim Gurun | a85b7e7 | 2019-06-07 11:01:42 -0700 | [diff] [blame] | 49 | // Directory used for keeping temporary files |
| 50 | constexpr const char* kTempDirectory = "/data/user_de/0/com.android.shell/temp_bugreport_files"; |
Selim Gurun | c4d7c01 | 2019-05-13 20:27:22 -0700 | [diff] [blame] | 51 | // Socket to write the progress information. |
| 52 | constexpr const char* kCarBrProgressSocket = "car_br_progress_socket"; |
| 53 | // Socket to write the zipped bugreport file. |
| 54 | constexpr const char* kCarBrOutputSocket = "car_br_output_socket"; |
Selim Gurun | a85b7e7 | 2019-06-07 11:01:42 -0700 | [diff] [blame] | 55 | // Socket to write the extra bugreport zip file. This zip file contains data that does not exist |
| 56 | // in bugreport file generated by dumpstate. |
| 57 | constexpr const char* kCarBrExtraOutputSocket = "car_br_extra_output_socket"; |
Selim Gurun | c4d7c01 | 2019-05-13 20:27:22 -0700 | [diff] [blame] | 58 | // The prefix used by bugreportz protocol to indicate bugreport finished successfully. |
| 59 | constexpr const char* kOkPrefix = "OK:"; |
| 60 | // Number of connect attempts to dumpstate socket |
| 61 | constexpr const int kMaxDumpstateConnectAttempts = 20; |
| 62 | // Wait time between connect attempts |
| 63 | constexpr const int kWaitTimeBetweenConnectAttemptsInSec = 1; |
Zhomart Mukhamejanov | cd25b85 | 2019-09-11 18:35:29 -0700 | [diff] [blame] | 64 | // 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. |
| 67 | constexpr const int kDumpstateTimeoutInSec = 600; |
Selim Gurun | a85b7e7 | 2019-06-07 11:01:42 -0700 | [diff] [blame] | 68 | // The prefix for screenshot filename in the generated zip file. |
| 69 | constexpr const char* kScreenshotPrefix = "/screenshot"; |
| 70 | |
| 71 | using android::OK; |
| 72 | using android::PhysicalDisplayId; |
| 73 | using android::status_t; |
| 74 | using android::SurfaceComposerClient; |
Selim Gurun | c4d7c01 | 2019-05-13 20:27:22 -0700 | [diff] [blame] | 75 | |
| 76 | // Returns a valid socket descriptor or -1 on failure. |
| 77 | int 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. |
| 102 | void 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 Gurun | a85b7e7 | 2019-06-07 11:01:42 -0700 | [diff] [blame] | 116 | // Sends the contents of the zip fileto |outfd|. |
| 117 | // Returns true if success |
| 118 | void 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 Gurun | c4d7c01 | 2019-05-13 20:27:22 -0700 | [diff] [blame] | 176 | int 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 | |
| 198 | bool 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 Mukhamejanov | cd25b85 | 2019-09-11 18:35:29 -0700 | [diff] [blame] | 201 | ALOGE("Failed to open zip file %s.", zip_path.c_str()); |
Selim Gurun | c4d7c01 | 2019-05-13 20:27:22 -0700 | [diff] [blame] | 202 | 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 Mukhamejanov | cd25b85 | 2019-09-11 18:35:29 -0700 | [diff] [blame] | 211 | ALOGE("Failed to copy zip file %s to the output_socket.", zip_path.c_str()); |
Selim Gurun | c4d7c01 | 2019-05-13 20:27:22 -0700 | [diff] [blame] | 212 | 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 |
| 220 | bool 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 Mukhamejanov | cd25b85 | 2019-09-11 18:35:29 -0700 | [diff] [blame] | 245 | char buffer[65536]; |
Selim Gurun | c4d7c01 | 2019-05-13 20:27:22 -0700 | [diff] [blame] | 246 | while (true) { |
Selim Gurun | c4d7c01 | 2019-05-13 20:27:22 -0700 | [diff] [blame] | 247 | 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 Mukhamejanov | cd25b85 | 2019-09-11 18:35:29 -0700 | [diff] [blame] | 252 | ALOGE("Failed to copy progress to the progress_socket."); |
Selim Gurun | c4d7c01 | 2019-05-13 20:27:22 -0700 | [diff] [blame] | 253 | 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 Gurun | a85b7e7 | 2019-06-07 11:01:42 -0700 | [diff] [blame] | 265 | *out_bytes_written += bytes_read; |
Selim Gurun | c4d7c01 | 2019-05-13 20:27:22 -0700 | [diff] [blame] | 266 | } |
| 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 Gurun | a85b7e7 | 2019-06-07 11:01:42 -0700 | [diff] [blame] | 278 | bool 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. |
| 322 | int 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 Mukhamejanov | 2770ccd | 2019-06-25 14:49:17 -0700 | [diff] [blame] | 379 | void takeScreenshotForDisplayId(PhysicalDisplayId id, const char* tmp_dir, |
Selim Gurun | a85b7e7 | 2019-06-07 11:01:42 -0700 | [diff] [blame] | 380 | 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 Mukhamejanov | 2770ccd | 2019-06-25 14:49:17 -0700 | [diff] [blame] | 384 | ALOGI("capturing screen for display (%s) as %s", id_as_string.c_str(), filename.c_str()); |
Selim Gurun | a85b7e7 | 2019-06-07 11:01:42 -0700 | [diff] [blame] | 385 | 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 | |
| 395 | void 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 | |
| 404 | bool 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 | |
| 423 | status_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 Gurun | c4d7c01 | 2019-05-13 20:27:22 -0700 | [diff] [blame] | 440 | // Removes bugreport |
| 441 | void 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 | |
| 449 | int main(void) { |
Selim Gurun | a85b7e7 | 2019-06-07 11:01:42 -0700 | [diff] [blame] | 450 | ALOGI("Starting bugreport collecting service"); |
Selim Gurun | c4d7c01 | 2019-05-13 20:27:22 -0700 | [diff] [blame] | 451 | |
| 452 | auto t0 = std::chrono::steady_clock::now(); |
| 453 | |
Selim Gurun | a85b7e7 | 2019-06-07 11:01:42 -0700 | [diff] [blame] | 454 | 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 Gurun | c4d7c01 | 2019-05-13 20:27:22 -0700 | [diff] [blame] | 460 | // 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 Mukhamejanov | cd25b85 | 2019-09-11 18:35:29 -0700 | [diff] [blame] | 475 | 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 Gurun | c4d7c01 | 2019-05-13 20:27:22 -0700 | [diff] [blame] | 481 | } |
| 482 | |
Selim Gurun | a85b7e7 | 2019-06-07 11:01:42 -0700 | [diff] [blame] | 483 | 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 Gurun | c4d7c01 | 2019-05-13 20:27:22 -0700 | [diff] [blame] | 491 | 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 Gurun | a85b7e7 | 2019-06-07 11:01:42 -0700 | [diff] [blame] | 499 | recursiveRemoveDir(kTempDirectory); |
| 500 | |
Selim Gurun | c4d7c01 | 2019-05-13 20:27:22 -0700 | [diff] [blame] | 501 | // 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 | } |