• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //
2 // Copyright (C) 2019 The Android Open Source Project
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 //      http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 
16 #include <iostream>
17 #include <iterator>
18 #include <string>
19 
20 #include <sys/stat.h>
21 #include <unistd.h>
22 
23 #include "android-base/logging.h"
24 #include "android-base/strings.h"
25 #include "gflags/gflags.h"
26 
27 #include "common/libs/fs/shared_fd.h"
28 #include "common/libs/utils/archive.h"
29 #include "common/libs/utils/files.h"
30 #include "common/libs/utils/subprocess.h"
31 
32 #include "host/libs/config/fetcher_config.h"
33 
34 #include "build_api.h"
35 #include "credential_source.h"
36 #include "install_zip.h"
37 
38 namespace {
39 
40 const std::string DEFAULT_BRANCH = "aosp-master";
41 const std::string DEFAULT_BUILD_TARGET = "aosp_cf_x86_64_phone-userdebug";
42 }
43 
44 using cuttlefish::CurrentDirectory;
45 
46 DEFINE_string(default_build, DEFAULT_BRANCH + "/" + DEFAULT_BUILD_TARGET,
47               "source for the cuttlefish build to use (vendor.img + host)");
48 DEFINE_string(system_build, "", "source for system.img and product.img");
49 DEFINE_string(kernel_build, "", "source for the kernel or gki target");
50 DEFINE_string(bootloader_build, "", "source for the bootloader target");
51 DEFINE_string(otatools_build, "", "source for the host ota tools");
52 
53 DEFINE_bool(download_img_zip, true, "Whether to fetch the -img-*.zip file.");
54 DEFINE_bool(download_target_files_zip, false, "Whether to fetch the "
55                                               "-target_files-*.zip file.");
56 
57 DEFINE_string(credential_source, "", "Build API credential source");
58 DEFINE_string(directory, CurrentDirectory(), "Target directory to fetch "
59                                              "files into");
60 DEFINE_bool(run_next_stage, false, "Continue running the device through the next stage.");
61 DEFINE_string(wait_retry_period, "20", "Retry period for pending builds given "
62                                        "in seconds. Set to 0 to not wait.");
63 
64 namespace cuttlefish {
65 namespace {
66 
67 const std::string HOST_TOOLS = "cvd-host_package.tar.gz";
68 const std::string OTA_TOOLS = "otatools.zip";
69 const std::string OTA_TOOLS_DIR = "/otatools/";
70 
71 /** Returns the name of one of the artifact target zip files.
72  *
73  * For example, for a target "aosp_cf_x86_phone-userdebug" at a build "5824130",
74  * the image zip file would be "aosp_cf_x86_phone-img-5824130.zip"
75  */
TargetBuildZipFromArtifacts(const Build & build,const std::string & name,const std::vector<Artifact> & artifacts)76 std::string TargetBuildZipFromArtifacts(
77     const Build& build, const std::string& name,
78     const std::vector<Artifact>& artifacts) {
79   std::string product = std::visit([](auto&& arg) { return arg.product; }, build);
80   auto id = std::visit([](auto&& arg) { return arg.id; }, build);
81   auto match = product + "-" + name + "-" + id;
82   for (const auto& artifact : artifacts) {
83     if (artifact.Name().find(match) != std::string::npos) {
84       return artifact.Name();
85     }
86   }
87   return "";
88 }
89 
download_images(BuildApi * build_api,const Build & build,const std::string & target_directory,const std::vector<std::string> & images)90 std::vector<std::string> download_images(BuildApi* build_api,
91                                          const Build& build,
92                                          const std::string& target_directory,
93                                          const std::vector<std::string>& images) {
94   auto artifacts = build_api->Artifacts(build);
95   std::string img_zip_name = TargetBuildZipFromArtifacts(build, "img", artifacts);
96   if (img_zip_name.size() == 0) {
97     LOG(ERROR) << "Target " << build << " did not have an img zip";
98     return {};
99   }
100   std::string local_path = target_directory + "/" + img_zip_name;
101   if (!build_api->ArtifactToFile(build, img_zip_name, local_path)) {
102     LOG(ERROR) << "Unable to download " << build << ":" << img_zip_name << " to "
103                << local_path;
104     return {};
105   }
106 
107   std::vector<std::string> files = ExtractImages(local_path, target_directory, images);
108   if (files.empty()) {
109     LOG(ERROR) << "Could not extract " << local_path;
110     return {};
111   }
112   if (unlink(local_path.c_str()) != 0) {
113     LOG(ERROR) << "Could not delete " << local_path;
114     files.push_back(local_path);
115   }
116   return files;
117 }
download_images(BuildApi * build_api,const Build & build,const std::string & target_directory)118 std::vector<std::string> download_images(BuildApi* build_api,
119                                          const Build& build,
120                                          const std::string& target_directory) {
121   return download_images(build_api, build, target_directory, {});
122 }
123 
download_target_files(BuildApi * build_api,const Build & build,const std::string & target_directory)124 std::vector<std::string> download_target_files(BuildApi* build_api,
125                                                const Build& build,
126                                                const std::string& target_directory) {
127   auto artifacts = build_api->Artifacts(build);
128   std::string target_zip = TargetBuildZipFromArtifacts(build, "target_files", artifacts);
129   if (target_zip.size() == 0) {
130     LOG(ERROR) << "Target " << build << " did not have a target files zip";
131     return {};
132   }
133   std::string local_path = target_directory + "/" + target_zip;
134   if (!build_api->ArtifactToFile(build, target_zip, local_path)) {
135     LOG(ERROR) << "Unable to download " << build << ":" << target_zip << " to "
136                << local_path;
137     return {};
138   }
139   return {local_path};
140 }
141 
download_host_package(BuildApi * build_api,const Build & build,const std::string & target_directory)142 std::vector<std::string> download_host_package(BuildApi* build_api,
143                                                const Build& build,
144                                                const std::string& target_directory) {
145   auto artifacts = build_api->Artifacts(build);
146   bool has_host_package = false;
147   for (const auto& artifact : artifacts) {
148     has_host_package |= artifact.Name() == HOST_TOOLS;
149   }
150   if (!has_host_package) {
151     LOG(ERROR) << "Target " << build << " did not have " << HOST_TOOLS;
152     return {};
153   }
154   std::string local_path = target_directory + "/" + HOST_TOOLS;
155 
156   if (!build_api->ArtifactToFile(build, HOST_TOOLS, local_path)) {
157     LOG(ERROR) << "Unable to download " << build << ":" << HOST_TOOLS << " to "
158                << local_path;
159     return {};
160   }
161 
162   Archive archive(local_path);
163   if (!archive.ExtractAll(target_directory)) {
164     LOG(ERROR) << "Could not extract " << local_path;
165     return {};
166   }
167   std::vector<std::string> files = archive.Contents();
168   for (auto& file : files) {
169     file = target_directory + "/" + file;
170   }
171   if (unlink(local_path.c_str()) != 0) {
172     LOG(ERROR) << "Could not delete " << local_path;
173     files.push_back(local_path);
174   }
175   return files;
176 }
177 
download_ota_tools(BuildApi * build_api,const Build & build,const std::string & target_directory)178 std::vector<std::string> download_ota_tools(BuildApi* build_api,
179                                             const Build& build,
180                                             const std::string& target_directory) {
181   auto artifacts = build_api->Artifacts(build);
182   bool has_host_package = false;
183   for (const auto& artifact : artifacts) {
184     has_host_package |= artifact.Name() == OTA_TOOLS;
185   }
186   if (!has_host_package) {
187     LOG(ERROR) << "Target " << build << " did not have " << OTA_TOOLS;
188     return {};
189   }
190   std::string local_path = target_directory + "/" + OTA_TOOLS;
191 
192   if (!build_api->ArtifactToFile(build, OTA_TOOLS, local_path)) {
193     LOG(ERROR) << "Unable to download " << build << ":" << OTA_TOOLS << " to "
194         << local_path;
195     return {};
196   }
197 
198   std::string otatools_dir = target_directory + OTA_TOOLS_DIR;
199   if (!DirectoryExists(otatools_dir) && mkdir(otatools_dir.c_str(), 0777) != 0) {
200     LOG(ERROR) << "Could not create " << otatools_dir;
201     return {};
202   }
203   Archive archive(local_path);
204   if (!archive.ExtractAll(otatools_dir)) {
205     LOG(ERROR) << "Could not extract " << local_path;
206     return {};
207   }
208   std::vector<std::string> files = archive.Contents();
209   for (auto& file : files) {
210     file = target_directory + OTA_TOOLS_DIR + file;
211   }
212   files.push_back(local_path);
213   return files;
214 }
215 
AddFilesToConfig(FileSource purpose,const Build & build,const std::vector<std::string> & paths,FetcherConfig * config,const std::string & directory_prefix,bool override_entry=false)216 void AddFilesToConfig(FileSource purpose, const Build& build,
217                       const std::vector<std::string>& paths,
218                       FetcherConfig* config,
219                       const std::string& directory_prefix,
220                       bool override_entry = false) {
221   for (const std::string& path : paths) {
222     std::string_view local_path(path);
223     if (!android::base::ConsumePrefix(&local_path, directory_prefix)) {
224       LOG(ERROR) << "Failed to remove prefix " << directory_prefix << " from "
225                  << local_path;
226     }
227     while (android::base::StartsWith(local_path, "/")) {
228       android::base::ConsumePrefix(&local_path, "/");
229     }
230     // TODO(schuffelen): Do better for local builds here.
231     auto id = std::visit([](auto&& arg) { return arg.id; }, build);
232     auto target = std::visit([](auto&& arg) { return arg.target; }, build);
233     CvdFile file(purpose, id, target, std::string(local_path));
234     bool added = config->add_cvd_file(file, override_entry);
235     if (!added) {
236       LOG(ERROR) << "Duplicate file " << file;
237       LOG(ERROR) << "Existing file: " << config->get_cvd_files()[path];
238       LOG(FATAL) << "Failed to add path " << path;
239     }
240   }
241 }
242 
243 std::string USAGE_MESSAGE =
244     "<flags>\n"
245     "\n"
246     "\"*_build\" flags accept values in the following format:\n"
247     "\"branch/build_target\" - latest build of \"branch\" for \"build_target\"\n"
248     "\"build_id/build_target\" - build \"build_id\" for \"build_target\"\n"
249     "\"branch\" - latest build of \"branch\" for \"aosp_cf_x86_phone-userdebug\"\n"
250     "\"build_id\" - build \"build_id\" for \"aosp_cf_x86_phone-userdebug\"\n";
251 
252 } // namespace
253 
FetchCvdMain(int argc,char ** argv)254 int FetchCvdMain(int argc, char** argv) {
255   ::android::base::InitLogging(argv, android::base::StderrLogger);
256   gflags::SetUsageMessage(USAGE_MESSAGE);
257   gflags::ParseCommandLineFlags(&argc, &argv, true);
258 
259   FetcherConfig config;
260   config.RecordFlags();
261 
262   std::string target_dir = AbsolutePath(FLAGS_directory);
263   if (!DirectoryExists(target_dir) && mkdir(target_dir.c_str(), 0777) != 0) {
264     LOG(FATAL) << "Could not create " << target_dir;
265   }
266   std::string target_dir_slash = target_dir;
267   std::chrono::seconds retry_period(std::stoi(FLAGS_wait_retry_period));
268 
269   curl_global_init(CURL_GLOBAL_DEFAULT);
270   {
271     std::unique_ptr<CredentialSource> credential_source;
272     if (FLAGS_credential_source == "gce") {
273       credential_source = GceMetadataCredentialSource::make();
274     } else if (FLAGS_credential_source != "") {
275       credential_source = FixedCredentialSource::make(FLAGS_credential_source);
276     }
277     BuildApi build_api(std::move(credential_source));
278 
279     auto default_build = ArgumentToBuild(&build_api, FLAGS_default_build,
280                                          DEFAULT_BUILD_TARGET,
281                                          retry_period);
282 
283     std::vector<std::string> host_package_files =
284         download_host_package(&build_api, default_build, target_dir);
285     if (host_package_files.empty()) {
286       LOG(FATAL) << "Could not download host package for " << default_build;
287     }
288     AddFilesToConfig(FileSource::DEFAULT_BUILD, default_build,
289                      host_package_files, &config, target_dir);
290 
291     if (FLAGS_system_build != "" || FLAGS_kernel_build != "" || FLAGS_otatools_build != "") {
292       auto ota_build = default_build;
293       if (FLAGS_otatools_build != "") {
294         ota_build = ArgumentToBuild(&build_api, FLAGS_otatools_build,
295                                     DEFAULT_BUILD_TARGET, retry_period);
296       }
297       std::vector<std::string> ota_tools_files =
298           download_ota_tools(&build_api, ota_build, target_dir);
299       if (ota_tools_files.empty()) {
300         LOG(FATAL) << "Could not download ota tools for " << ota_build;
301       }
302       AddFilesToConfig(FileSource::DEFAULT_BUILD, default_build,
303                        ota_tools_files, &config, target_dir);
304     }
305     if (FLAGS_download_img_zip) {
306       std::vector<std::string> image_files =
307           download_images(&build_api, default_build, target_dir);
308       if (image_files.empty()) {
309         LOG(FATAL) << "Could not download images for " << default_build;
310       }
311       LOG(INFO) << "Adding img-zip files for default build";
312       for (auto& file : image_files) {
313         LOG(INFO) << file;
314       }
315       AddFilesToConfig(FileSource::DEFAULT_BUILD, default_build, image_files,
316                        &config, target_dir);
317     }
318     if (FLAGS_system_build != "" || FLAGS_download_target_files_zip) {
319       std::string default_target_dir = target_dir + "/default";
320       if (mkdir(default_target_dir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) < 0) {
321         LOG(FATAL) << "Could not create " << default_target_dir;
322       }
323       std::vector<std::string> target_files =
324           download_target_files(&build_api, default_build, default_target_dir);
325       if (target_files.empty()) {
326         LOG(FATAL) << "Could not download target files for " << default_build;
327       }
328       LOG(INFO) << "Adding target files for default build";
329       AddFilesToConfig(FileSource::DEFAULT_BUILD, default_build, target_files,
330                        &config, target_dir);
331     }
332 
333     if (FLAGS_system_build != "") {
334       auto system_build = ArgumentToBuild(&build_api, FLAGS_system_build,
335                                           DEFAULT_BUILD_TARGET,
336                                           retry_period);
337       bool system_in_img_zip = true;
338       if (FLAGS_download_img_zip) {
339         std::vector<std::string> image_files =
340             download_images(&build_api, system_build, target_dir,
341                             {"system.img", "product.img"});
342         if (image_files.empty()) {
343           LOG(INFO) << "Could not find system image for " << system_build
344                     << "in the img zip. Assuming a super image build, which will "
345                     << "get the system image from the target zip.";
346           system_in_img_zip = false;
347         } else {
348           LOG(INFO) << "Adding img-zip files for system build";
349           AddFilesToConfig(FileSource::SYSTEM_BUILD, system_build, image_files,
350                            &config, target_dir, true);
351         }
352       }
353       std::string system_target_dir = target_dir + "/system";
354       if (mkdir(system_target_dir.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH) < 0) {
355         LOG(FATAL) << "Could not create " << system_target_dir;
356       }
357       std::vector<std::string> target_files =
358           download_target_files(&build_api, system_build, system_target_dir);
359       if (target_files.empty()) {
360         LOG(FATAL) << "Could not download target files for " << system_build;
361         return -1;
362       }
363       AddFilesToConfig(FileSource::SYSTEM_BUILD, system_build, target_files,
364                        &config, target_dir);
365       if (!system_in_img_zip) {
366         if (ExtractImages(target_files[0], target_dir, {"IMAGES/system.img"})
367             != std::vector<std::string>{}) {
368           std::string extracted_system = target_dir + "/IMAGES/system.img";
369           std::string target_system = target_dir + "/system.img";
370           if (rename(extracted_system.c_str(), target_system.c_str())) {
371             int error_num = errno;
372             LOG(FATAL) << "Could not replace system.img in target directory: "
373                        << strerror(error_num);
374             return -1;
375           }
376 	} else {
377           LOG(FATAL) << "Could not get system.img from the target zip";
378           return -1;
379         }
380 	if (ExtractImages(target_files[0], target_dir, {"IMAGES/product.img"})
381 	  != std::vector<std::string>{}) {
382           std::string extracted_product = target_dir + "/IMAGES/product.img";
383           std::string target_product = target_dir + "/product.img";
384           if (rename(extracted_product.c_str(), target_product.c_str())) {
385             int error_num = errno;
386             LOG(FATAL) << "Could not replace product.img in target directory"
387                        << strerror(error_num);
388             return -1;
389           }
390 	}
391         if (ExtractImages(target_files[0], target_dir, {"IMAGES/system_ext.img"})
392             != std::vector<std::string>{}) {
393           std::string extracted_system_ext = target_dir + "/IMAGES/system_ext.img";
394           std::string target_system_ext = target_dir + "/system_ext.img";
395           if (rename(extracted_system_ext.c_str(), target_system_ext.c_str())) {
396             int error_num = errno;
397             LOG(FATAL) << "Could not move system_ext.img in target directory: "
398                        << strerror(error_num);
399             return -1;
400           }
401         }
402         if (ExtractImages(target_files[0], target_dir, {"IMAGES/vbmeta_system.img"})
403             != std::vector<std::string>{}) {
404           std::string extracted_vbmeta_system = target_dir + "/IMAGES/vbmeta_system.img";
405           std::string target_vbmeta_system = target_dir + "/vbmeta_system.img";
406           if (rename(extracted_vbmeta_system.c_str(), target_vbmeta_system.c_str())) {
407             int error_num = errno;
408             LOG(FATAL) << "Could not move vbmeta_system.img in target directory: "
409                        << strerror(error_num);
410             return -1;
411           }
412         }
413         // This should technically call AddFilesToConfig with the produced files,
414         // but it will conflict with the ones produced from the default system image
415         // and pie doesn't care about the produced file list anyway.
416       }
417     }
418 
419     if (FLAGS_kernel_build != "") {
420       auto kernel_build = ArgumentToBuild(&build_api, FLAGS_kernel_build,
421                                           "kernel", retry_period);
422 
423       std::string local_path = target_dir + "/kernel";
424       if (build_api.ArtifactToFile(kernel_build, "bzImage", local_path)) {
425         AddFilesToConfig(FileSource::KERNEL_BUILD, kernel_build, {local_path},
426                          &config, target_dir);
427       }
428       // If the kernel is from an arm/aarch64 build, the artifact will be called
429       // Image.
430       else if (build_api.ArtifactToFile(kernel_build, "Image", local_path)) {
431         AddFilesToConfig(FileSource::KERNEL_BUILD, kernel_build, {local_path},
432                          &config, target_dir);
433       } else {
434         LOG(FATAL) << "Could not download " << kernel_build << ":bzImage to "
435             << local_path;
436       }
437       std::vector<Artifact> kernel_artifacts = build_api.Artifacts(kernel_build);
438       for (const auto& artifact : kernel_artifacts) {
439         if (artifact.Name() != "initramfs.img") {
440           continue;
441         }
442         bool downloaded = build_api.ArtifactToFile(
443             kernel_build, "initramfs.img", target_dir + "/initramfs.img");
444         if (!downloaded) {
445           LOG(FATAL) << "Could not download " << kernel_build << ":initramfs.img to "
446                      << target_dir + "/initramfs.img";
447         }
448         AddFilesToConfig(FileSource::KERNEL_BUILD, kernel_build,
449                          {target_dir + "/initramfs.img"}, &config, target_dir);
450       }
451     }
452 
453     if (FLAGS_bootloader_build != "") {
454       auto bootloader_build = ArgumentToBuild(&build_api,
455                                               FLAGS_bootloader_build,
456                                               "u-boot_crosvm_x86_64",
457 					      retry_period);
458 
459       std::string local_path = target_dir + "/bootloader";
460       if (build_api.ArtifactToFile(bootloader_build, "u-boot.rom", local_path)) {
461         AddFilesToConfig(FileSource::BOOTLOADER_BUILD, bootloader_build,
462                          {local_path}, &config, target_dir, true);
463       }
464       // If the bootloader is from an arm/aarch64 build, the artifact will be of
465       // filetype bin.
466       else if (build_api.ArtifactToFile(bootloader_build, "u-boot.bin",
467                                         local_path)) {
468         AddFilesToConfig(FileSource::BOOTLOADER_BUILD, bootloader_build,
469                          {local_path}, &config, target_dir, true);
470       } else {
471         LOG(FATAL) << "Could not download " << bootloader_build << ":u-boot.rom to "
472             << local_path;
473       }
474     }
475   }
476   curl_global_cleanup();
477 
478   // Due to constraints of the build system, artifacts intentionally cannot determine
479   // their own build id. So it's unclear which build number fetch_cvd itself was built at.
480   // https://android.googlesource.com/platform/build/+/979c9f3/Changes.md#build_number
481   std::string fetcher_path = target_dir + "/fetcher_config.json";
482   AddFilesToConfig(GENERATED, DeviceBuild("", ""), {fetcher_path}, &config,
483                    target_dir);
484   config.SaveToFile(fetcher_path);
485 
486   for (const auto& file : config.get_cvd_files()) {
487     std::cout << target_dir << "/" << file.second.file_path << "\n";
488   }
489   std::cout << std::flush;
490 
491   if (!FLAGS_run_next_stage) {
492     return 0;
493   }
494 
495   // Ignore return code. We want to make sure there is no running instance,
496   // and stop_cvd will exit with an error code if there is already no running instance.
497   Command stop_cmd(target_dir + "/bin/stop_cvd");
498   stop_cmd.RedirectStdIO(Subprocess::StdIOChannel::kStdOut,
499                          Subprocess::StdIOChannel::kStdErr);
500   stop_cmd.Start().Wait();
501 
502   // gflags::ParseCommandLineFlags will remove fetch_cvd's flags from this.
503   // This depends the remove_flags argument (3rd) is "true".
504 
505   auto filelist_fd = SharedFD::MemfdCreate("files_list");
506   if (!filelist_fd->IsOpen()) {
507     LOG(FATAL) << "Unable to create temp file to write file list. "
508                << filelist_fd->StrError() << " (" << filelist_fd->GetErrno() << ")";
509   }
510 
511   for (const auto& file : config.get_cvd_files()) {
512     std::string file_entry = file.second.file_path + "\n";
513     auto chars_written = filelist_fd->Write(file_entry.c_str(), file_entry.size());
514     if (chars_written != file_entry.size()) {
515       LOG(FATAL) << "Unable to write entry to file list. Expected to write "
516                  << file_entry.size() << " but wrote " << chars_written << ". "
517                  << filelist_fd->StrError() << " (" << filelist_fd->GetErrno() << ")";
518     }
519   }
520   auto seek_result = filelist_fd->LSeek(0, SEEK_SET);
521   if (seek_result != 0) {
522     LOG(FATAL) << "Unable to seek on file list file. Expected 0, received " << seek_result
523                << filelist_fd->StrError() << " (" << filelist_fd->GetErrno() << ")";
524   }
525 
526   if (filelist_fd->UNMANAGED_Dup2(0) == -1) {
527     LOG(FATAL) << "Unable to set file list to stdin. "
528                << filelist_fd->StrError() << " (" << filelist_fd->GetErrno() << ")";
529   }
530 
531   // TODO(b/139199114): Go into assemble_cvd when the interface is stable and implemented.
532 
533   std::string next_stage = target_dir + "/bin/launch_cvd";
534   std::vector<const char*> next_stage_argv = {"launch_cvd"};
535   LOG(INFO) << "Running " << next_stage;
536   for (int i = 1; i < argc; i++) {
537     LOG(INFO) << argv[i];
538     next_stage_argv.push_back(argv[i]);
539   }
540   next_stage_argv.push_back(nullptr);
541   execv(next_stage.c_str(), const_cast<char* const*>(next_stage_argv.data()));
542   int error = errno;
543   LOG(FATAL) << "execv returned with errno " << error << ":" << strerror(error);
544 
545   return -1;
546 }
547 
548 } // namespace cuttlefish
549 
main(int argc,char ** argv)550 int main(int argc, char** argv) {
551   return cuttlefish::FetchCvdMain(argc, argv);
552 }
553