Merge "Add kernel version detection to emulator"
diff --git a/Makefile.common b/Makefile.common
index c3f6820..5e21ccd 100644
--- a/Makefile.common
+++ b/Makefile.common
@@ -150,6 +150,7 @@
 	android/utils/stralloc.c \
 	android/utils/system.c \
 	android/utils/tempfile.c \
+	android/utils/uncompress.cpp \
 	android/utils/vector.c \
 	android/utils/win32_cmdline_quote.c \
 
diff --git a/Makefile.qemu-launcher b/Makefile.qemu-launcher
index 7be48d7..9c26567 100644
--- a/Makefile.qemu-launcher
+++ b/Makefile.qemu-launcher
@@ -25,7 +25,8 @@
 LOCAL_SRC_FILES := $(qemu_launcher_SOURCES)
 LOCAL_CFLAGS := $(qemu_launcher_CFLAGS)
 LOCAL_STATIC_LIBRARIES := \
-    emulator-common
+    emulator-common \
+    emulator-zlib
 LOCAL_LDLIBS := $(qemu_launcher_LDLIBS)
 $(call gen-hw-config-defs)
 $(call end-emulator-program)
@@ -34,7 +35,8 @@
 LOCAL_SRC_FILES := $(qemu_launcher_SOURCES)
 LOCAL_CFLAGS := $(qemu_launcher_CFLAGS)
 LOCAL_STATIC_LIBRARIES := \
-    emulator64-common
+    emulator64-common \
+    emulator64-zlib
 LOCAL_LDLIBS := $(qemu_launcher_LDLIBS)
 $(call gen-hw-config-defs)
 $(call end-emulator-program)
diff --git a/android/kernel/kernel_utils.cpp b/android/kernel/kernel_utils.cpp
old mode 100644
new mode 100755
index 50b7094..cbc4f4e
--- a/android/kernel/kernel_utils.cpp
+++ b/android/kernel/kernel_utils.cpp
@@ -9,14 +9,16 @@
 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 // GNU General Public License for more details.
 
-#include "android/kernel/kernel_utils.h"
-
+#include "android/base/containers/PodVector.h"
+#include "android/base/Limits.h"
 #include "android/base/Log.h"
-#include "android/base/files/ScopedStdioFile.h"
-#include "android/base/String.h"
+#include "android/kernel/kernel_utils.h"
 #include "android/kernel/kernel_utils_testing.h"
+#include "android/utils/file_data.h"
 #include "android/utils/path.h"
+#include "android/utils/uncompress.h"
 
+#include <algorithm>
 #include <stdint.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -30,144 +32,179 @@
 #define KERNEL_ERROR   LOG_IF(ERROR, DEBUG_KERNEL)
 #define KERNEL_PERROR  PLOG_IF(ERROR, DEBUG_KERNEL)
 
-using android::base::String;
-
-namespace {
-
-#ifndef _WIN32
-// Helper class to perform launch a command through popen() and call
-// pclose() on destruction.
-class ScopedPopenFile {
-public:
-    ScopedPopenFile(const char* command) {
-        mFile = ::popen(command, "r");
-    }
-
-    FILE* get() const { return mFile; }
-
-    ~ScopedPopenFile() {
-        if (mFile) {
-            ::pclose(mFile);
-        }
-    }
-
-private:
-    FILE* mFile;
-};
-#endif  // !_WIN32
-
-bool getFileDescription(void* opaque, const char* filePath, String* text) {
-    if (!filePath) {
-        KERNEL_ERROR << "NULL path parameter";
-        return false;
-    }
-
-    if (!path_exists(filePath)) {
-        KERNEL_ERROR << "Kernel file doesn't exist: " << filePath;
-        return false;
-    }
-
-#ifdef _WIN32
-    // TODO(digit): Better/portable detection based on libmagic or something.
-    KERNEL_ERROR << "Can't detect kernel version on Windows!";
-    return false;
-#else
-    // NOTE: Use /usr/bin/file instead of 'file' because the latter can
-    // be broken in certain environments (e.g. some versions of MacPorts).
-    String command("/usr/bin/file ");
-    command += filePath;
-
-    ScopedPopenFile file(command.c_str());
-    if (!file.get()) {
-        KERNEL_PERROR << "Could not launch command: " << command.c_str();
-        return false;
-    }
-
-    String result;
-    const size_t kReserveSize = 256U;
-    result.resize(kReserveSize);
-
-    int ret = ::fread(&result[0], 1, kReserveSize, file.get());
-    if (ret < static_cast<int>(kReserveSize) && ferror(file.get())) {
-        KERNEL_ERROR << "Could not read file command output!?";
-        return false;
-    }
-    result.resize(ret);
-    text->assign(result);
-    return true;
-#endif
-}
-
-android::kernel::GetFileDescriptionFunction* sGetFileDescription =
-        getFileDescription;
-
-void* sGetFileDescriptionOpaque = NULL;
-
-}  // namespace
+using android::base::PodVector;
 
 namespace android {
 namespace kernel {
 
-void setFileDescriptionFunction(GetFileDescriptionFunction* file_func,
-                                void* file_opaque) {
-    sGetFileDescription = file_func ? file_func : &getFileDescription;
-    sGetFileDescriptionOpaque = file_func ? file_opaque : NULL;
-}
+static const char kVersionStringPrefix[] = "Linux version ";
+static const size_t kVersionStringPrefixLen = sizeof(kVersionStringPrefix) - 1U;
 
 }  // namespace kernel
 }  // namespace android
 
