blob: 4c56e4268c5b5fdb09090153b3a329840220ea39 [file] [log] [blame]
// 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 = skipName + skipFile;
// 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__);
} 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;
}