2424#include < unistd.h>
2525
2626#include < algorithm>
27+ #include < chrono>
28+ #include < cstdio>
2729#include < cstdlib>
2830#include < cstring>
2931#include < iostream>
3234#include < optional>
3335#include < set>
3436#include < string>
37+ #include < thread>
3538#include < utility>
3639#include < vector>
3740
38- #include < fmt/core.h>
39- #include < fmt/format.h>
4041#include " absl/log/log.h"
4142#include " absl/strings/str_join.h"
4243#include " absl/strings/str_split.h"
44+ #include " fmt/core.h"
45+ #include " fmt/format.h"
4346
47+ #include " cuttlefish/common/libs/fs/shared_buf.h"
4448#include " cuttlefish/common/libs/fs/shared_fd.h"
4549#include " cuttlefish/common/libs/utils/contains.h"
4650#include " cuttlefish/common/libs/utils/files.h"
47- #include " cuttlefish/flag_parser/flag.h"
48- #include " cuttlefish/flag_parser/gflags_compat.h"
4951#include " cuttlefish/common/libs/utils/json.h"
5052#include " cuttlefish/common/libs/utils/subprocess.h"
5153#include " cuttlefish/common/libs/utils/users.h"
54+ #include " cuttlefish/flag_parser/flag.h"
55+ #include " cuttlefish/flag_parser/gflags_compat.h"
5256#include " cuttlefish/host/commands/cvd/cli/command_request.h"
5357#include " cuttlefish/host/commands/cvd/cli/commands/command_handler.h"
5458#include " cuttlefish/host/commands/cvd/cli/commands/host_tool_target.h"
59+ #include " cuttlefish/host/commands/cvd/cli/commands/monitor/monitor.h"
5560#include " cuttlefish/host/commands/cvd/cli/help_format.h"
5661#include " cuttlefish/host/commands/cvd/cli/selector/selector.h"
5762#include " cuttlefish/host/commands/cvd/cli/types.h"
@@ -279,7 +284,8 @@ Result<Command> ConstructCvdNonHelpCommand(const std::string& bin_file,
279284 const LocalInstanceGroup& group,
280285 const cvd_common::Args& args,
281286 const cvd_common::Envs& envs,
282- const CommandRequest& request) {
287+ const CommandRequest& request,
288+ SharedFD output_fd = SharedFD()) {
283289 auto bin_path = group.HostArtifactsPath ();
284290 CF_EXPECTF (PotentiallyHostArtifactsPath (bin_path),
285291 " ANDROID_HOST_OUT, \" {}\" is not a tool directory" , bin_path);
@@ -292,10 +298,17 @@ Result<Command> ConstructCvdNonHelpCommand(const std::string& bin_file,
292298 .working_dir = CurrentDirectory (),
293299 .command_name = bin_file};
294300 Command non_help_command = CF_EXPECT (ConstructCommand (construct_cmd_param));
295- // Print everything to stderr, cvd needs to print JSON to stdout which
296- // would be unparseable with the subcommand's output.
297- non_help_command.RedirectStdIO (Subprocess::StdIOChannel::kStdOut ,
298- Subprocess::StdIOChannel::kStdErr );
301+ if (output_fd->IsOpen ()) {
302+ non_help_command.RedirectStdIO (Subprocess::StdIOChannel::kStdOut ,
303+ output_fd);
304+ non_help_command.RedirectStdIO (Subprocess::StdIOChannel::kStdErr ,
305+ output_fd);
306+ } else {
307+ // Print everything to stderr, cvd needs to print JSON to stdout which
308+ // would be unparseable with the subcommand's output.
309+ non_help_command.RedirectStdIO (Subprocess::StdIOChannel::kStdOut ,
310+ Subprocess::StdIOChannel::kStdErr );
311+ }
299312 return non_help_command;
300313}
301314
@@ -315,18 +328,8 @@ Result<std::vector<Flag>> GetCvdInternalStartFlags(
315328
316329CvdStartCommandHandler::CvdStartCommandHandler (
317330 InstanceManager& instance_manager)
318- : instance_manager_(instance_manager) {}
319-
320- static Result<void > ConsumeDaemonModeFlag (cvd_common::Args& args) {
321- bool daemon = true ;
322- Flag flag =
323- GflagsCompatFlag (" daemon" , daemon)
324- .AddValidator ([&daemon]() -> Result<void > {
325- CF_EXPECT (!!daemon, " `cvd start` must always run in daemon mode." );
326- return {};
327- });
328- CF_EXPECT (ConsumeFlags ({flag}, args));
329- return {};
331+ : instance_manager_(instance_manager) {
332+ own_flags_.daemon = true ;
330333}
331334
332335static bool HasUnsafeFlagsForBypass (const std::vector<std::string>& args) {
@@ -370,8 +373,17 @@ Result<void> CvdStartCommandHandler::Handle(const CommandRequest& request) {
370373 }
371374 }
372375
373- CF_EXPECT (ConsumeDaemonModeFlag ( subcmd_args));
376+ CF_EXPECT (ConsumeFlags ( BuildOwnFlags (), subcmd_args));
374377 subcmd_args.push_back (" --daemon=true" );
378+ if (own_flags_.report_anonymous_usage_stats ) {
379+ subcmd_args.push_back (" --report_anonymous_usage_stats=" +
380+ *own_flags_.report_anonymous_usage_stats );
381+ } else if (!own_flags_.daemon ) {
382+ // If the user did not specify the anonymous usage stats flag, default it
383+ // to 'n' to ensure that cvd_internal_start does not block waiting for
384+ // input on stdin.
385+ subcmd_args.push_back (" --report_anonymous_usage_stats=n" );
386+ }
375387
376388 LocalInstanceGroup group =
377389 CF_EXPECT (selector::SelectGroup (instance_manager_, request),
@@ -388,46 +400,109 @@ Result<void> CvdStartCommandHandler::Handle(const CommandRequest& request) {
388400 CF_EXPECT (UpdateEnvs (envs, group));
389401 const auto bin = CF_EXPECT (FindStartBin (group.HostArtifactsPath ()));
390402
391- CF_EXPECT (ConsumeFlags (BuildOwnFlags (), subcmd_args));
392-
393403 CF_EXPECT (HostPackageSubstitution (group.HostArtifactsPath (),
394404 own_flags_.host_substitutions ));
395405
396- Command command = CF_EXPECT (
397- ConstructCvdNonHelpCommand (bin, group, subcmd_args, envs, request));
398-
399- // The instance database needs to be updated if an interrupt is received.
400- auto handle_res = PushInterruptListener ([this , &group](int signal) {
401- LOG (WARNING ) << strsignal (signal) << " signal received, cleanning up" ;
402- auto interrupt_res = subprocess_waiter_.Interrupt ();
403- if (!interrupt_res.ok ()) {
404- LOG (ERROR ) << " Failed to stop subprocesses: " << interrupt_res.error ();
405- LOG (ERROR ) << " Devices may still be executing in the background, run "
406- " `cvd reset` to ensure a clean state" ;
407- }
408-
409- group.SetAllStates (cvd::INSTANCE_STATE_CANCELLED );
410- auto update_res = instance_manager_.UpdateInstanceGroup (group);
411- if (!update_res.ok ()) {
412- LOG (ERROR ) << " Failed to update group status: " << update_res.error ();
413- }
414- // It's technically possible for the group's state to be set to
415- // "running" before abort has a chance to run, but that can only happen
416- // if the instances are indeed running, so it's OK.
406+ SharedFD memfd;
407+ SharedFD stop_eventfd;
408+ if (!own_flags_.daemon ) {
409+ memfd = SharedFD::MemfdCreate (" cvd_internal_start_output" );
410+ CF_EXPECT (memfd->IsOpen (), " Failed to create memfd for subprocess output: "
411+ << memfd->StrError ());
412+
413+ stop_eventfd = SharedFD::Event ();
414+ CF_EXPECT (stop_eventfd->IsOpen (),
415+ " Failed to create eventfd for stopping monitor: "
416+ << stop_eventfd->StrError ());
417+ }
417418
418- std::abort ();
419- });
420- auto listener_handle = CF_EXPECT (std::move (handle_res));
419+ Command command = CF_EXPECT (ConstructCvdNonHelpCommand (
420+ bin, group, subcmd_args, envs, request, memfd));
421+
422+ Result<std::unique_ptr<InterruptListenerHandle>> handle_res =
423+ PushInterruptListener ([this , &group, stop_eventfd](int signal) {
424+ LOG (WARNING ) << strsignal (signal) << " signal received, cleanning up" ;
425+ if (stop_eventfd->IsOpen ()) {
426+ stop_eventfd->EventfdWrite (1 );
427+ }
428+ Result<void > interrupt_res = subprocess_waiter_.Interrupt ();
429+ if (!interrupt_res.ok ()) {
430+ LOG (ERROR ) << " Failed to stop subprocesses: "
431+ << interrupt_res.error ();
432+ LOG (ERROR ) << " Devices may still be executing in the background, "
433+ " run `cvd reset` to ensure a clean state" ;
434+ }
435+
436+ group.SetAllStates (cvd::INSTANCE_STATE_CANCELLED );
437+ Result<void > update_res = instance_manager_.UpdateInstanceGroup (group);
438+ if (!update_res.ok ()) {
439+ LOG (ERROR ) << " Failed to update group status: " << update_res.error ();
440+ }
441+ // It's technically possible for the group's state to be set to
442+ // "running" before abort has a chance to run, but that can only happen
443+ // if the instances are indeed running, so it's OK.
444+
445+ std::abort ();
446+ });
447+
448+ std::unique_ptr<InterruptListenerHandle> listener_handle =
449+ CF_EXPECT (std::move (handle_res));
421450 group.SetAllStates (cvd::INSTANCE_STATE_STARTING );
422451 group.SetStartTime (CvdServerClock::now ());
423452 CF_EXPECT (instance_manager_.UpdateInstanceGroup (group));
424- CF_EXPECT (
425- LaunchDeviceInterruptible (std::move (command), group, envs, request));
453+
454+ std::thread monitor_thread;
455+ Result<void > monitor_res;
456+
457+ if (!own_flags_.daemon ) {
458+ monitor_thread = std::thread ([&group, stop_eventfd, &monitor_res]() {
459+ const LocalInstance& first_instance = *group.Instances ().begin ();
460+ monitor_res = MonitorLogs (first_instance, stop_eventfd);
461+ });
462+ }
463+
464+ Result<void > launch_res =
465+ LaunchDeviceInterruptible (std::move (command), group, envs, request);
466+
467+ if (!launch_res.ok ()) {
468+ if (!own_flags_.daemon ) {
469+ if (stop_eventfd->IsOpen ()) {
470+ stop_eventfd->EventfdWrite (1 );
471+ }
472+ if (monitor_thread.joinable ()) {
473+ monitor_thread.join ();
474+ }
475+ memfd->LSeek (0 , SEEK_SET );
476+ std::string full_output;
477+ ReadAll (memfd, &full_output);
478+ std::cout << full_output << std::flush;
479+ }
480+ return launch_res;
481+ }
482+
426483 group.SetAllStates (cvd::INSTANCE_STATE_RUNNING );
427484 CF_EXPECT (instance_manager_.UpdateInstanceGroup (group));
485+
486+ if (!own_flags_.daemon ) {
487+ listener_handle.reset ();
488+ std::unique_ptr<InterruptListenerHandle> stop_listener =
489+ CF_EXPECT (PushInterruptListener (
490+ [stop_eventfd](int ) { stop_eventfd->EventfdWrite (1 ); }));
491+
492+ if (monitor_thread.joinable ()) {
493+ monitor_thread.join ();
494+ }
495+ stop_listener.reset ();
496+
497+ LOG (INFO ) << " Stopping device..." ;
498+ CF_EXPECT (instance_manager_.StopInstanceGroup (
499+ group, std::chrono::seconds (5 ), InstanceDirActionOnStop::Keep, {}));
500+ LOG (INFO ) << " Device stopped." ;
501+ return monitor_res;
502+ }
503+
428504 listener_handle.reset ();
429505
430- // show user a more succinct output
431506 if (isatty (0 )) {
432507 std::vector<std::string> instance_names;
433508 for (const auto & instance : group.Instances ()) {
@@ -609,7 +684,15 @@ std::vector<Flag> CvdStartCommandHandler::BuildOwnFlags() {
609684 " cuttlefish-base package. The special value \" all\" causes "
610685 " it to replace everything it can, while with an empty it "
611686 " will replace what the android build specifies in the "
612- " 'debian_substitution_marker' file." )};
687+ " 'debian_substitution_marker' file." ),
688+ GflagsCompatFlag (" daemon" , own_flags_.daemon )
689+ .Help (" Run the start command in the background as a daemon. "
690+ " If false, the command runs in the foreground and monitors "
691+ " logs." ),
692+ GflagsCompatFlag (" report_anonymous_usage_stats" ,
693+ own_flags_.report_anonymous_usage_stats )
694+ .Help (" Report anonymous usage stats. In foreground mode, "
695+ " defaults to 'n' if not specified." )};
613696}
614697
615698std::unique_ptr<CvdCommandHandler> NewCvdStartCommandHandler (
0 commit comments