-bool android_pathProbeKernelType(const char* kernelPath, KernelType* ktype) {
-    String description;
+#ifndef __APPLE__
+size_t strlcpy(char* dst, const char * src, size_t size)
+{
+    size_t srcLen = strlen(src);
+    if (size > 0) {
+        size_t copyLen = std::min(srcLen, size-1);
+        memcpy(dst, src, copyLen);
+        dst[copyLen] = 0;
+    }
+    return srcLen;
+}
+#endif
 
-    if (!sGetFileDescription(sGetFileDescriptionOpaque,
-                             kernelPath,
-                             &description)) {
-        return false;
+#ifdef _WIN32
+// TODO: (vharron) move somewhere more generally useful?
+// Returns a pointer to the first occurrence of |needle| in |haystack|, or a 
+// NULL pointer if |needle| is not part of |haystack|.
+const void* memmem(const void* haystack, size_t haystackLen,
+                   const void* needle, size_t needleLen) {
+    if (needleLen == 0 ) {
+        return haystack;
     }
-    const char* bzImage = ::strstr(description.c_str(), "bzImage");
-    if (!bzImage) {
-        KERNEL_ERROR << "Not a compressed Linux kernel image!";
-        return false;
-    }
-    const char* version = ::strstr(bzImage, "version ");
-    if (!version) {
-        KERNEL_ERROR << "Could not determine version!";
-        return false;
-    }
-    version += ::strlen("version ");
-    KERNEL_LOG << "Found kernel version " << version;
 
-    char* end;
-    unsigned long major = ::strtoul(version, &end, 10);
-    if (end == version || *end != '.') {
-        KERNEL_ERROR << "Could not find kernel major version!";
+    if (haystackLen < needleLen) {
+        return NULL;
+    }
+
+    const char* haystackPos = (const char*)haystack;
+    const char* haystackEnd = haystackPos + (haystackLen - needleLen);
+    for (; haystackPos < haystackEnd; haystackPos++) {
+        if (0==memcmp(haystackPos, needle, needleLen)) {
+            return haystackPos;
+        }
+    }
+    return NULL;
+}
+#endif
+
+
+bool android_parseLinuxVersionString(const char* versionString,
+                                     KernelVersion* kernelVersion) {
+    if (strncmp(versionString, android::kernel::kVersionStringPrefix,
+                android::kernel::kVersionStringPrefixLen)) {
+        KERNEL_ERROR << "unsupported kernel version string:" << versionString;
         return false;
     }
-    KERNEL_LOG << "Kernel major version: " << major;
-    if (major > 3) {
-        *ktype = KERNEL_TYPE_3_10_OR_ABOVE;
-    } else if (major < 3) {
-        *ktype = KERNEL_TYPE_LEGACY;
-    } else /* major == 3 */ {
-        version = end + 1;
-        unsigned long minor = ::strtoul(version, &end, 10);
-        if (end == version) {
-            KERNEL_ERROR << "Could not find kernel minor version!";
+    // skip past the prefix to the version number
+    versionString += android::kernel::kVersionStringPrefixLen;
+
+    uint32_t temp = 0;
+    for (int i = 0; i < 3; i++) {
+        // skip '.' delimeters
+        while (i > 0 && *versionString == '.') {
+            versionString++;
+        }
+
+        char* end;
+        unsigned long number = ::strtoul(versionString, &end, 10);
+        if (end == versionString || number > 0xff) {
+            KERNEL_ERROR << "unsupported kernel version string:"
+                         << versionString;
             return false;
         }
-        KERNEL_LOG << "Kernel minor version: " << minor;
-
-        *ktype = (minor >= 10)
-                ? KERNEL_TYPE_3_10_OR_ABOVE : KERNEL_TYPE_LEGACY;
+        temp <<= 8;
+        temp |= number;
+        versionString = end;
     }
+    *kernelVersion = (KernelVersion)temp;
+
+    KERNEL_LOG << android::base::LogString("Kernel version hex 0x%06x", temp);
     return true;
 }
 
-const char* android_kernelSerialDevicePrefix(KernelType ktype) {
-    switch (ktype) {
-        case KERNEL_TYPE_LEGACY: return "ttyS";
-        case KERNEL_TYPE_3_10_OR_ABOVE: return "ttyGF";
-        default: return "";
+const char* android_kernelSerialDevicePrefix(KernelVersion kernelVersion) {
+    if (kernelVersion >= KERNEL_VERSION_3_10_0) {
+        return "ttyGF";
     }
+    return "ttyS";
+}
+
+bool android_imageProbeKernelVersionString(const uint8_t* kernelFileData,
+                                           size_t kernelFileSize,
+                                           char* dst/*[dstLen]*/,
+                                           size_t dstLen) {
+    PodVector<uint8_t> uncompressed;
+
+    const uint8_t* uncompressedKernel = NULL;
+    size_t uncompressedKernelLen = 0;
+
+    const char kElfHeader[] = { 0x7f, 'E', 'L', 'F' };
+
+    if (kernelFileSize < sizeof(kElfHeader)) {
+        KERNEL_ERROR << "Kernel image too short";
+        return false;
+    }
+
+    if (0 == memcmp(kElfHeader, kernelFileData, sizeof(kElfHeader))) {
+        // this is an uncompressed ELF file (probably mips)
+        uncompressedKernel = kernelFileData;
+        uncompressedKernelLen = kernelFileSize;
+    }
+    else {
+        // handle compressed kernels here
+        const uint8_t kGZipMagic[4] = { 0x1f, 0x8b, 0x08, 0x00 };
+        const uint8_t* compressedKernel = (const uint8_t*)memmem(kernelFileData,
+                                                                 kernelFileSize,
+                                                                 kGZipMagic,
+                                                                 sizeof(kGZipMagic));
+        if (!compressedKernel) {
+            KERNEL_ERROR << "Could not find gzip header in kernel file!";
+            return false;
+        }
+
+        size_t compressedKernelLen = kernelFileSize -
+            (compressedKernel - kernelFileData);
+
+        // inflate ratios for all prebuilt kernels on 2014-07-14 is 1.9:1 ~
+        // 3.43:1 not sure how big the uncompressed size is, so make an
+        // absurdly large buffer
+        uncompressedKernelLen = compressedKernelLen * 10;
+        uncompressed.resize(uncompressedKernelLen);
+        uncompressedKernel = uncompressed.begin();
+
+        bool zOk = uncompress_gzipStream(uncompressed.begin(),
+                                         &uncompressedKernelLen,
+                                         compressedKernel,
+                                         compressedKernelLen);
+        if (!zOk) {
+            KERNEL_ERROR << "Kernel decompression error";
+            // it may have been partially decompressed, so we're going to 
+            // try to find the version string anyway
+        }
+    }
+
+    // okay, now we have a pointer to an uncompressed kernel, let's find the
+    // version string
+    const char* versionStringStart = (const char*)memmem(
+        uncompressedKernel,
+        uncompressedKernelLen,
+        android::kernel::kVersionStringPrefix,
+        android::kernel::kVersionStringPrefixLen);
+    if (!versionStringStart) {
+        KERNEL_ERROR << "Could not find 'Linux version ' in kernel!";
+        return false;
+    }
+
+    strlcpy(dst, versionStringStart, dstLen);
+
+    return true;
+}
+
+bool android_pathProbeKernelVersionString(const char* kernelPath,
+                                          char* dst/*[dstLen]*/,
+                                          size_t dstLen) {
+    FileData kernelFileData;
+    if (fileData_initFromFile(&kernelFileData, kernelPath) < 0) {
+        KERNEL_ERROR << "Could not open kernel file!";
+        return false;
+    }
+
+    return android_imageProbeKernelVersionString(kernelFileData.data,
+                                                 kernelFileData.size,
+                                                 dst,
+                                                 dstLen);
 }
diff --git a/android/kernel/kernel_utils.h b/android/kernel/kernel_utils.h
old mode 100644
new mode 100755
index e10eb34..2e6fb45
--- a/android/kernel/kernel_utils.h
+++ b/android/kernel/kernel_utils.h
@@ -13,6 +13,8 @@
 #define ANDROID_KERNEL_KERNEL_UTILS_H
 
 #include "android/utils/compiler.h"
+
+#include <stddef.h>
 #include <stdint.h>
 
 ANDROID_BEGIN_HEADER
@@ -20,24 +22,36 @@
 // An enum used to list of the types of Linux kernel images we need to
 // handle. Unfortunately, this affects how we setup the kernel command-line
 // when launching the system.
-//
-// KERNEL_TYPE_LEGACY is any Linux kernel image before 3.10
-// KERNEL_TYPE_3_10_OR_ABOVE is anything at 3.10 or above.
 typedef enum {
-    KERNEL_TYPE_LEGACY = 0,
-    KERNEL_TYPE_3_10_OR_ABOVE = 1,
-} KernelType;
+    KERNEL_VERSION_2_6_29 = 0x02061d,
+    KERNEL_VERSION_3_4_0  = 0x030400,
+    KERNEL_VERSION_3_4_67 = 0x030443,
+    KERNEL_VERSION_3_10_0 = 0x030a00,
+} KernelVersion;
 
-// Probe the kernel image at |kernelPath| and returns the corresponding
-// KernelType value. On success, returns true and sets |*ktype| appropriately.
+// Converts a string at |versionString| in the format "Linux version MM.mm.rr..."
+// into a hex value 0x00MMmmrr.  On success, returns true and sets |*kernelVersion|
+// appropriately
+// On failure, returns false and doesn't touch |*kernelVersion|.
+bool android_parseLinuxVersionString(const char* versionString,
+                                     KernelVersion* kernelVersion);
+
+// Probe the kernel image at |kernelPath| and copy the corresponding
+// 'Linux version ' string into the |dst| buffer.  On success, returns true
+// and copies up to |dstLen-1| characters into dst.  dst will always be NUL
+// terminated if |dstLen| >= 1
+//
 // On failure (e.g. if the file doesn't exist or cannot be probed), return
-// false and doesn't touch |*ktype|.
-bool android_pathProbeKernelType(const char* kernelPath, KernelType *ktype);
+// false and doesn't touch |dst| buffer.
+bool android_pathProbeKernelVersionString(const char* kernelPath,
+                                          char* dst,
+                                          size_t dstLen);
 
-// Return the serial device name prefix matching a given kernel type |ktype|.
-// I.e. this should be "/dev/ttyS" for KERNEL_TYPE_LEGACY, and
-// "/dev/ttyGF" for KERNEL_TYPE_3_10_OR_ABOVE.
-const char* android_kernelSerialDevicePrefix(KernelType ktype);
+// Return the serial device name prefix matching a given kernel type 
+// |kernelVersion|.  I.e. this should be "/dev/ttyS" for before 3.10.0 and 
+// "/dev/ttyGF" for 3.10.0 and later.
+const char* android_kernelSerialDevicePrefix(KernelVersion kernelVersion);
+
 
 ANDROID_END_HEADER
 
diff --git a/android/kernel/kernel_utils_testing.h b/android/kernel/kernel_utils_testing.h
old mode 100644
new mode 100755
index f2d64ec..51b476a
--- a/android/kernel/kernel_utils_testing.h
+++ b/android/kernel/kernel_utils_testing.h
@@ -12,36 +12,17 @@
 #ifndef ANDROID_KERNEL_KERNEL_UTILS_TESTING_H
 #define ANDROID_KERNEL_KERNEL_UTILS_TESTING_H
 
-namespace android {
-
-namespace base {
-class String;
-}  // namespace base
-
-namespace kernel {
-
-// Type of a function used to retrieve the textual description of a given
-// file at |filePath|. On success, return true and sets |*text| to the
-// description text, as if running through the 'file' command on Unix.
-// |opaque| is a client-provided value set by calling
-// setFileDescriptionFunction() below.
-typedef bool (GetFileDescriptionFunction)(void* opaque,
-                                          const char* filePath,
-                                          android::base::String* text);
-
-// Change the implementation of the function that extracts textual
-// descriptions from a given file. |file_func| is a pointer to the
-// new function, and |file_opaque| is the value that will be passed
-// as its first parameter. Note that if |file_func| is NULL, the
-// default implementation will be selected instead.
+// Probe the kernel image at |kernelPath| and copy the corresponding
+// 'Linux version ' string into the |dst| buffer.  On success, returns true
+// and copies up to |dstLen-1| characters into dst.  dst will always be NUL
+// terminated if |dstLen| >= 1
 //
-// Only use this during unit-testing to force different description
-// values on arbitrary file content.
-void setFileDescriptionFunction(GetFileDescriptionFunction* file_func,
-                                void* file_opaque);
+// On failure (e.g. if the file doesn't exist or cannot be probed), return
+// false and doesn't touch |dst| buffer.
+bool android_imageProbeKernelVersionString(const uint8_t* kernelFileData,
+                                           size_t kernelFileSize,
+                                           char* dst,
+                                           size_t dstLen);
 
-}  // namespace kernel
-
-}  // namespace android
 
 #endif  // ANDROID_KERNEL_KERNEL_UTILS_TESTING_H
diff --git a/android/kernel/kernel_utils_unittest.cpp b/android/kernel/kernel_utils_unittest.cpp
old mode 100644
new mode 100755
index a5afa2a..40ad06c
--- a/android/kernel/kernel_utils_unittest.cpp
+++ b/android/kernel/kernel_utils_unittest.cpp
@@ -11,121 +11,141 @@
 
 #include "android/kernel/kernel_utils.h"
 
-#include "android/base/String.h"
 #include "android/kernel/kernel_utils_testing.h"
 
 #include <gtest/gtest.h>
 
-using android::base::String;
-
 namespace android {
 namespace kernel {
 
-namespace {
-
-class ScopedDescriptionFunc {
-public:
-    explicit ScopedDescriptionFunc(GetFileDescriptionFunction* file_func) {
-        setFileDescriptionFunction(file_func, NULL);
-    }
-
-    ScopedDescriptionFunc(GetFileDescriptionFunction* file_func,
-                          void* file_opaque) {
-        setFileDescriptionFunction(file_func, file_opaque);
-    }
-
-    ~ScopedDescriptionFunc() {
-        setFileDescriptionFunction(NULL, NULL);
-    }
-};
-
-}  // namespace
-
 TEST(KernelUtils, GetKernelSerialDevicePrefix) {
     EXPECT_STREQ("ttyS",
-            android_kernelSerialDevicePrefix(KERNEL_TYPE_LEGACY));
+                 android_kernelSerialDevicePrefix(KERNEL_VERSION_2_6_29));
+    EXPECT_STREQ("ttyS",
+                 android_kernelSerialDevicePrefix(KERNEL_VERSION_3_4_0));
+    EXPECT_STREQ("ttyS",
+                 android_kernelSerialDevicePrefix(KERNEL_VERSION_3_4_67));
     EXPECT_STREQ("ttyGF",
-            android_kernelSerialDevicePrefix(KERNEL_TYPE_3_10_OR_ABOVE));
+                 android_kernelSerialDevicePrefix(KERNEL_VERSION_3_10_0));
 }
 
-static bool failFunc(void* opaque, const char* path, String* text) {
-    return false;
-}
+TEST(KernelUtils, ProbeKernelVersionString) {
+    // you can regenerate these tables using 
+    // android/kernel/testing/print_mock_kernel_data.sh
 
-TEST(KernelUtils, ProbeKernelTypeWithNoKernelFile) {
-    ScopedDescriptionFunc func(&failFunc);
+    const char kMockKernelVersion[] =
+        "Linux version 3.10.0+ (vharron@tifa.mtv.corp.google.com) "
+        "(gcc version 4.7 (GCC) ) #1 PREEMPT Sat Jan 5 2:45:24 PDT 2008\n";
 
-    KernelType ktype;
-    EXPECT_FALSE(android_pathProbeKernelType("/tmp/kernel", &ktype));
-}
-
-struct Expectation {
-    const char* description;
-    bool result;
-    KernelType ktype;
-
-    static bool getDescriptionFunc(void* opaque,
-                                   const char* path,
-                                   String* text) {
-        const Expectation* expectation = static_cast<const Expectation*>(opaque);
-        if (!expectation->result)
-            return false;
-        text->assign(expectation->description);
-        return true;
-    }
-};
-
-#ifdef _WIN32
-#define DISABLED_ON_WIN32(x)  DISABLED_ ## x
-#else
-#define DISABLED_ON_WIN32(x)  x
-#endif
-
-TEST(KernelUtils, DISABLED_ON_WIN32(PathProbeKernelType)) {
-    static const Expectation kData[] = {
-        { NULL, false, KERNEL_TYPE_LEGACY },
-        // Missing bzImage.
-        { "Linux kernel x86 boot executable raw image, version 3.10.0+",
-                false, KERNEL_TYPE_LEGACY },
-        // Missing version
-        { "Linux kernel x86 boot executable bzImage, 3.10.0+",
-                false, KERNEL_TYPE_LEGACY },
-        // Legacy 2.6.29 kernel
-        { "Linux kernel x86 boot executable bzImage, version 2.6.29 (foo...)",
-                true,
-                KERNEL_TYPE_LEGACY },
-        // Legacy 3.4
-        { "Linux kernel x86 boot executable bzImage, version 3.4.1 (foo...)",
-                true,
-                KERNEL_TYPE_LEGACY },
-        // 3.10
-        { "Linux kernel x86 boot executable bzImage, version 3.10.0+",
-                true,
-                KERNEL_TYPE_3_10_OR_ABOVE },
-        // 3.40
-        { "Linux kernel x86 boot executable bzImage, version 3.40.0",
-                true,
-                KERNEL_TYPE_3_10_OR_ABOVE },
-        // 4.0
-        { "Linux kernel x86 boot executable bzImage, version 4.0.9",
-                true,
-                KERNEL_TYPE_3_10_OR_ABOVE },
+    // a mock uncompressed kernel
+    // ELF header, followed by an unspecified number of bytes
+    // followed by a 'Linux version ' string
+    static const unsigned char kMockKernelElf[] = {
+        0x7f, 0x45, 0x4c, 0x46, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
+        0x38, 0x39, 0x4c, 0x69, 0x6e, 0x75, 0x78, 0x20, 0x76, 0x65, 0x72, 0x73,
+        0x69, 0x6f, 0x6e, 0x20, 0x33, 0x2e, 0x31, 0x30, 0x2e, 0x30, 0x2b, 0x20,
+        0x28, 0x76, 0x68, 0x61, 0x72, 0x72, 0x6f, 0x6e, 0x40, 0x74, 0x69, 0x66,
+        0x61, 0x2e, 0x6d, 0x74, 0x76, 0x2e, 0x63, 0x6f, 0x72, 0x70, 0x2e, 0x67,
+        0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x29, 0x20, 0x28,
+        0x67, 0x63, 0x63, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x20,
+        0x34, 0x2e, 0x37, 0x20, 0x28, 0x47, 0x43, 0x43, 0x29, 0x20, 0x29, 0x20,
+        0x23, 0x31, 0x20, 0x50, 0x52, 0x45, 0x45, 0x4d, 0x50, 0x54, 0x20, 0x53,
+        0x61, 0x74, 0x20, 0x4a, 0x61, 0x6e, 0x20, 0x35, 0x20, 0x32, 0x3a, 0x34,
+        0x35, 0x3a, 0x32, 0x34, 0x20, 0x50, 0x44, 0x54, 0x20, 0x32, 0x30, 0x30,
+        0x38, 0x0a, 0x00, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39
     };
-    const size_t kDataSize = sizeof(kData) / sizeof(kData[0]);
-    static const char kKernelPath[] = "/tmp/kernel";
-    for (size_t n = 0; n < kDataSize; ++n) {
-        KernelType kernelType;
-        const Expectation& expectation = kData[n];
-        ScopedDescriptionFunc func(&Expectation::getDescriptionFunc,
-                                   (void*)&expectation);
-        EXPECT_EQ(expectation.result,
-                  android_pathProbeKernelType(kKernelPath, &kernelType))
-                        << "For [" << expectation.description << "]";
-        if (expectation.result) {
-            EXPECT_EQ(expectation.ktype, kernelType) << "For ["
-                    << expectation.description << "]";
-        }
-    }
+
+    // a mock uncompressed kernel without version string
+    // ELF header, followed by an unspecified number of bytes
+    static const unsigned char kMockKernelElfNoString[] = {
+        0x7f, 0x45, 0x4c, 0x46, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
+        0x38, 0x39, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
+        0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x30, 0x31,
+        0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39
+    };
+
+    // a mock compressed kernel
+    // an unspecified number of bytes, followed by a gzip header
+    // gzip stream starts 10 bytes after gzip header, uncompressed gzip stream
+    // begins with an ELF header as above
+    static const unsigned char kMockKernelGzip[] = {
+        0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x1f, 0x8b,
+        0x08, 0x00, 0x24, 0xc7, 0xc6, 0x53, 0x00, 0x03, 0xab, 0x77, 0xf5, 0x71,
+        0x33, 0x30, 0x34, 0x32, 0x36, 0x31, 0x35, 0x33, 0xb7, 0xb0, 0xf4, 0xc9,
+        0xcc, 0x2b, 0xad, 0x50, 0x28, 0x4b, 0x2d, 0x2a, 0xce, 0xcc, 0xcf, 0x53,
+        0x30, 0xd6, 0x33, 0x34, 0xd0, 0x33, 0xd0, 0x56, 0xd0, 0x28, 0xcb, 0x48,
+        0x2c, 0x2a, 0xca, 0xcf, 0x73, 0x28, 0xc9, 0x4c, 0x4b, 0xd4, 0xcb, 0x2d,
+        0x29, 0xd3, 0x4b, 0xce, 0x2f, 0x2a, 0xd0, 0x4b, 0xcf, 0xcf, 0x4f, 0xcf,
+        0x49, 0x05, 0xb2, 0x73, 0x35, 0x15, 0x34, 0xd2, 0x93, 0x93, 0xe1, 0xfa,
+        0x4c, 0xf4, 0xcc, 0x15, 0x34, 0xdc, 0x9d, 0x9d, 0x35, 0x15, 0x34, 0x15,
+        0x94, 0x0d, 0x15, 0x02, 0x82, 0x5c, 0x5d, 0x7d, 0x03, 0x42, 0x14, 0x82,
+        0x13, 0x4b, 0x14, 0xbc, 0x12, 0xf3, 0x14, 0x4c, 0x15, 0x8c, 0xac, 0x4c,
+        0x4c, 0xad, 0x8c, 0x4c, 0x14, 0x02, 0x5c, 0x42, 0x14, 0x8c, 0x0c, 0x0c,
+        0x2c, 0xb8, 0x18, 0xe0, 0x2e, 0x00, 0x00, 0xd3, 0x6e, 0x68, 0xa6, 0x90,
+        0x00, 0x00, 0x00
+    };
+
+    char kernelVersionString[256];
+
+    kernelVersionString[0] = 0;
+    EXPECT_EQ(true, android_imageProbeKernelVersionString(
+        kMockKernelElf,
+        sizeof(kMockKernelElf),
+        kernelVersionString,
+        sizeof(kernelVersionString)));
+    EXPECT_STREQ(kMockKernelVersion, kernelVersionString);
+
+    kernelVersionString[0] = 0;
+    EXPECT_EQ(true, android_imageProbeKernelVersionString(
+        kMockKernelGzip,
+        sizeof(kMockKernelGzip),
+        kernelVersionString,
+        sizeof(kernelVersionString)));
+    EXPECT_STREQ(kMockKernelVersion, kernelVersionString);
+
+    kernelVersionString[0] = 127;
+    EXPECT_EQ(false, android_imageProbeKernelVersionString(
+        0,
+        0,
+        kernelVersionString,
+        sizeof(kernelVersionString)));
+    EXPECT_EQ(127, kernelVersionString[0]);
+
+    kernelVersionString[0] = 127;
+    EXPECT_EQ(false, android_imageProbeKernelVersionString(
+        kMockKernelElfNoString,
+        sizeof(kMockKernelElfNoString),
+        kernelVersionString,
+        sizeof(kernelVersionString)));
+    EXPECT_EQ(127, kernelVersionString[0]);
+}
+
+void ParseKernelVersionString(const char* versionString,
+                              KernelVersion expectedVersion) {
+    KernelVersion actualVersion;
+    EXPECT_EQ(true, android_parseLinuxVersionString(versionString,
+                                                    &actualVersion));
+    EXPECT_EQ(expectedVersion, actualVersion);
+}
+
+TEST(KernelUtils, ParseKernelVersionString) {
+    ParseKernelVersionString("Linux version 2.6.29-gea477bb (kroo...\n",
+                             KERNEL_VERSION_2_6_29);
+
+    ParseKernelVersionString("Linux version 2.6.29 (vcht...\n",
+                             KERNEL_VERSION_2_6_29);
+
+    ParseKernelVersionString("Linux version 3.4.0-66985-gb04946b (digi...\n",
+                             KERNEL_VERSION_3_4_0);
+
+    ParseKernelVersionString("Linux version 3.4.67+ (ghac...\n",
+                             KERNEL_VERSION_3_4_67 );
+
+    ParseKernelVersionString("Linux version 3.4.67-01413-g9ac497f (ghac...\n",
+                             KERNEL_VERSION_3_4_67);
+
+    ParseKernelVersionString("Linux version 3.10.0+ (ghac...\n",
+                             KERNEL_VERSION_3_10_0);
 }
 
 }  // namespace kernel
diff --git a/android/kernel/testing/print_mock_kernel_data.sh b/android/kernel/testing/print_mock_kernel_data.sh
new file mode 100755
index 0000000..1a1a5cb
--- /dev/null
+++ b/android/kernel/testing/print_mock_kernel_data.sh
@@ -0,0 +1,31 @@
+#!/bin/bash -e
+
+LINUX_VERSION="Linux version 3.10.0+ (vharron@tifa.mtv.corp.google.com) (gcc version 4.7 (GCC) ) #1 PREEMPT Sat Jan 5 2:45:24 PDT 2008\xA\x0"
+TMP_FILE=/tmp/print_mock_kernel_tmp
+
+printf "// a mock uncompressed kernel\n"
+printf "// ELF header, followed by an unspecified number of bytes\n"
+printf "// followed by a 'Linux version ' string\n"
+printf "static const unsigned char kMockKernelElf[] = {\n"
+printf '\x7f'ELF0123456789"${LINUX_VERSION}"0123456789 | xxd -i
+printf "};\n\n"
+
+printf "// a mock uncompressed kernel without version string\n"
+printf "// ELF header, followed by an unspecified number of bytes\n"
+printf "static const unsigned char kMockKernelElfNoString[] = {\n"
+printf '\x7f'ELF0123456789012345678901234567890123456789 | xxd -i
+printf "};\n\n"
+
+printf "// a mock compressed kernel\n"
+printf "// an unspecified number of bytes, followed by a gzip header\n"
+printf "// gzip stream starts 10 bytes after gzip header, uncompressed gzip stream\n"
+printf "// begins with an ELF header as above\n"
+printf "static const unsigned char kMockKernelGzip[] = {\n"
+printf "0123456789" > $TMP_FILE
+printf '\x7f'ELF0123456789"${LINUX_VERSION}"0123456789 | gzip >> $TMP_FILE
+cat $TMP_FILE | xxd -i
+printf "};\n\n"
+
+rm $TMP_FILE
+
+
diff --git a/android/main.c b/android/main.c
old mode 100644
new mode 100755
index b1a3053..a5b479a
--- a/android/main.c
+++ b/android/main.c
@@ -453,17 +453,26 @@
          }
     }
 
-    KernelType kernelType = KERNEL_TYPE_LEGACY;
-    if (!android_pathProbeKernelType(hw->kernel_path, &kernelType)) {
-        D("WARNING: Could not determine kernel device naming scheme. Assuming legacy\n"
-            "If this AVD doesn't boot, and uses a recent kernel (3.10 or above) try setting\n"
-            "'kernel.newDeviceNaming' to 'yes' in its configuration.\n");
+    char versionString[256];
+    if (!android_pathProbeKernelVersionString(hw->kernel_path,
+                                              versionString,
+                                              sizeof(versionString))) {
+        derror("Can't find 'Linux version ' string in kernel image file: %s",
+               hw->kernel_path);
+        exit(2);
+    }
+
+    KernelVersion kernelVersion = 0;
+    if (!android_parseLinuxVersionString(versionString, &kernelVersion)) {
+        derror("Can't parse 'Linux version ' string in kernel image file: '%s'",
+               versionString);
+        exit(2);
     }
 
     // Auto-detect kernel device naming scheme if needed.
     if (androidHwConfig_getKernelDeviceNaming(hw) < 0) {
         const char* newDeviceNaming = "no";
-        if (kernelType == KERNEL_TYPE_3_10_OR_ABOVE) {
+        if (kernelVersion >= KERNEL_VERSION_3_10_0) {
             D("Auto-detect: Kernel image requires new device naming scheme.");
             newDeviceNaming = "yes";
         } else {
diff --git a/android/qemu-launcher/emulator-qemu.cpp b/android/qemu-launcher/emulator-qemu.cpp
index 899cc9d..280bd58 100644
--- a/android/qemu-launcher/emulator-qemu.cpp
+++ b/android/qemu-launcher/emulator-qemu.cpp
@@ -299,17 +299,26 @@
         hw->kernel_path = kernelFile;
     }
 
-    KernelType kernelType = KERNEL_TYPE_LEGACY;
-    if (!android_pathProbeKernelType(hw->kernel_path, &kernelType)) {
-        D("WARNING: Could not determine kernel device naming scheme. Assuming legacy\n"
-            "If this AVD doesn't boot, and uses a recent kernel (3.10 or above) try setting\n"
-            "'kernel.newDeviceNaming' to 'yes' in its configuration.\n");
+    char versionString[256];
+    if (!android_pathProbeKernelVersionString(hw->kernel_path,
+                                              versionString,
+                                              sizeof(versionString))) {
+        derror("Can't find 'Linux version ' string in kernel image file: %s",
+               hw->kernel_path);
+        exit(2);
+    }
+
+    KernelVersion kernelVersion;
+    if (!android_parseLinuxVersionString(versionString, &kernelVersion)) {
+        derror("Can't parse 'Linux version ' string in kernel image file: '%s'",
+               versionString);
+        exit(2);
     }
 
     // Auto-detect kernel device naming scheme if needed.
     if (androidHwConfig_getKernelDeviceNaming(hw) < 0) {
         const char* newDeviceNaming = "no";
-        if (kernelType == KERNEL_TYPE_3_10_OR_ABOVE) {
+        if (kernelVersion >= KERNEL_VERSION_3_10_0) {
             D("Auto-detect: Kernel image requires new device naming scheme.");
             newDeviceNaming = "yes";
         } else {
diff --git a/android/utils/uncompress.cpp b/android/utils/uncompress.cpp
new file mode 100755
index 0000000..3a3a939
--- /dev/null
+++ b/android/utils/uncompress.cpp
@@ -0,0 +1,43 @@
+// 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/uncompress.h"
+#include "zlib.h"
+
+bool uncompress_gzipStream(uint8_t* dst, size_t* dstLen, const uint8_t* src,
+                   size_t srcLen) {
+    z_stream stream;
+    stream.next_in = (Bytef*)src;
+    stream.avail_in = srcLen;
+    stream.next_out = dst;
+    stream.avail_out = *dstLen;
+    stream.zalloc = (alloc_func)0;
+    stream.zfree = (free_func)0;
+
+    // magic number from gz_read
+    const int GZIP_WINDOW_BITS = 15 + 16;
+
+    int result = inflateInit2(&stream, GZIP_WINDOW_BITS);
+    if (result != Z_OK) {
+        return result;
+    }
+
+    result = inflate(&stream, Z_FINISH);
+    *dstLen = stream.total_out;
+    if (result == Z_STREAM_END) {
+        result = inflateEnd(&stream);
+    }
+    else {
+        // preserve error code
+        inflateEnd(&stream);
+    }
+    return result == Z_OK;
+}
diff --git a/android/utils/uncompress.h b/android/utils/uncompress.h
new file mode 100755
index 0000000..8ba8968
--- /dev/null
+++ b/android/utils/uncompress.h
@@ -0,0 +1,39 @@
+/* Copyright (C) 2009 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_UNCOMPRESS_H
+#define ANDROID_UTILS_UNCOMPRESS_H
+
+#include "android/utils/compiler.h"
+
+#include <stdint.h>
+#include <stddef.h>
+
+ANDROID_BEGIN_HEADER
+
+// uncompress a gzip file in memory into memory in one function call
+// the dstLen must be large enough to hold all the decompressed data
+//
+// src - pointer to the beginning of the gzip file data
+// srcLen - total number of bytes in the gzip file
+// dst - pointer that receives the decompressed bytes
+// dstLen - number of bytes available for decompressed data
+//
+// return values 
+// true - all data decompressed correctly
+// false - dst buffer too small or corrupt zstream or out of memory
+// |dstLen| hold the number of bytes written to dst
+bool uncompress_gzipStream(uint8_t* dst, size_t* dstLen, const uint8_t* src,
+                           size_t srcLen);
+
+ANDROID_END_HEADER
+
+#endif /* ANDROID_UTILS_UNCOMPRESS_H */