blob: fdd38a7940f1a0a9043dea55c1adad2db36022d0 [file] [log] [blame]
// Copyright 2014 The Android Open Source Project
//
// This software is licensed under the terms of the GNU General Public
// License version 2, as published by the Free Software Foundation, and
// may be copied, distributed, and modified under those terms.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// This source file implements emulator-arm64 and emulator64-arm64
// which are used to launch QEMU binaries located under
// $PROGRAM_DIR/qemu/<host>/qemu-system-aarch64<exe>
#include "android/base/containers/StringVector.h"
#include "android/base/files/PathUtils.h"
#include "android/base/Limits.h"
#include "android/base/Log.h"
#include "android/base/String.h"
#include "android/base/StringFormat.h"
#include "android/cmdline-option.h"
#include "android/globals.h"
#include "android/help.h"
#include "android/filesystems/ext4_utils.h"
#include "android/kernel/kernel_utils.h"
#include "android/main-common.h"
#include "android/utils/bufprint.h"
#include "android/utils/debug.h"
#include "android/utils/path.h"
#include "android/utils/stralloc.h"
#include "android/utils/win32_cmdline_quote.h"
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define STRINGIFY(x) _STRINGIFY(x)
#define _STRINGIFY(x) #x
#ifdef ANDROID_SDK_TOOLS_REVISION
# define VERSION_STRING STRINGIFY(ANDROID_SDK_TOOLS_REVISION)".0"
#else
# define VERSION_STRING "standalone"
#endif
#define D(...) do { if (VERBOSE_CHECK(init)) dprint(__VA_ARGS__); } while (0)
/* The execv() definition in older mingw is slightly bogus.
* It takes a second argument of type 'const char* const*'
* while POSIX mandates char** instead.
*
* To avoid compiler warnings, define the safe_execv macro
* to perform an explicit cast with mingw.
*/
#if defined(_WIN32) && !ANDROID_GCC_PREREQ(4,4)
# define safe_execv(_filepath,_argv) execv((_filepath),(const char* const*)(_argv))
#else
# define safe_execv(_filepath,_argv) execv((_filepath),(_argv))
#endif
using namespace android::base;
namespace {
// The host CPU architecture.
#ifdef __i386__
const char kHostArch[] = "x86";
#elif defined(__x86_64__)
const char kHostArch[] = "x86_64";
#else
#error "Your host CPU is not supported!"
#endif
// The host operating system name.
#ifdef __linux__
static const char kHostOs[] = "linux";
#elif defined(__APPLE__)
static const char kHostOs[] = "darwin";
#elif defined(_WIN32)
static const char kHostOs[] = "windows";
#endif
// The target CPU architecture.
const char kTargetArch[] = "aarch64";
String getNthParentDir(const char* path, size_t n) {
StringVector dir = PathUtils::decompose(path);
PathUtils::simplifyComponents(&dir);
if (dir.size() < n + 1U) {
return String("");
}
dir.resize(dir.size() - n);
return PathUtils::recompose(dir);
}
// Return the path of the QEMU executable
String getQemuExecutablePath(const char* programPath) {
StringVector path = PathUtils::decompose(programPath);
if (path.size() < 1) {
return String();
}
// Remove program from path.
path.resize(path.size() - 1U);
// Add sub-directories.
path.append(String("qemu"));
String host = kHostOs;
host += "-";
host += kHostArch;
path.append(host);
String qemuProgram = "qemu-system-";
qemuProgram += kTargetArch;
#ifdef _WIN32
qemuProgram += ".exe";
#endif
path.append(qemuProgram);
return PathUtils::recompose(path);
}
void emulator_help( void ) {
STRALLOC_DEFINE(out);
android_help_main(out);
printf("%.*s", out->n, out->s);
stralloc_reset(out);
exit(1);
}
/* TODO: Put in shared source file */
char* _getFullFilePath(const char* rootPath, const char* fileName) {
if (path_is_absolute(fileName)) {
return ASTRDUP(fileName);
} else {
char temp[PATH_MAX], *p=temp, *end=p+sizeof(temp);
p = bufprint(temp, end, "%s/%s", rootPath, fileName);
if (p >= end) {
return NULL;
}
return ASTRDUP(temp);
}
}
uint64_t _adjustPartitionSize(const char* description,
uint64_t imageBytes,
uint64_t defaultBytes,
int inAndroidBuild ) {
char temp[64];
unsigned imageMB;
unsigned defaultMB;
if (imageBytes <= defaultBytes)
return defaultBytes;
imageMB = convertBytesToMB(imageBytes);
defaultMB = convertBytesToMB(defaultBytes);
if (imageMB > defaultMB) {
snprintf(temp, sizeof temp, "(%d MB > %d MB)", imageMB, defaultMB);
} else {
snprintf(temp, sizeof temp, "(%" PRIu64 " bytes > %" PRIu64 " bytes)", imageBytes, defaultBytes);
}
if (inAndroidBuild) {
dwarning("%s partition size adjusted to match image file %s\n", description, temp);
}
return convertMBToBytes(imageMB);
}
bool android_op_wipe_data;
} // namespace
extern "C" int main(int argc, char **argv, char **envp) {
if (argc < 1) {
fprintf(stderr, "Invalid invokation (no program path)\n");
return 1;
}
AndroidOptions opts[1];
if (android_parse_options(&argc, &argv, opts) < 0) {
return 1;
}
// TODO(digit): This code is very similar to the one in main.c,
// refactor everything so that it fits into a single shared source
// file, if possible, with the least amount of dependencies.
while (argc-- > 1) {
const char* opt = (++argv)[0];
if(!strcmp(opt, "-qemu")) {
argc--;
argv++;
break;
}
if (!strcmp(opt, "-help")) {
emulator_help();
}
if (!strncmp(opt, "-help-",6)) {
STRALLOC_DEFINE(out);
opt += 6;
if (!strcmp(opt, "all")) {
android_help_all(out);
}
else if (android_help_for_option(opt, out) == 0) {
/* ok */
}
else if (android_help_for_topic(opt, out) == 0) {
/* ok */
}
if (out->n > 0) {
printf("\n%.*s", out->n, out->s);
exit(0);
}
fprintf(stderr, "unknown option: -help-%s\n", opt);
fprintf(stderr, "please use -help for a list of valid topics\n");
exit(1);
}
if (opt[0] == '-') {
fprintf(stderr, "unknown option: %s\n", opt);
fprintf(stderr, "please use -help for a list of valid options\n");
exit(1);
}
fprintf(stderr, "invalid command-line parameter: %s.\n", opt);
fprintf(stderr, "Hint: use '@foo' to launch a virtual device named 'foo'.\n");
fprintf(stderr, "please use -help for more information\n");
exit(1);
}
if (opts->version) {
printf("Android emulator version %s\n"
"Copyright (C) 2006-2011 The Android Open Source Project and many others.\n"
"This program is a derivative of the QEMU CPU emulator (www.qemu.org).\n\n",
#if defined ANDROID_BUILD_ID
VERSION_STRING " (build_id " STRINGIFY(ANDROID_BUILD_ID) ")" );
#else
VERSION_STRING);
#endif
printf(" This software is licensed under the terms of the GNU General Public\n"
" License version 2, as published by the Free Software Foundation, and\n"
" may be copied, distributed, and modified under those terms.\n\n"
" This program is distributed in the hope that it will be useful,\n"
" but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
" GNU General Public License for more details.\n\n");
exit(0);
}
sanitizeOptions(opts);
// Ignore snapshot storage here.
int inAndroidBuild = 0;
AvdInfo* avd = createAVD(opts, &inAndroidBuild);
// Ignore skin options here.
// Read hardware configuration, apply overrides from options.
AndroidHwConfig* hw = android_hw;
if (avdInfo_initHwConfig(avd, hw) < 0) {
derror("could not read hardware configuration ?");
exit(1);
}
/* Update CPU architecture for HW configs created from build dir. */
if (inAndroidBuild) {
#if defined(TARGET_ARM)
reassign_string(&android_hw->hw_cpu_arch, "arm");
#elif defined(TARGET_I386)
reassign_string(&android_hw->hw_cpu_arch, "x86");
#elif defined(TARGET_MIPS)
reassign_string(&android_hw->hw_cpu_arch, "mips");
#elif defined(TARGET_ARM64)
reassign_string(&android_hw->hw_cpu_arch, "arm64");
#endif
}
/* generate arguments for the underlying qemu main() */
{
char* kernelFile = opts->kernel;
if (kernelFile == NULL) {
kernelFile = avdInfo_getKernelPath(avd);
if (kernelFile == NULL) {
derror( "This AVD's configuration is missing a kernel file!!" );
exit(2);
}
D("autoconfig: -kernel %s", kernelFile);
}
if (!path_exists(kernelFile)) {
derror( "Invalid or missing kernel image file: %s", kernelFile );
exit(2);
}
hw->kernel_path = kernelFile;
}
char versionString[256];
if (!android_pathProbeKernelVersionString(hw->kernel_path,
versionString,
sizeof(versionString))) {
derror("Can't find 'Linux version ' string in kernel image file: %s",
hw->kernel_path);
exit(2);
}
KernelVersion kernelVersion;
if (!android_parseLinuxVersionString(versionString, &kernelVersion)) {
derror("Can't parse 'Linux version ' string in kernel image file: '%s'",
versionString);
exit(2);
}
// Auto-detect kernel device naming scheme if needed.
if (androidHwConfig_getKernelDeviceNaming(hw) < 0) {
const char* newDeviceNaming = "no";
if (kernelVersion >= KERNEL_VERSION_3_10_0) {
D("Auto-detect: Kernel image requires new device naming scheme.");
newDeviceNaming = "yes";
} else {
D("Auto-detect: Kernel image requires legacy device naming scheme.");
}
AFREE(hw->kernel_newDeviceNaming);
hw->kernel_newDeviceNaming = ASTRDUP(newDeviceNaming);
}
// Auto-detect YAFFS2 partition support if needed.
if (androidHwConfig_getKernelYaffs2Support(hw) < 0) {
// Essentially, anything before API level 20 supports Yaffs2
const char* newYaffs2Support = "no";
if (avdInfo_getApiLevel(avd) < 20) {
newYaffs2Support = "yes";
D("Auto-detect: Kernel does support YAFFS2 partitions.");
} else {
D("Auto-detect: Kernel does not support YAFFS2 partitions.");
}
AFREE(hw->kernel_supportsYaffs2);
hw->kernel_supportsYaffs2 = ASTRDUP(newYaffs2Support);
}
/* opts->ramdisk is never NULL (see createAVD) here */
if (opts->ramdisk) {
reassign_string(&hw->disk_ramdisk_path, opts->ramdisk);
}
else if (!hw->disk_ramdisk_path[0]) {
hw->disk_ramdisk_path = avdInfo_getRamdiskPath(avd);
D("autoconfig: -ramdisk %s", hw->disk_ramdisk_path);
}
/* -partition-size is used to specify the max size of both the system
* and data partition sizes.
*/
uint64_t defaultPartitionSize = convertMBToBytes(200);
if (opts->partition_size) {
char* end;
long sizeMB = strtol(opts->partition_size, &end, 0);
long minSizeMB = 10;
long maxSizeMB = LONG_MAX / ONE_MB;
if (sizeMB < 0 || *end != 0) {
derror( "-partition-size must be followed by a positive integer" );
exit(1);
}
if (sizeMB < minSizeMB || sizeMB > maxSizeMB) {
derror( "partition-size (%d) must be between %dMB and %dMB",
sizeMB, minSizeMB, maxSizeMB );
exit(1);
}
defaultPartitionSize = (uint64_t) sizeMB * ONE_MB;
}
/** SYSTEM PARTITION **/
if (opts->sysdir == NULL) {
if (avdInfo_inAndroidBuild(avd)) {
opts->sysdir = ASTRDUP(avdInfo_getContentPath(avd));
D("autoconfig: -sysdir %s", opts->sysdir);
}
}
if (opts->sysdir != NULL) {
if (!path_exists(opts->sysdir)) {
derror("Directory does not exist: %s", opts->sysdir);
exit(1);
}
}
{
char* rwImage = NULL;
char* initImage = NULL;
do {
if (opts->system == NULL) {
/* If -system is not used, try to find a runtime system image
* (i.e. system-qemu.img) in the content directory.
*/
rwImage = avdInfo_getSystemImagePath(avd);
if (rwImage != NULL) {
break;
}
/* Otherwise, try to find the initial system image */
initImage = avdInfo_getSystemInitImagePath(avd);
if (initImage == NULL) {
derror("No initial system image for this configuration!");
exit(1);
}
break;
}
/* If -system <name> is used, use it to find the initial image */
if (opts->sysdir != NULL && !path_exists(opts->system)) {
initImage = _getFullFilePath(opts->sysdir, opts->system);
} else {
initImage = ASTRDUP(opts->system);
}
if (!path_exists(initImage)) {
derror("System image file doesn't exist: %s", initImage);
exit(1);
}
} while (0);
if (rwImage != NULL) {
/* Use the read/write image file directly */
hw->disk_systemPartition_path = rwImage;
hw->disk_systemPartition_initPath = NULL;
D("Using direct system image: %s", rwImage);
} else if (initImage != NULL) {
hw->disk_systemPartition_path = NULL;
hw->disk_systemPartition_initPath = initImage;
D("Using initial system image: %s", initImage);
}
/* Check the size of the system partition image.
* If we have an AVD, it must be smaller than
* the disk.systemPartition.size hardware property.
*
* Otherwise, we need to adjust the systemPartitionSize
* automatically, and print a warning.
*
*/
const char* systemImage = hw->disk_systemPartition_path;
uint64_t systemBytes;
if (systemImage == NULL)
systemImage = hw->disk_systemPartition_initPath;
if (path_get_size(systemImage, &systemBytes) < 0) {
derror("Missing system image: %s", systemImage);
exit(1);
}
hw->disk_systemPartition_size =
_adjustPartitionSize("system", systemBytes, defaultPartitionSize,
avdInfo_inAndroidBuild(avd));
}
/** DATA PARTITION **/
if (opts->datadir) {
if (!path_exists(opts->datadir)) {
derror("Invalid -datadir directory: %s", opts->datadir);
}
}
{
char* dataImage = NULL;
char* initImage = NULL;
do {
if (!opts->data) {
dataImage = avdInfo_getDataImagePath(avd);
if (dataImage != NULL) {
D("autoconfig: -data %s", dataImage);
break;
}
dataImage = avdInfo_getDefaultDataImagePath(avd);
if (dataImage == NULL) {
derror("No data image path for this configuration!");
exit (1);
}
opts->wipe_data = 1;
break;
}
if (opts->datadir) {
dataImage = _getFullFilePath(opts->datadir, opts->data);
} else {
dataImage = ASTRDUP(opts->data);
}
} while (0);
if (opts->initdata != NULL) {
initImage = ASTRDUP(opts->initdata);
if (!path_exists(initImage)) {
derror("Invalid initial data image path: %s", initImage);
exit(1);
}
} else {
initImage = avdInfo_getDataInitImagePath(avd);
D("autoconfig: -initdata %s", initImage);
}
hw->disk_dataPartition_path = dataImage;
if (opts->wipe_data) {
hw->disk_dataPartition_initPath = initImage;
} else {
hw->disk_dataPartition_initPath = NULL;
}
android_op_wipe_data = opts->wipe_data;
uint64_t defaultBytes =
hw->disk_dataPartition_size == 0 ?
defaultPartitionSize :
hw->disk_dataPartition_size;
uint64_t dataBytes;
const char* dataPath = hw->disk_dataPartition_initPath;
if (dataPath == NULL)
dataPath = hw->disk_dataPartition_path;
path_get_size(dataPath, &dataBytes);
hw->disk_dataPartition_size =
_adjustPartitionSize("data", dataBytes, defaultBytes,
avdInfo_inAndroidBuild(avd));
}
/** CACHE PARTITION **/
if (opts->no_cache) {
/* No cache partition at all */
hw->disk_cachePartition = 0;
}
else if (!hw->disk_cachePartition) {
if (opts->cache) {
dwarning( "Emulated hardware doesn't support a cache partition. -cache option ignored!" );
opts->cache = NULL;
}
}
else
{
if (!opts->cache) {
/* Find the current cache partition file */
opts->cache = avdInfo_getCachePath(avd);
if (opts->cache == NULL) {
/* The file does not exists, we will force its creation
* if we are not in the Android build system. Otherwise,
* a temporary file will be used.
*/
if (!avdInfo_inAndroidBuild(avd)) {
opts->cache = avdInfo_getDefaultCachePath(avd);
}
}
if (opts->cache) {
D("autoconfig: -cache %s", opts->cache);
}
}
if (opts->cache) {
hw->disk_cachePartition_path = ASTRDUP(opts->cache);
}
}
if (hw->disk_cachePartition_path && opts->cache_size) {
/* Set cache partition size per user options. */
char* end;
long sizeMB = strtol(opts->cache_size, &end, 0);
if (sizeMB < 0 || *end != 0) {
derror( "-cache-size must be followed by a positive integer" );
exit(1);
}
hw->disk_cachePartition_size = (uint64_t) sizeMB * ONE_MB;
}
/** SD CARD PARTITION */
if (!hw->hw_sdCard) {
/* No SD Card emulation, so -sdcard will be ignored */
if (opts->sdcard) {
dwarning( "Emulated hardware doesn't support SD Cards. -sdcard option ignored." );
opts->sdcard = NULL;
}
} else {
/* Auto-configure -sdcard if it is not available */
if (!opts->sdcard) {
do {
/* If -datadir <path> is used, look for a sdcard.img file here */
if (opts->datadir) {
char tmp[PATH_MAX], *tmpend = tmp + sizeof(tmp);
bufprint(tmp, tmpend, "%s/%s", opts->datadir, "system.img");
if (path_exists(tmp)) {
opts->sdcard = strdup(tmp);
break;
}
}
/* Otherwise, look at the AVD's content */
opts->sdcard = avdInfo_getSdCardPath(avd);
if (opts->sdcard != NULL) {
break;
}
/* Nothing */
} while (0);
if (opts->sdcard) {
D("autoconfig: -sdcard %s", opts->sdcard);
}
}
}
if(opts->sdcard) {
uint64_t size;
if (path_get_size(opts->sdcard, &size) == 0) {
/* see if we have an sdcard image. get its size if it exists */
/* due to what looks like limitations of the MMC protocol, one has
* to use an SD Card image that is equal or larger than 9 MB
*/
if (size < 9*1024*1024ULL) {
fprintf(stderr, "### WARNING: SD Card files must be at least 9MB, ignoring '%s'\n", opts->sdcard);
} else {
hw->hw_sdCard_path = ASTRDUP(opts->sdcard);
}
} else {
dwarning("no SD Card image at '%s'", opts->sdcard);
}
}
if (opts->memory) {
char* end;
long ramSize = strtol(opts->memory, &end, 0);
if (ramSize < 0 || *end != 0) {
derror( "-memory must be followed by a positive integer" );
exit(1);
}
if (ramSize < 32 || ramSize > 4096) {
derror( "physical memory size must be between 32 and 4096 MB" );
exit(1);
}
hw->hw_ramSize = ramSize;
} else {
int ramSize = hw->hw_ramSize;
if (ramSize <= 0) {
#if 1
// For ARM64, use 1GB by default.
ramSize = 1024 * 1024ULL;
#else
/* Compute the default RAM size based on the size of screen.
* This is only used when the skin doesn't provide the ram
* size through its hardware.ini (i.e. legacy ones) or when
* in the full Android build system.
*/
int64_t pixels = hw->hw_lcd_width * hw->hw_lcd_height;
/* The following thresholds are a bit liberal, but we
* essentially want to ensure the following mappings:
*
* 320x480 -> 96
* 800x600 -> 128
* 1024x768 -> 256
*
* These are just simple heuristics, they could change in
* the future.
*/
if (pixels <= 250000)
ramSize = 96;
else if (pixels <= 500000)
ramSize = 128;
else
ramSize = 256;
}
#endif
hw->hw_ramSize = ramSize;
}
D("Physical RAM size: %dMB\n", hw->hw_ramSize);
if (opts->gpu) {
const char* gpu = opts->gpu;
if (!strcmp(gpu,"on") || !strcmp(gpu,"enable")) {
hw->hw_gpu_enabled = 1;
} else if (!strcmp(gpu,"off") || !strcmp(gpu,"disable")) {
hw->hw_gpu_enabled = 0;
} else if (!strcmp(gpu,"auto")) {
/* Nothing to do */
} else {
derror("Invalid value for -gpu <mode> parameter: %s\n", gpu);
derror("Valid values are: on, off or auto\n");
exit(1);
}
}
hw->avd_name = ASTRDUP(avdInfo_getName(avd));
String qemuExecutable = getQemuExecutablePath(argv[0]);
D("QEMU EXECUTABLE=%s\n", qemuExecutable.c_str());
// Create userdata file from init version if needed.
if (!path_exists(hw->disk_dataPartition_path)) {
if (!path_exists(hw->disk_dataPartition_initPath)) {
derror("Missing initial data partition file: %s",
hw->disk_dataPartition_initPath);
exit(1);
}
D("Creating: %s\n", hw->disk_dataPartition_path);
if (path_copy_file(hw->disk_dataPartition_path,
hw->disk_dataPartition_initPath) < 0) {
derror("Could not create %s: %s", hw->disk_dataPartition_path,
strerror(errno));
exit(1);
}
}
// Create cache partition image if it doesn't exist already.
if (!path_exists(hw->disk_cachePartition_path)) {
D("Creating empty ext4 cache partition: %s",
hw->disk_cachePartition_path);
int ret = android_createEmptyExt4Image(
hw->disk_cachePartition_path,
hw->disk_cachePartition_size,
"cache");
if (ret < 0) {
derror("Could not create %s: %s", hw->disk_cachePartition_path,
strerror(-ret));
exit(1);
}
}
// Now build the QEMU parameters.
const char* args[128];
int n = 0;
args[n++] = qemuExecutable.c_str();
args[n++] = "-cpu";
args[n++] = "cortex-a57";
args[n++] = "-machine";
args[n++] = "type=ranchu";
// Memory size
args[n++] = "-m";
String memorySize = StringFormat("%ld", hw->hw_ramSize);
args[n++] = memorySize.c_str();
// Command-line
args[n++] = "-append";
String kernelCommandLine =
"console=ttyAMA0,38400 keep_bootcon earlyprintk=ttyAMA0";
args[n++] = kernelCommandLine.c_str();
args[n++] = "-serial";
args[n++] = "mon:stdio";
// Kernel image
args[n++] = "-kernel";
args[n++] = hw->kernel_path;
// Ramdisk
args[n++] = "-initrd";
args[n++] = hw->disk_ramdisk_path;
// Data partition.
args[n++] = "-drive";
String userDataParam =
StringFormat("index=2,id=userdata,file=%s",
hw->disk_dataPartition_path);
args[n++] = userDataParam.c_str();
args[n++] = "-device";
args[n++] = "virtio-blk-device,drive=userdata";
// Cache partition.
args[n++] = "-drive";
String cacheParam =
StringFormat("index=1,id=cache,file=%s",
hw->disk_cachePartition_path);
args[n++] = cacheParam.c_str();
args[n++] = "-device";
args[n++] = "virtio-blk-device,drive=cache";
// System partition.
args[n++] = "-drive";
String systemParam =
StringFormat("index=0,id=system,file=%s",
hw->disk_systemPartition_initPath);
args[n++] = systemParam.c_str();
args[n++] = "-device";
args[n++] = "virtio-blk-device,drive=system";
// Network
args[n++] = "-netdev";
args[n++] = "user,id=mynet";
args[n++] = "-device";
args[n++] = "virtio-net-device,netdev=mynet";
args[n++] = "-show-cursor";
// Data directory (for keymaps and PC Bios).
args[n++] = "-L";
String dataDir = getNthParentDir(qemuExecutable.c_str(), 2U);
dataDir += "/pc-bios";
args[n++] = dataDir.c_str();
if(VERBOSE_CHECK(init)) {
int i;
printf("QEMU options list:\n");
for(i = 0; i < n; i++) {
printf("emulator: argv[%02d] = \"%s\"\n", i, args[i]);
}
/* Dump final command-line option to make debugging the core easier */
printf("Concatenated QEMU options:\n");
for (i = 0; i < n; i++) {
/* To make it easier to copy-paste the output to a command-line,
* quote anything that contains spaces.
*/
if (strchr(args[i], ' ') != NULL) {
printf(" '%s'", args[i]);
} else {
printf(" %s", args[i]);
}
}
printf("\n");
}
if (!path_exists(qemuExecutable.c_str())) {
fprintf(stderr, "Missing QEMU executable: %s\n",
qemuExecutable.c_str());
return 1;
}
// Now launch executable.
#ifdef _WIN32
// Take care of quoting all parameters before sending them to execv().
// See the "Eveyone quotes command line arguments the wrong way" on
// MSDN.
int i;
for (i = 0; i < n; ++i) {
// Technically, this leaks the quoted strings, but we don't care
// since this process will terminate after the execv() anyway.
args[i] = win32_cmdline_quote(args[i]);
D("Quoted param: [%s]\n", args[i]);
}
#endif
safe_execv(qemuExecutable.c_str(), (char* const*)args);
fprintf(stderr,
"Could not launch QEMU [%s]: %s\n",
qemuExecutable.c_str(),
strerror(errno));
return errno;
}
}
}
}