| /* Copyright (C) 2008 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. |
| */ |
| #include "android/avd/info.h" |
| #include "android/utils/path.h" |
| #include "android/utils/bufprint.h" |
| #include "android/utils/filelock.h" |
| #include "android/utils/tempfile.h" |
| #include "android/utils/debug.h" |
| #include "android/utils/dirscanner.h" |
| #include "qemu-common.h" |
| #include <stddef.h> |
| #include <string.h> |
| #include <stdlib.h> |
| #include <errno.h> |
| |
| /* global variables - see android/globals.h */ |
| AvdInfoParams android_avdParams[1]; |
| AvdInfo* android_avdInfo; |
| |
| /* for debugging */ |
| #define D(...) VERBOSE_PRINT(init,__VA_ARGS__) |
| #define DD(...) VERBOSE_PRINT(avd_config,__VA_ARGS__) |
| |
| /* technical note on how all of this is supposed to work: |
| * |
| * we assume the following SDK layout: |
| * |
| * SDK/ |
| * tools/ |
| * emulator[.exe] |
| * libs/ |
| * hardware-properties.ini |
| * ... |
| * |
| * platforms/ |
| * <platform1>/ |
| * build.prop |
| * images/ |
| * <default kernel/disk images> |
| * skins/ |
| * default/ --> default skin |
| * layout |
| * <skin bitmaps> |
| * <skin2>/ --> another skin |
| * layout |
| * <skin bitmaps> |
| * <skin3>/ --> skin alias to <skin2> |
| * alias-<skin2> |
| * |
| * <platform2>/ |
| * build.prop |
| * images/ |
| * <other default kernel/disk images> |
| * |
| * add-ons/ |
| * <partner1>/ |
| * manifest.ini |
| * images/ |
| * <replacement disk images> |
| * |
| * <partner2>/ |
| * manifest.ini |
| * <replacement disk images> |
| * hardware.ini |
| * skins/ |
| * default/ |
| * layout |
| * <skin bitmaps> |
| * <skin2>/ |
| * layout |
| * <skin bitmaps> |
| * |
| * |
| * we define a 'platform' as a directory that provides a complete |
| * set of disk/kernel images, some skins, as well as a build.prop |
| * file. |
| * |
| * we define an 'addon' as a directory that provides additionnal |
| * or replacement files related to a given existing platform. |
| * each add-on provides at the minimum a 'manifest.ini' file |
| * that describes it (see below). |
| * |
| * important notes: |
| * |
| * - the build.prop file of a given platform directory contains |
| * a line that reads 'ro.build.version.sdk=<version>' where |
| * <version> is an integer corresponding to the corresponding |
| * official API version number as defined by Android. |
| * |
| * each platform provided with the SDK must have a unique |
| * version number. |
| * |
| * - the manifest.ini of a given addon must contain lines |
| * that include: |
| * |
| * name=<addOnName> |
| * vendor=<vendorName> |
| * api=<version> |
| * |
| * where <version> is used to identify the platform the add-on |
| * refers to. Note that the platform's directory name is |
| * irrelevant to the matching algorithm. |
| * |
| * each addon available must have a unique |
| * <vendor>:<name>:<sdk> triplet |
| * |
| * - an add-on can provide a hardware.ini file. If present, this |
| * is used to force the hardware setting of any virtual device |
| * built from the add-on. |
| * |
| * - the file in SDK/tools/lib/hardware-properties.ini declares which |
| * hardware properties are supported by the emulator binary. |
| * these can appear in the config.ini file of a given virtual |
| * device, or the hardware.ini of a given add-on. |
| * |
| * normally, a virtual device corresponds to: |
| * |
| * - a root configuration file, placed in ~/.android/avd/<foo>.ini |
| * where <foo> is the name of the virtual device. |
| * |
| * - a "content" directory, which contains disk images for the |
| * virtual device (e.g. at a minimum, the userdata.img file) |
| * plus some configuration information. |
| * |
| * - the root config file must have at least two lines like: |
| * |
| * path=<pathToContentDirectory> |
| * target=<targetAddonOrPlatform> |
| * |
| * the 'path' value must point to the location of |
| * the virtual device's content directory. By default, this |
| * should be ~/.android/avd/<foo>/, though the user should be |
| * able to choose an alternative path at creation time. |
| * |
| * the 'target' value can be one of: |
| * |
| * android-<version> |
| * <vendor>:<name>:<version> |
| * |
| * the first form is used to refer to a given platform. |
| * the second form is used to refer to a unique add-on. |
| * in both forms, <version> must be an integer that |
| * matches one of the available platforms. |
| * |
| * <vendor>:<name>:<version> must match the triplet of one |
| * of the available add-ons |
| * |
| * if the target value is incorrect, or if the content path |
| * is invalid, the emulator will abort with an error. |
| * |
| * - the content directory shall contain a 'config.ini' that |
| * contains hardware properties for the virtual device |
| * (as defined by SDK/tools/lib/hardware-properties.ini), as |
| * well as additional lines like: |
| * |
| * sdcard=<pathToDefaultSDCard> |
| * skin=<defaultSkinName> |
| * options=<additionalEmulatorStartupOptions> |
| * |
| * |
| * Finally, to find the skin to be used with a given virtual |
| * device, the following logic is used: |
| * |
| * - if no skin name has been manually specified on |
| * the command line, or in the config.ini file, |
| * look in $CONTENT/skin/layout and use it if available. |
| * |
| * - otherwise, set SKINNAME to 'default' if not manually |
| * specified, and look for $ADDON/skins/$SKINNAME/layout |
| * and use it if available |
| * |
| * - otherwise, look for $PLATFORM/skins/$SKINNAME/layout |
| * and use it if available. |
| * |
| * - otherwise, look for $PLATFORM/skins/$SKINNAME/alias-<other>. |
| * if a file exist by that name, look at $PLATFORM/skins/<other>/layout |
| * and use it if available. Aliases are not recursives :-) |
| */ |
| |
| /* now, things get a little bit more complicated when working |
| * within the Android build system. In this mode, which can be |
| * detected by looking at the definition of the ANDROID_PRODUCT_OUT |
| * environment variable, we're going to simply pick the image files |
| * from the out directory, or from $BUILDROOT/prebuilt |
| */ |
| |
| /* the name of the $SDKROOT subdirectory that contains all platforms */ |
| #define PLATFORMS_SUBDIR "platforms" |
| |
| /* the name of the $SDKROOT subdirectory that contains add-ons */ |
| #define ADDONS_SUBDIR "add-ons" |
| |
| /* this is the subdirectory of $HOME/.android where all |
| * root configuration files (and default content directories) |
| * are located. |
| */ |
| #define ANDROID_AVD_DIR "avd" |
| |
| /* certain disk image files are mounted read/write by the emulator |
| * to ensure that several emulators referencing the same files |
| * do not corrupt these files, we need to lock them and respond |
| * to collision depending on the image type. |
| * |
| * the enumeration below is used to record information about |
| * each image file path. |
| * |
| * READONLY means that the file will be mounted read-only |
| * and this doesn't need to be locked. must be first in list |
| * |
| * MUSTLOCK means that the file should be locked before |
| * being mounted by the emulator |
| * |
| * TEMPORARY means that the file has been copied to a |
| * temporary image, which can be mounted read/write |
| * but doesn't require locking. |
| */ |
| typedef enum { |
| IMAGE_STATE_READONLY, /* unlocked */ |
| IMAGE_STATE_MUSTLOCK, /* must be locked */ |
| IMAGE_STATE_LOCKED, /* locked */ |
| IMAGE_STATE_LOCKED_EMPTY, /* locked and empty */ |
| IMAGE_STATE_TEMPORARY, /* copied to temp file (no lock needed) */ |
| } AvdImageState; |
| |
| struct AvdInfo { |
| /* for the Android build system case */ |
| char inAndroidBuild; |
| char* androidOut; |
| char* androidBuildRoot; |
| |
| /* for the normal virtual device case */ |
| char* deviceName; |
| char* sdkRootPath; |
| int platformVersion; |
| char* platformPath; |
| char* addonTarget; |
| char* addonPath; |
| char* contentPath; |
| IniFile* rootIni; /* root <foo>.ini file */ |
| IniFile* configIni; /* virtual device's config.ini */ |
| |
| /* for both */ |
| char* skinName; /* skin name */ |
| char* skinDirPath; /* skin directory */ |
| |
| /* image files */ |
| char* imagePath [ AVD_IMAGE_MAX ]; |
| char imageState[ AVD_IMAGE_MAX ]; |
| }; |
| |
| |
| void |
| avdInfo_free( AvdInfo* i ) |
| { |
| if (i) { |
| int nn; |
| |
| for (nn = 0; nn < AVD_IMAGE_MAX; nn++) |
| qemu_free(i->imagePath[nn]); |
| |
| qemu_free(i->skinName); |
| qemu_free(i->skinDirPath); |
| |
| if (i->configIni) { |
| iniFile_free(i->configIni); |
| i->configIni = NULL; |
| } |
| |
| if (i->rootIni) { |
| iniFile_free(i->rootIni); |
| i->rootIni = NULL; |
| } |
| |
| qemu_free(i->contentPath); |
| qemu_free(i->sdkRootPath); |
| |
| if (i->inAndroidBuild) { |
| qemu_free(i->androidOut); |
| qemu_free(i->androidBuildRoot); |
| } else { |
| qemu_free(i->platformPath); |
| qemu_free(i->addonTarget); |
| qemu_free(i->addonPath); |
| } |
| |
| qemu_free(i->deviceName); |
| qemu_free(i); |
| } |
| } |
| |
| /* list of default file names for each supported image file type */ |
| static const char* const _imageFileNames[ AVD_IMAGE_MAX ] = { |
| #define _AVD_IMG(x,y,z) y, |
| AVD_IMAGE_LIST |
| #undef _AVD_IMG |
| }; |
| |
| /* list of short text description for each supported image file type */ |
| static const char* const _imageFileText[ AVD_IMAGE_MAX ] = { |
| #define _AVD_IMG(x,y,z) z, |
| AVD_IMAGE_LIST |
| #undef _AVD_IMG |
| }; |
| |
| /*************************************************************** |
| *************************************************************** |
| ***** |
| ***** NORMAL VIRTUAL DEVICE SUPPORT |
| ***** |
| *****/ |
| |
| /* compute path to the root SDK directory |
| * assume we are in $SDKROOT/tools/emulator[.exe] |
| */ |
| static int |
| _getSdkRoot( AvdInfo* i ) |
| { |
| const char* env; |
| char temp[PATH_MAX], *p=temp, *end=p+sizeof(temp); |
| |
| #define SDK_ROOT_ENV "ANDROID_SDK_ROOT" |
| |
| env = getenv(SDK_ROOT_ENV); |
| if (env != NULL && env[0] != 0) { |
| if (path_exists(env)) { |
| D("found " SDK_ROOT_ENV ": %s", env); |
| i->sdkRootPath = qemu_strdup(env); |
| return 0; |
| } |
| D(SDK_ROOT_ENV " points to unknown directory: %s", env); |
| } |
| |
| (void) bufprint_app_dir(temp, end); |
| |
| i->sdkRootPath = path_parent(temp, 1); |
| if (i->sdkRootPath == NULL) { |
| derror("can't find root of SDK directory"); |
| return -1; |
| } |
| D("found SDK root at %s", i->sdkRootPath); |
| return 0; |
| } |
| |
| /* returns the full path of the platform subdirectory |
| * corresponding to a given API version |
| */ |
| static char* |
| _findPlatformByVersion( const char* sdkRoot, int version ) |
| { |
| char temp[PATH_MAX], *p=temp, *end=p+sizeof temp; |
| char* subdir = NULL; |
| DirScanner* scanner; |
| |
| DD("> %s(%s,%d)", __FUNCTION__, sdkRoot, version); |
| p = bufprint(temp, end, "%s/%s", sdkRoot, PLATFORMS_SUBDIR); |
| if (p >= end) { |
| DD("! path too long"); |
| return NULL; |
| } |
| |
| scanner = dirScanner_new(temp); |
| if (scanner == NULL) { |
| DD("! cannot scan path %s: %s", temp, strerror(errno)); |
| return NULL; |
| } |
| |
| for (;;) { |
| IniFile* ini; |
| int apiVersion; |
| |
| subdir = (char*) dirScanner_nextFull(scanner); |
| if (subdir == NULL) |
| break; |
| |
| /* look for a file named "build.prop */ |
| p = bufprint(temp, end, "%s/build.prop", subdir); |
| if (p >= end) |
| continue; |
| |
| if (!path_exists(temp)) { |
| DD("! no file at %s", temp); |
| continue; |
| } |
| |
| ini = iniFile_newFromFile(temp); |
| if (ini == NULL) |
| continue; |
| |
| apiVersion = iniFile_getInteger(ini, "ro.build.version.sdk", -1); |
| iniFile_free(ini); |
| |
| DD("! found %s (version %d)", temp, apiVersion); |
| |
| if (apiVersion == version) { |
| /* Bingo */ |
| subdir = qemu_strdup(subdir); |
| break; |
| } |
| } |
| |
| if (!subdir) { |
| DD("< didn't found anything"); |
| } |
| |
| dirScanner_free(scanner); |
| return subdir; |
| } |
| |
| /* returns the full path of the addon corresponding to a given target, |
| * or NULL if not found. on success, *pversion will contain the SDK |
| * version number |
| */ |
| static char* |
| _findAddonByTarget( const char* sdkRoot, const char* target, int *pversion ) |
| { |
| char* targetCopy = qemu_strdup(target); |
| char* targetVendor = NULL; |
| char* targetName = NULL; |
| int targetVersion = -1; |
| |
| char temp[PATH_MAX]; |
| char* p; |
| char* end; |
| DirScanner* scanner; |
| char* subdir; |
| |
| DD("> %s(%s,%s)", __FUNCTION__, sdkRoot, target); |
| |
| /* extract triplet from target string */ |
| targetVendor = targetCopy; |
| |
| p = strchr(targetVendor, ':'); |
| if (p == NULL) { |
| DD("< missing first column separator"); |
| goto FAIL; |
| } |
| *p = 0; |
| targetName = p + 1; |
| p = strchr(targetName, ':'); |
| if (p == NULL) { |
| DD("< missing second column separator"); |
| goto FAIL; |
| } |
| *p++ = 0; |
| |
| targetVersion = atoi(p); |
| |
| if (targetVersion == 0) { |
| DD("< invalid version number"); |
| goto FAIL; |
| } |
| /* now scan addons directory */ |
| p = temp; |
| end = p + sizeof temp; |
| |
| p = bufprint(p, end, "%s/%s", sdkRoot, ADDONS_SUBDIR); |
| if (p >= end) { |
| DD("< add-on path too long"); |
| goto FAIL; |
| } |
| scanner = dirScanner_new(temp); |
| if (scanner == NULL) { |
| DD("< cannot scan add-on path %s: %s", temp, strerror(errno)); |
| goto FAIL; |
| } |
| for (;;) { |
| IniFile* ini; |
| const char* vendor; |
| const char* name; |
| int version; |
| int matches; |
| |
| subdir = (char*) dirScanner_nextFull(scanner); |
| if (subdir == NULL) |
| break; |
| |
| /* try to open the manifest.ini file */ |
| p = bufprint(temp, end, "%s/manifest.ini", subdir); |
| if (p >= end) |
| continue; |
| |
| ini = iniFile_newFromFile(temp); |
| if (ini == NULL) |
| continue; |
| |
| DD("! scanning manifest.ini in %s", temp); |
| |
| /* find the vendor, name and version */ |
| vendor = iniFile_getValue(ini, "vendor"); |
| name = iniFile_getValue(ini, "name"); |
| version = iniFile_getInteger(ini, "api", -1); |
| |
| matches = 0; |
| |
| matches += (version == targetVersion); |
| matches += (vendor && !strcmp(vendor, targetVendor)); |
| matches += (name && !strcmp(name, targetName)); |
| |
| DD("! matches=%d vendor=[%s] name=[%s] version=%d", |
| matches, |
| vendor ? vendor : "<NULL>", |
| name ? name : "<NULL>", |
| version); |
| |
| iniFile_free(ini); |
| |
| if (matches == 3) { |
| /* bingo */ |
| *pversion = version; |
| subdir = qemu_strdup(subdir); |
| break; |
| } |
| } |
| |
| dirScanner_free(scanner); |
| |
| DD("< returning %s", subdir ? subdir : "<NULL>"); |
| return subdir; |
| |
| FAIL: |
| qemu_free(targetCopy); |
| return NULL; |
| } |
| |
| static int |
| _checkAvdName( const char* name ) |
| { |
| int len = strlen(name); |
| int len2 = strspn(name, "ABCDEFGHIJKLMNOPQRSTUVWXYZ" |
| "abcdefghijklmnopqrstuvwxyz" |
| "0123456789_.-"); |
| return (len == len2); |
| } |
| |
| /* parse the root config .ini file. it is located in |
| * ~/.android/avd/<name>.ini or Windows equivalent |
| */ |
| static int |
| _getRootIni( AvdInfo* i ) |
| { |
| char temp[PATH_MAX], *p=temp, *end=p+sizeof(temp); |
| |
| p = bufprint_config_path(temp, end); |
| p = bufprint(p, end, "/" ANDROID_AVD_DIR "/%s.ini", i->deviceName); |
| if (p >= end) { |
| derror("device name too long"); |
| return -1; |
| } |
| |
| i->rootIni = iniFile_newFromFile(temp); |
| if (i->rootIni == NULL) { |
| derror("unknown virtual device name: '%s'", i->deviceName); |
| return -1; |
| } |
| D("root virtual device file at %s", temp); |
| return 0; |
| } |
| |
| /* the .ini variable name that points to the content directory |
| * in a root AVD ini file. This is required */ |
| # define ROOT_PATH_KEY "path" |
| # define ROOT_TARGET_KEY "target" |
| |
| /* retrieve the content path and target from the root .ini file */ |
| static int |
| _getTarget( AvdInfo* i ) |
| { |
| i->contentPath = iniFile_getString(i->rootIni, ROOT_PATH_KEY); |
| i->addonTarget = iniFile_getString(i->rootIni, ROOT_TARGET_KEY); |
| |
| iniFile_free(i->rootIni); |
| i->rootIni = NULL; |
| |
| if (i->contentPath == NULL) { |
| derror("bad config: %s", |
| "virtual device file lacks a "ROOT_PATH_KEY" entry"); |
| return -1; |
| } |
| |
| if (i->addonTarget == NULL) { |
| derror("bad config: %s", |
| "virtual device file lacks a "ROOT_TARGET_KEY" entry"); |
| return -1; |
| } |
| |
| D("virtual device content at %s", i->contentPath); |
| D("virtual device target is %s", i->addonTarget); |
| |
| if (!strncmp(i->addonTarget, "android-", 8)) { /* target is platform */ |
| char* end; |
| const char* versionString = i->addonTarget+8; |
| int version = (int) strtol(versionString, &end, 10); |
| if (*end != 0 || version <= 0) { |
| derror("bad config: invalid platform version: '%s'", versionString); |
| return -1; |
| } |
| i->platformVersion = version; |
| i->platformPath = _findPlatformByVersion(i->sdkRootPath, |
| version); |
| if (i->platformPath == NULL) { |
| derror("bad config: unknown platform version: '%d'", version); |
| return -1; |
| } |
| } |
| else /* target is add-on */ |
| { |
| i->addonPath = _findAddonByTarget(i->sdkRootPath, i->addonTarget, |
| &i->platformVersion); |
| if (i->addonPath == NULL) { |
| derror("bad config: %s", |
| "unknown add-on target: '%s'", i->addonTarget); |
| return -1; |
| } |
| |
| i->platformPath = _findPlatformByVersion(i->sdkRootPath, |
| i->platformVersion); |
| if (i->platformPath == NULL) { |
| derror("bad config: %s", |
| "unknown add-on platform version: '%d'", i->platformVersion); |
| return -1; |
| } |
| D("virtual device add-on path: %s", i->addonPath); |
| } |
| D("virtual device platform path: %s", i->platformPath); |
| D("virtual device platform version %d", i->platformVersion); |
| return 0; |
| } |
| |
| |
| /* find and parse the config.ini file from the content directory */ |
| static int |
| _getConfigIni(AvdInfo* i) |
| { |
| char temp[PATH_MAX], *p=temp, *end=p+sizeof(temp); |
| |
| p = bufprint(p, end, "%s/config.ini", i->contentPath); |
| if (p >= end) { |
| derror("can't access virtual device content directory"); |
| return -1; |
| } |
| |
| #if 1 /* XXX: TODO: remove this in the future */ |
| /* for now, allow a non-existing config.ini */ |
| if (!path_exists(temp)) { |
| D("virtual device has no config file - no problem"); |
| return 0; |
| } |
| #endif |
| |
| i->configIni = iniFile_newFromFile(temp); |
| if (i->configIni == NULL) { |
| derror("bad config: %s", |
| "virtual device directory lacks config.ini"); |
| return -1; |
| } |
| D("virtual device config file: %s", temp); |
| return 0; |
| } |
| |
| /*************************************************************** |
| *************************************************************** |
| ***** |
| ***** KERNEL/DISK IMAGE LOADER |
| ***** |
| *****/ |
| |
| /* a structure used to handle the loading of |
| * kernel/disk images. |
| */ |
| typedef struct { |
| AvdInfo* info; |
| AvdInfoParams* params; |
| AvdImageType id; |
| const char* imageFile; |
| const char* imageText; |
| char** pPath; |
| char* pState; |
| char temp[PATH_MAX]; |
| } ImageLoader; |
| |
| static void |
| imageLoader_init( ImageLoader* l, AvdInfo* info, AvdInfoParams* params ) |
| { |
| memset(l, 0, sizeof(*l)); |
| l->info = info; |
| l->params = params; |
| } |
| |
| /* set the type of the image to load */ |
| static void |
| imageLoader_set( ImageLoader* l, AvdImageType id ) |
| { |
| l->id = id; |
| l->imageFile = _imageFileNames[id]; |
| l->imageText = _imageFileText[id]; |
| l->pPath = &l->info->imagePath[id]; |
| l->pState = &l->info->imageState[id]; |
| |
| l->pState[0] = IMAGE_STATE_READONLY; |
| } |
| |
| /* change the image path */ |
| static char* |
| imageLoader_setPath( ImageLoader* l, const char* path ) |
| { |
| path = path ? qemu_strdup(path) : NULL; |
| |
| qemu_free(l->pPath[0]); |
| l->pPath[0] = (char*) path; |
| |
| return (char*) path; |
| } |
| |
| static char* |
| imageLoader_extractPath( ImageLoader* l ) |
| { |
| char* result = l->pPath[0]; |
| l->pPath[0] = NULL; |
| return result; |
| } |
| |
| /* flags used when loading images */ |
| enum { |
| IMAGE_REQUIRED = (1<<0), /* image is required */ |
| IMAGE_SEARCH_SDK = (1<<1), /* search image in SDK */ |
| IMAGE_EMPTY_IF_MISSING = (1<<2), /* create empty file if missing */ |
| IMAGE_DONT_LOCK = (1<<4), /* don't try to lock image */ |
| IMAGE_IGNORE_IF_LOCKED = (1<<5), /* ignore file if it's locked */ |
| }; |
| |
| #define IMAGE_OPTIONAL 0 |
| |
| /* find an image from the SDK add-on and/or platform |
| * directories. returns the full path or NULL if |
| * the file could not be found. |
| * |
| * note: this stores the result in the image's path as well |
| */ |
| static char* |
| imageLoader_lookupSdk( ImageLoader* l ) |
| { |
| AvdInfo* i = l->info; |
| char* temp = l->temp, *p = temp, *end = p + sizeof(l->temp); |
| |
| do { |
| /* try the add-on directory, if any */ |
| if (i->addonPath != NULL) { |
| DD("searching %s in add-on directory: %s", |
| l->imageFile, i->addonPath); |
| |
| p = bufprint(temp, end, "%s/images/%s", |
| i->addonPath, l->imageFile); |
| |
| if (p < end && path_exists(temp)) |
| break; |
| } |
| |
| /* or try the platform directory */ |
| DD("searching %s in platform directory: %s", |
| l->imageFile, i->platformPath); |
| |
| p = bufprint(temp, end, "%s/images/%s", |
| i->platformPath, l->imageFile); |
| if (p < end && path_exists(temp)) |
| break; |
| |
| DD("could not find %s in SDK", l->imageFile); |
| return NULL; |
| |
| } while (0); |
| |
| l->pState[0] = IMAGE_STATE_READONLY; |
| |
| return imageLoader_setPath(l, temp); |
| } |
| |
| /* search for a file in the content directory. |
| * returns NULL if the file cannot be found. |
| * |
| * note that this formats l->temp with the file's path |
| * allowing you to retrieve it if the function returns NULL |
| */ |
| static char* |
| imageLoader_lookupContent( ImageLoader* l ) |
| { |
| AvdInfo* i = l->info; |
| char* temp = l->temp, *p = temp, *end = p + sizeof(l->temp); |
| |
| DD("searching %s in content directory", l->imageFile); |
| p = bufprint(temp, end, "%s/%s", i->contentPath, l->imageFile); |
| if (p >= end) { |
| derror("content directory path too long"); |
| exit(2); |
| } |
| if (!path_exists(temp)) { |
| DD("not found %s in content directory", l->imageFile); |
| return NULL; |
| } |
| |
| /* assume content image files must be locked */ |
| l->pState[0] = IMAGE_STATE_MUSTLOCK; |
| |
| return imageLoader_setPath(l, temp); |
| } |
| |
| /* lock a file image depending on its state and user flags |
| * note that this clears l->pPath[0] if the lock could not |
| * be acquired and that IMAGE_IGNORE_IF_LOCKED is used. |
| */ |
| static void |
| imageLoader_lock( ImageLoader* l, unsigned flags ) |
| { |
| const char* path = l->pPath[0]; |
| |
| if (flags & IMAGE_DONT_LOCK) |
| return; |
| |
| if (l->pState[0] != IMAGE_STATE_MUSTLOCK) |
| return; |
| |
| D("locking %s image at %s", l->imageText, path); |
| |
| if (filelock_create(path) != NULL) { |
| /* succesful lock */ |
| l->pState[0] = IMAGE_STATE_LOCKED; |
| return; |
| } |
| |
| if (flags & IMAGE_IGNORE_IF_LOCKED) { |
| dwarning("ignoring locked %s image at %s", l->imageText, path); |
| imageLoader_setPath(l, NULL); |
| return; |
| } |
| |
| derror("the %s image is used by another emulator. aborting", |
| l->imageText); |
| exit(2); |
| } |
| |
| /* make a file image empty, this may require locking */ |
| static void |
| imageLoader_empty( ImageLoader* l, unsigned flags ) |
| { |
| const char* path; |
| |
| imageLoader_lock(l, flags); |
| |
| path = l->pPath[0]; |
| if (path == NULL) /* failed to lock, caller will handle it */ |
| return; |
| |
| if (path_empty_file(path) < 0) { |
| derror("could not create %s image at %s: %s", |
| l->imageText, path, strerror(errno)); |
| exit(2); |
| } |
| l->pState[0] = IMAGE_STATE_LOCKED_EMPTY; |
| } |
| |
| |
| /* copy image file from a given source |
| * assumes locking is needed. |
| */ |
| static void |
| imageLoader_copyFrom( ImageLoader* l, const char* srcPath ) |
| { |
| const char* dstPath = NULL; |
| |
| /* find destination file */ |
| if (l->params) { |
| dstPath = l->params->forcePaths[l->id]; |
| } |
| if (!dstPath) { |
| imageLoader_lookupContent(l); |
| dstPath = l->temp; |
| } |
| |
| /* lock destination */ |
| imageLoader_setPath(l, dstPath); |
| l->pState[0] = IMAGE_STATE_MUSTLOCK; |
| imageLoader_lock(l, 0); |
| |
| /* make the copy */ |
| if (path_copy_file(dstPath, srcPath) < 0) { |
| derror("can't initialize %s image from SDK: %s: %s", |
| l->imageText, dstPath, strerror(errno)); |
| exit(2); |
| } |
| } |
| |
| /* this will load and eventually lock and image file, depending |
| * on the flags being used. on exit, this function udpates |
| * l->pState[0] and l->pPath[0] |
| * |
| * returns the path to the file. Note that it returns NULL |
| * only if the file was optional and could not be found. |
| * |
| * if the file is required and missing, the function aborts |
| * the program. |
| */ |
| static char* |
| imageLoader_load( ImageLoader* l, |
| unsigned flags ) |
| { |
| const char* path = NULL; |
| |
| DD("looking for %s image (%s)", l->imageText, l->imageFile); |
| |
| /* first, check user-provided path */ |
| path = l->params->forcePaths[l->id]; |
| if (path != NULL) { |
| imageLoader_setPath(l, path); |
| if (path_exists(path)) |
| goto EXIT; |
| |
| D("user-provided %s image does not exist: %s", |
| l->imageText, path); |
| |
| /* if the file is required, abort */ |
| if (flags & IMAGE_REQUIRED) { |
| derror("user-provided %s image at %s doesn't exist", |
| l->imageText, path); |
| exit(2); |
| } |
| } |
| else { |
| const char* contentFile; |
| |
| /* second, look in the content directory */ |
| path = imageLoader_lookupContent(l); |
| if (path) goto EXIT; |
| |
| contentFile = qemu_strdup(l->temp); |
| |
| /* it's not there */ |
| if (flags & IMAGE_SEARCH_SDK) { |
| /* third, look in the SDK directory */ |
| path = imageLoader_lookupSdk(l); |
| if (path) { |
| qemu_free((char*)contentFile); |
| goto EXIT; |
| } |
| } |
| DD("found no %s image (%s)", l->imageText, l->imageFile); |
| |
| /* if the file is required, abort */ |
| if (flags & IMAGE_REQUIRED) { |
| derror("could not find required %s image (%s)", |
| l->imageText, l->imageFile); |
| exit(2); |
| } |
| |
| path = imageLoader_setPath(l, contentFile); |
| qemu_free((char*)contentFile); |
| } |
| |
| /* otherwise, do we need to create it ? */ |
| if (flags & IMAGE_EMPTY_IF_MISSING) { |
| imageLoader_empty(l, flags); |
| return l->pPath[0]; |
| } |
| return NULL; |
| |
| EXIT: |
| imageLoader_lock(l, flags); |
| return l->pPath[0]; |
| } |
| |
| |
| |
| /* find the correct path of all image files we're going to need |
| * and lock the files that need it. |
| */ |
| static int |
| _getImagePaths(AvdInfo* i, AvdInfoParams* params ) |
| { |
| int wipeData = (params->flags & AVDINFO_WIPE_DATA) != 0; |
| int wipeCache = (params->flags & AVDINFO_WIPE_CACHE) != 0; |
| int noCache = (params->flags & AVDINFO_NO_CACHE) != 0; |
| int noSdCard = (params->flags & AVDINFO_NO_SDCARD) != 0; |
| |
| ImageLoader l[1]; |
| |
| imageLoader_init(l, i, params); |
| |
| /* pick up the kernel and ramdisk image files - these don't |
| * need a specific handling. |
| */ |
| imageLoader_set ( l, AVD_IMAGE_KERNEL ); |
| imageLoader_load( l, IMAGE_REQUIRED | IMAGE_SEARCH_SDK | IMAGE_DONT_LOCK ); |
| |
| imageLoader_set ( l, AVD_IMAGE_RAMDISK ); |
| imageLoader_load( l, IMAGE_REQUIRED | IMAGE_SEARCH_SDK | IMAGE_DONT_LOCK ); |
| |
| /* the system image |
| * |
| * if there is one in the content directory just lock |
| * and use it. |
| */ |
| imageLoader_set ( l, AVD_IMAGE_SYSTEM ); |
| imageLoader_load( l, IMAGE_REQUIRED | IMAGE_SEARCH_SDK ); |
| |
| /* the data partition - this one is special because if it |
| * is missing, we need to copy the initial image file into it. |
| * |
| * first, try to see if it is in the content directory |
| * (or the user-provided path) |
| */ |
| imageLoader_set( l, AVD_IMAGE_USERDATA ); |
| if ( !imageLoader_load( l, IMAGE_OPTIONAL | |
| IMAGE_EMPTY_IF_MISSING | |
| IMAGE_DONT_LOCK ) ) |
| { |
| /* it's not, we're going to initialize it. simply |
| * forcing a data wipe should be enough */ |
| D("initializing new data partition image: %s", l->pPath[0]); |
| wipeData = 1; |
| } |
| |
| if (wipeData) { |
| /* find SDK source file */ |
| const char* srcPath; |
| |
| if (imageLoader_lookupSdk(l)) { |
| derror("can't locate initial %s image in SDK", |
| l->imageText); |
| exit(2); |
| } |
| srcPath = imageLoader_extractPath(l); |
| |
| imageLoader_copyFrom( l, srcPath ); |
| qemu_free((char*) srcPath); |
| } |
| else |
| { |
| /* lock the data partition image */ |
| l->pState[0] = IMAGE_STATE_MUSTLOCK; |
| imageLoader_lock( l, 0 ); |
| } |
| |
| /* the cache partition: unless the user doesn't want one, |
| * we're going to create it in the content directory |
| */ |
| if (!noCache) { |
| imageLoader_set (l, AVD_IMAGE_CACHE); |
| imageLoader_load(l, IMAGE_OPTIONAL | |
| IMAGE_EMPTY_IF_MISSING ); |
| |
| if (wipeCache) { |
| if (path_empty_file(l->pPath[0]) < 0) { |
| derror("cannot wipe %s image at %s: %s", |
| l->imageText, l->pPath[0], |
| strerror(errno)); |
| exit(2); |
| } |
| } |
| } |
| |
| /* the SD Card image. unless the user doesn't want to, we're |
| * going to mount it if available. Note that if the image is |
| * already used, we must ignore it. |
| */ |
| if (!noSdCard) { |
| imageLoader_set (l, AVD_IMAGE_SDCARD); |
| imageLoader_load(l, IMAGE_OPTIONAL | |
| IMAGE_IGNORE_IF_LOCKED); |
| |
| /* if the file was not found, ignore it */ |
| if (l->pPath[0] && !path_exists(l->pPath[0])) |
| { |
| D("ignoring non-existing %s at %s: %s", |
| l->imageText, l->pPath[0], strerror(errno)); |
| |
| /* if the user provided the SD Card path by hand, |
| * warn him. */ |
| if (params->forcePaths[AVD_IMAGE_SDCARD] != NULL) |
| dwarning("ignoring non-existing SD Card image"); |
| |
| imageLoader_setPath(l, NULL); |
| } |
| } |
| |
| return 0; |
| } |
| |
| /* check that there is a skin named 'skinName' listed from 'skinDirRoot' |
| * this returns 1 on success, 0 on failure |
| * on success, the 'temp' buffer will get the path containing the real |
| * skin directory (after alias expansion), including the skin name. |
| */ |
| static int |
| _checkSkinDir( char* temp, char* end, const char* skinDirRoot, const char* skinName ) |
| { |
| DirScanner* scanner; |
| char *p, *q; |
| int result; |
| |
| p = bufprint(temp, end, "%s/skins/%s", |
| skinDirRoot, skinName); |
| |
| DD("probing skin content in %s", temp); |
| |
| if (p >= end || !path_exists(temp)) { |
| return 0; |
| } |
| |
| /* first, is this a normal skin directory ? */ |
| q = bufprint(p, end, "/layout"); |
| if (q < end && path_exists(temp)) { |
| /* yes */ |
| *p = 0; |
| return 1; |
| } |
| |
| /* second, is it an alias to another skin ? */ |
| *p = 0; |
| result = 0; |
| scanner = dirScanner_new(temp); |
| if (scanner != NULL) { |
| for (;;) { |
| const char* file = dirScanner_next(scanner); |
| |
| if (file == NULL) |
| break; |
| |
| if (strncmp(file, "alias-", 6) || file[6] == 0) |
| continue; |
| |
| p = bufprint(temp, end, "%s/skins/%s", |
| skinDirRoot, file+6); |
| |
| q = bufprint(p, end, "/layout"); |
| if (q < end && path_exists(temp)) { |
| /* yes, it's an alias */ |
| *p = 0; |
| result = 1; |
| break; |
| } |
| } |
| dirScanner_free(scanner); |
| } |
| return result; |
| } |
| |
| static int |
| _getSkin( AvdInfo* i, AvdInfoParams* params ) |
| { |
| char* skinName; |
| char temp[PATH_MAX], *p=temp, *end=p+sizeof(temp); |
| char explicitSkin = 1; |
| |
| /* determine the skin name, the default is "HVGA" |
| * unless specified by the caller or in config.ini |
| */ |
| if (params->skinName) { |
| skinName = qemu_strdup(params->skinName); |
| } else { |
| skinName = iniFile_getString( i->configIni, "skin" ); |
| if (skinName == NULL) { |
| skinName = qemu_strdup("HVGA"); |
| explicitSkin = 0; |
| } |
| } |
| |
| i->skinName = skinName; |
| |
| /* if the skin name has the format 'NNNNxNNN' where |
| * NNN is a decimal value, then this is a 'magic' skin |
| * name that doesn't require a skin directory |
| */ |
| if (isdigit(skinName[0])) { |
| int width, height; |
| if (sscanf(skinName, "%dx%d", &width, &height) == 2) { |
| D("'magic' skin format detected: %s", skinName); |
| i->skinDirPath = NULL; |
| return 0; |
| } |
| } |
| |
| /* now try to find the skin directory for that name - |
| * first try the content directory */ |
| do { |
| /* if there is a single 'skin' directory in |
| * the content directory, assume that's what the |
| * user wants, unless an explicit name was given |
| */ |
| if (!explicitSkin) { |
| char* q; |
| |
| p = bufprint(temp, end, "%s/skin", i->contentPath); |
| q = bufprint(p, end, "/layout"); |
| if (q < end && path_exists(temp)) { |
| /* use this one - cheat a little */ |
| *p = 0; |
| D("using skin content from %s", temp); |
| qemu_free(i->skinName); |
| i->skinName = qemu_strdup("skin"); |
| i->skinDirPath = qemu_strdup(i->contentPath); |
| return 0; |
| } |
| } |
| |
| /* look in content directory */ |
| if (_checkSkinDir(temp, end, i->contentPath, skinName)) |
| break; |
| |
| /* look in the add-on directory, if any */ |
| if (i->addonPath && |
| _checkSkinDir(temp, end, i->addonPath, skinName)) |
| break; |
| |
| /* look in the platforms directory */ |
| if (_checkSkinDir(temp, end, i->platformPath, skinName)) |
| break; |
| |
| /* didn't find it */ |
| if (explicitSkin) |
| dwarning("could not find directory for skin '%s'", skinName); |
| |
| return -1; |
| |
| } while (0); |
| |
| /* separate skin name from parent directory. the skin name |
| * returned in 'temp' might be different from the original |
| * one due to alias expansion so strip it. |
| */ |
| p = strrchr(temp, '/'); |
| if (p == NULL) { |
| /* should not happen */ |
| DD("weird skin path: %s", temp); |
| return -1; |
| } |
| |
| *p = 0; |
| DD("found skin content in %s", temp); |
| i->skinDirPath = qemu_strdup(temp); |
| return 0; |
| } |
| |
| |
| AvdInfo* |
| avdInfo_new( const char* name, AvdInfoParams* params ) |
| { |
| AvdInfo* i; |
| |
| if (name == NULL) |
| return NULL; |
| |
| if (!_checkAvdName(name)) { |
| derror("virtual device name contains invalid characters"); |
| exit(1); |
| } |
| |
| i = qemu_mallocz(sizeof *i); |
| i->deviceName = qemu_strdup(name); |
| |
| if ( _getSdkRoot(i) < 0 || |
| _getRootIni(i) < 0 || |
| _getTarget(i) < 0 || |
| _getConfigIni(i) < 0 ) |
| goto FAIL; |
| |
| if ( _getImagePaths(i, params) < 0 || |
| _getSkin (i, params) < 0 ) |
| goto FAIL; |
| |
| return i; |
| |
| FAIL: |
| avdInfo_free(i); |
| return NULL; |
| } |
| |
| /*************************************************************** |
| *************************************************************** |
| ***** |
| ***** ANDROID BUILD SUPPORT |
| ***** |
| ***** The code below corresponds to the case where we're |
| ***** starting the emulator inside the Android build |
| ***** system. The main differences are that: |
| ***** |
| ***** - the $ANDROID_PRODUCT_OUT directory is used as the |
| ***** content file. |
| ***** |
| ***** - built images must not be modified by the emulator, |
| ***** so system.img must be copied to a temporary file |
| ***** and userdata.img must be copied to userdata-qemu.img |
| ***** if the latter doesn't exist. |
| ***** |
| ***** - the kernel and default skin directory are taken from |
| ***** prebuilt |
| ***** |
| ***** - there is no root .ini file, or any config.ini in |
| ***** the content directory, no SDK platform version |
| ***** and no add-on to consider. |
| *****/ |
| |
| /* used to fake a config.ini located in the content directory */ |
| static int |
| _getBuildConfigIni( AvdInfo* i ) |
| { |
| /* a blank file is ok at the moment */ |
| i->configIni = iniFile_newFromMemory( "", 0 ); |
| return 0; |
| } |
| |
| static int |
| _getBuildImagePaths( AvdInfo* i, AvdInfoParams* params ) |
| { |
| int wipeData = (params->flags & AVDINFO_WIPE_DATA) != 0; |
| int noCache = (params->flags & AVDINFO_NO_CACHE) != 0; |
| int noSdCard = (params->flags & AVDINFO_NO_SDCARD) != 0; |
| |
| char temp[PATH_MAX], *p=temp, *end=p+sizeof temp; |
| char* srcData; |
| ImageLoader l[1]; |
| |
| imageLoader_init(l, i, params); |
| |
| /** load the kernel image |
| **/ |
| |
| /* if it is not in the out directory, get it from prebuilt |
| */ |
| imageLoader_set ( l, AVD_IMAGE_KERNEL ); |
| |
| if ( !imageLoader_load( l, IMAGE_OPTIONAL | |
| IMAGE_DONT_LOCK ) ) |
| { |
| #define PREBUILT_KERNEL_PATH "prebuilt/android-arm/kernel/kernel-qemu" |
| p = bufprint(temp, end, "%s/%s", i->androidBuildRoot, |
| PREBUILT_KERNEL_PATH); |
| if (p >= end || !path_exists(temp)) { |
| derror("bad workspace: cannot find prebuilt kernel in: %s", temp); |
| exit(1); |
| } |
| imageLoader_setPath(l, temp); |
| } |
| |
| /** load the data partition. note that we use userdata-qemu.img |
| ** since we don't want to modify userdata.img at all |
| **/ |
| imageLoader_set ( l, AVD_IMAGE_USERDATA ); |
| imageLoader_load( l, IMAGE_OPTIONAL | IMAGE_DONT_LOCK ); |
| |
| /* get the path of the source file, and check that it actually exists |
| * if the user didn't provide an explicit data file |
| */ |
| srcData = imageLoader_extractPath(l); |
| if (srcData == NULL && params->forcePaths[AVD_IMAGE_USERDATA] == NULL) { |
| derror("There is no %s image in your build directory. Please make a full build", |
| l->imageText, l->imageFile); |
| exit(2); |
| } |
| |
| /* get the path of the target file */ |
| l->imageFile = "userdata-qemu.img"; |
| imageLoader_load( l, IMAGE_OPTIONAL | |
| IMAGE_EMPTY_IF_MISSING | |
| IMAGE_IGNORE_IF_LOCKED ); |
| |
| /* force a data wipe if we just created the image */ |
| if (l->pState[0] == IMAGE_STATE_LOCKED_EMPTY) |
| wipeData = 1; |
| |
| /* if the image was already locked, create a temp file |
| * then force a data wipe. |
| */ |
| if (l->pPath[0] == NULL) { |
| TempFile* temp = tempfile_create(); |
| imageLoader_setPath(l, tempfile_path(temp)); |
| dwarning( "Another emulator is running. user data changes will *NOT* be saved"); |
| wipeData = 1; |
| } |
| |
| /* in the case of a data wipe, copy userdata.img into |
| * the destination */ |
| if (wipeData) { |
| if (srcData == NULL || !path_exists(srcData)) { |
| derror("There is no %s image in your build directory. Please make a full build", |
| l->imageText, _imageFileNames[l->id]); |
| exit(2); |
| } |
| if (path_copy_file( l->pPath[0], srcData ) < 0) { |
| derror("could not initialize %s image from %s: %s", |
| l->imageText, temp, strerror(errno)); |
| exit(2); |
| } |
| } |
| |
| qemu_free(srcData); |
| |
| /** load the ramdisk image |
| **/ |
| imageLoader_set ( l, AVD_IMAGE_RAMDISK ); |
| imageLoader_load( l, IMAGE_REQUIRED | |
| IMAGE_DONT_LOCK ); |
| |
| /** load the system image. read-only. the caller must |
| ** take care of checking the state |
| **/ |
| imageLoader_set ( l, AVD_IMAGE_SYSTEM ); |
| imageLoader_load( l, IMAGE_REQUIRED | IMAGE_DONT_LOCK ); |
| |
| /* force the system image to read-only status */ |
| l->pState[0] = IMAGE_STATE_READONLY; |
| |
| /** cache partition handling |
| **/ |
| if (!noCache) { |
| imageLoader_set (l, AVD_IMAGE_CACHE); |
| |
| /* if the user provided one cache image, lock & use it */ |
| if ( params->forcePaths[l->id] != NULL ) { |
| imageLoader_load(l, IMAGE_REQUIRED | |
| IMAGE_IGNORE_IF_LOCKED); |
| } |
| } |
| |
| /** SD Card image |
| **/ |
| if (!noSdCard) { |
| imageLoader_set (l, AVD_IMAGE_SDCARD); |
| imageLoader_load(l, IMAGE_OPTIONAL | IMAGE_IGNORE_IF_LOCKED); |
| } |
| |
| return 0; |
| } |
| |
| static int |
| _getBuildSkin( AvdInfo* i, AvdInfoParams* params ) |
| { |
| /* the (current) default skin name for our build system */ |
| const char* skinName = params->skinName; |
| const char* skinDir = params->skinRootPath; |
| char temp[PATH_MAX], *p=temp, *end=p+sizeof(temp); |
| char* q; |
| |
| if (!skinName) { |
| /* the (current) default skin name for the build system */ |
| skinName = "HVGA"; |
| DD("selecting default skin name '%s'", skinName); |
| } |
| |
| i->skinName = qemu_strdup(skinName); |
| |
| if (!skinDir) { |
| |
| #define PREBUILT_SKINS_DIR "development/emulator/skins" |
| |
| /* the (current) default skin directory */ |
| p = bufprint( temp, end, "%s/%s", |
| i->androidBuildRoot, PREBUILT_SKINS_DIR ); |
| } else { |
| p = bufprint( temp, end, "%s", skinDir ); |
| } |
| |
| q = bufprint(p, end, "/%s/layout", skinName); |
| if (q >= end || !path_exists(temp)) { |
| DD("skin content directory does not exist: %s", temp); |
| if (skinDir) |
| dwarning("could not find valid skin '%s' in %s:\n", |
| skinName, temp); |
| return -1; |
| } |
| *p = 0; |
| DD("found skin path: %s", temp); |
| i->skinDirPath = qemu_strdup(temp); |
| |
| return 0; |
| } |
| |
| AvdInfo* |
| avdInfo_newForAndroidBuild( const char* androidBuildRoot, |
| const char* androidOut, |
| AvdInfoParams* params ) |
| { |
| AvdInfo* i; |
| |
| i = qemu_mallocz(sizeof *i); |
| |
| i->inAndroidBuild = 1; |
| i->androidBuildRoot = qemu_strdup(androidBuildRoot); |
| i->androidOut = qemu_strdup(androidOut); |
| i->contentPath = qemu_strdup(androidOut); |
| |
| /* TODO: find a way to provide better information from the build files */ |
| i->deviceName = qemu_strdup("<build>"); |
| |
| if (_getBuildConfigIni(i) < 0 || |
| _getBuildImagePaths(i, params) < 0 ) |
| goto FAIL; |
| |
| /* we don't need to fail if there is no valid skin */ |
| _getBuildSkin(i, params); |
| |
| return i; |
| |
| FAIL: |
| avdInfo_free(i); |
| return NULL; |
| } |
| |
| const char* |
| avdInfo_getName( AvdInfo* i ) |
| { |
| return i ? i->deviceName : NULL; |
| } |
| |
| const char* |
| avdInfo_getImageFile( AvdInfo* i, AvdImageType imageType ) |
| { |
| if (i == NULL || (unsigned)imageType >= AVD_IMAGE_MAX) |
| return NULL; |
| |
| return i->imagePath[imageType]; |
| } |
| |
| int |
| avdInfo_isImageReadOnly( AvdInfo* i, AvdImageType imageType ) |
| { |
| if (i == NULL || (unsigned)imageType >= AVD_IMAGE_MAX) |
| return 1; |
| |
| return (i->imageState[imageType] == IMAGE_STATE_READONLY); |
| } |
| |
| const char* |
| avdInfo_getSkinName( AvdInfo* i ) |
| { |
| return i->skinName; |
| } |
| |
| const char* |
| avdInfo_getSkinDir ( AvdInfo* i ) |
| { |
| return i->skinDirPath; |
| } |
| |
| int |
| avdInfo_getHwConfig( AvdInfo* i, AndroidHwConfig* hw ) |
| { |
| IniFile* ini = i->configIni; |
| int ret; |
| |
| if (ini == NULL) |
| ini = iniFile_newFromMemory("", 0); |
| |
| ret = androidHwConfig_read(hw, ini); |
| |
| if (ini != i->configIni) |
| iniFile_free(ini); |
| |
| return ret; |
| } |
| |
| |
| char* |
| avdInfo_getTracePath( AvdInfo* i, const char* traceName ) |
| { |
| char tmp[MAX_PATH], *p=tmp, *end=p + sizeof(tmp); |
| |
| if (i == NULL || traceName == NULL || traceName[0] == 0) |
| return NULL; |
| |
| if (i->inAndroidBuild) { |
| p = bufprint( p, end, "%s" PATH_SEP "traces" PATH_SEP "%s", |
| i->androidOut, traceName ); |
| } else { |
| p = bufprint( p, end, "%s" PATH_SEP "traces" PATH_SEP "%s", |
| i->contentPath, traceName ); |
| } |
| return qemu_strdup(tmp); |
| } |