| // Copyright 2014 The ChromiumOS Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "feedback/feedback_daemon.h" |
| |
| #include <memory> |
| |
| #include "base/at_exit.h" |
| #include "base/command_line.h" |
| #include "base/containers/ring_buffer.h" |
| #include "base/functional/bind.h" |
| #include "base/logging.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_split.h" |
| #include "base/strings/string_util.h" |
| #include "base/synchronization/waitable_event.h" |
| #include "base/uuid.h" |
| #include <brillo/process/process.h> |
| #include "brillo/syslog_logging.h" |
| #include "components/feedback/feedback_common.h" |
| #include "feedback/feedback_service_interface.h" |
| |
| #include <sysexits.h> |
| |
| namespace { |
| |
| static const char kSwitchProductId[] = "product_id"; // int |
| static const char kSwitchDescription[] = "desc"; // string |
| static const char kSwitchBucket[] = "bucket"; // string |
| static const char kSwitchUserEmail[] = "user_email"; // string |
| static const char kSwitchPageUrl[] = "page_url"; // string |
| static const char kSwitchRawFiles[] = "raw_files"; // colon-separated strings |
| |
| const char kListSeparator[] = ":"; |
| |
| // Buffer size for feedback attachment files in bytes. Given that maximum |
| // feedback report size is ~7M and that majority of log files are under 1M, we |
| // set a per-file limit of 1MiB. |
| const int64_t kMaxFileSize = 1024 * 1024; |
| const int64_t kChunkSize = 64 * 1024; |
| |
| bool ReadFileFromBack(const base::FilePath path, std::string* contents) { |
| if (!contents) { |
| LOG(ERROR) << "contents buffer is null."; |
| return false; |
| } |
| |
| if (path.ReferencesParent()) { |
| LOG(ERROR) << "ReadFileFromBack can't be called on file paths with parent " |
| "references."; |
| return false; |
| } |
| |
| base::ScopedFILE fp(base::OpenFile(path, "r")); |
| if (!fp) { |
| PLOG(ERROR) << "Failed to open file " << path.value(); |
| return false; |
| } |
| |
| std::unique_ptr<char[]> chunk(new char[kChunkSize]); |
| base::RingBuffer<std::string, kMaxFileSize / kChunkSize> buf; |
| size_t bytes_read = 0; |
| |
| // Since most logs are not seekable, read until the end with a circular |
| // buffer. Note that logs will not always be kMaxFileSize even if the file |
| // exceeds kMaxFileSize, depending on kChunkSize and the size of the file. It |
| // could vary anywhere from (kMaxFileSize - kChunkSize + 1) to kMaxFileSize. |
| while ((bytes_read = fread(chunk.get(), 1, kChunkSize, fp.get())) != 0) { |
| if (bytes_read < kChunkSize) { |
| chunk[bytes_read] = '\0'; |
| } |
| buf.SaveToBuffer(std::string(chunk.get())); |
| } |
| |
| contents->clear(); |
| for (auto it = buf.Begin(); it == buf.End(); ++it) { |
| contents->append(**it); |
| } |
| |
| return true; |
| } |
| |
| void CommandlineReportStatus(base::WaitableEvent* event, |
| bool* status, |
| bool result) { |
| *status = result; |
| event->Signal(); |
| } |
| |
| bool FillReportFromCommandline(FeedbackCommon* report) { |
| base::CommandLine* args = base::CommandLine::ForCurrentProcess(); |
| if (!args->HasSwitch(kSwitchProductId)) { |
| LOG(ERROR) << "No product id provided"; |
| return false; |
| } |
| std::string product_id_string = args->GetSwitchValueASCII(kSwitchProductId); |
| int product_id; |
| if (!base::StringToInt(product_id_string, &product_id) || product_id <= 0) { |
| LOG(ERROR) << "Invalid product id provided, must be a positive number"; |
| return false; |
| } |
| if (!args->HasSwitch(kSwitchDescription)) { |
| LOG(ERROR) << "No description provided"; |
| return false; |
| } |
| |
| report->AddLog("unique_guid", |
| base::Uuid::GenerateRandomV4().AsLowercaseString()); |
| report->set_product_id(product_id); |
| report->set_description(args->GetSwitchValueASCII(kSwitchDescription)); |
| report->set_user_email(args->GetSwitchValueASCII(kSwitchUserEmail)); |
| report->set_page_url(args->GetSwitchValueASCII(kSwitchPageUrl)); |
| report->set_category_tag(args->GetSwitchValueASCII(kSwitchBucket)); |
| |
| std::vector<std::string> raw_files = base::SplitString( |
| args->GetSwitchValueNative(kSwitchRawFiles), kListSeparator, |
| base::KEEP_WHITESPACE, base::SPLIT_WANT_NONEMPTY); |
| |
| for (const std::string& path : raw_files) { |
| auto content = std::make_unique<std::string>(); |
| |
| if (!base::ReadFileToStringWithMaxSize(base::FilePath(path), content.get(), |
| kMaxFileSize)) { |
| if (content->empty()) { |
| LOG(ERROR) << "Could not read raw file: " << path; |
| return false; |
| } |
| // Skip files that are too large as it doesn't make sense to send partial |
| // raw/binary files. |
| LOG(WARNING) << "Skipping raw file. Exceeds max file size: " << path; |
| continue; |
| } |
| |
| report->AddFile(path, std::move(content)); |
| } |
| |
| std::vector<std::string> log_files = args->GetArgs(); |
| for (const std::string& path : log_files) { |
| std::string content; |
| if (ReadFileFromBack(base::FilePath(path), &content)) { |
| report->AddLog(path, content); |
| } else { |
| LOG(ERROR) << "Could not read log file: " << path; |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool SendReport(FeedbackServiceInterface* interface, FeedbackCommon* report) { |
| base::WaitableEvent event(base::WaitableEvent::ResetPolicy::AUTOMATIC, |
| base::WaitableEvent::InitialState::NOT_SIGNALED); |
| bool status; |
| |
| report->CompressLogs(); |
| interface->SendFeedback( |
| *report, base::BindOnce(&CommandlineReportStatus, &event, &status)); |
| event.Wait(); |
| return status; |
| } |
| |
| } // namespace |
| |
| int main(int argc, char** argv) { |
| base::CommandLine::Init(argc, argv); |
| |
| // Some libchrome calls need this. |
| base::AtExitManager at_exit_manager; |
| |
| brillo::InitLog(brillo::kLogToSyslog | brillo::kLogToStderr); |
| scoped_refptr<FeedbackServiceInterface> itf = |
| new DBusFeedbackServiceInterface(); |
| scoped_refptr<FeedbackCommon> report = new FeedbackCommon(); |
| |
| if (!FillReportFromCommandline(report.get())) { |
| LOG(ERROR) << "Not sending report"; |
| return EX_USAGE; |
| } |
| |
| return SendReport(itf.get(), report.get()) ? EX_OK : EX_UNAVAILABLE; |
| } |