Support kernels with version >= 3.10

Newer Android Linux kernels modify the way certain devices are
named, i.e.:

  /dev/qemu_pipe is renamed as /dev/goldfish_pipe.
  /dev/ttyS<num> is renamed as /dev/ttyGF<num>

This patch adds support code to the emulator to deal with this
as transparently as possible:

1) Add a new hardware property 'kernel.newDeviceNaming', a string
   which can only take the values 'autodetect', 'no', and 'yes'.

2) Support code to probe the type of a kernel image.
   IMPORTANT: The kernel implementation depends on the host
   /usr/bin/file to properly recognize kernel files and extract
   their version number. This really only works on Linux and
   OS X, and only for x86 and x86_64 kernel images.

   A future patch will implement more robust detection by
   essentially doing its own probing through libmagic or equivalent.

   Note that there doesn't seem to be any existing libmagic rules
   to recognize ARM and MIPS kernel images at the moment :-(

   See android/kernel/kernel_utils*

3) Modify the emulator startup code to perform auto-detection
   when possible (broken on Windows, and non Intel archs, see
   comment above).

4) Modify the kernel command line generation to handle the new
   TTY device naming

5) Modify the Goldfish pipe virtual device implementation
   (since the device name presented to the kernel also changed).

This should be enough to auto-detect Linux 3.10+ x86_64 kernel
images on Linux.

Change-Id: Ied517f8a1fdeb18d84fa9a12ebcdc3daa1f41d9a
diff --git a/android/kernel/kernel_utils.cpp b/android/kernel/kernel_utils.cpp
new file mode 100644
index 0000000..e396f4a
--- /dev/null
+++ b/android/kernel/kernel_utils.cpp
@@ -0,0 +1,173 @@
+// 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/kernel/kernel_utils.h"
+
+#include "android/base/Log.h"
+#include "android/base/files/ScopedStdioFile.h"
+#include "android/base/String.h"
+#include "android/kernel/kernel_utils_testing.h"
+#include "android/utils/path.h"
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+
+#define DEBUG_KERNEL  0
+
+#define KERNEL_LOG     LOG_IF(INFO, DEBUG_KERNEL)
+#define KERNEL_PLOG    PLOG_IF(INFO, DEBUG_KERNEL)
+#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
+
+namespace android {
+namespace kernel {
+
+void setFileDescriptionFunction(GetFileDescriptionFunction* file_func,
+                                void* file_opaque) {
+    sGetFileDescription = file_func ? file_func : &getFileDescription;
+    sGetFileDescriptionOpaque = file_func ? file_opaque : NULL;
+}
+
+}  // namespace kernel
+}  // namespace android
+
+bool android_pathProbeKernelType(const char* kernelPath, KernelType* ktype) {
+    String description;
+
+    if (!sGetFileDescription(sGetFileDescriptionOpaque,
+                             kernelPath,
+                             &description)) {
+        return false;
+    }
+    char* bzImage = ::strstr(description.c_str(), "bzImage");
+    if (!bzImage) {
+        KERNEL_ERROR << "Not a compressed Linux kernel image!";
+        return false;
+    }
+    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!";
+        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!";
+            return false;
+        }
+        KERNEL_LOG << "Kernel minor version: " << minor;
+
+        *ktype = (minor >= 10)
+                ? KERNEL_TYPE_3_10_OR_ABOVE : KERNEL_TYPE_LEGACY;
+    }
+    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 "";
+    }
+}