blob: 5f5ace855a39ef914e293978231f8ba1901fde97 [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/base/containers/PodVector.h"
#include "android/base/Limits.h"
#include "android/base/Log.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>
#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::PodVector;
namespace {
const char kLinuxVersionStringPrefix[] = "Linux version ";
const size_t kLinuxVersionStringPrefixLen =
sizeof(kLinuxVersionStringPrefix) - 1U;
} // namespace
#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
#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;
}
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, kLinuxVersionStringPrefix,
kLinuxVersionStringPrefixLen)) {
KERNEL_ERROR << "unsupported kernel version string:" << versionString;
return false;
}
// skip past the prefix to the version number
versionString += kLinuxVersionStringPrefixLen;
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;
}
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(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;
}
const char* versionStringStart = NULL;
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;
}
// Special case: certain images, like the ARM64 one, contain a GZip
// header _after_ the actual Linux version string. So first try to
// see if there is something before the header.
versionStringStart = (const char*)memmem(
kernelFileData,
(compressedKernel - kernelFileData),
kLinuxVersionStringPrefix,
kLinuxVersionStringPrefixLen);
if (!versionStringStart) {
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
}
}
}
if (!versionStringStart) {
versionStringStart = (const char*)memmem(
uncompressedKernel,
uncompressedKernelLen,
kLinuxVersionStringPrefix,
kLinuxVersionStringPrefixLen);
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);
}