You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
505 lines
17 KiB
505 lines
17 KiB
/*
|
|
* Copyright (C) 2019 The Android Open Source Project
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
#define LOG_TAG "carbugreportd"
|
|
|
|
#include <android-base/errors.h>
|
|
#include <android-base/file.h>
|
|
#include <android-base/macros.h>
|
|
#include <android-base/properties.h>
|
|
#include <android-base/stringprintf.h>
|
|
#include <android-base/strings.h>
|
|
#include <android-base/unique_fd.h>
|
|
#include <cutils/sockets.h>
|
|
#include <gui/SurfaceComposerClient.h>
|
|
#include <log/log_main.h>
|
|
#include <private/android_filesystem_config.h>
|
|
#include <utils/SystemClock.h>
|
|
#include <ziparchive/zip_writer.h>
|
|
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <ftw.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <sys/prctl.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
namespace {
|
|
// Directory used for keeping temporary files
|
|
constexpr const char* kTempDirectory = "/data/user_de/0/com.android.shell/temp_bugreport_files";
|
|
// Socket to write the progress information.
|
|
constexpr const char* kCarBrProgressSocket = "car_br_progress_socket";
|
|
// Socket to write the zipped bugreport file.
|
|
constexpr const char* kCarBrOutputSocket = "car_br_output_socket";
|
|
// Socket to write the extra bugreport zip file. This zip file contains data that does not exist
|
|
// in bugreport file generated by dumpstate.
|
|
constexpr const char* kCarBrExtraOutputSocket = "car_br_extra_output_socket";
|
|
// The prefix used by bugreportz protocol to indicate bugreport finished successfully.
|
|
constexpr const char* kOkPrefix = "OK:";
|
|
// Number of connect attempts to dumpstate socket
|
|
constexpr const int kMaxDumpstateConnectAttempts = 20;
|
|
// Wait time between connect attempts
|
|
constexpr const int kWaitTimeBetweenConnectAttemptsInSec = 1;
|
|
// Wait time for dumpstate. Set a timeout so that if nothing is read in 10 minutes, we'll stop
|
|
// reading and quit. No timeout in dumpstate is longer than 60 seconds, so this gives lots of leeway
|
|
// in case of unforeseen time outs.
|
|
constexpr const int kDumpstateTimeoutInSec = 600;
|
|
// The prefix for screenshot filename in the generated zip file.
|
|
constexpr const char* kScreenshotPrefix = "/screenshot";
|
|
|
|
using android::OK;
|
|
using android::PhysicalDisplayId;
|
|
using android::status_t;
|
|
using android::SurfaceComposerClient;
|
|
|
|
// Returns a valid socket descriptor or -1 on failure.
|
|
int openSocket(const char* service) {
|
|
int s = android_get_control_socket(service);
|
|
if (s < 0) {
|
|
ALOGE("android_get_control_socket(%s): %s", service, strerror(errno));
|
|
return -1;
|
|
}
|
|
fcntl(s, F_SETFD, FD_CLOEXEC);
|
|
if (listen(s, 4) < 0) {
|
|
ALOGE("listen(control socket): %s", strerror(errno));
|
|
return -1;
|
|
}
|
|
|
|
struct sockaddr addr;
|
|
socklen_t alen = sizeof(addr);
|
|
int fd = accept(s, &addr, &alen);
|
|
if (fd < 0) {
|
|
ALOGE("accept(control socket): %s", strerror(errno));
|
|
return -1;
|
|
}
|
|
return fd;
|
|
}
|
|
|
|
// Processes the given dumpstate progress protocol |line| and updates
|
|
// |out_last_nonempty_line| when |line| is non-empty, and |out_zip_path| when
|
|
// the bugreport is finished.
|
|
void processLine(const std::string& line, std::string* out_zip_path,
|
|
std::string* out_last_nonempty_line) {
|
|
// The protocol is documented in frameworks/native/cmds/bugreportz/readme.md
|
|
if (line.empty()) {
|
|
return;
|
|
}
|
|
*out_last_nonempty_line = line;
|
|
if (line.find(kOkPrefix) != 0) {
|
|
return;
|
|
}
|
|
*out_zip_path = line.substr(strlen(kOkPrefix));
|
|
return;
|
|
}
|
|
|
|
// Sends the contents of the zip fileto |outfd|.
|
|
// Returns true if success
|
|
void zipFilesToFd(const std::vector<std::string>& extra_files, int outfd) {
|
|
// pass fclose as Deleter to close the file when unique_ptr is destroyed.
|
|
std::unique_ptr<FILE, decltype(fclose)*> outfile = {fdopen(outfd, "wb"), fclose};
|
|
if (outfile == nullptr) {
|
|
ALOGE("Failed to open output descriptor");
|
|
return;
|
|
}
|
|
auto writer = std::make_unique<ZipWriter>(outfile.get());
|
|
|
|
int error = 0;
|
|
for (const auto& filepath : extra_files) {
|
|
const auto name = android::base::Basename(filepath);
|
|
|
|
error = writer->StartEntry(name.c_str(), 0);
|
|
if (error) {
|
|
ALOGE("Failed to start entry: [%d] %s", error, writer->ErrorCodeString(error));
|
|
return;
|
|
}
|
|
android::base::unique_fd fd(
|
|
TEMP_FAILURE_RETRY(open(filepath.c_str(), O_RDONLY | O_NOFOLLOW)));
|
|
if (fd == -1) {
|
|
return;
|
|
}
|
|
while (1) {
|
|
char buffer[65536];
|
|
|
|
ssize_t bytes_read = TEMP_FAILURE_RETRY(read(fd, buffer, sizeof(buffer)));
|
|
if (bytes_read == 0) {
|
|
break;
|
|
}
|
|
if (bytes_read == -1) {
|
|
if (errno == EAGAIN) {
|
|
ALOGE("timed out while reading %s", name.c_str());
|
|
} else {
|
|
ALOGE("read terminated abnormally (%s)", strerror(errno));
|
|
}
|
|
// fail immediately
|
|
return;
|
|
}
|
|
error = writer->WriteBytes(buffer, bytes_read);
|
|
if (error) {
|
|
ALOGE("WriteBytes() failed: [%d] %s", error, ZipWriter::ErrorCodeString(error));
|
|
// fail immediately
|
|
return;
|
|
}
|
|
}
|
|
|
|
error = writer->FinishEntry();
|
|
if (error) {
|
|
ALOGW("failed to finish entry: [%d] %s", error, writer->ErrorCodeString(error));
|
|
continue;
|
|
}
|
|
}
|
|
error = writer->Finish();
|
|
if (error) {
|
|
ALOGW("Failed to finish zip writer to: [%d] %s", error, writer->ErrorCodeString(error));
|
|
}
|
|
}
|
|
|
|
int copyTo(int fd_in, int fd_out, void* buffer, size_t buffer_len) {
|
|
ssize_t bytes_read = TEMP_FAILURE_RETRY(read(fd_in, buffer, buffer_len));
|
|
if (bytes_read == 0) {
|
|
return 0;
|
|
}
|
|
if (bytes_read == -1) {
|
|
// EAGAIN really means time out, so make that clear.
|
|
if (errno == EAGAIN) {
|
|
ALOGE("read timed out");
|
|
} else {
|
|
ALOGE("read terminated abnormally (%s)", strerror(errno));
|
|
}
|
|
return -1;
|
|
}
|
|
// copy all bytes to the output socket
|
|
if (!android::base::WriteFully(fd_out, buffer, bytes_read)) {
|
|
ALOGE("write failed");
|
|
return -1;
|
|
}
|
|
return bytes_read;
|
|
}
|
|
|
|
bool copyFile(const std::string& zip_path, int output_socket) {
|
|
android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(zip_path.c_str(), O_RDONLY | O_NOFOLLOW)));
|
|
if (fd == -1) {
|
|
ALOGE("Failed to open zip file %s.", zip_path.c_str());
|
|
return false;
|
|
}
|
|
while (1) {
|
|
char buffer[65536];
|
|
int bytes_copied = copyTo(fd, output_socket, buffer, sizeof(buffer));
|
|
if (bytes_copied == 0) {
|
|
break;
|
|
}
|
|
if (bytes_copied == -1) {
|
|
ALOGE("Failed to copy zip file %s to the output_socket.", zip_path.c_str());
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Triggers a bugreport and waits until it is all collected.
|
|
// returns false if error, true if success
|
|
bool doBugreport(int progress_socket, size_t* out_bytes_written, std::string* zip_path) {
|
|
// Socket will not be available until service starts.
|
|
android::base::unique_fd s;
|
|
for (int i = 0; i < kMaxDumpstateConnectAttempts; i++) {
|
|
s.reset(socket_local_client("dumpstate", ANDROID_SOCKET_NAMESPACE_RESERVED, SOCK_STREAM));
|
|
if (s != -1) break;
|
|
sleep(kWaitTimeBetweenConnectAttemptsInSec);
|
|
}
|
|
|
|
if (s == -1) {
|
|
ALOGE("failed to connect to dumpstatez service");
|
|
return false;
|
|
}
|
|
|
|
// Set a timeout so that if nothing is read by the timeout, stop reading and quit
|
|
struct timeval tv = {
|
|
.tv_sec = kDumpstateTimeoutInSec,
|
|
.tv_usec = 0,
|
|
};
|
|
if (setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) != 0) {
|
|
ALOGW("Cannot set socket timeout (%s)", strerror(errno));
|
|
}
|
|
|
|
std::string line;
|
|
std::string last_nonempty_line;
|
|
char buffer[65536];
|
|
while (true) {
|
|
ssize_t bytes_read = copyTo(s, progress_socket, buffer, sizeof(buffer));
|
|
if (bytes_read == 0) {
|
|
break;
|
|
}
|
|
if (bytes_read == -1) {
|
|
ALOGE("Failed to copy progress to the progress_socket.");
|
|
return false;
|
|
}
|
|
// Process the buffer line by line. this is needed for the filename.
|
|
for (int i = 0; i < bytes_read; i++) {
|
|
char c = buffer[i];
|
|
if (c == '\n') {
|
|
processLine(line, zip_path, &last_nonempty_line);
|
|
line.clear();
|
|
} else {
|
|
line.append(1, c);
|
|
}
|
|
}
|
|
*out_bytes_written += bytes_read;
|
|
}
|
|
s.reset();
|
|
// Process final line, in case it didn't finish with newline.
|
|
processLine(line, zip_path, &last_nonempty_line);
|
|
// if doBugReport finished successfully, zip path should be set.
|
|
if (zip_path->empty()) {
|
|
ALOGE("no zip file path was found in bugreportz progress data");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool waitpid_with_timeout(pid_t pid, int timeout_secs, int* status) {
|
|
sigset_t child_mask, old_mask;
|
|
sigemptyset(&child_mask);
|
|
sigaddset(&child_mask, SIGCHLD);
|
|
|
|
if (sigprocmask(SIG_BLOCK, &child_mask, &old_mask) == -1) {
|
|
ALOGE("*** sigprocmask failed: %s\n", strerror(errno));
|
|
return false;
|
|
}
|
|
|
|
timespec ts = {.tv_sec = timeout_secs, .tv_nsec = 0};
|
|
int ret = TEMP_FAILURE_RETRY(sigtimedwait(&child_mask, nullptr, &ts));
|
|
int saved_errno = errno;
|
|
|
|
// Set the signals back the way they were.
|
|
if (sigprocmask(SIG_SETMASK, &old_mask, nullptr) == -1) {
|
|
ALOGE("*** sigprocmask failed: %s\n", strerror(errno));
|
|
if (ret == 0) {
|
|
return false;
|
|
}
|
|
}
|
|
if (ret == -1) {
|
|
errno = saved_errno;
|
|
if (errno == EAGAIN) {
|
|
errno = ETIMEDOUT;
|
|
} else {
|
|
ALOGE("*** sigtimedwait failed: %s\n", strerror(errno));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
pid_t child_pid = waitpid(pid, status, WNOHANG);
|
|
if (child_pid != pid) {
|
|
if (child_pid != -1) {
|
|
ALOGE("*** Waiting for pid %d, got pid %d instead\n", pid, child_pid);
|
|
} else {
|
|
ALOGE("*** waitpid failed: %s\n", strerror(errno));
|
|
}
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Runs the given command. Kills the command if it does not finish by timeout.
|
|
int runCommand(int timeout_secs, const char* file, std::vector<const char*> args) {
|
|
pid_t pid = fork();
|
|
|
|
// handle error case
|
|
if (pid < 0) {
|
|
ALOGE("fork failed %s", strerror(errno));
|
|
return pid;
|
|
}
|
|
|
|
// handle child case
|
|
if (pid == 0) {
|
|
/* make sure the child dies when parent dies */
|
|
prctl(PR_SET_PDEATHSIG, SIGKILL);
|
|
|
|
/* just ignore SIGPIPE, will go down with parent's */
|
|
struct sigaction sigact;
|
|
memset(&sigact, 0, sizeof(sigact));
|
|
sigact.sa_handler = SIG_IGN;
|
|
sigaction(SIGPIPE, &sigact, nullptr);
|
|
|
|
execvp(file, const_cast<char* const*>(args.data()));
|
|
// execvp's result will be handled after waitpid_with_timeout() below, but
|
|
// if it failed, it's safer to exit dumpstate.
|
|
ALOGE("execvp on command %s failed (error: %s)", file, strerror(errno));
|
|
_exit(EXIT_FAILURE);
|
|
}
|
|
|
|
// handle parent case
|
|
int status;
|
|
bool ret = waitpid_with_timeout(pid, timeout_secs, &status);
|
|
|
|
if (!ret) {
|
|
if (errno == ETIMEDOUT) {
|
|
ALOGE("command %s timed out (killing pid %d)", file, pid);
|
|
} else {
|
|
ALOGE("command %s: Error (killing pid %d)\n", file, pid);
|
|
}
|
|
kill(pid, SIGTERM);
|
|
if (!waitpid_with_timeout(pid, 5, nullptr)) {
|
|
kill(pid, SIGKILL);
|
|
if (!waitpid_with_timeout(pid, 5, nullptr)) {
|
|
ALOGE("could not kill command '%s' (pid %d) even with SIGKILL.\n", file, pid);
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
if (WIFSIGNALED(status)) {
|
|
ALOGE("command '%s' failed: killed by signal %d\n", file, WTERMSIG(status));
|
|
} else if (WIFEXITED(status) && WEXITSTATUS(status) > 0) {
|
|
status = WEXITSTATUS(status);
|
|
ALOGE("command '%s' failed: exit code %d\n", file, status);
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
void takeScreenshotForDisplayId(PhysicalDisplayId id, const char* tmp_dir,
|
|
std::vector<std::string>* extra_files) {
|
|
std::string id_as_string = to_string(id);
|
|
std::string filename = std::string(tmp_dir) + kScreenshotPrefix + id_as_string + ".png";
|
|
std::vector<const char*> args{"-p", "-d", id_as_string.c_str(), filename.c_str(), nullptr};
|
|
ALOGI("capturing screen for display (%s) as %s", id_as_string.c_str(), filename.c_str());
|
|
int status = runCommand(10, "/system/bin/screencap", args);
|
|
if (status == 0) {
|
|
ALOGI("Screenshot saved for display: %s", id_as_string.c_str());
|
|
}
|
|
// add the file regardless of the exit status of the screencap util.
|
|
extra_files->push_back(filename);
|
|
|
|
ALOGW("Failed to take screenshot for display: %s", id_as_string.c_str());
|
|
}
|
|
|
|
void takeScreenshot(const char* tmp_dir, std::vector<std::string>* extra_files) {
|
|
// Now send the screencaptures
|
|
std::vector<PhysicalDisplayId> ids = SurfaceComposerClient::getPhysicalDisplayIds();
|
|
|
|
for (PhysicalDisplayId display_id : ids) {
|
|
takeScreenshotForDisplayId(display_id, tmp_dir, extra_files);
|
|
}
|
|
}
|
|
|
|
bool recursiveRemoveDir(const std::string& path) {
|
|
auto callback = [](const char* child, const struct stat*, int file_type, struct FTW*) -> int {
|
|
if (file_type == FTW_DP) {
|
|
if (rmdir(child) == -1) {
|
|
ALOGE("rmdir(%s): %s", child, strerror(errno));
|
|
return -1;
|
|
}
|
|
} else if (file_type == FTW_F) {
|
|
if (unlink(child) == -1) {
|
|
ALOGE("unlink(%s): %s", child, strerror(errno));
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
};
|
|
// do a file tree walk with a sufficiently large depth.
|
|
return nftw(path.c_str(), callback, 128, FTW_DEPTH) == 0;
|
|
}
|
|
|
|
status_t createTempDir(const char* dir) {
|
|
struct stat sb;
|
|
if (TEMP_FAILURE_RETRY(stat(dir, &sb)) == 0) {
|
|
if (!recursiveRemoveDir(dir)) {
|
|
return -errno;
|
|
}
|
|
} else if (errno != ENOENT) {
|
|
ALOGE("Failed to stat %s ", dir);
|
|
return -errno;
|
|
}
|
|
if (TEMP_FAILURE_RETRY(mkdir(dir, 0700)) == -1) {
|
|
ALOGE("Failed to mkdir %s", dir);
|
|
return -errno;
|
|
}
|
|
return OK;
|
|
}
|
|
|
|
// Removes bugreport
|
|
void cleanupBugreportFile(const std::string& zip_path) {
|
|
if (unlink(zip_path.c_str()) != 0) {
|
|
ALOGE("Could not unlink %s (%s)", zip_path.c_str(), strerror(errno));
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
int main(void) {
|
|
ALOGI("Starting bugreport collecting service");
|
|
|
|
auto started_at_millis = android::elapsedRealtime();
|
|
|
|
std::vector<std::string> extra_files;
|
|
if (createTempDir(kTempDirectory) == OK) {
|
|
// take screenshots of the physical displays as early as possible
|
|
takeScreenshot(kTempDirectory, &extra_files);
|
|
}
|
|
|
|
// Start the dumpstatez service.
|
|
android::base::SetProperty("ctl.start", "cardumpstatez");
|
|
|
|
size_t bytes_written = 0;
|
|
|
|
std::string zip_path;
|
|
int progress_socket = openSocket(kCarBrProgressSocket);
|
|
if (progress_socket < 0) {
|
|
// early out. in this case we will not print the final message, but that is ok.
|
|
android::base::SetProperty("ctl.stop", "cardumpstatez");
|
|
return EXIT_FAILURE;
|
|
}
|
|
bool is_success = doBugreport(progress_socket, &bytes_written, &zip_path);
|
|
close(progress_socket);
|
|
|
|
if (is_success) {
|
|
int output_socket = openSocket(kCarBrOutputSocket);
|
|
if (output_socket != -1) {
|
|
is_success = copyFile(zip_path, output_socket);
|
|
close(output_socket);
|
|
}
|
|
}
|
|
|
|
int extra_output_socket = openSocket(kCarBrExtraOutputSocket);
|
|
if (extra_output_socket != -1 && is_success) {
|
|
zipFilesToFd(extra_files, extra_output_socket);
|
|
}
|
|
if (extra_output_socket != -1) {
|
|
close(extra_output_socket);
|
|
}
|
|
|
|
auto delta_sec = (android::elapsedRealtime() - started_at_millis) / 1000.0;
|
|
std::string result = is_success ? "success" : "failed";
|
|
ALOGI("bugreport %s in %.02fs, %zu bytes written", result.c_str(), delta_sec, bytes_written);
|
|
cleanupBugreportFile(zip_path);
|
|
|
|
recursiveRemoveDir(kTempDirectory);
|
|
|
|
// No matter how doBugreport() finished, let's try to explicitly stop
|
|
// cardumpstatez in case it stalled.
|
|
android::base::SetProperty("ctl.stop", "cardumpstatez");
|
|
|
|
return is_success ? EXIT_SUCCESS : EXIT_FAILURE;
|
|
}
|