1 #include "host/commands/assemble_cvd/flags.h"
2
3 #include <android-base/logging.h>
4 #include <android-base/parseint.h>
5 #include <android-base/strings.h>
6 #include <gflags/gflags.h>
7 #include <json/json.h>
8 #include <json/writer.h>
9 #include <sys/types.h>
10 #include <unistd.h>
11
12 #include <algorithm>
13 #include <array>
14 #include <fstream>
15 #include <iostream>
16 #include <optional>
17 #include <regex>
18 #include <set>
19 #include <sstream>
20 #include <unordered_map>
21
22 #include <fruit/fruit.h>
23
24 #include "common/libs/utils/environment.h"
25 #include "common/libs/utils/files.h"
26 #include "common/libs/utils/flag_parser.h"
27 #include "host/commands/assemble_cvd/alloc.h"
28 #include "host/commands/assemble_cvd/boot_config.h"
29 #include "host/commands/assemble_cvd/disk_flags.h"
30 #include "host/libs/config/config_flag.h"
31 #include "host/libs/config/host_tools_version.h"
32 #include "host/libs/graphics_detector/graphics_detector.h"
33 #include "host/libs/vm_manager/crosvm_manager.h"
34 #include "host/libs/vm_manager/gem5_manager.h"
35 #include "host/libs/vm_manager/qemu_manager.h"
36 #include "host/libs/vm_manager/vm_manager.h"
37
38 using cuttlefish::DefaultHostArtifactsPath;
39 using cuttlefish::HostBinaryPath;
40 using cuttlefish::StringFromEnv;
41 using cuttlefish::vm_manager::CrosvmManager;
42 using google::FlagSettingMode::SET_FLAGS_DEFAULT;
43 using google::FlagSettingMode::SET_FLAGS_VALUE;
44
45 DEFINE_int32(cpus, 2, "Virtual CPU count.");
46 DEFINE_string(data_policy, "use_existing", "How to handle userdata partition."
47 " Either 'use_existing', 'create_if_missing', 'resize_up_to', or "
48 "'always_create'.");
49 DEFINE_int32(blank_data_image_mb, 0,
50 "The size of the blank data image to generate, MB.");
51 DEFINE_int32(gdb_port, 0,
52 "Port number to spawn kernel gdb on e.g. -gdb_port=1234. The"
53 "kernel must have been built with CONFIG_RANDOMIZE_BASE "
54 "disabled.");
55
56 constexpr const char kDisplayHelp[] =
57 "Comma separated key=value pairs of display properties. Supported "
58 "properties:\n"
59 " 'width': required, width of the display in pixels\n"
60 " 'height': required, height of the display in pixels\n"
61 " 'dpi': optional, default 320, density of the display\n"
62 " 'refresh_rate_hz': optional, default 60, display refresh rate in Hertz\n"
63 ". Example usage: \n"
64 "--display0=width=1280,height=720\n"
65 "--display1=width=1440,height=900,dpi=480,refresh_rate_hz=30\n";
66
67 // TODO(b/192495477): combine these into a single repeatable '--display' flag
68 // when assemble_cvd switches to using the new flag parsing library.
69 DEFINE_string(display0, "", kDisplayHelp);
70 DEFINE_string(display1, "", kDisplayHelp);
71 DEFINE_string(display2, "", kDisplayHelp);
72 DEFINE_string(display3, "", kDisplayHelp);
73
74 // TODO(b/171305898): mark these as deprecated after multi-display is fully
75 // enabled.
76 DEFINE_int32(x_res, 0, "Width of the screen in pixels");
77 DEFINE_int32(y_res, 0, "Height of the screen in pixels");
78 DEFINE_int32(dpi, 0, "Pixels per inch for the screen");
79 DEFINE_int32(refresh_rate_hz, 60, "Screen refresh rate in Hertz");
80 DEFINE_string(kernel_path, "",
81 "Path to the kernel. Overrides the one from the boot image");
82 DEFINE_string(initramfs_path, "", "Path to the initramfs");
83 DEFINE_string(extra_kernel_cmdline, "",
84 "Additional flags to put on the kernel command line");
85 DEFINE_string(extra_bootconfig_args, "",
86 "Space-separated list of extra bootconfig args. "
87 "Note: overwriting an existing bootconfig argument "
88 "requires ':=' instead of '='.");
89 DEFINE_bool(guest_enforce_security, true,
90 "Whether to run in enforcing mode (non permissive).");
91 DEFINE_int32(memory_mb, 0, "Total amount of memory available for guest, MB.");
92 DEFINE_string(serial_number, cuttlefish::ForCurrentInstance("CUTTLEFISHCVD"),
93 "Serial number to use for the device");
94 DEFINE_bool(use_random_serial, false,
95 "Whether to use random serial for the device.");
96 DEFINE_string(vm_manager, "",
97 "What virtual machine manager to use, one of {qemu_cli, crosvm}");
98 DEFINE_string(gpu_mode, cuttlefish::kGpuModeAuto,
99 "What gpu configuration to use, one of {auto, drm_virgl, "
100 "gfxstream, guest_swiftshader}");
101 DEFINE_string(hwcomposer, cuttlefish::kHwComposerAuto,
102 "What hardware composer to use, one of {auto, drm, ranchu} ");
103 DEFINE_string(gpu_capture_binary, "",
104 "Path to the GPU capture binary to use when capturing GPU traces"
105 "(ngfx, renderdoc, etc)");
106 DEFINE_bool(enable_gpu_udmabuf,
107 false,
108 "Use the udmabuf driver for zero-copy virtio-gpu");
109
110 DEFINE_bool(enable_gpu_angle,
111 false,
112 "Use ANGLE to provide GLES implementation (always true for"
113 " guest_swiftshader");
114 DEFINE_bool(deprecated_boot_completed, false, "Log boot completed message to"
115 " host kernel. This is only used during transition of our clients."
116 " Will be deprecated soon.");
117
118 DEFINE_bool(use_allocd, false,
119 "Acquire static resources from the resource allocator daemon.");
120 DEFINE_bool(enable_minimal_mode, false,
121 "Only enable the minimum features to boot a cuttlefish device and "
122 "support minimal UI interactions.\nNote: Currently only supports "
123 "handheld/phone targets");
124 DEFINE_bool(pause_in_bootloader, false,
125 "Stop the bootflow in u-boot. You can continue the boot by connecting "
126 "to the device console and typing in \"boot\".");
127 DEFINE_bool(enable_host_bluetooth, true,
128 "Enable the root-canal which is Bluetooth emulator in the host.");
129
130 DEFINE_string(bluetooth_controller_properties_file,
131 "etc/rootcanal/data/controller_properties.json",
132 "The configuartion file path for root-canal which is a Bluetooth "
133 "emulator.");
134 DEFINE_string(
135 bluetooth_default_commands_file, "etc/rootcanal/data/default_commands",
136 "The default commands which root-canal executes when it launches.");
137
138 /**
139 *
140 * crosvm sandbox feature requires /var/empty and seccomp directory
141 *
142 * --enable-sandbox: will enforce the sandbox feature
143 * failing to meet the requirements result in assembly_cvd termination
144 *
145 * --enable-sandbox=no, etc: will disable sandbox
146 *
147 * no option given: it is enabled if /var/empty exists and an empty directory
148 * or if it does not exist and can be created
149 *
150 * if seccomp dir doesn't exist, assembly_cvd will terminate
151 *
152 * See SetDefaultFlagsForCrosvm()
153 *
154 */
155 DEFINE_bool(enable_sandbox,
156 false,
157 "Enable crosvm sandbox. Use this when you are sure about what you are doing.");
158
159 static const std::string kSeccompDir =
160 std::string("usr/share/crosvm/") + cuttlefish::HostArchStr() + "-linux-gnu/seccomp";
161 DEFINE_string(seccomp_policy_dir, DefaultHostArtifactsPath(kSeccompDir),
162 "With sandbox'ed crosvm, overrieds the security comp policy directory");
163
164 DEFINE_bool(start_webrtc, false, "Whether to start the webrtc process.");
165
166 DEFINE_string(
167 webrtc_assets_dir, DefaultHostArtifactsPath("usr/share/webrtc/assets"),
168 "[Experimental] Path to WebRTC webpage assets.");
169
170 DEFINE_string(
171 webrtc_certs_dir, DefaultHostArtifactsPath("usr/share/webrtc/certs"),
172 "[Experimental] Path to WebRTC certificates directory.");
173
174 DEFINE_string(
175 webrtc_public_ip,
176 "0.0.0.0",
177 "[Deprecated] Ignored, webrtc can figure out its IP address");
178
179 DEFINE_bool(
180 webrtc_enable_adb_websocket,
181 false,
182 "[Experimental] If enabled, exposes local adb service through a websocket.");
183
184 static constexpr auto HOST_OPERATOR_SOCKET_PATH = "/run/cuttlefish/operator";
185
186 DEFINE_bool(
187 // The actual default for this flag is set with SetCommandLineOption() in
188 // GetKernelConfigsAndSetDefaults() at the end of this file.
189 start_webrtc_sig_server, true,
190 "Whether to start the webrtc signaling server. This option only applies to "
191 "the first instance, if multiple instances are launched they'll share the "
192 "same signaling server, which is owned by the first one.");
193
194 DEFINE_string(webrtc_sig_server_addr, "",
195 "The address of the webrtc signaling server.");
196
197 DEFINE_int32(
198 webrtc_sig_server_port, 443,
199 "The port of the signaling server if started outside of this launch. If "
200 "-start_webrtc_sig_server is given it will choose 8443+instance_num1-1 and "
201 "this parameter is ignored.");
202
203 // TODO (jemoreira): We need a much bigger range to reliably support several
204 // simultaneous connections.
205 DEFINE_string(tcp_port_range, "15550:15558",
206 "The minimum and maximum TCP port numbers to allocate for ICE "
207 "candidates as 'min:max'. To use any port just specify '0:0'");
208
209 DEFINE_string(udp_port_range, "15550:15558",
210 "The minimum and maximum UDP port numbers to allocate for ICE "
211 "candidates as 'min:max'. To use any port just specify '0:0'");
212
213 DEFINE_string(webrtc_sig_server_path, "/register_device",
214 "The path section of the URL where the device should be "
215 "registered with the signaling server.");
216
217 DEFINE_bool(webrtc_sig_server_secure, true,
218 "Whether the WebRTC signaling server uses secure protocols (WSS vs WS).");
219
220 DEFINE_bool(verify_sig_server_certificate, false,
221 "Whether to verify the signaling server's certificate with a "
222 "trusted signing authority (Disallow self signed certificates). "
223 "This is ignored if an insecure server is configured.");
224
225 DEFINE_string(sig_server_headers_file, "",
226 "Path to a file containing HTTP headers to be included in the "
227 "connection to the signaling server. Each header should be on a "
228 "line by itself in the form <name>: <value>");
229
230 DEFINE_string(
231 webrtc_device_id, "cvd-{num}",
232 "The for the device to register with the signaling server. Every "
233 "appearance of the substring '{num}' in the device id will be substituted "
234 "with the instance number to support multiple instances");
235
236 DEFINE_string(uuid, cuttlefish::ForCurrentInstance(cuttlefish::kDefaultUuidPrefix),
237 "UUID to use for the device. Random if not specified");
238 DEFINE_bool(daemon, false,
239 "Run cuttlefish in background, the launcher exits on boot "
240 "completed/failed");
241
242 DEFINE_string(setupwizard_mode, "DISABLED",
243 "One of DISABLED,OPTIONAL,REQUIRED");
244
245 DEFINE_string(qemu_binary_dir, "/usr/bin",
246 "Path to the directory containing the qemu binary to use");
247 DEFINE_string(crosvm_binary, HostBinaryPath("crosvm"),
248 "The Crosvm binary to use");
249 DEFINE_string(gem5_binary_dir, HostBinaryPath("gem5"),
250 "Path to the gem5 build tree root");
251 DEFINE_bool(restart_subprocesses, true, "Restart any crashed host process");
252 DEFINE_bool(enable_vehicle_hal_grpc_server, true, "Enables the vehicle HAL "
253 "emulation gRPC server on the host");
254 DEFINE_string(bootloader, "", "Bootloader binary path");
255 DEFINE_string(boot_slot, "", "Force booting into the given slot. If empty, "
256 "the slot will be chosen based on the misc partition if using a "
257 "bootloader. It will default to 'a' if empty and not using a "
258 "bootloader.");
259 DEFINE_int32(num_instances, 1, "Number of Android guests to launch");
260 DEFINE_string(report_anonymous_usage_stats, "", "Report anonymous usage "
261 "statistics for metrics collection and analysis.");
262 DEFINE_string(ril_dns, "8.8.8.8", "DNS address of mobile network (RIL)");
263 DEFINE_bool(kgdb, false, "Configure the virtual device for debugging the kernel "
264 "with kgdb/kdb. The kernel must have been built with "
265 "kgdb support, and serial console must be enabled.");
266
267 DEFINE_bool(start_gnss_proxy, false, "Whether to start the gnss proxy.");
268
269 DEFINE_string(gnss_file_path, "",
270 "Local gnss file path for the gnss proxy");
271
272 // by default, this modem-simulator is disabled
273 DEFINE_bool(enable_modem_simulator, true,
274 "Enable the modem simulator to process RILD AT commands");
275 // modem_simulator_sim_type=2 for test CtsCarrierApiTestCases
276 DEFINE_int32(modem_simulator_sim_type, 1,
277 "Sim type: 1 for normal, 2 for CtsCarrierApiTestCases");
278
279 DEFINE_bool(console, false, "Enable the serial console");
280
281 DEFINE_bool(vhost_net, false, "Enable vhost acceleration of networking");
282
283 DEFINE_string(
284 vhost_user_mac80211_hwsim, "",
285 "Unix socket path for vhost-user of mac80211_hwsim, typically served by "
286 "wmediumd. You can set this when using an external wmediumd instance.");
287 DEFINE_string(wmediumd_config, "",
288 "Path to the wmediumd config file. When missing, the default "
289 "configuration is used which adds MAC addresses for up to 16 "
290 "cuttlefish instances including AP.");
291 DEFINE_string(ap_rootfs_image,
292 DefaultHostArtifactsPath("etc/openwrt/images/openwrt_rootfs"),
293 "rootfs image for AP instance");
294 DEFINE_string(ap_kernel_image,
295 DefaultHostArtifactsPath("etc/openwrt/images/kernel_for_openwrt"),
296 "kernel image for AP instance");
297
298 DEFINE_bool(record_screen, false, "Enable screen recording. "
299 "Requires --start_webrtc");
300
301 DEFINE_bool(smt, false, "Enable simultaneous multithreading (SMT/HT)");
302
303 DEFINE_int32(vsock_guest_cid,
304 cuttlefish::GetDefaultVsockCid(),
305 "vsock_guest_cid is used to determine the guest vsock cid as well as all the ports"
306 "of all vsock servers such as tombstone or modem simulator(s)."
307 "The vsock ports and guest vsock cid are a function of vsock_guest_cid and instance number."
308 "An instance number of i th instance is determined by --num_instances=N and --base_instance_num=B"
309 "The instance number of i th instance is B + i where i in [0, N-1] and B >= 1."
310 "See --num_instances, and --base_instance_num for more information"
311 "If --vsock_guest_cid=C is given and C >= 3, the guest vsock cid is C + i. Otherwise,"
312 "the guest vsock cid is 2 + instance number, which is 2 + (B + i)."
313 "If --vsock_guest_cid is not given, each vsock server port number for i th instance is"
314 "base + instance number - 1. vsock_guest_cid is by default B + i + 2."
315 "Thus, by default, each port is base + vsock_guest_cid - 3."
316 "The same formula holds when --vsock_guest_cid=C is given, for algorithm's sake."
317 "Each vsock server port number is base + C - 3.");
318
319 DEFINE_string(secure_hals, "keymint,gatekeeper",
320 "Which HALs to use enable host security features for. Supports "
321 "keymint and gatekeeper at the moment.");
322
323 DEFINE_bool(use_sdcard, true, "Create blank SD-Card image and expose to guest");
324
325 DEFINE_bool(protected_vm, false, "Boot in Protected VM mode");
326
327 DEFINE_bool(enable_audio, cuttlefish::HostArch() != cuttlefish::Arch::Arm64,
328 "Whether to play or capture audio");
329
330 DEFINE_uint32(camera_server_port, 0, "camera vsock port");
331
332 DEFINE_string(userdata_format, "f2fs", "The userdata filesystem format");
333
334 DECLARE_string(assembly_dir);
335 DECLARE_string(boot_image);
336 DECLARE_string(system_image_dir);
337
338 namespace cuttlefish {
339 using vm_manager::QemuManager;
340 using vm_manager::Gem5Manager;
341 using vm_manager::GetVmManager;
342
343 namespace {
344
ParsePortRange(const std::string & flag)345 std::pair<uint16_t, uint16_t> ParsePortRange(const std::string& flag) {
346 static const std::regex rgx("[0-9]+:[0-9]+");
347 CHECK(std::regex_match(flag, rgx))
348 << "Port range flag has invalid value: " << flag;
349 std::pair<uint16_t, uint16_t> port_range;
350 std::stringstream ss(flag);
351 char c;
352 ss >> port_range.first;
353 ss.read(&c, 1);
354 ss >> port_range.second;
355 return port_range;
356 }
357
StrForInstance(const std::string & prefix,int num)358 std::string StrForInstance(const std::string& prefix, int num) {
359 std::ostringstream stream;
360 stream << prefix << std::setfill('0') << std::setw(2) << num;
361 return stream.str();
362 }
363
ParseDisplayConfig(const std::string & flag)364 std::optional<CuttlefishConfig::DisplayConfig> ParseDisplayConfig(
365 const std::string& flag) {
366 if (flag.empty()) {
367 return std::nullopt;
368 }
369
370 std::unordered_map<std::string, std::string> props;
371
372 const std::vector<std::string> pairs = android::base::Split(flag, ",");
373 for (const std::string& pair : pairs) {
374 const std::vector<std::string> keyvalue = android::base::Split(pair, "=");
375 CHECK_EQ(2, keyvalue.size()) << "Invalid display: " << flag;
376
377 const std::string& prop_key = keyvalue[0];
378 const std::string& prop_val = keyvalue[1];
379 props[prop_key] = prop_val;
380 }
381
382 CHECK(props.find("width") != props.end())
383 << "Display configuration missing 'width' in " << flag;
384 CHECK(props.find("height") != props.end())
385 << "Display configuration missing 'height' in " << flag;
386
387 int display_width;
388 CHECK(android::base::ParseInt(props["width"], &display_width))
389 << "Display configuration invalid 'width' in " << flag;
390
391 int display_height;
392 CHECK(android::base::ParseInt(props["height"], &display_height))
393 << "Display configuration invalid 'height' in " << flag;
394
395 int display_dpi = 320;
396 auto display_dpi_it = props.find("dpi");
397 if (display_dpi_it != props.end()) {
398 CHECK(android::base::ParseInt(display_dpi_it->second, &display_dpi))
399 << "Display configuration invalid 'dpi' in " << flag;
400 }
401
402 int display_refresh_rate_hz = 60;
403 auto display_refresh_rate_hz_it = props.find("refresh_rate_hz");
404 if (display_refresh_rate_hz_it != props.end()) {
405 CHECK(android::base::ParseInt(display_refresh_rate_hz_it->second,
406 &display_refresh_rate_hz))
407 << "Display configuration invalid 'refresh_rate_hz' in " << flag;
408 }
409
410 return CuttlefishConfig::DisplayConfig{
411 .width = display_width,
412 .height = display_height,
413 .dpi = display_dpi,
414 .refresh_rate_hz = display_refresh_rate_hz,
415 };
416 }
417
418 #ifdef __ANDROID__
ReadKernelConfig()419 Result<KernelConfig> ReadKernelConfig() {
420 // QEMU isn't on Android, so always follow host arch
421 KernelConfig ret{};
422 ret.target_arch = HostArch();
423 ret.bootconfig_supported = true;
424 return ret;
425 }
426 #else
ReadKernelConfig()427 Result<KernelConfig> ReadKernelConfig() {
428 // extract-ikconfig can be called directly on the boot image since it looks
429 // for the ikconfig header in the image before extracting the config list.
430 // This code is liable to break if the boot image ever includes the
431 // ikconfig header outside the kernel.
432 const std::string kernel_image_path =
433 FLAGS_kernel_path.size() ? FLAGS_kernel_path : FLAGS_boot_image;
434
435 Command ikconfig_cmd(HostBinaryPath("extract-ikconfig"));
436 ikconfig_cmd.AddParameter(kernel_image_path);
437
438 std::string current_path = StringFromEnv("PATH", "");
439 std::string bin_folder = DefaultHostArtifactsPath("bin");
440 ikconfig_cmd.SetEnvironment({"PATH=" + current_path + ":" + bin_folder});
441
442 std::string ikconfig_path =
443 StringFromEnv("TEMP", "/tmp") + "/ikconfig.XXXXXX";
444 auto ikconfig_fd = SharedFD::Mkstemp(&ikconfig_path);
445 CF_EXPECT(ikconfig_fd->IsOpen(),
446 "Unable to create ikconfig file: " << ikconfig_fd->StrError());
447 ikconfig_cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, ikconfig_fd);
448
449 auto ikconfig_proc = ikconfig_cmd.Start();
450 CF_EXPECT(ikconfig_proc.Started() && ikconfig_proc.Wait() == 0,
451 "Failed to extract ikconfig from " << kernel_image_path);
452
453 std::string config = ReadFile(ikconfig_path);
454
455 KernelConfig kernel_config;
456 if (config.find("\nCONFIG_ARM=y") != std::string::npos) {
457 kernel_config.target_arch = Arch::Arm;
458 } else if (config.find("\nCONFIG_ARM64=y") != std::string::npos) {
459 kernel_config.target_arch = Arch::Arm64;
460 } else if (config.find("\nCONFIG_X86_64=y") != std::string::npos) {
461 kernel_config.target_arch = Arch::X86_64;
462 } else if (config.find("\nCONFIG_X86=y") != std::string::npos) {
463 kernel_config.target_arch = Arch::X86;
464 } else {
465 return CF_ERR("Unknown target architecture");
466 }
467 kernel_config.bootconfig_supported =
468 config.find("\nCONFIG_BOOT_CONFIG=y") != std::string::npos;
469
470 unlink(ikconfig_path.c_str());
471 return kernel_config;
472 }
473 #endif // #ifdef __ANDROID__
474
475 } // namespace
476
InitializeCuttlefishConfiguration(const std::string & root_dir,int modem_simulator_count,KernelConfig kernel_config,fruit::Injector<> & injector)477 CuttlefishConfig InitializeCuttlefishConfiguration(
478 const std::string& root_dir, int modem_simulator_count,
479 KernelConfig kernel_config, fruit::Injector<>& injector) {
480 CuttlefishConfig tmp_config_obj;
481
482 for (const auto& fragment : injector.getMultibindings<ConfigFragment>()) {
483 CHECK(tmp_config_obj.SaveFragment(*fragment))
484 << "Failed to save fragment " << fragment->Name();
485 }
486
487 tmp_config_obj.set_root_dir(root_dir);
488
489 tmp_config_obj.set_target_arch(kernel_config.target_arch);
490 tmp_config_obj.set_bootconfig_supported(kernel_config.bootconfig_supported);
491 auto vmm = GetVmManager(FLAGS_vm_manager, kernel_config.target_arch);
492 if (!vmm) {
493 LOG(FATAL) << "Invalid vm_manager: " << FLAGS_vm_manager;
494 }
495 tmp_config_obj.set_vm_manager(FLAGS_vm_manager);
496
497 std::vector<CuttlefishConfig::DisplayConfig> display_configs;
498
499 auto display0 = ParseDisplayConfig(FLAGS_display0);
500 if (display0) {
501 display_configs.push_back(*display0);
502 }
503 auto display1 = ParseDisplayConfig(FLAGS_display1);
504 if (display1) {
505 display_configs.push_back(*display1);
506 }
507 auto display2 = ParseDisplayConfig(FLAGS_display2);
508 if (display2) {
509 display_configs.push_back(*display2);
510 }
511 auto display3 = ParseDisplayConfig(FLAGS_display3);
512 if (display3) {
513 display_configs.push_back(*display3);
514 }
515
516 if (FLAGS_x_res > 0 && FLAGS_y_res > 0) {
517 if (display_configs.empty()) {
518 display_configs.push_back({
519 .width = FLAGS_x_res,
520 .height = FLAGS_y_res,
521 .dpi = FLAGS_dpi,
522 .refresh_rate_hz = FLAGS_refresh_rate_hz,
523 });
524 } else {
525 LOG(WARNING) << "Ignoring --x_res and --y_res when --displayN specified.";
526 }
527 }
528
529 tmp_config_obj.set_display_configs(display_configs);
530
531 const GraphicsAvailability graphics_availability =
532 GetGraphicsAvailabilityWithSubprocessCheck();
533
534 LOG(DEBUG) << graphics_availability;
535
536 tmp_config_obj.set_gpu_mode(FLAGS_gpu_mode);
537 if (tmp_config_obj.gpu_mode() != kGpuModeAuto &&
538 tmp_config_obj.gpu_mode() != kGpuModeDrmVirgl &&
539 tmp_config_obj.gpu_mode() != kGpuModeGfxStream &&
540 tmp_config_obj.gpu_mode() != kGpuModeGuestSwiftshader) {
541 LOG(FATAL) << "Invalid gpu_mode: " << FLAGS_gpu_mode;
542 }
543 if (tmp_config_obj.gpu_mode() == kGpuModeAuto) {
544 if (ShouldEnableAcceleratedRendering(graphics_availability)) {
545 LOG(INFO) << "GPU auto mode: detected prerequisites for accelerated "
546 "rendering support.";
547 if (FLAGS_vm_manager == QemuManager::name()) {
548 LOG(INFO) << "Enabling --gpu_mode=drm_virgl.";
549 tmp_config_obj.set_gpu_mode(kGpuModeDrmVirgl);
550 } else {
551 LOG(INFO) << "Enabling --gpu_mode=gfxstream.";
552 tmp_config_obj.set_gpu_mode(kGpuModeGfxStream);
553 }
554 } else {
555 LOG(INFO) << "GPU auto mode: did not detect prerequisites for "
556 "accelerated rendering support, enabling "
557 "--gpu_mode=guest_swiftshader.";
558 tmp_config_obj.set_gpu_mode(kGpuModeGuestSwiftshader);
559 }
560 } else if (tmp_config_obj.gpu_mode() == kGpuModeGfxStream ||
561 tmp_config_obj.gpu_mode() == kGpuModeDrmVirgl) {
562 if (!ShouldEnableAcceleratedRendering(graphics_availability)) {
563 LOG(ERROR) << "--gpu_mode="
564 << tmp_config_obj.gpu_mode()
565 << " was requested but the prerequisites for accelerated "
566 "rendering were not detected so the device may not "
567 "function correctly. Please consider switching to "
568 "--gpu_mode=auto or --gpu_mode=guest_swiftshader.";
569 }
570 }
571
572 tmp_config_obj.set_restart_subprocesses(FLAGS_restart_subprocesses);
573 tmp_config_obj.set_gpu_capture_binary(FLAGS_gpu_capture_binary);
574 if (!tmp_config_obj.gpu_capture_binary().empty()) {
575 CHECK(tmp_config_obj.gpu_mode() == kGpuModeGfxStream)
576 << "GPU capture only supported with --gpu_mode=gfxstream";
577
578 // GPU capture runs in a detached mode where the "launcher" process
579 // intentionally exits immediately.
580 CHECK(!tmp_config_obj.restart_subprocesses())
581 << "GPU capture only supported with --norestart_subprocesses";
582 }
583
584 tmp_config_obj.set_hwcomposer(FLAGS_hwcomposer);
585 if (!tmp_config_obj.hwcomposer().empty()) {
586 if (tmp_config_obj.hwcomposer() == kHwComposerRanchu) {
587 CHECK(tmp_config_obj.gpu_mode() != kGpuModeDrmVirgl)
588 << "ranchu hwcomposer not supported with --gpu_mode=drm_virgl";
589 }
590 }
591
592 if (tmp_config_obj.hwcomposer() == kHwComposerAuto) {
593 if (tmp_config_obj.gpu_mode() == kGpuModeDrmVirgl) {
594 tmp_config_obj.set_hwcomposer(kHwComposerDrm);
595 } else {
596 tmp_config_obj.set_hwcomposer(kHwComposerRanchu);
597 }
598 }
599
600 tmp_config_obj.set_enable_gpu_udmabuf(FLAGS_enable_gpu_udmabuf);
601 tmp_config_obj.set_enable_gpu_angle(FLAGS_enable_gpu_angle);
602
603 // Sepolicy rules need to be updated to support gpu mode. Temporarily disable
604 // auto-enabling sandbox when gpu is enabled (b/152323505).
605 if (tmp_config_obj.gpu_mode() != kGpuModeGuestSwiftshader) {
606 SetCommandLineOptionWithMode("enable_sandbox", "false", SET_FLAGS_DEFAULT);
607 }
608
609 if (vmm->ConfigureGraphics(tmp_config_obj).empty()) {
610 LOG(FATAL) << "Invalid (gpu_mode=," << FLAGS_gpu_mode <<
611 " hwcomposer= " << FLAGS_hwcomposer <<
612 ") does not work with vm_manager=" << FLAGS_vm_manager;
613 }
614
615 CHECK(!FLAGS_smt || FLAGS_cpus % 2 == 0)
616 << "CPUs must be a multiple of 2 in SMT mode";
617 tmp_config_obj.set_cpus(FLAGS_cpus);
618 tmp_config_obj.set_smt(FLAGS_smt);
619
620 tmp_config_obj.set_memory_mb(FLAGS_memory_mb);
621
622 tmp_config_obj.set_setupwizard_mode(FLAGS_setupwizard_mode);
623
624 auto secure_hals = android::base::Split(FLAGS_secure_hals, ",");
625 tmp_config_obj.set_secure_hals(
626 std::set<std::string>(secure_hals.begin(), secure_hals.end()));
627
628 tmp_config_obj.set_gdb_port(FLAGS_gdb_port);
629
630 tmp_config_obj.set_guest_enforce_security(FLAGS_guest_enforce_security);
631 tmp_config_obj.set_extra_kernel_cmdline(FLAGS_extra_kernel_cmdline);
632 tmp_config_obj.set_extra_bootconfig_args(FLAGS_extra_bootconfig_args);
633
634 if (FLAGS_console) {
635 SetCommandLineOptionWithMode("enable_sandbox", "false", SET_FLAGS_DEFAULT);
636 }
637
638 tmp_config_obj.set_console(FLAGS_console);
639 tmp_config_obj.set_kgdb(FLAGS_console && FLAGS_kgdb);
640
641 tmp_config_obj.set_host_tools_version(HostToolsCrc());
642
643 tmp_config_obj.set_deprecated_boot_completed(FLAGS_deprecated_boot_completed);
644
645 tmp_config_obj.set_qemu_binary_dir(FLAGS_qemu_binary_dir);
646 tmp_config_obj.set_crosvm_binary(FLAGS_crosvm_binary);
647 tmp_config_obj.set_gem5_binary_dir(FLAGS_gem5_binary_dir);
648
649 tmp_config_obj.set_seccomp_policy_dir(FLAGS_seccomp_policy_dir);
650
651 tmp_config_obj.set_enable_webrtc(FLAGS_start_webrtc);
652 tmp_config_obj.set_webrtc_assets_dir(FLAGS_webrtc_assets_dir);
653 tmp_config_obj.set_webrtc_certs_dir(FLAGS_webrtc_certs_dir);
654 tmp_config_obj.set_sig_server_secure(FLAGS_webrtc_sig_server_secure);
655 // Note: This will be overridden if the sig server is started by us
656 tmp_config_obj.set_sig_server_port(FLAGS_webrtc_sig_server_port);
657 tmp_config_obj.set_sig_server_address(FLAGS_webrtc_sig_server_addr);
658 tmp_config_obj.set_sig_server_path(FLAGS_webrtc_sig_server_path);
659 tmp_config_obj.set_sig_server_strict(FLAGS_verify_sig_server_certificate);
660 tmp_config_obj.set_sig_server_headers_path(FLAGS_sig_server_headers_file);
661
662 auto tcp_range = ParsePortRange(FLAGS_tcp_port_range);
663 tmp_config_obj.set_webrtc_tcp_port_range(tcp_range);
664 auto udp_range = ParsePortRange(FLAGS_udp_port_range);
665 tmp_config_obj.set_webrtc_udp_port_range(udp_range);
666
667 tmp_config_obj.set_enable_modem_simulator(FLAGS_enable_modem_simulator &&
668 !FLAGS_enable_minimal_mode);
669 tmp_config_obj.set_modem_simulator_instance_number(modem_simulator_count);
670 tmp_config_obj.set_modem_simulator_sim_type(FLAGS_modem_simulator_sim_type);
671
672 tmp_config_obj.set_webrtc_enable_adb_websocket(
673 FLAGS_webrtc_enable_adb_websocket);
674
675 tmp_config_obj.set_run_as_daemon(FLAGS_daemon);
676
677 tmp_config_obj.set_data_policy(FLAGS_data_policy);
678 tmp_config_obj.set_blank_data_image_mb(FLAGS_blank_data_image_mb);
679
680 tmp_config_obj.set_enable_gnss_grpc_proxy(FLAGS_start_gnss_proxy);
681
682 tmp_config_obj.set_enable_vehicle_hal_grpc_server(
683 FLAGS_enable_vehicle_hal_grpc_server);
684
685 tmp_config_obj.set_bootloader(FLAGS_bootloader);
686
687 tmp_config_obj.set_enable_metrics(FLAGS_report_anonymous_usage_stats);
688
689 if (!FLAGS_boot_slot.empty()) {
690 tmp_config_obj.set_boot_slot(FLAGS_boot_slot);
691 }
692
693 tmp_config_obj.set_cuttlefish_env_path(GetCuttlefishEnvPath());
694
695 tmp_config_obj.set_ril_dns(FLAGS_ril_dns);
696
697 tmp_config_obj.set_enable_minimal_mode(FLAGS_enable_minimal_mode);
698
699 tmp_config_obj.set_vhost_net(FLAGS_vhost_net);
700
701 tmp_config_obj.set_vhost_user_mac80211_hwsim(FLAGS_vhost_user_mac80211_hwsim);
702
703 if ((FLAGS_ap_rootfs_image.empty()) != (FLAGS_ap_kernel_image.empty())) {
704 LOG(FATAL) << "Either both ap_rootfs_image and ap_kernel_image should be "
705 "set or neither should be set.";
706 }
707
708 tmp_config_obj.set_ap_rootfs_image(FLAGS_ap_rootfs_image);
709 tmp_config_obj.set_ap_kernel_image(FLAGS_ap_kernel_image);
710
711 tmp_config_obj.set_wmediumd_config(FLAGS_wmediumd_config);
712
713 tmp_config_obj.set_rootcanal_hci_port(7300);
714 tmp_config_obj.set_rootcanal_link_port(7400);
715 tmp_config_obj.set_rootcanal_test_port(7500);
716 tmp_config_obj.set_rootcanal_config_file(
717 FLAGS_bluetooth_controller_properties_file);
718 tmp_config_obj.set_rootcanal_default_commands_file(
719 FLAGS_bluetooth_default_commands_file);
720
721 tmp_config_obj.set_record_screen(FLAGS_record_screen);
722
723 tmp_config_obj.set_enable_host_bluetooth(FLAGS_enable_host_bluetooth);
724
725 tmp_config_obj.set_protected_vm(FLAGS_protected_vm);
726
727 tmp_config_obj.set_userdata_format(FLAGS_userdata_format);
728
729 std::vector<int> num_instances;
730 for (int i = 0; i < FLAGS_num_instances; i++) {
731 num_instances.push_back(GetInstance() + i);
732 }
733 std::vector<std::string> gnss_file_paths = android::base::Split(FLAGS_gnss_file_path, ",");
734
735 bool is_first_instance = true;
736 for (const auto& num : num_instances) {
737 IfaceConfig iface_config;
738 if (FLAGS_use_allocd) {
739 auto iface_opt = AllocateNetworkInterfaces();
740 if (!iface_opt.has_value()) {
741 LOG(FATAL) << "Failed to acquire network interfaces";
742 }
743 iface_config = iface_opt.value();
744 } else {
745 iface_config = DefaultNetworkInterfaces(num);
746 }
747
748 auto instance = tmp_config_obj.ForInstance(num);
749 auto const_instance =
750 const_cast<const CuttlefishConfig&>(tmp_config_obj).ForInstance(num);
751 instance.set_use_allocd(FLAGS_use_allocd);
752 if (FLAGS_use_random_serial) {
753 instance.set_serial_number(
754 RandomSerialNumber("CFCVD" + std::to_string(num)));
755 } else {
756 instance.set_serial_number(FLAGS_serial_number + std::to_string(num));
757 }
758 // call this before all stuff that has vsock server: e.g. touchpad, keyboard, etc
759 const auto vsock_guest_cid = FLAGS_vsock_guest_cid + num - GetInstance();
760 instance.set_vsock_guest_cid(vsock_guest_cid);
761 auto calc_vsock_port = [vsock_guest_cid](const int base_port) {
762 // a base (vsock) port is like 9600 for modem_simulator, etc
763 return cuttlefish::GetVsockServerPort(base_port, vsock_guest_cid);
764 };
765 instance.set_session_id(iface_config.mobile_tap.session_id);
766
767 instance.set_mobile_bridge_name(StrForInstance("cvd-mbr-", num));
768 instance.set_mobile_tap_name(iface_config.mobile_tap.name);
769 instance.set_wifi_tap_name(iface_config.wireless_tap.name);
770 instance.set_ethernet_tap_name(iface_config.ethernet_tap.name);
771
772 instance.set_uuid(FLAGS_uuid);
773
774 instance.set_modem_simulator_host_id(1000 + num); // Must be 4 digits
775 // the deprecated vnc was 6444 + num - 1, and qemu_vnc was vnc - 5900
776 instance.set_qemu_vnc_server_port(544 + num - 1);
777 instance.set_adb_host_port(6520 + num - 1);
778 instance.set_adb_ip_and_port("0.0.0.0:" + std::to_string(6520 + num - 1));
779 instance.set_confui_host_vsock_port(7700 + num - 1);
780 instance.set_tombstone_receiver_port(calc_vsock_port(6600));
781 instance.set_vehicle_hal_server_port(9300 + num - 1);
782 instance.set_audiocontrol_server_port(9410); /* OK to use the same port number across instances */
783 instance.set_config_server_port(calc_vsock_port(6800));
784
785 if (tmp_config_obj.gpu_mode() != kGpuModeDrmVirgl &&
786 tmp_config_obj.gpu_mode() != kGpuModeGfxStream) {
787 if (FLAGS_vm_manager == QemuManager::name()) {
788 instance.set_keyboard_server_port(calc_vsock_port(7000));
789 instance.set_touch_server_port(calc_vsock_port(7100));
790 }
791 }
792
793 instance.set_gnss_grpc_proxy_server_port(7200 + num -1);
794
795 if (num <= gnss_file_paths.size()) {
796 instance.set_gnss_file_path(gnss_file_paths[num-1]);
797 }
798
799 instance.set_camera_server_port(FLAGS_camera_server_port);
800
801 if (FLAGS_protected_vm) {
802 instance.set_virtual_disk_paths(
803 {const_instance.PerInstancePath("os_composite.img")});
804 } else {
805 std::vector<std::string> virtual_disk_paths = {
806 const_instance.PerInstancePath("persistent_composite.img"),
807 };
808 if (FLAGS_vm_manager != Gem5Manager::name()) {
809 virtual_disk_paths.insert(virtual_disk_paths.begin(),
810 const_instance.PerInstancePath("overlay.img"));
811 } else {
812 // Gem5 already uses CoW wrappers around disk images
813 virtual_disk_paths.insert(virtual_disk_paths.begin(),
814 tmp_config_obj.os_composite_disk_path());
815 }
816 if (FLAGS_use_sdcard) {
817 virtual_disk_paths.push_back(const_instance.sdcard_path());
818 }
819 instance.set_virtual_disk_paths(virtual_disk_paths);
820 }
821
822 // We'd like to set mac prefix to be 5554, 5555, 5556, ... in normal cases.
823 // When --base_instance_num=3, this might be 5556, 5557, 5558, ... (skipping
824 // first two)
825 instance.set_wifi_mac_prefix(5554 + (num - 1));
826
827 instance.set_start_webrtc_signaling_server(false);
828
829 if (FLAGS_webrtc_device_id.empty()) {
830 // Use the instance's name as a default
831 instance.set_webrtc_device_id(const_instance.instance_name());
832 } else {
833 std::string device_id = FLAGS_webrtc_device_id;
834 size_t pos;
835 while ((pos = device_id.find("{num}")) != std::string::npos) {
836 device_id.replace(pos, strlen("{num}"), std::to_string(num));
837 }
838 instance.set_webrtc_device_id(device_id);
839 }
840 if (!is_first_instance || !FLAGS_start_webrtc) {
841 // Only the first instance starts the signaling server or proxy
842 instance.set_start_webrtc_signaling_server(false);
843 instance.set_start_webrtc_sig_server_proxy(false);
844 } else {
845 auto port = 8443 + num - 1;
846 // Change the signaling server port for all instances
847 tmp_config_obj.set_sig_server_port(port);
848 // Either the signaling server or the proxy is started, never both
849 instance.set_start_webrtc_signaling_server(FLAGS_start_webrtc_sig_server);
850 // The proxy is only started if the host operator is available
851 instance.set_start_webrtc_sig_server_proxy(
852 cuttlefish::FileIsSocket(HOST_OPERATOR_SOCKET_PATH) &&
853 !FLAGS_start_webrtc_sig_server);
854 }
855
856 // Start wmediumd process for the first instance if
857 // vhost_user_mac80211_hwsim is not specified.
858 const bool start_wmediumd =
859 FLAGS_vhost_user_mac80211_hwsim.empty() && is_first_instance;
860 if (start_wmediumd) {
861 // TODO(b/199020470) move this to the directory for shared resources
862 auto vhost_user_socket_path =
863 const_instance.PerInstanceInternalPath("vhost_user_mac80211");
864 auto wmediumd_api_socket_path =
865 const_instance.PerInstanceInternalPath("wmediumd_api_server");
866
867 tmp_config_obj.set_vhost_user_mac80211_hwsim(vhost_user_socket_path);
868 tmp_config_obj.set_wmediumd_api_server_socket(wmediumd_api_socket_path);
869 instance.set_start_wmediumd(true);
870 } else {
871 instance.set_start_wmediumd(false);
872 }
873
874 instance.set_start_rootcanal(is_first_instance);
875
876 instance.set_start_ap(!FLAGS_ap_rootfs_image.empty() &&
877 !FLAGS_ap_kernel_image.empty() && is_first_instance);
878
879 is_first_instance = false;
880
881 // instance.modem_simulator_ports := "" or "[port,]*port"
882 if (modem_simulator_count > 0) {
883 std::stringstream modem_ports;
884 for (auto index {0}; index < modem_simulator_count - 1; index++) {
885 auto port = 9600 + (modem_simulator_count * (num - 1)) + index;
886 modem_ports << calc_vsock_port(port) << ",";
887 }
888 auto port = 9600 + (modem_simulator_count * (num - 1)) +
889 modem_simulator_count - 1;
890 modem_ports << calc_vsock_port(port);
891 instance.set_modem_simulator_ports(modem_ports.str());
892 } else {
893 instance.set_modem_simulator_ports("");
894 }
895 } // end of num_instances loop
896
897 std::vector<std::string> names;
898 for (const auto& instance : tmp_config_obj.Instances()) {
899 names.emplace_back(instance.instance_name());
900 }
901 tmp_config_obj.set_instance_names(names);
902
903 tmp_config_obj.set_enable_sandbox(FLAGS_enable_sandbox);
904
905 // Audio is not available for Arm64
906 SetCommandLineOptionWithMode(
907 "enable_audio",
908 (cuttlefish::HostArch() == cuttlefish::Arch::Arm64) ? "false" : "true",
909 SET_FLAGS_DEFAULT);
910 tmp_config_obj.set_enable_audio(FLAGS_enable_audio);
911
912 return tmp_config_obj;
913 }
914
SetDefaultFlagsForQemu(Arch target_arch)915 void SetDefaultFlagsForQemu(Arch target_arch) {
916 // for now, we don't set non-default options for QEMU
917 if (FLAGS_gpu_mode == kGpuModeGuestSwiftshader && !FLAGS_start_webrtc) {
918 // This makes WebRTC the default streamer unless the user requests
919 // another via a --star_<streamer> flag, while at the same time it's
920 // possible to run without any streamer by setting --start_webrtc=false.
921 SetCommandLineOptionWithMode("start_webrtc", "true", SET_FLAGS_DEFAULT);
922 }
923 std::string default_bootloader =
924 DefaultHostArtifactsPath("etc/bootloader_");
925 if(target_arch == Arch::Arm) {
926 // Bootloader is unstable >512MB RAM on 32-bit ARM
927 SetCommandLineOptionWithMode("memory_mb", "512", SET_FLAGS_VALUE);
928 default_bootloader += "arm";
929 } else if (target_arch == Arch::Arm64) {
930 default_bootloader += "aarch64";
931 } else {
932 default_bootloader += "x86_64";
933 }
934 default_bootloader += "/bootloader.qemu";
935 SetCommandLineOptionWithMode("bootloader", default_bootloader.c_str(),
936 SET_FLAGS_DEFAULT);
937 }
938
SetDefaultFlagsForCrosvm()939 void SetDefaultFlagsForCrosvm() {
940 if (!FLAGS_start_webrtc) {
941 // This makes WebRTC the default streamer unless the user requests
942 // another via a --star_<streamer> flag, while at the same time it's
943 // possible to run without any streamer by setting --start_webrtc=false.
944 SetCommandLineOptionWithMode("start_webrtc", "true", SET_FLAGS_DEFAULT);
945 }
946
947 std::set<Arch> supported_archs{Arch::X86_64};
948 bool default_enable_sandbox =
949 supported_archs.find(HostArch()) != supported_archs.end() &&
950 EnsureDirectoryExists(kCrosvmVarEmptyDir).ok() &&
951 IsDirectoryEmpty(kCrosvmVarEmptyDir) && !IsRunningInContainer();
952 SetCommandLineOptionWithMode("enable_sandbox",
953 (default_enable_sandbox ? "true" : "false"),
954 SET_FLAGS_DEFAULT);
955
956 std::string default_bootloader = FLAGS_system_image_dir + "/bootloader";
957 SetCommandLineOptionWithMode("bootloader", default_bootloader.c_str(),
958 SET_FLAGS_DEFAULT);
959 }
960
SetDefaultFlagsForGem5()961 void SetDefaultFlagsForGem5() {
962 // TODO: Add support for gem5 gpu models
963 SetCommandLineOptionWithMode("gpu_mode", kGpuModeGuestSwiftshader,
964 SET_FLAGS_DEFAULT);
965
966 SetCommandLineOptionWithMode("cpus", "1", SET_FLAGS_DEFAULT);
967 }
968
GetKernelConfigAndSetDefaults()969 Result<KernelConfig> GetKernelConfigAndSetDefaults() {
970 CF_EXPECT(ResolveInstanceFiles(), "Failed to resolve instance files");
971
972 KernelConfig kernel_config = CF_EXPECT(ReadKernelConfig());
973
974 if (FLAGS_vm_manager == "") {
975 if (IsHostCompatible(kernel_config.target_arch)) {
976 FLAGS_vm_manager = CrosvmManager::name();
977 } else {
978 FLAGS_vm_manager = QemuManager::name();
979 }
980 }
981
982 if (FLAGS_vm_manager == QemuManager::name()) {
983 SetDefaultFlagsForQemu(kernel_config.target_arch);
984 } else if (FLAGS_vm_manager == CrosvmManager::name()) {
985 SetDefaultFlagsForCrosvm();
986 } else if (FLAGS_vm_manager == Gem5Manager::name()) {
987 // TODO: Get the other architectures working
988 if (kernel_config.target_arch != Arch::Arm64) {
989 return CF_ERR("Gem5 only supports ARM64");
990 }
991 SetDefaultFlagsForGem5();
992 } else {
993 return CF_ERR("Unknown Virtual Machine Manager: " << FLAGS_vm_manager);
994 }
995 if (FLAGS_vm_manager != Gem5Manager::name()) {
996 auto host_operator_present =
997 cuttlefish::FileIsSocket(HOST_OPERATOR_SOCKET_PATH);
998 // The default for starting signaling server depends on whether or not webrtc
999 // is to be started and the presence of the host orchestrator.
1000 SetCommandLineOptionWithMode(
1001 "start_webrtc_sig_server",
1002 FLAGS_start_webrtc && !host_operator_present ? "true" : "false",
1003 SET_FLAGS_DEFAULT);
1004 SetCommandLineOptionWithMode(
1005 "webrtc_sig_server_addr",
1006 host_operator_present ? HOST_OPERATOR_SOCKET_PATH : "0.0.0.0",
1007 SET_FLAGS_DEFAULT);
1008 }
1009 // Set the env variable to empty (in case the caller passed a value for it).
1010 unsetenv(kCuttlefishConfigEnvVarName);
1011
1012 return kernel_config;
1013 }
1014
GetConfigFilePath(const CuttlefishConfig & config)1015 std::string GetConfigFilePath(const CuttlefishConfig& config) {
1016 return config.AssemblyPath("cuttlefish_config.json");
1017 }
1018
GetCuttlefishEnvPath()1019 std::string GetCuttlefishEnvPath() {
1020 return StringFromEnv("HOME", ".") + "/.cuttlefish.sh";
1021 }
1022
1023 } // namespace cuttlefish
1024