diff --git a/base/cvd/cuttlefish/host/commands/cvd/cli/BUILD.bazel b/base/cvd/cuttlefish/host/commands/cvd/cli/BUILD.bazel index 269d16489cd..7d8a7a22ef8 100644 --- a/base/cvd/cuttlefish/host/commands/cvd/cli/BUILD.bazel +++ b/base/cvd/cuttlefish/host/commands/cvd/cli/BUILD.bazel @@ -79,6 +79,18 @@ cf_cc_library( ], ) +cf_cc_library( + name = "log_tail", + srcs = ["log_tail.cpp"], + hdrs = ["log_tail.h"], + deps = [ + "//cuttlefish/common/libs/fs", + "//cuttlefish/result", + "@abseil-cpp//absl/strings", + "@abseil-cpp//absl/strings:cord", + ], +) + cf_cc_test( name = "log_files_test", srcs = ["log_files_test.cpp"], @@ -118,6 +130,7 @@ cf_cc_library( "//cuttlefish/host/commands/cvd/cli:command_request", "//cuttlefish/host/commands/cvd/cli:help_format", "//cuttlefish/host/commands/cvd/cli:types", + "//cuttlefish/host/commands/cvd/cli/commands:bug", "//cuttlefish/host/commands/cvd/cli/commands:bugreport", "//cuttlefish/host/commands/cvd/cli/commands:cache", "//cuttlefish/host/commands/cvd/cli/commands:clear", diff --git a/base/cvd/cuttlefish/host/commands/cvd/cli/commands/BUILD.bazel b/base/cvd/cuttlefish/host/commands/cvd/cli/commands/BUILD.bazel index e27d3cf39db..6355320d3b0 100644 --- a/base/cvd/cuttlefish/host/commands/cvd/cli/commands/BUILD.bazel +++ b/base/cvd/cuttlefish/host/commands/cvd/cli/commands/BUILD.bazel @@ -14,6 +14,32 @@ exports_files([ "load_configs.h", ]) +cf_cc_library( + name = "bug", + srcs = ["bug.cpp"], + hdrs = ["bug.h"], + clang_format_enabled = True, + deps = [ + "//cuttlefish/common/libs/fs", + "//cuttlefish/common/libs/utils:files", + "//cuttlefish/common/libs/utils:subprocess", + "//cuttlefish/common/libs/utils:subprocess_managed_stdio", + "//cuttlefish/common/libs/utils:users", + ":bugreport", + "//cuttlefish/host/commands/cvd/cli:command_request", + "//cuttlefish/host/commands/cvd/cli:log_files", + "//cuttlefish/host/commands/cvd/cli:log_tail", + "//cuttlefish/host/commands/cvd/cli:types", + "//cuttlefish/host/commands/cvd/cli/commands:command_handler", + "//cuttlefish/host/commands/cvd/instances", + "//cuttlefish/host/commands/cvd/version", + "//cuttlefish/result", + "//libbase", + "@abseil-cpp//absl/strings", + "@fmt", + ], +) + cf_cc_library( name = "bugreport", srcs = ["bugreport.cpp"], @@ -254,6 +280,7 @@ cf_cc_library( "//cuttlefish/common/libs/fs", "//cuttlefish/common/libs/utils:flag_parser", "//cuttlefish/host/commands/cvd/cli:command_request", + "//cuttlefish/host/commands/cvd/cli:log_tail", "//cuttlefish/host/commands/cvd/cli:types", "//cuttlefish/host/commands/cvd/cli:utils", "//cuttlefish/host/commands/cvd/cli/commands:command_handler", @@ -265,7 +292,6 @@ cf_cc_library( "//libbase", "@abseil-cpp//absl/log:check", "@abseil-cpp//absl/strings", - "@abseil-cpp//absl/strings:cord", "@fmt", ], ) diff --git a/base/cvd/cuttlefish/host/commands/cvd/cli/commands/bug.cpp b/base/cvd/cuttlefish/host/commands/cvd/cli/commands/bug.cpp new file mode 100644 index 00000000000..bbcf860130e --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/cli/commands/bug.cpp @@ -0,0 +1,231 @@ +/* + * Copyright (C) 2026 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. + */ + +#include "cuttlefish/host/commands/cvd/cli/commands/bug.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "cuttlefish/common/libs/fs/shared_fd.h" +#include "cuttlefish/common/libs/utils/files.h" +#include "cuttlefish/common/libs/utils/subprocess.h" +#include "cuttlefish/common/libs/utils/subprocess_managed_stdio.h" +#include "cuttlefish/common/libs/utils/users.h" +#include "cuttlefish/host/commands/cvd/cli/command_request.h" +#include "cuttlefish/host/commands/cvd/cli/commands/bugreport.h" +#include "cuttlefish/host/commands/cvd/cli/commands/command_handler.h" +#include "cuttlefish/host/commands/cvd/cli/log_files.h" +#include "cuttlefish/host/commands/cvd/cli/log_tail.h" +#include "cuttlefish/host/commands/cvd/cli/types.h" +#include "cuttlefish/host/commands/cvd/instances/instance_database.h" +#include "cuttlefish/host/commands/cvd/instances/local_instance.h" +#include "cuttlefish/host/commands/cvd/version/version.h" +#include "cuttlefish/result/result.h" + +namespace cuttlefish { +namespace { + +constexpr char kSummaryHelpText[] = "File an issue using go/bugged"; +constexpr char kHelpMessage[] = R"( +usage: cvd bug + + `cvd bug` will invoke `bugged create` to file an issue against Cuttlefish. + It requires `bugged` to be installed on the system. + See go/bugged for more information. +)"; + +Result ParseBugId(std::string_view stdout_str) { + const std::string_view prefix = "http://b/"; + const size_t pos = stdout_str.find(prefix); + CF_EXPECT(pos != std::string_view::npos, "Prefix not found"); + + const size_t start = pos + prefix.size(); + size_t end = start; + while (end < stdout_str.size() && std::isdigit(stdout_str[end])) { + end++; + } + CF_EXPECT(end > start, "No digits found after prefix"); + + return std::string(stdout_str.substr(start, end - start)); +} + +Result GetLatestLocalInstance( + const InstanceDatabase& instance_db) { + const std::vector groups = + CF_EXPECT(instance_db.InstanceGroups()); + CF_EXPECT(!groups.empty(), "No instance groups found."); + + const auto latest_group = std::max_element( + groups.begin(), groups.end(), + [](const LocalInstanceGroup& a, const LocalInstanceGroup& b) { + return a.StartTime() < b.StartTime(); + }); + + CF_EXPECT(!latest_group->Instances().empty(), + "Latest instance group has no instances."); + return latest_group->Instances().front(); +} + +Result GenerateIssueText() { + const std::optional previous_log_opt = + CF_EXPECT(GetPreviousLogFile()); + CF_EXPECT(previous_log_opt.has_value(), "No previous log file found."); + const std::string previous_log = *previous_log_opt; + + const SharedFD fd = SharedFD::Open(previous_log, O_RDONLY); + CF_EXPECTF(fd->IsOpen(), "Failed to open log file {}: {}", previous_log, + fd->StrError()); + + const std::vector lines = CF_EXPECTF( + GetLastNLines(fd, 30), "Failed to read log file {}", previous_log); + const std::string log_tail = + fmt::format("```\n{}\n```\n", fmt::join(lines, "\n")); + + const std::string username = CF_EXPECT(CurrentUserName()); + + return fmt::format( + "Cuttlefish bug report\n\n" + "{}\n" + "CVD Version:\n{}\n\n" + "CC+=cloud-android-devs\n" + "COMPONENT=162041\n" + "HOTLIST+=1883485\n" + "PRIORITY=P2\n" + "REPORTER={}\n" + "SEVERITY=S2\n" + "STATUS=NEW\n" + "TYPE=BUG\n", + log_tail, GetVersionIds().ToPrettyString(), username); +} + +Result GetBuggedBinary() { + CF_EXPECT(FileExists("/usr/bin/gcertstatus"), "Not a Googler desktop."); + const int gcert_status = Execute({"/usr/bin/gcertstatus"}); + CF_EXPECT(gcert_status == 0, "Please run gcert."); + + if (FileExists("/usr/bin/bugged")) { + return "/usr/bin/bugged"; + } + return "/google/bin/releases/bugged/bugged"; +} + +Result ProduceBugreport(const InstanceDatabase& instance_db, + const cvd_common::Envs& env) { + const LocalInstance latest_instance = + CF_EXPECT(GetLatestLocalInstance(instance_db)); + const std::string android_host_out = latest_instance.host_artifacts_path(); + const std::string home = latest_instance.home_directory(); + const std::string log_dir = CvdUserLogDir(); + + CF_EXPECT(RunHostBugreportCommand(android_host_out, home, env, {}, log_dir)); + return {}; +} + +Result FileIssue(const std::string& bugged_bin, + const std::string& issue_text) { + Command command(bugged_bin); + command.AddParameter("create"); + command.AddParameter("--format=MARKDOWN"); + + std::string stdout_str; + const int exit_code = RunWithManagedStdio(std::move(command), &issue_text, + &stdout_str, nullptr); + CF_EXPECTF(exit_code == 0, "bugged exited with code {}", exit_code); + + const std::string bug_id = + CF_EXPECTF(ParseBugId(stdout_str), + "Failed to parse bug ID from bugged output: {}", stdout_str); + return bug_id; +} + +Result AttachFile(const std::string& bugged_bin, + const std::string& bug_id, + const std::string& file_path) { + if (FileExists(file_path)) { + Command attach_cmd(bugged_bin); + attach_cmd.AddParameter("attach"); + attach_cmd.AddParameter(bug_id); + attach_cmd.AddParameter(file_path); + + const int attach_status = + RunWithManagedStdio(std::move(attach_cmd), nullptr, nullptr, nullptr); + if (attach_status != 0) { + std::cerr << "Failed to attach file " << file_path << " to bug " << bug_id + << std::endl; + } + } else { + std::cerr << "File " << file_path << " does not exist to attach." + << std::endl; + } + return {}; +} + +} // namespace + +CvdBugCommandHandler::CvdBugCommandHandler(const InstanceDatabase& instance_db) + : instance_db_(instance_db) {} + +Result CvdBugCommandHandler::Handle(const CommandRequest& request) { + const std::string bugged_bin = CF_EXPECT(GetBuggedBinary()); + const std::string issue_text = CF_EXPECT(GenerateIssueText()); + CF_EXPECT(ProduceBugreport(instance_db_, request.Env())); + const std::string bug_id = CF_EXPECT(FileIssue(bugged_bin, issue_text)); + std::cout << "Created issue http://b/" << bug_id << std::endl; + + const std::string bugreport_zip = CvdUserLogDir() + "/host_bugreport.zip"; + CF_EXPECT(AttachFile(bugged_bin, bug_id, bugreport_zip)); + + const std::optional previous_log_opt = + CF_EXPECT(GetPreviousLogFile()); + if (previous_log_opt) { + CF_EXPECT(AttachFile(bugged_bin, bug_id, *previous_log_opt)); + } else { + std::cerr << "No previous log file found to attach." << std::endl; + } + + return {}; +} + +std::vector CvdBugCommandHandler::CmdList() const { + return {"bug"}; +} + +std::string CvdBugCommandHandler::SummaryHelp() const { + return kSummaryHelpText; +} + +Result CvdBugCommandHandler::DetailedHelp(const CommandRequest&) { + return kHelpMessage; +} + +std::unique_ptr NewCvdBugCommandHandler( + const InstanceDatabase& instance_db) { + return std::make_unique(instance_db); +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/cli/commands/bug.h b/base/cvd/cuttlefish/host/commands/cvd/cli/commands/bug.h new file mode 100644 index 00000000000..e20dfd70f47 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/cli/commands/bug.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2026 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. + */ + +#pragma once + +#include +#include +#include + +#include "cuttlefish/host/commands/cvd/cli/command_request.h" +#include "cuttlefish/host/commands/cvd/cli/commands/command_handler.h" +#include "cuttlefish/host/commands/cvd/instances/instance_database.h" +#include "cuttlefish/result/result.h" + +namespace cuttlefish { + +class CvdBugCommandHandler : public CvdCommandHandler { + public: + CvdBugCommandHandler(const InstanceDatabase& instance_db); + + Result Handle(const CommandRequest& request) override; + std::vector CmdList() const override; + std::string SummaryHelp() const override; + Result DetailedHelp(const CommandRequest& request) override; + + private: + const InstanceDatabase& instance_db_; +}; + +std::unique_ptr NewCvdBugCommandHandler( + const InstanceDatabase& instance_db); + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/cli/commands/bugreport.cpp b/base/cvd/cuttlefish/host/commands/cvd/cli/commands/bugreport.cpp index 0c85d0d3076..b98674341cd 100644 --- a/base/cvd/cuttlefish/host/commands/cvd/cli/commands/bugreport.cpp +++ b/base/cvd/cuttlefish/host/commands/cvd/cli/commands/bugreport.cpp @@ -88,6 +88,27 @@ Result AddFetchLogIfPresent(const LocalInstanceGroup& instance_group, } } // namespace +Result RunHostBugreportCommand(const std::string& android_host_out, + const std::string& home, + cvd_common::Envs env, + const std::vector& args, + const std::string& working_dir) { + env["HOME"] = home; + env[kAndroidHostOut] = android_host_out; + const std::string bin_path = + absl::StrCat(android_host_out, "/bin/", kHostBugreportBin); + + ConstructCommandParam construct_cmd_param{.bin_path = bin_path, + .home = home, + .args = args, + .envs = env, + .working_dir = working_dir, + .command_name = kHostBugreportBin}; + Command command = CF_EXPECT(ConstructCommand(construct_cmd_param)); + (void)command.Start().Wait(); + return {}; +} + CvdBugreportCommandHandler::CvdBugreportCommandHandler( InstanceManager& instance_manager) : instance_manager_(instance_manager) {} @@ -106,24 +127,9 @@ Result CvdBugreportCommandHandler::Handle(const CommandRequest& request) { CF_EXPECT(selector::SelectGroup(instance_manager_, request)); std::string android_host_out = instance_group.HostArtifactsPath(); std::string home = instance_group.HomeDir(); - env["HOME"] = home; - env[kAndroidHostOut] = android_host_out; - const std::string bin_path = - absl::StrCat(android_host_out, "/bin/", kHostBugreportBin); - - ConstructCommandParam construct_cmd_param{.bin_path = bin_path, - .home = home, - .args = cmd_args, - .envs = env, - .working_dir = CurrentDirectory(), - .command_name = kHostBugreportBin}; - Command command = CF_EXPECT(ConstructCommand(construct_cmd_param)); - // Wait for the command to finish but ignore the result. The command will fail - // for reasons like the device failing to initialize the home directory or - // errors during fetch, which are still debuggable states that require a - // report. - (void)command.Start().Wait(); + CF_EXPECT(RunHostBugreportCommand(android_host_out, home, env, cmd_args, + CurrentDirectory())); auto result = AddFetchLogIfPresent(instance_group, output_file); if (!result.ok()) { diff --git a/base/cvd/cuttlefish/host/commands/cvd/cli/commands/bugreport.h b/base/cvd/cuttlefish/host/commands/cvd/cli/commands/bugreport.h index 549afe9aeb4..2487bf2b4d2 100644 --- a/base/cvd/cuttlefish/host/commands/cvd/cli/commands/bugreport.h +++ b/base/cvd/cuttlefish/host/commands/cvd/cli/commands/bugreport.h @@ -18,13 +18,21 @@ #include #include +#include #include "cuttlefish/host/commands/cvd/cli/commands/command_handler.h" +#include "cuttlefish/host/commands/cvd/cli/types.h" #include "cuttlefish/host/commands/cvd/instances/instance_manager.h" #include "cuttlefish/result/result.h" namespace cuttlefish { +Result RunHostBugreportCommand(const std::string& android_host_out, + const std::string& home, + cvd_common::Envs env, + const std::vector& args, + const std::string& working_dir); + class CvdBugreportCommandHandler : public CvdCommandHandler { public: CvdBugreportCommandHandler(InstanceManager& instance_manager); diff --git a/base/cvd/cuttlefish/host/commands/cvd/cli/commands/monitor.cpp b/base/cvd/cuttlefish/host/commands/cvd/cli/commands/monitor.cpp index ca99ff17d75..ddaf3649823 100644 --- a/base/cvd/cuttlefish/host/commands/cvd/cli/commands/monitor.cpp +++ b/base/cvd/cuttlefish/host/commands/cvd/cli/commands/monitor.cpp @@ -17,10 +17,8 @@ #include "cuttlefish/host/commands/cvd/cli/commands/monitor.h" #include -#include #include -#include #include #include #include @@ -34,16 +32,15 @@ #include #include "absl/log/check.h" -#include "absl/strings/cord.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_replace.h" #include "absl/strings/str_split.h" -#include "cuttlefish/common/libs/fs/shared_buf.h" #include "cuttlefish/common/libs/fs/shared_fd.h" #include "cuttlefish/common/libs/utils/flag_parser.h" #include "cuttlefish/host/commands/cvd/cli/command_request.h" #include "cuttlefish/host/commands/cvd/cli/commands/command_handler.h" +#include "cuttlefish/host/commands/cvd/cli/log_tail.h" #include "cuttlefish/host/commands/cvd/cli/selector/selector.h" #include "cuttlefish/host/commands/cvd/cli/types.h" #include "cuttlefish/host/commands/cvd/cli/utils.h" @@ -53,47 +50,6 @@ namespace cuttlefish { -namespace { - -Result> GetLastNLines(SharedFD fd, size_t n) { - off_t file_size = fd->LSeek(0, SEEK_END); - CF_EXPECT(file_size != -1, "Failed to seek to end of file"); - - absl::Cord accumulated_data; - off_t offset = file_size; - size_t newline_count = 0; - - while (offset > 0 && newline_count < n + 1) { - static constexpr off_t kChunkSize = 4096; - size_t to_read = std::min(kChunkSize, offset); - offset -= to_read; - fd->LSeek(offset, SEEK_SET); - - std::string chunk(to_read, '\0'); - ssize_t bytes_read = ReadExact(fd, &chunk); - CF_EXPECTF(bytes_read == static_cast(to_read), "Read failed: '{}'", - fd->StrError()); - - newline_count += std::count(chunk.begin(), chunk.end(), '\n'); - accumulated_data.Prepend(std::move(chunk)); - } - - std::vector all_lines = - absl::StrSplit(std::string(accumulated_data), '\n'); - - // Handle trailing newline - if (!all_lines.empty() && all_lines.back().empty()) { - all_lines.pop_back(); - } - - size_t start_idx = all_lines.size() > n ? all_lines.size() - n : 0; - all_lines.erase(all_lines.begin(), all_lines.begin() + start_idx); - - return all_lines; -} - -} // namespace - LogMonitorDisplay::LogMonitorDisplay(size_t width) : width_(width), total_lines_drawn_(0) {} diff --git a/base/cvd/cuttlefish/host/commands/cvd/cli/log_files.cpp b/base/cvd/cuttlefish/host/commands/cvd/cli/log_files.cpp index 72ce4c48530..01bf4f44b7c 100644 --- a/base/cvd/cuttlefish/host/commands/cvd/cli/log_files.cpp +++ b/base/cvd/cuttlefish/host/commands/cvd/cli/log_files.cpp @@ -86,4 +86,22 @@ Result PruneLogsDirectory(const std::string& log_dir, size_t retain) { return {}; } +Result> GetPreviousLogFile( + const std::string& log_dir) { + if (!DirectoryExists(log_dir)) { + return std::nullopt; + } + std::vector log_files = + CF_EXPECTF(DirectoryContents(log_dir), + "Failed to list log directory: '{}'", log_dir); + std::erase_if(log_files, IsNotLogFile); + std::sort(log_files.begin(), log_files.end()); + + if (log_files.size() < 2) { + return std::nullopt; + } + + return log_dir + "/" + log_files[log_files.size() - 2]; +} + } // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/cli/log_files.h b/base/cvd/cuttlefish/host/commands/cvd/cli/log_files.h index bc706f55d5d..99291d7f3f0 100644 --- a/base/cvd/cuttlefish/host/commands/cvd/cli/log_files.h +++ b/base/cvd/cuttlefish/host/commands/cvd/cli/log_files.h @@ -17,6 +17,7 @@ #pragma once #include +#include #include #include "cuttlefish/result/result.h" @@ -42,4 +43,11 @@ std::string GetCvdLogFileName(const std::string& log_dir = CvdUserLogDir(), Result PruneLogsDirectory(const std::string& log_dir = CvdUserLogDir(), size_t retain = kCvdRetainLogFilesNum); +/** + * Returns the path to the previous log file (second most recent) in the log + * directory. If fewer than 2 log files exist, returns std::nullopt. + */ +Result> GetPreviousLogFile( + const std::string& log_dir = CvdUserLogDir()); + } // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/cli/log_tail.cpp b/base/cvd/cuttlefish/host/commands/cvd/cli/log_tail.cpp new file mode 100644 index 00000000000..a9edbeca588 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/cli/log_tail.cpp @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2026 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. + */ + +#include "cuttlefish/host/commands/cvd/cli/log_tail.h" + +#include +#include +#include +#include +#include + +#include "absl/strings/cord.h" +#include "absl/strings/str_split.h" + +#include "cuttlefish/common/libs/fs/shared_buf.h" +#include "cuttlefish/common/libs/fs/shared_fd.h" +#include "cuttlefish/result/result.h" + +namespace cuttlefish { + +Result> GetLastNLines(SharedFD fd, size_t n) { + off_t file_size = fd->LSeek(0, SEEK_END); + CF_EXPECT(file_size != -1, "Failed to seek to end of file"); + + absl::Cord accumulated_data; + off_t offset = file_size; + size_t newline_count = 0; + + while (offset > 0 && newline_count < n + 1) { + static constexpr off_t kChunkSize = 4096; + size_t to_read = std::min(kChunkSize, offset); + offset -= to_read; + fd->LSeek(offset, SEEK_SET); + + std::string chunk(to_read, '\0'); + ssize_t bytes_read = ReadExact(fd, &chunk); + CF_EXPECTF(bytes_read == static_cast(to_read), "Read failed: '{}'", + fd->StrError()); + + newline_count += std::count(chunk.begin(), chunk.end(), '\n'); + accumulated_data.Prepend(std::move(chunk)); + } + + std::vector all_lines = + absl::StrSplit(std::string(accumulated_data), '\n'); + + if (!all_lines.empty() && all_lines.back().empty()) { + all_lines.pop_back(); + } + + size_t start_idx = all_lines.size() > n ? all_lines.size() - n : 0; + all_lines.erase(all_lines.begin(), all_lines.begin() + start_idx); + + return all_lines; +} + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/cli/log_tail.h b/base/cvd/cuttlefish/host/commands/cvd/cli/log_tail.h new file mode 100644 index 00000000000..0b48108d037 --- /dev/null +++ b/base/cvd/cuttlefish/host/commands/cvd/cli/log_tail.h @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2026 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. + */ + +#pragma once + +#include +#include +#include + +#include "cuttlefish/common/libs/fs/shared_fd.h" +#include "cuttlefish/result/result.h" + +namespace cuttlefish { + +Result> GetLastNLines(SharedFD fd, size_t n); + +} // namespace cuttlefish diff --git a/base/cvd/cuttlefish/host/commands/cvd/cli/request_context.cpp b/base/cvd/cuttlefish/host/commands/cvd/cli/request_context.cpp index dd44a30ef9b..a6098c17e2b 100644 --- a/base/cvd/cuttlefish/host/commands/cvd/cli/request_context.cpp +++ b/base/cvd/cuttlefish/host/commands/cvd/cli/request_context.cpp @@ -24,6 +24,7 @@ #include "cuttlefish/common/libs/utils/contains.h" #include "cuttlefish/host/commands/cvd/cli/command_request.h" +#include "cuttlefish/host/commands/cvd/cli/commands/bug.h" #include "cuttlefish/host/commands/cvd/cli/commands/bugreport.h" #include "cuttlefish/host/commands/cvd/cli/commands/cache.h" #include "cuttlefish/host/commands/cvd/cli/commands/clear.h" @@ -94,6 +95,8 @@ RequestContext::RequestContext(InstanceManager& instance_manager, request_handlers_.emplace_back(NewCvdFetchCommandHandler()); request_handlers_.emplace_back(NewCvdFleetCommandHandler(instance_manager)); request_handlers_.emplace_back(NewCvdClearCommandHandler(instance_manager)); + request_handlers_.emplace_back( + NewCvdBugCommandHandler(instance_manager.GetInstanceDatabase())); request_handlers_.emplace_back( NewCvdBugreportCommandHandler(instance_manager)); request_handlers_.emplace_back(NewCvdStopCommandHandler(instance_manager)); diff --git a/base/cvd/cuttlefish/host/commands/cvd/instances/instance_manager.h b/base/cvd/cuttlefish/host/commands/cvd/instances/instance_manager.h index fadd57062b3..b292fe13675 100644 --- a/base/cvd/cuttlefish/host/commands/cvd/instances/instance_manager.h +++ b/base/cvd/cuttlefish/host/commands/cvd/instances/instance_manager.h @@ -57,6 +57,8 @@ class InstanceManager { }; InstanceManager(InstanceLockFileManager&, InstanceDatabase& instance_db); + const InstanceDatabase& GetInstanceDatabase() const { return instance_db_; } + Result HasInstanceGroups() const; Result CreateInstanceGroup( InstanceGroupParams group_params, GroupDirectories group_directories);