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));
+}