| // 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); |
| } |