android/filesystems/ramdisk_extractor.h: new file.

This patch introduces a new function that can be used to
extract single data file content from an Android ramdisk.img
image.

This will be used to extract fstab.goldfish in a future patch
to reliably determine the format of each partition image before
the emulator is run.

Change-Id: I4e8178ce1cedf0e93b852c632003d089bcd41701
diff --git a/Makefile.common b/Makefile.common
index 17f886e..6d8fcca 100644
--- a/Makefile.common
+++ b/Makefile.common
@@ -124,6 +124,7 @@
 	android/emulation/CpuAccelerator.cpp \
 	android/filesystems/ext4_utils.cpp \
 	android/filesystems/partition_types.cpp \
+	android/filesystems/ramdisk_extractor.cpp \
 	android/kernel/kernel_utils.cpp \
 	android/utils/assert.c \
 	android/utils/bufprint.c \
diff --git a/Makefile.tests b/Makefile.tests
index b9a5a28..2daabff 100644
--- a/Makefile.tests
+++ b/Makefile.tests
@@ -24,6 +24,7 @@
   android/emulation/CpuAccelerator_unittest.cpp \
   android/filesystems/ext4_utils_unittest.cpp \
   android/filesystems/partition_types_unittest.cpp \
+  android/filesystems/ramdisk_extractor_unittest.cpp \
   android/filesystems/testing/TestSupport.cpp \
   android/kernel/kernel_utils_unittest.cpp \
 
