blob: 8fd9a44358e19a02f288e8d64e95826fe801415a [file] [log] [blame]
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "tools/base/deploy/installer/apk_archive.h"
#include <iostream>
#include <fcntl.h>
#include <libgen.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <cstring>
#include "tools/base/deploy/common/event.h"
#include "tools/base/deploy/common/io.h"
#include "tools/base/deploy/common/trace.h"
namespace deploy {
ApkArchive::ApkArchive(const std::string& path) : start_(nullptr), size_(0) {
ready_ = Prepare(path);
}
ApkArchive::~ApkArchive() {
munmap(start_, size_);
}
ApkArchive::Location ApkArchive::GetSignatureLocation(
size_t offset_to_cdrecord) noexcept {
Location location;
uint8_t* cdRecord = start_ + offset_to_cdrecord;
// Check if there is a v2/v3 Signature block here.
uint8_t* signature = cdRecord - 16;
if (signature >= start_ &&
!memcmp((const char*)signature, "APK Sig Block 42", 16)) {
// This is likely a signature block.
location.size = *(uint64_t*)(signature - 8);
location.offset = offset_to_cdrecord - location.size - 8;
// Check we have the block size at the start and at the end match.
if (*(uint64_t*)(start_ + location.offset) == location.size) {
location.valid = true;
}
}
return location;
}
size_t ApkArchive::GetArchiveSize(const std::string& path) const noexcept {
struct stat st;
IO::stat(path, &st);
return st.st_size;
}
uint8_t* ApkArchive::FindEndOfCDRecord() const noexcept {
constexpr int kMinEndCDRecordSize = 21;
constexpr int endCDSignature = 0x06054b50;
// Start scanning from the end
uint8_t* cursor = start_ + size_ - 1 - kMinEndCDRecordSize;
// Search for End of Central Directory record signature.
while (cursor >= start_) {
if (*(int32_t*)cursor == endCDSignature) {
return cursor;
}
cursor--;
}
return nullptr;
}
ApkArchive::Location ApkArchive::FindCDRecord(const uint8_t* cursor) noexcept {
struct ecdr_t {
uint8_t signature[4];
uint16_t diskNumber;
uint16_t numDisk;
uint16_t diskEntries;
uint16_t numEntries;
uint32_t crSize;
uint32_t offsetToCdHeader;
uint16_t commnetSize;
uint8_t comment[0];
} __attribute__((packed));
ecdr_t* header = (ecdr_t*)cursor;
Location location;
location.offset = header->offsetToCdHeader;
location.size = header->crSize;
location.valid = true;
return location;
}
ApkArchive::Location ApkArchive::GetCDLocation() noexcept {
constexpr int cdRecordFileHeaderSignature = 0x02014b50;
Location location;
// Find End of Central Directory Record
uint8_t* cursor = FindEndOfCDRecord();
if (cursor == nullptr) {
std::cerr << "Unable to find End of Central Directory record." << std::endl;
return location;
}
// Find Central Directory Record
location = FindCDRecord(cursor);
if (cdRecordFileHeaderSignature != *(uint32_t*)(start_ + location.offset)) {
std::cerr << "Unable to find Central Directory File Header." << std::endl;
return location;
}
location.valid = true;
return location;
}
bool ApkArchive::Prepare(const std::string& path) noexcept {
Trace traceDump("Prepare");
// Search End of Central Directory Record
int fd = IO::open(path, O_RDONLY, 0);
if (fd == -1) {
std::cerr << "Unable to open file '" << path << "'" << std::endl;
return false;
}
size_ = GetArchiveSize(path);
start_ = (uint8_t*)mmap(0, size_, PROT_READ, MAP_PRIVATE, fd, 0);
if (start_ == MAP_FAILED) {
ErrEvent("Unable to mmap file '" + path + "'");
close(fd);
return false;
}
close(fd);
return true;
}
std::unique_ptr<std::string> ApkArchive::ReadMetadata(Location loc) const
noexcept {
std::unique_ptr<std::string> payload;
payload.reset(new std::string((const char*)(start_ + loc.offset), loc.size));
return payload;
}
Dump ApkArchive::ExtractMetadata() noexcept {
Trace traceDump("ExtractMetadata");
Dump dump;
if (!ready_) {
ErrEvent("Unable to ExtracMetadata (not ready)");
return dump;
}
Location cdLoc = GetCDLocation();
if (!cdLoc.valid) {
return dump;
}
dump.cd = ReadMetadata(cdLoc);
Location sigLoc = GetSignatureLocation(cdLoc.offset);
if (sigLoc.valid) {
dump.signature = ReadMetadata(sigLoc);
}
return dump;
}
} // namespace deploy