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
@@ -317,18 +330,6 @@ CvdStartCommandHandler::CvdStartCommandHandler(
317330 InstanceManager& instance_manager)
318331 : instance_manager_(instance_manager) {}
319332
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 {};
330- }
331-
332333static bool HasUnsafeFlagsForBypass (const std::vector<std::string>& args) {
333334 std::vector<std::string> args_copy = args;
334335 bool daemon = true ;
@@ -370,8 +371,18 @@ Result<void> CvdStartCommandHandler::Handle(const CommandRequest& request) {
370371 }
371372 }
372373
373- CF_EXPECT (ConsumeDaemonModeFlag (subcmd_args));
374+ CF_EXPECT (ConsumeFlags (BuildOwnFlags (), subcmd_args));
375+ bool is_daemon = own_flags_.daemon .value_or (true );
374376 subcmd_args.push_back (" --daemon=true" );
377+ if (own_flags_.report_anonymous_usage_stats ) {
378+ subcmd_args.push_back (" --report_anonymous_usage_stats=" +
379+ *own_flags_.report_anonymous_usage_stats );
380+ } else if (!is_daemon) {
381+ // If the user did not specify the anonymous usage stats flag, default it
382+ // to 'n' to ensure that cvd_internal_start does not block waiting for
383+ // input on stdin.
384+ subcmd_args.push_back (" --report_anonymous_usage_stats=n" );
385+ }
375386
376387 LocalInstanceGroup group =
377388 CF_EXPECT (selector::SelectGroup (instance_manager_, request),
@@ -388,46 +399,109 @@ Result<void> CvdStartCommandHandler::Handle(const CommandRequest& request) {
388399 CF_EXPECT (UpdateEnvs (envs, group));
389400 const auto bin = CF_EXPECT (FindStartBin (group.HostArtifactsPath ()));
390401
391- CF_EXPECT (ConsumeFlags (BuildOwnFlags (), subcmd_args));
392-
393402 CF_EXPECT (HostPackageSubstitution (group.HostArtifactsPath (),
394403 own_flags_.host_substitutions ));
395404
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.
405+ SharedFD memfd;
406+ SharedFD stop_eventfd;
407+ if (!is_daemon) {
408+ memfd = SharedFD::MemfdCreate (" cvd_internal_start_output" );
409+ CF_EXPECT (memfd->IsOpen (), " Failed to create memfd for subprocess output: "
410+ << memfd->StrError ());
411+
412+ stop_eventfd = SharedFD::Event ();
413+ CF_EXPECT (stop_eventfd->IsOpen (),
414+ " Failed to create eventfd for stopping monitor: "
415+ << stop_eventfd->StrError ());
416+ }
417417
418- std::abort ();
419- });
420- auto listener_handle = CF_EXPECT (std::move (handle_res));
418+ Command command = CF_EXPECT (ConstructCvdNonHelpCommand (
419+ bin, group, subcmd_args, envs, request, memfd));
420+
421+ Result<std::unique_ptr<InterruptListenerHandle>> handle_res =
422+ PushInterruptListener ([this , &group, stop_eventfd](int signal) {
423+ LOG (WARNING ) << strsignal (signal) << " signal received, cleanning up" ;
424+ if (stop_eventfd->IsOpen ()) {
425+ stop_eventfd->EventfdWrite (1 );
426+ }
427+ Result<void > interrupt_res = subprocess_waiter_.Interrupt ();
428+ if (!interrupt_res.ok ()) {
429+ LOG (ERROR ) << " Failed to stop subprocesses: "
430+ << interrupt_res.error ();
431+ LOG (ERROR ) << " Devices may still be executing in the background, "
432+ " run `cvd reset` to ensure a clean state" ;
433+ }
434+
435+ group.SetAllStates (cvd::INSTANCE_STATE_CANCELLED );
436+ Result<void > update_res = instance_manager_.UpdateInstanceGroup (group);
437+ if (!update_res.ok ()) {
438+ LOG (ERROR ) << " Failed to update group status: " << update_res.error ();
439+ }
440+ // It's technically possible for the group's state to be set to
441+ // "running" before abort has a chance to run, but that can only happen
442+ // if the instances are indeed running, so it's OK.
443+
444+ std::abort ();
445+ });
446+
447+ std::unique_ptr<InterruptListenerHandle> listener_handle =
448+ CF_EXPECT (std::move (handle_res));
421449 group.SetAllStates (cvd::INSTANCE_STATE_STARTING );
422450 group.SetStartTime (CvdServerClock::now ());
423451 CF_EXPECT (instance_manager_.UpdateInstanceGroup (group));
424- CF_EXPECT (
425- LaunchDeviceInterruptible (std::move (command), group, envs, request));
452+
453+ std::thread monitor_thread;
454+ Result<void > monitor_res;
455+
456+ if (!is_daemon) {
457+ monitor_thread = std::thread ([&group, stop_eventfd, &monitor_res]() {
458+ const LocalInstance& first_instance = *group.Instances ().begin ();
459+ monitor_res = MonitorLogs (first_instance, stop_eventfd);
460+ });
461+ }
462+
463+ Result<void > launch_res =
464+ LaunchDeviceInterruptible (std::move (command), group, envs, request);
465+
466+ if (!launch_res.ok ()) {
467+ if (!is_daemon) {
468+ if (stop_eventfd->IsOpen ()) {
469+ stop_eventfd->EventfdWrite (1 );
470+ }
471+ if (monitor_thread.joinable ()) {
472+ monitor_thread.join ();
473+ }
474+ memfd->LSeek (0 , SEEK_SET );
475+ std::string full_output;
476+ ReadAll (memfd, &full_output);
477+ std::cout << full_output << std::flush;
478+ }
479+ return launch_res;
480+ }
481+
426482 group.SetAllStates (cvd::INSTANCE_STATE_RUNNING );
427483 CF_EXPECT (instance_manager_.UpdateInstanceGroup (group));
484+
485+ if (!is_daemon) {
486+ listener_handle.reset ();
487+ std::unique_ptr<InterruptListenerHandle> stop_listener =
488+ CF_EXPECT (PushInterruptListener (
489+ [stop_eventfd](int ) { stop_eventfd->EventfdWrite (1 ); }));
490+
491+ if (monitor_thread.joinable ()) {
492+ monitor_thread.join ();
493+ }
494+ stop_listener.reset ();
495+
496+ LOG (INFO ) << " Stopping device..." ;
497+ CF_EXPECT (instance_manager_.StopInstanceGroup (
498+ group, std::chrono::seconds (5 ), InstanceDirActionOnStop::Keep, {}));
499+ LOG (INFO ) << " Device stopped." ;
500+ return monitor_res;
501+ }
502+
428503 listener_handle.reset ();
429504
430- // show user a more succinct output
431505 if (isatty (0 )) {
432506 std::vector<std::string> instance_names;
433507 for (const auto & instance : group.Instances ()) {
@@ -609,7 +683,15 @@ std::vector<Flag> CvdStartCommandHandler::BuildOwnFlags() {
609683 " cuttlefish-base package. The special value \" all\" causes "
610684 " it to replace everything it can, while with an empty it "
611685 " will replace what the android build specifies in the "
612- " 'debian_substitution_marker' file." )};
686+ " 'debian_substitution_marker' file." ),
687+ GflagsCompatFlag (" daemon" , own_flags_.daemon )
688+ .Help (" Run the start command in the background as a daemon. "
689+ " If false, the command runs in the foreground and monitors "
690+ " logs." ),
691+ GflagsCompatFlag (" report_anonymous_usage_stats" ,
692+ own_flags_.report_anonymous_usage_stats )
693+ .Help (" Report anonymous usage stats. In foreground mode, "
694+ " defaults to 'n' if not specified." )};
613695}
614696
615697std::unique_ptr<CvdCommandHandler> NewCvdStartCommandHandler (
0 commit comments