diff --git a/android/filesystems/ramdisk_extractor.cpp b/android/filesystems/ramdisk_extractor.cpp
new file mode 100644
index 0000000..62e4445
--- /dev/null
+++ b/android/filesystems/ramdisk_extractor.cpp
@@ -0,0 +1,315 @@
+// 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/filesystems/ramdisk_extractor.h"
+
+#include "android/base/Compiler.h"
+#include "android/base/Log.h"
+#include "android/base/String.h"
+
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <zlib.h>
+
+#define DEBUG 0
+
+#if DEBUG
+#  define D(...)   printf(__VA_ARGS__), fflush(stdout)
+#else
+#  define D(...)   ((void)0)
+#endif
+
+// Ramdisk images are gzipped cpio archives using the new ASCII
+// format as described at [1]. Hence this source file first implements
+// a gzip-based input stream class, then 
+//
+// [1] http://people.freebsd.org/~kientzle/libarchive/man/cpio.5.txt
+
+namespace {
+
+// Helper class used to implement a gzip-based input stream.
+// Usage is as follows:
+//
+//     GZipInputStream input(filePath);
+//     if (input.error()) {
+//        fprintf(stderr, "Could not open file: %s\n",
+//                filePath, strerror(input.error()));
+//     } else {
+//        uint8_t header[32];
+//        if (!doRead(&input, header, sizeof header)) {
+//           ... error, could not read header.
+//        }
+//     }
+//      .. stream is closed automatically on scope exit.
+class GZipInputStream {
+public:
+    // Open a new input stream to read from file |filePath|.
+    // The constructor can never fail, so call error() after
+    // this to see if an error occured.
+    explicit GZipInputStream(const char* filePath) {
+        mFile = gzopen(filePath, "rb");
+        if (mFile == Z_NULL) {
+            mFile = NULL;
+            mError = errno;
+        } else {
+            mError = 0;
+        }
+    }
+
+    // Return the last error that occured on this stream,
+    // or 0 if everything's well. Note that as soon as an
+    // error occurs, the stream cannot be used anymore.
+    int error() const { return mError; }
+
+    // Close the stream, note that this is called automatically
+    // from the destructor, but clients might want to do this
+    // before.
+    void close() {
+        if (mFile) {
+            gzclose(mFile);
+            mFile = NULL;
+        }
+    }
+
+    ~GZipInputStream() {
+        close();
+    }
+
+    // Try to read up to |len| bytes of data from the input
+    // stream into |buffer|. On success, return true and sets
+    // |*readBytes| to the number of bytes that were read.
+    // On failure, return false and set error().
+    bool tryRead(void* buffer, size_t len, size_t* readBytes) {
+        *readBytes = 0;
+
+        if (mError) {
+            return false;
+        }
+
+        if (len == 0) {
+            return true;
+        }
+
+        // Warning, gzread() takes an unsigned int parameter for
+        // the length, but will return an error if its value
+        // exceeds INT_MAX anyway.
+        char* buff = reinterpret_cast<char*>(buffer);
+
+        while (len > 0) {
+            size_t avail = len;
+            if (avail > INT_MAX)
+                avail = INT_MAX;
+
+            int ret = gzread(mFile, buff, static_cast<unsigned int>(avail));
+            if (ret < 0) {
+                gzerror(mFile, &mError);
+                break;
+            }
+            if (ret == 0) {
+                if (gzeof(mFile)) {
+                    break;
+                }
+                gzerror(mFile, &mError);
+                break;
+            }
+            len -= ret;
+            buff += ret;
+            *readBytes += ret;
+        }
+
+        return (!mError && *readBytes > 0);
+    }
+
+    // Read exactly |len| bytes of data into |buffer|. On success,
+    // return true, on failure, return false and set error().
+    bool doRead(void* buffer, size_t len) {
+        size_t readCount = 0;
+        if (!tryRead(buffer, len, &readCount)) {
+            return false;
+        }
+        if (readCount < len) {
+            mError = EIO;
+            return false;
+        }
+        return true;
+    }
+
+    bool doSkip(size_t len) {
+        if (mError) {
+            return false;
+        }
+        if (gzseek(mFile, len, SEEK_CUR) < 0) {
+            gzerror(mFile, &mError);
+            return false;
+        }
+        return true;
+    }
+
+private:
+    DISALLOW_COPY_AND_ASSIGN(GZipInputStream);
+
+    gzFile mFile;
+    int mError;
+};
+
+// Parse an hexadecimal string of 8 characters. On success,
+// return true and sets |*value| to its value. On failure,
+// return false.
+bool parse_hex8(const char* input, uint32_t* value) {
+    uint32_t result = 0;
+    for (int n = 0; n < 8; ++n) {
+        int c = input[n];
+        unsigned d = static_cast<unsigned>(c - '0');
+        if (d >= 10) {
+            d = static_cast<unsigned>(c - 'a');
+            if (d >= 6) {
+                d = static_cast<unsigned>(c - 'A');
+                if (d >= 6) {
+                    return false;
+                }
+            }
+            d += 10;
+        }
+        result = (result << 4) | d;
+    }
+    *value = result;
+    return true;
+}
+
+}  // namespace
+
+bool android_extractRamdiskFile(const char* ramdiskPath,
+                                const char* fileName,
+                                char** out,
+                                size_t* outSize) {
+    *out = NULL;
+    *outSize = 0;
+
+    GZipInputStream input(ramdiskPath);
+    if (input.error()) {
+        errno = input.error();
+        return false;
+    }
+
+    // Type of cpio new ASCII header.
+    struct cpio_newc_header {
+        char c_magic[6];
+        char c_ino[8];
+        char c_mode[8];
+        char c_uid[8];
+        char c_gid[8];
+        char c_nlink[8];
+        char c_mtime[8];
+        char c_filesize[8];
+        char c_devmajor[8];
+        char c_devminor[8];
+        char c_rdevmajor[8];
+        char c_rdevminor[8];
+        char c_namesize[8];
+        char c_check[8];
+    };
+
+    size_t fileNameLen = strlen(fileName);
+
+    for (;;) {
+        // Read the header then check it.
+        cpio_newc_header header;
+        if (!input.doRead(&header, sizeof header)) {
+            // Assume end of input here.
+            D("Could not find %s in ramdisk image at %s\n",
+              fileName, ramdiskPath);
+            return false;
+        }
+
+        D("HEADER %.6s\n", header.c_magic);
+        if (memcmp(header.c_magic, "070701", 6) != 0) {
+            D("Not a valid ramdisk image file: %s\n", ramdiskPath);
+            errno = EINVAL;
+            return false;
+        }
+
+        // Compare file names, note that files with a size of 0 are
+        // hard links and should be ignored.
+        uint32_t nameSize;
+        uint32_t entrySize;
+        if (!parse_hex8(header.c_namesize, &nameSize) ||
+            !parse_hex8(header.c_filesize, &entrySize)) {
+            D("Could not parse ramdisk file entry header!");
+            break;
+        }
+
+        D("---- %d nameSize=%d entrySize=%d\n", __LINE__, nameSize, entrySize);
+
+        // The header is followed by the name, followed by 4-byte padding
+        // with NUL bytes. Compute the number of bytes to skip over the
+        // name.
+        size_t skipName =
+                ((sizeof header + nameSize + 3) & ~3) - sizeof header;
+
+        // The file data is 4-byte padded with NUL bytes too.
+        size_t skipFile = (entrySize + 3) & ~3;
+        size_t skipCount = 0;
+
+        // Last record is named 'TRAILER!!!' and indicates end of archive.
+        static const char kTrailer[] = "TRAILER!!!";
+        static const size_t kTrailerSize = sizeof(kTrailer) - 1U;
+
+        if ((entrySize == 0 || nameSize != fileNameLen + 1U) &&
+            nameSize != kTrailerSize + 1U) {
+            D("---- %d Skipping\n", __LINE__);
+            skipCount = skipName + skipFile;
+        } else {
+            // Read the name and compare it.
+            nameSize -= 1U;
+            android::base::String entryName;
+            entryName.resize(nameSize);
+            if (!input.doRead(&entryName[0], nameSize)) {
+                D("Could not read ramdisk file entry name!");
+                break;
+            }
+            D("---- %d Name=[%s]\n", __LINE__, entryName.c_str());
+            skipCount -= nameSize;
+
+            // Check for last entry.
+            if (nameSize == kTrailerSize &&
+                !strcmp(entryName.c_str(), kTrailer)) {
+                D("End of archive reached. Could not find %s in ramdisk image at %s",
+                  fileName, ramdiskPath);
+                return false;
+            }
+
+            // Check for the search file name.
+            if (nameSize == entryName.size() &&
+                !strcmp(entryName.c_str(), fileName)) {
+                // Found it !! Skip over padding.
+                if (!input.doSkip(skipName - nameSize)) {
+                    D("Could not skip ramdisk name entry!");
+                    break;
+                }
+
+                // Then read data into head-allocated buffer.
+                *out = reinterpret_cast<char*>(malloc(entrySize));
+                *outSize = entrySize;
+
+                return input.doRead(*out, entrySize);
+            }
+        }
+        if (!input.doSkip(skipCount)) {
+            D("Could not skip ramdisk entry!");
+            break;
+        }
+    }
+
+    errno = input.error();
+    return false;
+}
diff --git a/android/filesystems/ramdisk_extractor.h b/android/filesystems/ramdisk_extractor.h
new file mode 100644
index 0000000..840d4de
--- /dev/null
+++ b/android/filesystems/ramdisk_extractor.h
@@ -0,0 +1,35 @@
+// 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_FILESYSTEMS_RAMDISK_EXTRACTOR_H
+#define ANDROID_FILESYSTEMS_RAMDISK_EXTRACTOR_H
+
+#include "android/utils/compiler.h"
+
+#include <stddef.h>
+
+
+ANDROID_BEGIN_HEADER
+
+// Extract the content of a given file from a ramdisk image.
+// |ramdisk_path| is the path to the ramdisk.img file.
+// |file_path| is the path of the file within the ramdisk.
+// On success, returns true and sets |*out| to point to a heap allocated
+// block containing the extracted content, of size |*out_size| bytes.
+// On failure, return false.
+bool android_extractRamdiskFile(const char* ramdisk_path,
+                                const char* file_path,
+                                char** out,
+                                size_t* out_size);
+
+ANDROID_END_HEADER
+
+#endif  // ANDROID_FILESYSTEMS_RAMDISK_EXTRACTOR_H
diff --git a/android/filesystems/ramdisk_extractor_unittest.cpp b/android/filesystems/ramdisk_extractor_unittest.cpp
new file mode 100644
index 0000000..2462b5d
--- /dev/null
+++ b/android/filesystems/ramdisk_extractor_unittest.cpp
@@ -0,0 +1,88 @@
+// 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/filesystems/ramdisk_extractor.h"
+
+#include "android/base/EintrWrapper.h"
+#include "android/filesystems/testing/TestSupport.h"
+
+#include <gtest/gtest.h>
+
+#include <stdio.h>
+
+namespace {
+
+#include "android/filesystems/testing/TestRamdiskImage.h"
+
+class RamdiskExtractorTest : public ::testing::Test {
+public:
+    RamdiskExtractorTest() :
+        mTempFilePath(android::testing::CreateTempFilePath()) {}
+
+    bool fillData(const void* data, size_t dataSize) {
+        FILE* file = ::fopen(mTempFilePath.c_str(), "wb");
+        if (!file) {
+            return false;
+        }
+
+        bool result = (fwrite(data, dataSize, 1, file) == 1);
+        fclose(file);
+        return result;
+    }
+
+    ~RamdiskExtractorTest() {
+        if (!mTempFilePath.empty()) {
+            HANDLE_EINTR(unlink(mTempFilePath.c_str()));
+        }
+    }
+
+    const char* path() const { return mTempFilePath.c_str(); }
+
+private:
+    std::string mTempFilePath;
+};
+
+}  // namespace
+
+TEST_F(RamdiskExtractorTest, FindFoo) {
+    static const char kExpected[] = "Hello World!\n";
+    static const size_t kExpectedSize = sizeof(kExpected) - 1U;
+    char* out = NULL;
+    size_t outSize = 0;
+
+    EXPECT_TRUE(fillData(kTestRamdiskImage, kTestRamdiskImageSize));
+    EXPECT_TRUE(android_extractRamdiskFile(path(), "foo", &out, &outSize));
+    EXPECT_EQ(kExpectedSize, outSize);
+    EXPECT_TRUE(out);
+    EXPECT_TRUE(!memcmp(out, kExpected, outSize));
+    free(out);
+}
+
+TEST_F(RamdiskExtractorTest, FindBar2) {
+    static const char kExpected[] = "La vie est un long fleuve tranquille\n";
+    static const size_t kExpectedSize = sizeof(kExpected) - 1U;
+    char* out = NULL;
+    size_t outSize = 0;
+
+    EXPECT_TRUE(fillData(kTestRamdiskImage, kTestRamdiskImageSize));
+    EXPECT_TRUE(android_extractRamdiskFile(path(), "bar2", &out, &outSize));
+    EXPECT_EQ(kExpectedSize, outSize);
+    EXPECT_TRUE(out);
+    EXPECT_TRUE(!memcmp(out, kExpected, outSize));
+    free(out);
+}
+
+TEST_F(RamdiskExtractorTest, MissingFile) {
+    char* out = NULL;
+    size_t outSize = 0;
+    EXPECT_TRUE(fillData(kTestRamdiskImage, kTestRamdiskImageSize));
+    EXPECT_FALSE(android_extractRamdiskFile(path(), "zoolander", &out, &outSize));
+}
diff --git a/android/filesystems/testing/TestRamdiskImage.h b/android/filesystems/testing/TestRamdiskImage.h
new file mode 100644
index 0000000..baec302
--- /dev/null
+++ b/android/filesystems/testing/TestRamdiskImage.h
@@ -0,0 +1,37 @@
+// 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_FILESYSTEMS_TESTING_TEST_RAMDISK_IMAGE_H
+#define ANDROID_FILESYSTEMS_TESTING_TEST_RAMDISK_IMAGE_H
+
+/* Auto-generated by create_ramdisk_test_data.sh on 2014-06-23 - DO NOT EDIT!! */
+
+static const unsigned char kTestRamdiskImage[] = {
+  0x1f, 0x8b, 0x08, 0x00, 0x6d, 0x1a, 0xa8, 0x53, 0x02, 0x03, 0xbd, 0x8e,
+  0x41, 0x0a, 0x83, 0x30, 0x10, 0x45, 0xb3, 0xee, 0x29, 0x26, 0x37, 0x98,
+  0xc4, 0x46, 0xd3, 0x65, 0xac, 0x86, 0x16, 0x5c, 0x49, 0xa1, 0x6b, 0x4b,
+  0x63, 0x11, 0x06, 0x43, 0xad, 0x7a, 0xfe, 0x2a, 0x41, 0x90, 0x2e, 0x8a,
+  0xdd, 0xf4, 0x6d, 0xfe, 0x1f, 0x66, 0x60, 0x1e, 0x26, 0x98, 0xa0, 0x40,
+  0x14, 0xb9, 0xd4, 0x07, 0x8b, 0x13, 0x5a, 0x98, 0x39, 0x30, 0xb6, 0x26,
+  0x9d, 0x53, 0x44, 0x5a, 0x63, 0x40, 0xa8, 0xc8, 0x4c, 0xeb, 0x38, 0x0b,
+  0xa3, 0x54, 0x21, 0xed, 0x11, 0xbf, 0xa3, 0x96, 0x72, 0xab, 0x3a, 0xc9,
+  0x58, 0x51, 0xc1, 0xd8, 0x38, 0x70, 0xaf, 0x1e, 0x86, 0x16, 0xc8, 0xb7,
+  0x0f, 0xa8, 0xc9, 0x0d, 0xa3, 0x83, 0xbe, 0xab, 0xda, 0xe7, 0xd0, 0x10,
+  0xb9, 0x1d, 0x63, 0x0c, 0xd7, 0x6e, 0xd9, 0x6f, 0x6e, 0x98, 0x6d, 0x74,
+  0xdb, 0x2f, 0xa5, 0xf6, 0x7e, 0xfa, 0x79, 0x72, 0x44, 0x1e, 0xae, 0xbe,
+  0xa3, 0x3b, 0x5f, 0x3b, 0x6c, 0x62, 0xeb, 0xdd, 0x27, 0xe9, 0x52, 0x2e,
+  0xa5, 0x39, 0x17, 0x79, 0xc9, 0x39, 0x67, 0x7f, 0xe2, 0x0d, 0xb9, 0x32,
+  0x87, 0xaa, 0x00, 0x02, 0x00, 0x00
+};
+
+static const size_t kTestRamdiskImageSize = sizeof(kTestRamdiskImage);
+
+#endif  // ANDROID_FILESYSTEMS_TESTING_TEST_RAMDISK_IMAGE_H
diff --git a/android/filesystems/testing/create_ramdisk_test_data.sh b/android/filesystems/testing/create_ramdisk_test_data.sh
new file mode 100755
index 0000000..1bd3e42
--- /dev/null
+++ b/android/filesystems/testing/create_ramdisk_test_data.sh
@@ -0,0 +1,39 @@
+#!/bin/sh
+
+# This script is used to generate souce files containing test
+# data for the ramdisk_extractor unit tests.
+
+set -e
+
+export LANG=C
+export LC_ALL=C
+
+PROGNAME=$(basename "$0")
+DATE=$(date +%Y-%m-%d)
+
+# $1: Root directory
+create_ramdisk_header_from () {
+    local FILE_LIST
+    FILE_LIST=$(cd $2 && find . -type f 2>/dev/null | sed -e 's|^./||g')
+    echo "$FILE_LIST" | cpio --create --format=newc --quiet | gzip -9c | xxd -i -
+}
+
+TMPDIR=/tmp/$USER-ramdisk-test-data
+rm -rf $TMPDIR/dir1 && mkdir -p $TMPDIR/dir1
+cd $TMPDIR/dir1
+cat > foo <<EOF
+Hello World!
+EOF
+cat > bar2 <<EOF
+La vie est un long fleuve tranquille
+EOF
+
+echo "/* Auto-generated by $PROGNAME on $DATE - DO NOT EDIT!! */"
+echo ""
+echo "static const unsigned char kTestRamdiskImage[] = {"
+create_ramdisk_header_from $TMPDIR/ramdisk1.img.h $TMPDIR/dir1
+echo "};"
+echo ""
+echo "static const size_t kTestRamdiskImageSize = sizeof(kTestRamdiskImage);"
+echo ""
+