Add boot.prop support.
This patch allows one to specify a custom list of boot properties that
will be injected into the guest system at boot time. The properties
must be placed in a file named 'boot.prop', which can appear in the
following locations:
For AVDs:
1) In the AVD's content directory.
2) Otherwise in the system image's directory.
For Android platform builds:
1) In the $ANDROID_BUILD_OUT directory.
Note that this is different from build.prop which will be read
from $ANDROID_BUILD_OUT/system/ instead (and isn't used to
inject any properties).
Note that any '-boot-property <name>=<value>' on the command-line will
be injected after the content of 'boot.prop', overriding it unless it
begins with a 'ro.' prefix.
The patch refactors a little how information is retrieved for builds
and AVD configurations. It also fixes a few bugs in the auto-detection
logic for the target architecture / ABI.
BUG=12819077
Change-Id: I9f41f21d9de3e4d25de427f0e5a6bb517f34c5ba
diff --git a/Makefile.common b/Makefile.common
index 47d7ddb..8e892c6 100644
--- a/Makefile.common
+++ b/Makefile.common
@@ -128,6 +128,7 @@
android/utils/dirscanner.c \
android/utils/eintr_wrapper.c \
android/utils/filelock.c \
+ android/utils/file_data.c \
android/utils/ini.c \
android/utils/intmap.c \
android/utils/lineinput.c \
@@ -135,6 +136,7 @@
android/utils/misc.c \
android/utils/panic.c \
android/utils/path.c \
+ android/utils/property_file.c \
android/utils/reflist.c \
android/utils/refset.c \
android/utils/stralloc.c \
diff --git a/Makefile.tests b/Makefile.tests
index 21977eb..bd48877 100644
--- a/Makefile.tests
+++ b/Makefile.tests
@@ -4,6 +4,8 @@
EMULATOR_UNITTESTS_SOURCES := \
android/utils/bufprint_unittest.cpp \
android/utils/eintr_wrapper_unittest.cpp \
+ android/utils/file_data_unittest.cpp \
+ android/utils/property_file_unittest.cpp \
android/utils/win32_cmdline_quote_unittest.cpp \
android/base/EintrWrapper_unittest.cpp \
android/base/files/PathUtils_unittest.cpp \
diff --git a/android/avd/info.c b/android/avd/info.c
index 8640a62..2f64d4b 100644
--- a/android/avd/info.c
+++ b/android/avd/info.c
@@ -13,7 +13,9 @@
#include "android/avd/util.h"
#include "android/avd/keys.h"
#include "android/config/config.h"
+#include "android/utils/file_data.h"
#include "android/utils/path.h"
+#include "android/utils/property_file.h"
#include "android/utils/bufprint.h"
#include "android/utils/filelock.h"
#include "android/utils/tempfile.h"
@@ -103,6 +105,7 @@
char* androidOut;
char* androidBuildRoot;
char* targetArch;
+ char* targetAbi;
/* for the normal virtual device case */
char* deviceName;
@@ -121,6 +124,9 @@
char* skinDirPath; /* skin directory */
char* coreHardwareIniPath; /* core hardware.ini path */
+ FileData buildProperties[1]; /* build.prop file */
+ FileData bootProperties[1]; /* boot.prop file */
+
/* image files */
char* imagePath [ AVD_IMAGE_MAX ];
char imageState[ AVD_IMAGE_MAX ];
@@ -140,6 +146,9 @@
AFREE(i->skinDirPath);
AFREE(i->coreHardwareIniPath);
+ fileData_done(i->buildProperties);
+ fileData_done(i->bootProperties);
+
for (nn = 0; nn < i->numSearchPaths; nn++)
AFREE(i->searchPaths[nn]);
@@ -162,6 +171,8 @@
AFREE(i->contentPath);
AFREE(i->sdkRootPath);
+ AFREE(i->targetArch);
+ AFREE(i->targetAbi);
if (i->inAndroidBuild) {
AFREE(i->androidOut);
@@ -696,6 +707,57 @@
return 0;
}
+
+static void
+_avdInfo_readPropertyFile(AvdInfo* i,
+ const char* filePath,
+ FileData* data) {
+ int ret = fileData_initFromFile(data, filePath);
+ if (ret < 0) {
+ D("Error reading property file %s: %s", filePath, strerror(-ret));
+ } else {
+ D("Read property file at %s", filePath);
+ }
+}
+
+
+static void
+_avdInfo_extractBuildProperties(AvdInfo* i) {
+ i->targetArch = propertyFile_getTargetArch(i->buildProperties);
+ if (!i->targetArch) {
+ i->targetArch = ASTRDUP("arm");
+ D("Cannot find target CPU architecture, defaulting to '%s'",
+ i->targetArch);
+ }
+ i->targetAbi = propertyFile_getTargetAbi(i->buildProperties);
+ if (!i->targetAbi) {
+ i->targetAbi = ASTRDUP("armeabi");
+ D("Cannot find target CPU ABI, defaulting to '%s'",
+ i->targetAbi);
+ }
+ i->apiLevel = propertyFile_getApiLevel(i->buildProperties);
+ if (i->apiLevel < 3) {
+ i->apiLevel = 3;
+ D("Cannot find target API level, defaulting to %d",
+ i->apiLevel);
+ }
+}
+
+
+static void
+_avdInfo_getPropertyFile(AvdInfo* i,
+ const char* propFileName,
+ FileData* data ) {
+ char* filePath = _avdInfo_getContentOrSdkFilePath(i, propFileName);
+ if (!filePath) {
+ D("No %s property file found.", propFileName);
+ return;
+ }
+
+ _avdInfo_readPropertyFile(i, filePath, data);
+ free(filePath);
+}
+
AvdInfo*
avdInfo_new( const char* name, AvdInfoParams* params )
{
@@ -726,6 +788,10 @@
*/
_avdInfo_getSearchPaths(i);
+ // Find the build.prop and boot.prop files and read them.
+ _avdInfo_getPropertyFile(i, "build.prop", i->buildProperties);
+ _avdInfo_getPropertyFile(i, "boot.prop", i->bootProperties);
+
/* don't need this anymore */
iniFile_free(i->rootIni);
i->rootIni = NULL;
@@ -813,8 +879,22 @@
i->androidBuildRoot = ASTRDUP(androidBuildRoot);
i->androidOut = ASTRDUP(androidOut);
i->contentPath = ASTRDUP(androidOut);
- i->targetArch = path_getBuildTargetArch(i->androidOut);
- i->apiLevel = path_getBuildTargetApiLevel(i->androidOut);
+
+ // Find the build.prop file and read it.
+ char* buildPropPath = path_getBuildBuildProp(i->androidOut);
+ if (buildPropPath) {
+ _avdInfo_readPropertyFile(i, buildPropPath, i->buildProperties);
+ free(buildPropPath);
+ }
+
+ // FInd the boot.prop file and read it.
+ char* bootPropPath = path_getBuildBootProp(i->androidOut);
+ if (bootPropPath) {
+ _avdInfo_readPropertyFile(i, bootPropPath, i->bootProperties);
+ free(bootPropPath);
+ }
+
+ _avdInfo_extractBuildProperties(i);
/* TODO: find a way to provide better information from the build files */
i->deviceName = ASTRDUP("<build>");
@@ -881,22 +961,22 @@
char* kernelPath = _avdInfo_getContentOrSdkFilePath(i, imageName);
- if (kernelPath == NULL && i->inAndroidBuild) {
+ do {
+ if (kernelPath || !i->inAndroidBuild)
+ break;
+
/* When in the Android build, look into the prebuilt directory
* for our target architecture.
*/
char temp[PATH_MAX], *p = temp, *end = p + sizeof(temp);
const char* suffix = "";
- char* abi;
- /* If the target ABI is armeabi-v7a, then look for
- * kernel-qemu-armv7 instead of kernel-qemu in the prebuilt
- * directory. */
- abi = path_getBuildTargetAbi(i->androidOut);
- if (!strcmp(abi,"armeabi-v7a")) {
+ // If the target ABI is armeabi-v7a, then look for
+ // kernel-qemu-armv7 instead of kernel-qemu in the prebuilt
+ // directory.
+ if (!strcmp(i->targetAbi, "armeabi-v7a")) {
suffix = "-armv7";
}
- AFREE(abi);
p = bufprint(temp, end, "%s/prebuilts/qemu-kernel/%s/kernel-qemu%s",
i->androidBuildRoot, i->targetArch, suffix);
@@ -905,7 +985,9 @@
exit(1);
}
kernelPath = ASTRDUP(temp);
- }
+
+ } while (0);
+
return kernelPath;
}
@@ -1045,10 +1127,7 @@
avdInfo_getTargetAbi( AvdInfo* i )
{
/* For now, we can't get the ABI from SDK AVDs */
- if (!i->inAndroidBuild)
- return NULL;
-
- return path_getBuildTargetAbi(i->androidOut);
+ return ASTRDUP(i->targetAbi);
}
char*
@@ -1220,7 +1299,7 @@
int avdInfo_getAdbdCommunicationMode( AvdInfo* i )
{
- return path_getAdbdCommunicationMode(i->androidOut);
+ return propertyFile_getAdbdCommunicationMode(i->buildProperties);
}
int avdInfo_getSnapshotPresent(AvdInfo* i)
@@ -1231,3 +1310,7 @@
return iniFile_getBoolean(i->configIni, "snapshot.present", "no");
}
}
+
+const FileData* avdInfo_getBootProperties(AvdInfo* i) {
+ return i->bootProperties;
+}
diff --git a/android/avd/info.h b/android/avd/info.h
index 556ba8e..41ee309 100644
--- a/android/avd/info.h
+++ b/android/avd/info.h
@@ -15,6 +15,7 @@
#include "android/utils/ini.h"
#include "android/avd/hw-config.h"
#include "android/config/config.h"
+#include "android/utils/file_data.h"
/* An Android Virtual Device (AVD for short) corresponds to a
* directory containing all kernel/disk images for a given virtual
@@ -171,6 +172,13 @@
char* avdInfo_getDefaultDataImagePath( AvdInfo* i );
char* avdInfo_getDataInitImagePath( AvdInfo* i );
+/* Return a reference to the boot.prop file for this AVD, if any.
+ * The file contains additionnal properties to inject at boot time
+ * into the guest system. Note that this never returns NULL, but
+ * the corresponding content can be empty.
+ */
+const FileData* avdInfo_getBootProperties( AvdInfo* i );
+
/* Returns the path to a given AVD image file. This will return NULL if
* the file cannot be found / does not exist.
*/
@@ -230,6 +238,12 @@
/* Returns TRUE iff in the Android build system */
int avdInfo_inAndroidBuild( AvdInfo* i );
+/* Return the target CPU architecture for this AVD.
+ * This returns NULL if that cannot be determined, or a string that
+ * must be freed by the caller otherwise.
+ */
+char* avdInfo_getTargetCpuArch(AvdInfo* i);
+
/* Returns the target ABI for the corresponding platform image.
* This may return NULL if it cannot be determined. Otherwise this is
* a string like "armeabi", "armeabi-v7a" or "x86" that must be freed
diff --git a/android/avd/util.c b/android/avd/util.c
index 09b5ccd..d9d9d68 100644
--- a/android/avd/util.c
+++ b/android/avd/util.c
@@ -9,12 +9,14 @@
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
** GNU General Public License for more details.
*/
+#include <limits.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include "android/utils/debug.h"
#include "android/utils/bufprint.h"
#include "android/utils/ini.h"
+#include "android/utils/property_file.h"
#include "android/utils/panic.h"
#include "android/utils/path.h"
#include "android/utils/system.h"
@@ -149,6 +151,139 @@
}
+char*
+propertyFile_getTargetAbi(const FileData* data) {
+ return propertyFile_getValue((const char*)data->data,
+ data->size,
+ "ro.product.cpu.abi");
+}
+
+
+char*
+propertyFile_getTargetArch(const FileData* data) {
+ char* ret = propertyFile_getTargetAbi(data);
+ if (ret) {
+ // Translate ABI name into architecture name.
+ // By default, there are the same with a few exceptions.
+ static const struct {
+ const char* input;
+ const char* output;
+ } kData[] = {
+ { "armeabi", "arm" },
+ { "armeabi-v7a", "arm" },
+ };
+ size_t n;
+ for (n = 0; n < sizeof(kData)/sizeof(kData[0]); ++n) {
+ if (!strcmp(ret, kData[n].input)) {
+ free(ret);
+ ret = ASTRDUP(kData[n].output);
+ break;
+ }
+ }
+ }
+ return ret;
+}
+
+
+int
+propertyFile_getApiLevel(const FileData* data) {
+ char* sdkVersion = propertyFile_getValue((const char*)data->data,
+ data->size,
+ "ro.build.version.sdk");
+ const int kMinLevel = 3;
+ const int kMaxLevel = 10000;
+ int level = kMinLevel;
+ if (!sdkVersion) {
+ D("Could not find target API sdkVersion / SDK version in build properties!");
+ level = kMaxLevel;
+ D("Default target API sdkVersion: %d", level);
+ } else {
+ char* end = NULL;
+ long levelLong = strtol(sdkVersion, &end, 10);
+ int level = (int)levelLong;
+ if (levelLong == LONG_MIN || levelLong == LONG_MAX ||
+ levelLong < 0 || !end || *end || level != levelLong) {
+ level = kMinLevel;
+ D("Invalid SDK version build property: '%s'", sdkVersion);
+ D("Defaulting to target API sdkVersion %d", level);
+ } else {
+ D("Found target API sdkVersion: %d\n", level);
+ }
+ }
+ free(sdkVersion);
+ return level;
+}
+
+
+int
+propertyFile_getAdbdCommunicationMode(const FileData* data) {
+ char* prop = propertyFile_getValue((const char*)data->data,
+ data->size,
+ "ro.adb.qemud");
+ if (!prop) {
+ // No ro.adb.qemud means 'legacy' ADBD.
+ return 0;
+ }
+
+ char* end;
+ long val = strtol(prop, &end, 10);
+ if (end == NULL || *end != '\0' || val != (int)val) {
+ D("Invalid ro.adb.qemud build property: '%s'", prop);
+ val = 0;
+ } else {
+ D("Found ro.adb.qemud build property: %d", val);
+ }
+ AFREE(prop);
+ return (int)val;
+}
+
+
+char* path_getBuildBuildProp(const char* androidOut) {
+ char temp[MAX_PATH], *p = temp, *end = p + sizeof(temp);
+ p = bufprint(temp, end, "%s/system/build.prop", androidOut);
+ if (p >= end) {
+ D("ANDROID_BUILD_OUT is too long: %s\n", androidOut);
+ return NULL;
+ }
+ if (!path_exists(temp)) {
+ D("Cannot find build properties file: %s\n", temp);
+ return NULL;
+ }
+ return ASTRDUP(temp);
+}
+
+
+char* path_getBuildBootProp(const char* androidOut) {
+ char temp[MAX_PATH], *p = temp, *end = p + sizeof(temp);
+ p = bufprint(temp, end, "%s/boot.prop", androidOut);
+ if (p >= end) {
+ D("ANDROID_BUILD_OUT is too long: %s\n", androidOut);
+ return NULL;
+ }
+ if (!path_exists(temp)) {
+ D("Cannot find boot properties file: %s\n", temp);
+ return NULL;
+ }
+ return ASTRDUP(temp);
+}
+
+
+char*
+path_getBuildTargetArch(const char* androidOut) {
+ char* buildPropPath = path_getBuildBuildProp(androidOut);
+ if (!buildPropPath) {
+ return NULL;
+ }
+
+ FileData buildProp[1];
+ fileData_initFromFile(buildProp, buildPropPath);
+ char* ret = propertyFile_getTargetArch(buildProp);
+ fileData_done(buildProp);
+ AFREE(buildPropPath);
+ return ret;
+}
+
+
static char*
_getAvdTargetArch(const char* avdPath)
{
@@ -174,181 +309,11 @@
{
char* avdPath = _getAvdContentPath(avdName);
char* avdArch = _getAvdTargetArch(avdPath);
+ AFREE(avdPath);
return avdArch;
}
-/* Retrieves the value of a given system property defined in a .prop
- * file. This is a text file that contains definitions of the format:
- * <name>=<value>
- *
- * Returns NULL if property <name> is undefined or empty.
- * Returned string must be freed by the caller.
- */
-static char*
-_getSystemProperty( const char* propFile, const char* propName )
-{
- FILE* file;
- char temp[PATH_MAX], *p=temp, *end=p+sizeof(temp);
- int propNameLen = strlen(propName);
- char* result = NULL;
-
- file = fopen(propFile, "rb");
- if (file == NULL) {
- D("Could not open file: %s: %s", propFile, strerror(errno));
- return NULL;
- }
-
- while (fgets(temp, sizeof temp, file) != NULL) {
- /* Trim trailing newlines, if any */
- p = memchr(temp, '\0', sizeof temp);
- if (p == NULL)
- p = end;
- if (p > temp && p[-1] == '\n') {
- *--p = '\0';
- }
- if (p > temp && p[-1] == '\r') {
- *--p = '\0';
- }
- /* force zero-termination in case of full-buffer */
- if (p == end)
- *--p = '\0';
-
- /* check that the line starts with the property name */
- if (memcmp(temp, propName, propNameLen) != 0) {
- continue;
- }
- p = temp + propNameLen;
-
- /* followed by an equal sign */
- if (p >= end || *p != '=')
- continue;
- p++;
-
- /* followed by something */
- if (p >= end || !*p)
- break;
-
- result = ASTRDUP(p);
- break;
- }
- fclose(file);
- return result;
-}
-
-static char*
-_getBuildProperty( const char* androidOut, const char* propName )
-{
- char temp[PATH_MAX], *p=temp, *end=p+sizeof(temp);
-
- p = bufprint(temp, end, "%s/system/build.prop", androidOut);
- if (p >= end) {
- D("%s: ANDROID_PRODUCT_OUT too long: %s", __FUNCTION__, androidOut);
- return NULL;
- }
- return _getSystemProperty(temp, propName);
-}
-
-char*
-path_getBuildTargetArch( const char* androidOut )
-{
- const char* defaultArch = "arm";
- char* result = NULL;
- char* cpuAbi = _getBuildProperty(androidOut, "ro.product.cpu.abi");
-
- if (cpuAbi == NULL) {
- D("Coult not find CPU ABI in build properties!");
- D("Default target architecture=%s", defaultArch);
- result = ASTRDUP(defaultArch);
- } else {
- /* Translate ABI to cpu arch if necessary */
- if (!strcmp("armeabi",cpuAbi))
- result = "arm";
- else if (!strcmp("armeabi-v7a", cpuAbi))
- result = "arm";
- else if (!strncmp("mips", cpuAbi, 4))
- result = "mips";
- else
- result = cpuAbi;
-
- D("Found target ABI=%s, architecture=%s", cpuAbi, result);
- result = ASTRDUP(result);
- AFREE(cpuAbi);
- }
- return result;
-}
-
-char*
-path_getBuildTargetAbi( const char* androidOut )
-{
- const char* defaultAbi = "armeabi";
- char* result = NULL;
- char* cpuAbi = _getBuildProperty(androidOut, "ro.product.cpu.abi");
-
- if (cpuAbi == NULL) {
- D("Coult not find CPU ABI in build properties!");
- D("Default target ABI: %s", defaultAbi);
- result = ASTRDUP(defaultAbi);
- } else {
- D("Found target ABI=%s", cpuAbi);
- result = cpuAbi;
- }
- return result;
-}
-
-
-int
-path_getBuildTargetApiLevel( const char* androidOut )
-{
- const int defaultLevel = 1000;
- int level = defaultLevel;
- char* sdkVersion = _getBuildProperty(androidOut, "ro.build.version.sdk");
-
- if (sdkVersion != NULL) {
- long value;
- char* end;
- value = strtol(sdkVersion, &end, 10);
- if (end == NULL || *end != '\0' || value != (int)value) {
- D("Invalid SDK version build property: '%s'", sdkVersion);
- D("Defaulting to target API level %d", level);
- } else {
- level = (int)value;
- /* Sanity check, the Android SDK doesn't support anything
- * before Android 1.5, a.k.a API level 3 */
- if (level < 3)
- level = 3;
- D("Found target API level: %d", level);
- }
- AFREE(sdkVersion);
- } else {
- D("Could not find target API level / SDK version in build properties!");
- D("Default target API level: %d", level);
- }
- return level;
-}
-
-int
-path_getAdbdCommunicationMode( const char* androidOut )
-{
- char* prop = _getBuildProperty(androidOut, "ro.adb.qemud");
- if (prop != NULL) {
- long val = 0;
- char* end;
- val = strtol(prop, &end, 10);
- if (end == NULL || *end != '\0' || val != (int)val) {
- D("Invalid ro.adb.qemud build property: '%s'", prop);
- val = 0;
- } else {
- D("Found ro.adb.qemud build property: %d", val);
- }
- AFREE(prop);
- return (int)val;
- } else {
- /* Missing ro.adb.qemud means "legacy" ADBD. */
- return 0;
- }
-}
-
static int
_isExt4Image( FILE* file )
{
diff --git a/android/avd/util.h b/android/avd/util.h
index f53e5e0..101876d 100644
--- a/android/avd/util.h
+++ b/android/avd/util.h
@@ -12,6 +12,11 @@
#ifndef _ANDROID_AVD_UTIL_H
#define _ANDROID_AVD_UTIL_H
+#include "android/utils/compiler.h"
+#include "android/utils/file_data.h"
+
+ANDROID_BEGIN_HEADER
+
/* A collection of simple functions to extract relevant AVD-related
* information either from an SDK AVD or a platform build.
*/
@@ -39,40 +44,54 @@
char* path_getAvdTargetArch( const char* avdName );
/* Retrieves a string corresponding to the target architecture
- * when in the Android platform tree. The only way to do that
- * properly for now is to look at $OUT/system/build.prop:
+ * extracted from a build properties file.
*
- * ro.product.cpu-abi=<abi>
- *
- * Where <abi> can be 'armeabi', 'armeabi-v7a' or 'x86'.
+ * |data| is a FileData instance holding the build.prop contents.
+ * Returns a a new string that must be freed by the caller, which can
+ * be 'armeabi', 'armeabi-v7a', 'x86', etc... or NULL if
+ * it cannot be determined.
+ */
+char* propertyFile_getTargetAbi(const FileData* data);
+
+/* Retrieves a string corresponding to the target architecture
+ * extracted from a build properties file.
+ *
+ * |data| is a FileData instance holding the build.prop contents.
+ * Returns a new string that must be freed by the caller, which can
+ * be 'arm', 'x86, 'mips', etc..., or NULL if if cannot be determined.
+ */
+char* propertyFile_getTargetArch(const FileData* data);
+
+/* Retrieve the target API level from the build.prop contents.
+ * Returns a very large value (e.g. 100000) if it cannot be determined
+ * (which happens for platform builds), or 3 (the minimum SDK API level)
+ * if there is invalid value.
+ */
+int propertyFile_getApiLevel(const FileData* data);
+
+/* Retrieve the mode describing how the ADB daemon is communicating with
+ * the emulator from inside the guest.
+ * Return 0 for legacy mode, which uses TCP port 5555.
+ * Return 1 for the 'qemud' mode, which uses a QEMUD service instead.
+ */
+int propertyFile_getAdbdCommunicationMode(const FileData* data);
+
+/* Return the path of the build properties file (build.prop) from an
+ * Android platform build, or NULL if it doesn't exist.
+ */
+char* path_getBuildBuildProp( const char* androidOut );
+
+/* Return the path of the boot properties file (boot.prop) from an
+ * Android platform build, or NULL if it doesn't exit.
+ */
+char* path_getBuildBootProp( const char* androidOut );
+
+/* Return the target architecture from the build properties file
+ * (build.prop) of an Android platformn build. Return NULL or a new
+ * string that must be freed by the caller.
*/
char* path_getBuildTargetArch( const char* androidOut );
-/* Retrieves a string corresponding to the target CPU ABI
- * when in the Android platform tree. The only way to do that
- * properly for now is to look at $OUT/system/build.prop:
- *
- * ro.product.cpu-abi=<abi>
- *
- * Where <abi> can be 'armeabi', 'armeabi-v7a' or 'x86'.
- */
-char* path_getBuildTargetAbi( const char* androidOut );
-
-/* Retrieve the target API level when in the Android platform tree.
- * This can be a very large number like 1000 if the value cannot
- * be extracted from the appropriate file
- */
-int path_getBuildTargetApiLevel( const char* androidOut );
-
-/* Returns mode in which ADB daemon running in the guest communicates with the
- * emulator
- * Return:
- * 0 - ADBD communicates with the emulator via forwarded TCP port 5555 (a
- * "legacy" mode).
- * 1 - ADBD communicates with the emulator via 'adb' QEMUD service.
- */
-int path_getAdbdCommunicationMode( const char* androidOut );
-
/* Check whether the image file is Ext4 or not.
*
* Return:
@@ -81,4 +100,6 @@
*/
int path_isExt4Image( const char* imagePath );
+ANDROID_END_HEADER
+
#endif /* _ANDROID_AVD_UTIL_H */
diff --git a/android/main-emulator.c b/android/main-emulator.c
index 2fe1290..3436a3c 100644
--- a/android/main-emulator.c
+++ b/android/main-emulator.c
@@ -134,10 +134,11 @@
/* Otherwise, using the ANDROID_PRODUCT_OUT directory */
const char* androidOut = getenv("ANDROID_PRODUCT_OUT");
- if (androidOut != NULL && *androidOut != '\0') {
+ if (androidOut != NULL) {
D("Found ANDROID_PRODUCT_OUT: %s\n", androidOut);
avdArch = path_getBuildTargetArch(androidOut);
- D("Found build target architecture: %s\n", avdArch);
+ D("Found build target architecture: %s\n",
+ avdArch ? avdArch : "<NULL>");
}
}
diff --git a/android/main.c b/android/main.c
index e0797d9..dc8bc83 100644
--- a/android/main.c
+++ b/android/main.c
@@ -41,6 +41,7 @@
#include "android/utils/filelock.h"
#include "android/utils/lineinput.h"
#include "android/utils/path.h"
+#include "android/utils/property_file.h"
#include "android/utils/tempfile.h"
#include "android/main-common.h"
@@ -991,7 +992,22 @@
args[n++] = "off";
}
- /* Pass boot properties to the core. */
+ /* Pass boot properties to the core. First, those from boot.prop,
+ * then those from the command-line */
+ const FileData* bootProperties = avdInfo_getBootProperties(avd);
+ if (!fileData_isEmpty(bootProperties)) {
+ PropertyFileIterator iter[1];
+ propertyFileIterator_init(iter,
+ bootProperties->data,
+ bootProperties->size);
+ while (propertyFileIterator_next(iter)) {
+ char temp[MAX_PROPERTY_NAME_LEN + MAX_PROPERTY_VALUE_LEN + 2];
+ snprintf(temp, sizeof temp, "%s=%s", iter->name, iter->value);
+ args[n++] = "-boot-property";
+ args[n++] = ASTRDUP(temp);
+ }
+ }
+
if (opts->prop != NULL) {
ParamList* pl = opts->prop;
for ( ; pl != NULL; pl = pl->next ) {
diff --git a/android/utils/file_data.c b/android/utils/file_data.c
new file mode 100644
index 0000000..84dd8fe
--- /dev/null
+++ b/android/utils/file_data.c
@@ -0,0 +1,174 @@
+// 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.
+
+#include "android/utils/file_data.h"
+
+#include "android/utils/panic.h"
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+// Use a magic value in the |flags| field to indicate that a FileData
+// value was properly initialized. Helps catch errors at runtime.
+#define FILE_DATA_MAGIC ((size_t)0x87002013U)
+
+
+bool fileData_isValid(const FileData* data) {
+ if (!data)
+ return false;
+ if (data->flags == FILE_DATA_MAGIC)
+ return true;
+ if (data->flags == 0 && data->data == NULL && data->size == 0)
+ return true;
+ return false;
+}
+
+bool fileData_isInited(const FileData* data) {
+ return (data->flags == FILE_DATA_MAGIC);
+}
+
+static inline void fileData_setValid(FileData* data) {
+ data->flags = FILE_DATA_MAGIC;
+}
+
+
+static inline void fileData_setInvalid(FileData* data) {
+ data->flags = (size_t)0xDEADBEEFU;
+}
+
+
+static void fileData_initWith(FileData* data,
+ const void* buff,
+ size_t size) {
+ data->data = size ? (uint8_t*)buff : NULL;
+ data->size = size;
+ fileData_setValid(data);
+}
+
+
+void fileData_initEmpty(FileData* data) {
+ fileData_initWith(data, NULL, 0);
+}
+
+
+int fileData_initFromFile(FileData* data, const char* filePath) {
+ if (fileData_isInited(data)) {
+ APANIC("Trying to re-init a FileData instance\n");
+ }
+
+ FILE* f = fopen(filePath, "rb");
+ if (!f)
+ return -errno;
+
+ int ret = 0;
+ do {
+ if (fseek(f, 0, SEEK_END) < 0) {
+ ret = -errno;
+ break;
+ }
+
+ long fileSize = ftell(f);
+ if (fileSize < 0) {
+ ret = -errno;
+ break;
+ }
+
+ if (fileSize == 0) {
+ fileData_initEmpty(data);
+ break;
+ }
+
+ if (fseek(f, 0, SEEK_SET) < 0) {
+ ret = -errno;
+ break;
+ }
+
+ char* buffer = malloc((size_t)fileSize);
+ if (!buffer) {
+ ret = -errno;
+ break;
+ }
+
+ size_t readLen = fread(buffer, 1, (size_t)fileSize, f);
+ if (readLen != (size_t)fileSize) {
+ if (feof(f)) {
+ ret = -EIO;
+ } else {
+ ret = -ferror(f);
+ }
+ break;
+ }
+
+ fileData_initWith(data, buffer, readLen);
+
+ } while (0);
+
+ fclose(f);
+ return ret;
+}
+
+
+int fileData_initFrom(FileData* data, const FileData* other) {
+ if (fileData_isInited(data)) {
+ APANIC("Trying to re-init a FileData instance\n");
+ }
+ if (!other || !fileData_isValid(other)) {
+ APANIC("Trying to copy an uninitialized FileData instance\n");
+ }
+ if (other->size == 0) {
+ fileData_initEmpty(data);
+ return 0;
+ }
+ void* copy = malloc(other->size);
+ if (!copy) {
+ return -errno;
+ }
+
+ memcpy(copy, other->data, other->size);
+ fileData_initWith(data, copy, other->size);
+ return 0;
+}
+
+
+int fileData_initFromMemory(FileData* data,
+ const void* input,
+ size_t inputLen) {
+ FileData other;
+ fileData_initWith(&other, input, inputLen);
+ return fileData_initFrom(data, &other);
+}
+
+
+void fileData_swap(FileData* data, FileData* other) {
+ if (!fileData_isValid(data) || !fileData_isValid(data))
+ APANIC("Trying to swap un-initialized FileData instance\n");
+
+ uint8_t* buffer = data->data;
+ data->data = other->data;
+ other->data = buffer;
+
+ size_t size = data->size;
+ data->size = other->size;
+ other->size = size;
+}
+
+
+void fileData_done(FileData* data) {
+ if (!fileData_isValid(data)) {
+ APANIC("Trying to finalize an un-initialized FileData instance\n");
+ }
+
+ free(data->data);
+ fileData_initWith(data, NULL, 0);
+ fileData_setInvalid(data);
+}
diff --git a/android/utils/file_data.h b/android/utils/file_data.h
new file mode 100644
index 0000000..5710a23
--- /dev/null
+++ b/android/utils/file_data.h
@@ -0,0 +1,78 @@
+// 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.
+
+#ifndef ANDROID_UTILS_FILE_UTILS_H
+#define ANDROID_UTILS_FILE_UTILS_H
+
+#include "android/utils/compiler.h"
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+ANDROID_BEGIN_HEADER
+
+// A simple structure used to own a buffer of heap-allocated memory that
+// typically comes from a file.
+// |data| is the address of the corresponding data in memory.
+// |size| is the size of the data in bytes.
+// |flags| is used internally, do not use it.
+// Note that |data| will always be NULL if |size| is 0.
+typedef struct {
+ uint8_t* data;
+ size_t size;
+ // private
+ size_t flags;
+} FileData;
+
+// Initializer value for a FileData instance.
+// Its important that this is all zeroes.
+#define FILE_DATA_INIT { NULL, 0, 0 }
+
+// Return true iff a |fileData| is empty.
+static inline bool fileData_isEmpty(const FileData* fileData) {
+ return fileData->size == 0;
+}
+
+// Returns true iff |fileData| is valid. Used for unit-testing.
+bool fileData_isValid(const FileData* fileData);
+
+// Initialize a FileData value to the empty value.
+void fileData_initEmpty(FileData* fileData);
+
+// Initialize a FileData value by reading the content of a given file
+// at |filePath|. On success, return 0 and initializes |fileData| properly.
+// On failure, return -errno code, and set |fileData| to FILE_DATA_INIT.
+int fileData_initFromFile(FileData* fileData, const char* filePath);
+
+// Initialize a FileData by copying a memory buffer.
+// |fileData| is the address of the FileData value to initialize.
+// |buffer| is the address of the input buffer that will be copied
+// into the FileData.
+// |bufferLen| is the buffer length in bytes.
+// Return 0 on success, -errno code on failure.
+int fileData_initFromMemory(FileData* fileData,
+ const void* buffer,
+ size_t bufferLen);
+
+// Copy a FileData value into another one. This copies the contents in
+// the heap. On success return 0, on failure -errno code.
+int fileData_initFrom(FileData* fileData, const FileData* other);
+
+// Swap two FileData values.
+void fileData_swap(FileData* fileData, FileData* other);
+
+// Finalize a FileData value. This releases the corresponding memory.
+void fileData_done(FileData* fileData);
+
+ANDROID_END_HEADER
+
+#endif // ANDROID_UTILS_FILE_UTILS_H
diff --git a/android/utils/file_data_unittest.cpp b/android/utils/file_data_unittest.cpp
new file mode 100644
index 0000000..52bdae7
--- /dev/null
+++ b/android/utils/file_data_unittest.cpp
@@ -0,0 +1,119 @@
+// 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.
+
+#include "android/utils/file_data.h"
+
+#include <gtest/gtest.h>
+
+class ScopedFileData {
+public:
+ ScopedFileData() : mStatus(0) { fileData_initEmpty(&mFileData); }
+
+ ScopedFileData(const void* buff, size_t length) {
+ mStatus = fileData_initFromMemory(&mFileData, buff, length);
+ }
+
+ explicit ScopedFileData(const ScopedFileData& other) {
+ mStatus = fileData_initFrom(&mFileData, other.ptr());
+ }
+
+ explicit ScopedFileData(const FileData* other) {
+ mStatus = fileData_initFrom(&mFileData, other);
+ }
+
+ ~ScopedFileData() { fileData_done(&mFileData); }
+
+ int status() const { return mStatus; }
+ FileData* ptr() { return &mFileData; }
+ const FileData* ptr() const { return &mFileData; }
+ FileData& operator*() { return mFileData; }
+ FileData* operator->() { return &mFileData; }
+private:
+ FileData mFileData;
+ int mStatus;
+};
+
+TEST(FileData, IsValid) {
+ EXPECT_FALSE(fileData_isValid(NULL));
+
+ FileData fakeData = { (uint8_t*)0x012345678, 12345, 23983 };
+ EXPECT_FALSE(fileData_isValid(&fakeData));
+}
+
+TEST(FileData, InitializerConstant) {
+ FileData data = FILE_DATA_INIT;
+ EXPECT_TRUE(fileData_isValid(&data));
+ EXPECT_TRUE(fileData_isEmpty(&data));
+}
+
+TEST(FileData, InitializerIsFullOfZeroes) {
+ FileData data;
+ memset(&data, 0, sizeof data);
+ EXPECT_TRUE(fileData_isEmpty(&data));
+ EXPECT_TRUE(fileData_isValid(&data));
+}
+
+TEST(FileData, EmptyInitializer) {
+ ScopedFileData data;
+ EXPECT_EQ(0, data.status());
+ EXPECT_TRUE(fileData_isEmpty(data.ptr()));
+}
+
+TEST(FileData, InitEmpty) {
+ ScopedFileData data("", 0U);
+ EXPECT_EQ(0, data.status());
+ EXPECT_TRUE(fileData_isEmpty(data.ptr()));
+ EXPECT_FALSE(data->data);
+ EXPECT_EQ(0U, data->size);
+}
+
+TEST(FileData, InitFromMemory) {
+ static const char kData[] = "Hello World!";
+ ScopedFileData data(kData, sizeof kData);
+ EXPECT_EQ(0, data.status());
+ EXPECT_NE(kData, (const char*)data->data);
+ for (size_t n = 0; n < sizeof kData; ++n) {
+ EXPECT_EQ(kData[n], data->data[n]) << "index " << n;
+ }
+}
+
+TEST(FileData, InitFromOther) {
+ static const char kData[] = "Hello World!";
+ ScopedFileData data1(kData, sizeof kData);
+ EXPECT_EQ(0, data1.status());
+
+ ScopedFileData data2(data1.ptr());
+ EXPECT_EQ(0, data2.status());
+
+ EXPECT_EQ(data1->size, data2->size);
+ EXPECT_NE(data1->data, data2->data);
+ for (size_t n = 0; n < data1->size; ++n) {
+ EXPECT_EQ(kData[n], data1->data[n]) << "index " << n;
+ EXPECT_EQ(kData[n], data2->data[n]) << "index " << n;
+ }
+}
+
+TEST(FileData, Swap) {
+ static const char kData[] = "Hello World!";
+ ScopedFileData data1(kData, sizeof kData);
+ EXPECT_EQ(0, data1.status());
+
+ ScopedFileData data2;
+ EXPECT_EQ(0, data2.status());
+ fileData_swap(data1.ptr(), data2.ptr());
+
+ EXPECT_TRUE(fileData_isEmpty(data1.ptr()));
+ EXPECT_FALSE(fileData_isEmpty(data2.ptr()));
+ EXPECT_EQ(sizeof kData, data2->size);
+ for (size_t n = 0; n < data2->size; ++n) {
+ EXPECT_EQ(kData[n], data2->data[n]) << "index " << n;
+ }
+}
diff --git a/android/utils/property_file.c b/android/utils/property_file.c
new file mode 100644
index 0000000..2e34678
--- /dev/null
+++ b/android/utils/property_file.c
@@ -0,0 +1,113 @@
+// 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.
+
+#include "android/utils/property_file.h"
+
+#include "android/utils/system.h"
+
+#include <string.h>
+#include <stdlib.h>
+
+// Return true iff |ch| is whitespace. Don't use isspace() to avoid
+// locale-related results and associated issues.
+static int isspace(int ch) {
+ return (ch == ' ' || ch == '\t');
+}
+
+void propertyFileIterator_init(PropertyFileIterator* iter,
+ const void* propFile,
+ size_t propFileLen) {
+ iter->name[0] = '\0';
+ iter->value[0] = '\0';
+ iter->p = propFile;
+ iter->end = iter->p + propFileLen;
+}
+
+
+bool propertyFileIterator_next(PropertyFileIterator* iter) {
+ const char* p = iter->p;
+ const char* end = iter->end;
+ while (p < end) {
+ // Get end of line, and compute next line position.
+ const char* line = p;
+ const char* lineEnd = (const char*)memchr(p, '\n', end - p);
+ if (!lineEnd) {
+ lineEnd = end;
+ p = end;
+ } else {
+ p = lineEnd + 1;
+ }
+
+ // Remove trailing \r before the \n, if any.
+ if (lineEnd > line && lineEnd[-1] == '\r')
+ lineEnd--;
+
+ // Skip leading whitespace.
+ while (line < lineEnd && isspace(line[0]))
+ line++;
+
+ // Skip empty lines, and those that begin with '#' for comments.
+ if (lineEnd == line || line[0] == '#')
+ continue;
+
+ const char* name = line;
+ const char* nameEnd =
+ (const char*)memchr(name, '=', lineEnd - name);
+ if (!nameEnd) {
+ // Skipping lines without a =
+ continue;
+ }
+ const char* value = nameEnd + 1;
+ while (nameEnd > name && isspace(nameEnd[-1]))
+ nameEnd--;
+
+ size_t nameLen = nameEnd - name;
+ if (nameLen == 0 || nameLen >= MAX_PROPERTY_NAME_LEN) {
+ // Skip lines without names, or with names too long.
+ continue;
+ }
+
+ memcpy(iter->name, name, nameLen);
+ iter->name[nameLen] = '\0';
+
+ // Truncate value's length.
+ size_t valueLen = (lineEnd - value);
+ if (valueLen >= MAX_PROPERTY_VALUE_LEN)
+ valueLen = (MAX_PROPERTY_VALUE_LEN - 1);
+
+ memcpy(iter->value, value, valueLen);
+ iter->value[valueLen] = '\0';
+
+ iter->p = p;
+ return true;
+ }
+ iter->p = p;
+ return false;
+}
+
+char* propertyFile_getValue(const char* propFile,
+ size_t propFileLen,
+ const char* propName) {
+ size_t propNameLen = strlen(propName);
+ if (propNameLen >= MAX_PROPERTY_NAME_LEN)
+ return NULL;
+
+ char* ret = NULL;
+ PropertyFileIterator iter[1];
+ propertyFileIterator_init(iter, propFile, propFileLen);
+ while (propertyFileIterator_next(iter)) {
+ if (!strcmp(iter->name, propName)) {
+ free(ret);
+ ret = ASTRDUP(iter->value);
+ }
+ }
+ return ret;
+}
diff --git a/android/utils/property_file.h b/android/utils/property_file.h
new file mode 100644
index 0000000..88b532a
--- /dev/null
+++ b/android/utils/property_file.h
@@ -0,0 +1,78 @@
+// 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.
+
+#ifndef ANDROID_UTILS_PROPERTY_FILE_H
+#define ANDROID_UTILS_PROPERTY_FILE_H
+
+#include <stdbool.h>
+#include <stddef.h>
+
+#include "android/utils/compiler.h"
+
+ANDROID_BEGIN_HEADER
+
+// Parse the content of a property file and retrieve the value of a given
+// named property, or NULL if it is undefined or empty. If a property
+// appears several times in a file, the last definition is returned.
+// |propertyFile| is the address of the file in memory.
+// |propertyFileLen| is its length in bytes.
+// |propertyName| is the name of the property.
+char* propertyFile_getValue(const char* propertyFile,
+ size_t propertyFileLen,
+ const char* propertyName);
+
+// Maximum length of a property name (including terminating zero).
+// Any property name that is equal or greater than this value will be
+// considered undefined / ignored.
+#define MAX_PROPERTY_NAME_LEN 32
+
+// Maximum length of a property value (including terminating zero).
+// Any value stored in a file that has a length equal or greater than this
+// will be truncated!.
+#define MAX_PROPERTY_VALUE_LEN 92
+
+// Structure used to hold an iterator over a property file.
+// Usage is simple:
+// 1) Initialize iterator with propertyFileIterator_init()
+//
+// 2) Call propertyFileIterator_next() in a loop. If it returns true
+// one can read the |name| and |value| zero-terminated strings to
+// get property names and values, in the order they appear in the
+// file.
+//
+// Once propertyFileIterator_next() returns false, you're done.
+//
+typedef struct {
+ char name[MAX_PROPERTY_NAME_LEN];
+ char value[MAX_PROPERTY_VALUE_LEN];
+ // private.
+ const char* p;
+ const char* end;
+} PropertyFileIterator;
+
+// Initialize a PropertyFileIterator.
+// |iter| is the iterator instance.
+// |propertyFile| is the address of the property file in memory.
+// |propertyFileLen| is its lengh in bytes.
+void propertyFileIterator_init(PropertyFileIterator* iter,
+ const void* propertyFile,
+ size_t propertyFileLen);
+
+// Extract one property from a property file iterator.
+// Returns true if there is one, or false if the iteration has stopped.
+// If true, one can read |iter->name| and |iter->value| to get the
+// property name and value, respectively, as zero-terminated strings
+// that need to be copied by the caller.
+bool propertyFileIterator_next(PropertyFileIterator* iter);
+
+ANDROID_END_HEADER
+
+#endif // ANDROID_UTILS_PROPERTY_FILE_H
diff --git a/android/utils/property_file_unittest.cpp b/android/utils/property_file_unittest.cpp
new file mode 100644
index 0000000..006eade
--- /dev/null
+++ b/android/utils/property_file_unittest.cpp
@@ -0,0 +1,152 @@
+// 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.
+
+#include "android/utils/property_file.h"
+
+#include <stdlib.h>
+
+#include <gtest/gtest.h>
+
+// Unlike std::string, accept NULL as input.
+class String {
+public:
+ explicit String(const char* str) : mStr(str) {}
+ ~String() { free(const_cast<char*>(mStr)); }
+ const char* str() const { return mStr; }
+private:
+ const char* mStr;
+};
+
+TEST(PropertyFile, EmptyFile) {
+ EXPECT_FALSE(propertyFile_getValue("", 0U, "sdk.version"));
+}
+
+TEST(PropertyFile, SingleLineFile) {
+ static const char kFile[] = "foo=bar\n";
+ String value(propertyFile_getValue(kFile, sizeof kFile, "foo"));
+ EXPECT_TRUE(value.str());
+ EXPECT_STREQ("bar", value.str());
+
+ String value2(propertyFile_getValue(kFile, sizeof kFile, "bar"));
+ EXPECT_FALSE(value2.str());
+}
+
+TEST(PropertyFile, SingleLineFileWithZeroTerminator) {
+ static const char kFile[] = "foo=bar";
+ String value(propertyFile_getValue(kFile, sizeof kFile, "foo"));
+ EXPECT_TRUE(value.str());
+ EXPECT_STREQ("bar", value.str());
+
+ String value2(propertyFile_getValue(kFile, sizeof kFile, "bar"));
+ EXPECT_FALSE(value2.str());
+}
+
+TEST(PropertyFile, MultiLineFile) {
+ static const char kFile[] =
+ "foo=bar\n"
+ "bar=zoo\n"
+ "sdk=4.2\n";
+
+ String foo(propertyFile_getValue(kFile, sizeof kFile, "foo"));
+ String bar(propertyFile_getValue(kFile, sizeof kFile, "bar"));
+ String sdk(propertyFile_getValue(kFile, sizeof kFile, "sdk"));
+
+ EXPECT_STREQ("bar", foo.str());
+ EXPECT_STREQ("zoo", bar.str());
+ EXPECT_STREQ("4.2", sdk.str());
+}
+
+TEST(PropertyFile, MultiLineFileWithZeroTerminator) {
+ static const char kFile[] =
+ "foo=bar\n"
+ "bar=zoo\n"
+ "sdk=4.2";
+
+ String foo(propertyFile_getValue(kFile, sizeof kFile, "foo"));
+ String bar(propertyFile_getValue(kFile, sizeof kFile, "bar"));
+ String sdk(propertyFile_getValue(kFile, sizeof kFile, "sdk"));
+
+ EXPECT_STREQ("bar", foo.str());
+ EXPECT_STREQ("zoo", bar.str());
+ EXPECT_STREQ("4.2", sdk.str());
+}
+
+TEST(PropertyFile, MultiLineFileWithCRLF) {
+ static const char kFile[] =
+ "foo=bar\r\n"
+ "bar=zoo\r\n"
+ "sdk=4.2\n";
+
+ String foo(propertyFile_getValue(kFile, sizeof kFile, "foo"));
+ String bar(propertyFile_getValue(kFile, sizeof kFile, "bar"));
+ String sdk(propertyFile_getValue(kFile, sizeof kFile, "sdk"));
+
+ EXPECT_STREQ("bar", foo.str());
+ EXPECT_STREQ("zoo", bar.str());
+ EXPECT_STREQ("4.2", sdk.str());
+}
+
+TEST(PropertyFile, SpaceAroundPropertyNameAndValues) {
+ static const char kFile[] = " foo = bar zoo \n";
+ String foo(propertyFile_getValue(kFile, sizeof kFile, "foo"));
+ EXPECT_STREQ(" bar zoo ", foo.str());
+}
+
+TEST(PropertyFile, ValueTooLongIsTruncated) {
+ static const char kFile[] =
+ "foo=This is an incredible long value that is going to be truncated"
+ " by the property file parser since it is longer than "
+ "MAX_PROPERTY_VALUE_LEN\n";
+ char expected[MAX_PROPERTY_VALUE_LEN];
+ memcpy(expected, kFile + 4, sizeof expected);
+ expected[(sizeof expected) - 1U] = 0;
+
+ String foo(propertyFile_getValue(kFile, sizeof kFile, "foo"));
+ EXPECT_STREQ(expected, foo.str());
+}
+
+TEST(PropertyFile, ReturnLatestVariableDefinition) {
+ static const char kFile[] = "foo=bar\nfoo=zoo\n";
+ String foo(propertyFile_getValue(kFile, sizeof kFile, "foo"));
+ EXPECT_STREQ("zoo", foo.str());
+}
+
+TEST(PropertyFile, Iterator) {
+ static const char kFile[] =
+ "foo=bar\n"
+ "this-name-is-too-long-and-will-be-ignored-by-the-parser=ahah\n"
+ "foo2=this-value-is-too-long-and-will-be-truncated-by-the-parser"
+ "-which-only-wants-something-smaller-than-92-bytes\n"
+ "foo3=bar\r\n"
+ "bar= zoo";
+
+ PropertyFileIterator iter[1];
+ propertyFileIterator_init(iter, kFile, sizeof kFile);
+
+ EXPECT_TRUE(propertyFileIterator_next(iter));
+ EXPECT_STREQ("foo", iter->name);
+ EXPECT_STREQ("bar", iter->value);
+
+ EXPECT_TRUE(propertyFileIterator_next(iter));
+ EXPECT_STREQ("foo2", iter->name);
+ EXPECT_STREQ("this-value-is-too-long-and-will-be-truncated-by-the-"
+ "parser-which-only-wants-something-small", iter->value);
+
+ EXPECT_TRUE(propertyFileIterator_next(iter));
+ EXPECT_STREQ("foo3", iter->name);
+ EXPECT_STREQ("bar", iter->value);
+
+ EXPECT_TRUE(propertyFileIterator_next(iter));
+ EXPECT_STREQ("bar", iter->name);
+ EXPECT_STREQ(" zoo", iter->value);
+
+ EXPECT_FALSE(propertyFileIterator_next(iter));
+}