diff options
Diffstat (limited to 'libs/androidfw')
-rw-r--r-- | libs/androidfw/AssetManager.cpp | 65 | ||||
-rw-r--r-- | libs/androidfw/BackupData.cpp | 6 | ||||
-rw-r--r-- | libs/androidfw/ResourceTypes.cpp | 359 | ||||
-rw-r--r-- | libs/androidfw/tests/Android.mk | 1 | ||||
-rw-r--r-- | libs/androidfw/tests/BackupData_test.cpp | 438 | ||||
-rw-r--r-- | libs/androidfw/tests/ObbFile_test.cpp | 2 |
6 files changed, 797 insertions, 74 deletions
diff --git a/libs/androidfw/AssetManager.cpp b/libs/androidfw/AssetManager.cpp index b4d482a..91dda75 100644 --- a/libs/androidfw/AssetManager.cpp +++ b/libs/androidfw/AssetManager.cpp @@ -74,6 +74,7 @@ static const char* kAssetsRoot = "assets"; static const char* kAppZipName = NULL; //"classes.jar"; static const char* kSystemAssets = "framework/framework-res.apk"; static const char* kResourceCache = "resource-cache"; +static const char* kAndroidManifest = "AndroidManifest.xml"; static const char* kExcludeExtension = ".EXCLUDE"; @@ -205,6 +206,16 @@ bool AssetManager::addAssetPath(const String8& path, int32_t* cookie) ALOGV("In %p Asset %s path: %s", this, ap.type == kFileTypeDirectory ? "dir" : "zip", ap.path.string()); + // Check that the path has an AndroidManifest.xml + Asset* manifestAsset = const_cast<AssetManager*>(this)->openNonAssetInPathLocked( + kAndroidManifest, Asset::ACCESS_BUFFER, ap); + if (manifestAsset == NULL) { + // This asset path does not contain any resources. + delete manifestAsset; + return false; + } + delete manifestAsset; + mAssetPaths.add(ap); // new paths are always added at the end @@ -356,7 +367,7 @@ void AssetManager::setLocaleLocked(const char* locale) delete[] mLocale; } mLocale = strdupNew(locale); - + updateResourceParamsLocked(); } @@ -461,7 +472,7 @@ Asset* AssetManager::open(const char* fileName, AccessMode mode) * The "fileName" is the partial path starting from the application * name. */ -Asset* AssetManager::openNonAsset(const char* fileName, AccessMode mode) +Asset* AssetManager::openNonAsset(const char* fileName, AccessMode mode, int32_t* outCookie) { AutoMutex _l(mLock); @@ -482,6 +493,7 @@ Asset* AssetManager::openNonAsset(const char* fileName, AccessMode mode) Asset* pAsset = openNonAssetInPathLocked( fileName, mode, mAssetPaths.itemAt(i)); if (pAsset != NULL) { + if (outCookie != NULL) *outCookie = static_cast<int32_t>(i + 1); return pAsset != kExcludedAsset ? pAsset : NULL; } } @@ -556,9 +568,14 @@ const ResTable* AssetManager::getResTable(bool required) const LOG_FATAL_IF(mAssetPaths.size() == 0, "No assets added to AssetManager"); } - if (mCacheMode != CACHE_OFF && !mCacheValid) + if (mCacheMode != CACHE_OFF && !mCacheValid) { const_cast<AssetManager*>(this)->loadFileNameCacheLocked(); + } + mResources = new ResTable(); + updateResourceParamsLocked(); + + bool onlyEmptyResources = true; const size_t N = mAssetPaths.size(); for (size_t i=0; i<N; i++) { Asset* ass = NULL; @@ -593,7 +610,7 @@ const ResTable* AssetManager::getResTable(bool required) const mZipSet.setZipResourceTableAsset(ap.path, ass); } } - + if (i == 0 && ass != NULL) { // If this is the first resource table in the asset // manager, then we are going to cache it so that we @@ -621,35 +638,39 @@ const ResTable* AssetManager::getResTable(bool required) const ap); shared = false; } + if ((ass != NULL || sharedRes != NULL) && ass != kExcludedAsset) { - if (rt == NULL) { - mResources = rt = new ResTable(); - updateResourceParamsLocked(); - } ALOGV("Installing resource asset %p in to table %p\n", ass, mResources); if (sharedRes != NULL) { ALOGV("Copying existing resources for %s", ap.path.string()); - rt->add(sharedRes); + mResources->add(sharedRes); } else { ALOGV("Parsing resources for %s", ap.path.string()); - rt->add(ass, i + 1, !shared, idmap); + mResources->add(ass, i + 1, !shared, idmap); } + onlyEmptyResources = false; if (!shared) { delete ass; } + } else { + ALOGW("Installing empty resources in to table %p\n", mResources); + mResources->addEmpty(i + 1); } + if (idmap != NULL) { delete idmap; } MY_TRACE_END(); } - if (required && !rt) ALOGW("Unable to find resources file resources.arsc"); - if (!rt) { - mResources = rt = new ResTable(); + if (required && onlyEmptyResources) { + ALOGW("Unable to find resources file resources.arsc"); + delete mResources; + mResources = NULL; } - return rt; + + return mResources; } void AssetManager::updateResourceParamsLocked() const @@ -846,7 +867,7 @@ Asset* AssetManager::openInLocaleVendorLocked(const char* fileName, AccessMode m /* look at the filesystem on disk */ String8 path(createPathNameLocked(ap, locale, vendor)); path.appendPath(fileName); - + String8 excludeName(path); excludeName.append(kExcludeExtension); if (::getFileType(excludeName.string()) != kFileTypeNonexistent) { @@ -854,28 +875,28 @@ Asset* AssetManager::openInLocaleVendorLocked(const char* fileName, AccessMode m //printf("+++ excluding '%s'\n", (const char*) excludeName); return kExcludedAsset; } - + pAsset = openAssetFromFileLocked(path, mode); - + if (pAsset == NULL) { /* try again, this time with ".gz" */ path.append(".gz"); pAsset = openAssetFromFileLocked(path, mode); } - + if (pAsset != NULL) pAsset->setAssetSource(path); } else { /* find in cache */ String8 path(createPathNameLocked(ap, locale, vendor)); path.appendPath(fileName); - + AssetDir::FileInfo tmpInfo; bool found = false; - + String8 excludeName(path); excludeName.append(kExcludeExtension); - + if (mCache.indexOf(excludeName) != NAME_NOT_FOUND) { /* go no farther */ //printf("+++ Excluding '%s'\n", (const char*) excludeName); @@ -1699,7 +1720,7 @@ bool AssetManager::fncScanAndMergeDirLocked( // XXX This is broken -- the filename cache needs to hold the base // asset path separately from its filename. - + partialPath = createPathNameLocked(ap, locale, vendor); if (dirName[0] != '\0') { partialPath.appendPath(dirName); diff --git a/libs/androidfw/BackupData.cpp b/libs/androidfw/BackupData.cpp index bd9dc76..d16d5498 100644 --- a/libs/androidfw/BackupData.cpp +++ b/libs/androidfw/BackupData.cpp @@ -59,9 +59,10 @@ padding_extra(size_t n) BackupDataWriter::BackupDataWriter(int fd) :m_fd(fd), m_status(NO_ERROR), - m_pos(0), m_entityCount(0) { + m_pos = (ssize_t) lseek(fd, 0, SEEK_CUR); + if (DEBUG) ALOGI("BackupDataWriter(%d) @ %ld", fd, (long)m_pos); } BackupDataWriter::~BackupDataWriter() @@ -184,10 +185,11 @@ BackupDataReader::BackupDataReader(int fd) :m_fd(fd), m_done(false), m_status(NO_ERROR), - m_pos(0), m_entityCount(0) { memset(&m_header, 0, sizeof(m_header)); + m_pos = (ssize_t) lseek(fd, 0, SEEK_CUR); + if (DEBUG) ALOGI("BackupDataReader(%d) @ %ld", fd, (long)m_pos); } BackupDataReader::~BackupDataReader() diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp index 9e59a13..098753b 100644 --- a/libs/androidfw/ResourceTypes.cpp +++ b/libs/androidfw/ResourceTypes.cpp @@ -42,6 +42,7 @@ #define TABLE_SUPER_NOISY(x) //x #define LOAD_TABLE_NOISY(x) //x #define TABLE_THEME(x) //x +#define LIB_NOISY(x) x namespace android { @@ -66,6 +67,9 @@ namespace android { // size measured in sizeof(uint32_t) #define IDMAP_HEADER_SIZE (ResTable::IDMAP_HEADER_SIZE_BYTES / sizeof(uint32_t)) +#define APP_PACKAGE_ID 0x7f +#define SYS_PACKAGE_ID 0x01 + // Standard C isspace() is only required to look at the low byte of its input, so // produces incorrect results for UTF-16 characters. For safety's sake, assume that // any high-byte UTF-16 code point is not whitespace. @@ -113,7 +117,7 @@ static status_t validate_chunk(const ResChunk_header* chunk, name, size, headerSize); return BAD_TYPE; } - ALOGW("%s header size 0x%x is too small.", + ALOGW("%s header size 0x%04x is too small.", name, headerSize); return BAD_TYPE; } @@ -264,7 +268,7 @@ static status_t idmapLookup(const uint32_t* map, size_t sizeBytes, uint32_t key, } const uint32_t index = typeOffset + 2 + entry - entryOffset; if (index > size) { - ALOGW("Resource ID map: entry index=%d exceeds size of map=%d\n", index, (int)size); + ALOGW("Resource ID map: entry index=%u exceeds size of map=%d\n", index, (int)size); *outValue = 0; return NO_ERROR; } @@ -279,7 +283,7 @@ static status_t getIdmapPackageId(const uint32_t* map, size_t mapSize, uint32_t return UNKNOWN_ERROR; } if (mapSize <= IDMAP_HEADER_SIZE + 1) { - ALOGW("corrupt idmap: map size %d too short\n", mapSize); + ALOGW("corrupt idmap: map size %d too short\n", (int)mapSize); return UNKNOWN_ERROR; } uint32_t typeCount = *(map + IDMAP_HEADER_SIZE); @@ -288,7 +292,7 @@ static status_t getIdmapPackageId(const uint32_t* map, size_t mapSize, uint32_t return UNKNOWN_ERROR; } if (IDMAP_HEADER_SIZE + 1 + typeCount > mapSize) { - ALOGW("corrupt idmap: number of types %d extends past idmap size %d\n", typeCount, mapSize); + ALOGW("corrupt idmap: number of types %u extends past idmap size %d\n", typeCount, (int)mapSize); return UNKNOWN_ERROR; } const uint32_t* p = map + IDMAP_HEADER_SIZE + 1; @@ -304,7 +308,7 @@ static status_t getIdmapPackageId(const uint32_t* map, size_t mapSize, uint32_t // determine package id from first entry of first type const uint32_t offset = *p + IDMAP_HEADER_SIZE + 2; if (offset > mapSize) { - ALOGW("corrupt idmap: entry offset %d points outside map size %d\n", offset, mapSize); + ALOGW("corrupt idmap: entry offset %u points outside map size %d\n", offset, (int)mapSize); return UNKNOWN_ERROR; } *outId = (map[offset] >> 24) & 0x000000ff; @@ -342,6 +346,22 @@ ResStringPool::~ResStringPool() uninit(); } +void ResStringPool::setToEmpty() +{ + uninit(); + + mOwnedData = calloc(1, sizeof(ResStringPool_header)); + ResStringPool_header* header = (ResStringPool_header*) mOwnedData; + mSize = 0; + mEntries = NULL; + mStrings = NULL; + mStringPoolSize = 0; + mEntryStyles = NULL; + mStyles = NULL; + mStylePoolSize = 0; + mHeader = (const ResStringPool_header*) header; +} + status_t ResStringPool::setTo(const void* data, size_t size, bool copyData) { if (!data || !size) { @@ -1111,7 +1131,14 @@ int32_t ResXMLParser::getAttributeDataType(size_t idx) const (((const uint8_t*)tag) + dtohs(tag->attributeStart) + (dtohs(tag->attributeSize)*idx)); - return attr->typedValue.dataType; + uint8_t type = attr->typedValue.dataType; + if (type != Res_value::TYPE_DYNAMIC_REFERENCE) { + return type; + } + + // This is a dynamic reference. We adjust those references + // to regular references at this level, so lie to the caller. + return Res_value::TYPE_REFERENCE; } } return Res_value::TYPE_NULL; @@ -1126,7 +1153,15 @@ int32_t ResXMLParser::getAttributeData(size_t idx) const (((const uint8_t*)tag) + dtohs(tag->attributeStart) + (dtohs(tag->attributeSize)*idx)); - return dtohl(attr->typedValue.data); + if (attr->typedValue.dataType != Res_value::TYPE_DYNAMIC_REFERENCE || + mTree.mDynamicRefTable == NULL) { + return dtohl(attr->typedValue.data); + } + + uint32_t data = dtohl(attr->typedValue.data); + if (mTree.mDynamicRefTable->lookupResourceId(&data) == NO_ERROR) { + return data; + } } } return 0; @@ -1142,6 +1177,10 @@ ssize_t ResXMLParser::getAttributeValue(size_t idx, Res_value* outValue) const + dtohs(tag->attributeStart) + (dtohs(tag->attributeSize)*idx)); outValue->copyFrom_dtoh(attr->typedValue); + if (mTree.mDynamicRefTable != NULL && + mTree.mDynamicRefTable->lookupResourceValue(outValue) != NO_ERROR) { + return BAD_TYPE; + } return sizeof(Res_value); } } @@ -1333,25 +1372,26 @@ void ResXMLParser::setPosition(const ResXMLParser::ResXMLPosition& pos) mCurExt = pos.curExt; } - // -------------------------------------------------------------------- static volatile int32_t gCount = 0; -ResXMLTree::ResXMLTree() +ResXMLTree::ResXMLTree(const DynamicRefTable* dynamicRefTable) : ResXMLParser(*this) + , mDynamicRefTable(dynamicRefTable) , mError(NO_INIT), mOwnedData(NULL) { //ALOGI("Creating ResXMLTree %p #%d\n", this, android_atomic_inc(&gCount)+1); restart(); } -ResXMLTree::ResXMLTree(const void* data, size_t size, bool copyData) +ResXMLTree::ResXMLTree() : ResXMLParser(*this) + , mDynamicRefTable(NULL) , mError(NO_INIT), mOwnedData(NULL) { //ALOGI("Creating ResXMLTree %p #%d\n", this, android_atomic_inc(&gCount)+1); - setTo(data, size, copyData); + restart(); } ResXMLTree::~ResXMLTree() @@ -2740,7 +2780,14 @@ struct ResTable::Package struct ResTable::PackageGroup { PackageGroup(ResTable* _owner, const String16& _name, uint32_t _id) - : owner(_owner), name(_name), id(_id), typeCount(0), bags(NULL) { } + : owner(_owner) + , name(_name) + , id(_id) + , typeCount(0) + , bags(NULL) + , dynamicRefTable(static_cast<uint8_t>(_id)) + { } + ~PackageGroup() { clearBagCache(); const size_t N = packages.size(); @@ -2793,6 +2840,13 @@ struct ResTable::PackageGroup // Computed attribute bags, first indexed by the type and second // by the entry in that type. bag_set*** bags; + + // The table mapping dynamic references to resolved references for + // this package group. + // TODO: We may be able to support dynamic references in overlays + // by having these tables in a per-package scope rather than + // per-package-group. + DynamicRefTable dynamicRefTable; }; struct ResTable::bag_set @@ -3080,7 +3134,7 @@ void ResTable::Theme::dumpToLog() const } ResTable::ResTable() - : mError(NO_INIT) + : mError(NO_INIT), mNextPackageId(2) { memset(&mParams, 0, sizeof(mParams)); memset(mPackageMap, 0, sizeof(mPackageMap)); @@ -3088,11 +3142,11 @@ ResTable::ResTable() } ResTable::ResTable(const void* data, size_t size, const int32_t cookie, bool copyData) - : mError(NO_INIT) + : mError(NO_INIT), mNextPackageId(2) { memset(&mParams, 0, sizeof(mParams)); memset(mPackageMap, 0, sizeof(mPackageMap)); - addInternal(data, size, cookie, NULL /* asset */, copyData, NULL /* idMap */); + addInternal(data, size, cookie, copyData, NULL /* idMap */); LOG_FATAL_IF(mError != NO_ERROR, "Error parsing resource table"); //ALOGI("Creating ResTable %p\n", this); } @@ -3109,7 +3163,7 @@ inline ssize_t ResTable::getResourcePackageIndex(uint32_t resID) const } status_t ResTable::add(const void* data, size_t size) { - return addInternal(data, size, 0 /* cookie */, NULL /* asset */, + return addInternal(data, size, 0 /* cookie */, false /* copyData */, NULL /* idMap */); } @@ -3121,7 +3175,7 @@ status_t ResTable::add(Asset* asset, const int32_t cookie, bool copyData, const return UNKNOWN_ERROR; } size_t size = (size_t)asset->getLength(); - return addInternal(data, size, cookie, asset, copyData, + return addInternal(data, size, cookie, copyData, reinterpret_cast<const Asset*>(idmap)); } @@ -3149,8 +3203,25 @@ status_t ResTable::add(ResTable* src) return mError; } +status_t ResTable::addEmpty(const int32_t cookie) { + Header* header = new Header(this); + header->index = mHeaders.size(); + header->cookie = cookie; + header->values.setToEmpty(); + header->ownedData = calloc(1, sizeof(ResTable_header)); + + ResTable_header* resHeader = (ResTable_header*) header->ownedData; + resHeader->header.type = RES_TABLE_TYPE; + resHeader->header.headerSize = sizeof(ResTable_header); + resHeader->header.size = sizeof(ResTable_header); + + header->header = (const ResTable_header*) resHeader; + mHeaders.add(header); + return NO_ERROR; +} + status_t ResTable::addInternal(const void* data, size_t size, const int32_t cookie, - Asset* /*asset*/, bool copyData, const Asset* idmap) + bool copyData, const Asset* idmap) { if (!data) return NO_ERROR; Header* header = new Header(this); @@ -3189,8 +3260,6 @@ status_t ResTable::addInternal(const void* data, size_t size, const int32_t cook //ALOGI("Got size 0x%x, again size 0x%x, raw size 0x%x\n", header->size, // dtohl(header->header->header.size), header->header->header.size); LOAD_TABLE_NOISY(ALOGV("Loading ResTable @%p:\n", header->header)); - LOAD_TABLE_NOISY(printHexData(2, header->header, header->size < 256 ? header->size : 256, - 16, 16, 0, false, printToLogFunc)); if (dtohs(header->header->header.headerSize) > header->size || header->size > size) { ALOGW("Bad resource table: header size 0x%x or total size 0x%x is larger than data size 0x%x\n", @@ -3477,9 +3546,6 @@ ssize_t ResTable::getResource(uint32_t resID, Res_value* outValue, bool mayBeBag continue; } - TABLE_NOISY(aout << "Resource type data: " - << HexDump(type, dtohl(type->header.size)) << endl); - if ((size_t)offset > (dtohl(type->header.size)-sizeof(Res_value))) { ALOGW("ResTable_item at %d is beyond type chunk data %d", (int)offset, dtohl(type->header.size)); @@ -3519,6 +3585,18 @@ ssize_t ResTable::getResource(uint32_t resID, Res_value* outValue, bool mayBeBag outValue->res0 = bestValue->res0; outValue->dataType = bestValue->dataType; outValue->data = dtohl(bestValue->data); + + // The reference may be pointing to a resource in a shared library. These + // references have build-time generated package IDs. These ids may not match + // the actual package IDs of the corresponding packages in this ResTable. + // We need to fix the package ID based on a mapping. + status_t err = grp->dynamicRefTable.lookupResourceValue(outValue); + if (err != NO_ERROR) { + ALOGW("Failed to resolve referenced package: 0x%08x", outValue->data); + rc = BAD_VALUE; + goto out; + } + if (outConfig != NULL) { *outConfig = bestItem; } @@ -3548,8 +3626,8 @@ ssize_t ResTable::resolveReference(Res_value* value, ssize_t blockIndex, ResTable_config* outConfig) const { int count=0; - while (blockIndex >= 0 && value->dataType == value->TYPE_REFERENCE - && value->data != 0 && count < 20) { + while (blockIndex >= 0 && value->dataType == Res_value::TYPE_REFERENCE + && value->data != 0 && count < 20) { if (outLastRef) *outLastRef = value->data; uint32_t lastRef = value->data; uint32_t newFlags = 0; @@ -3733,7 +3811,7 @@ ssize_t ResTable::getBagLocked(uint32_t resID, const bag_entry** outBag, const Type* typeClass; ALOGV("Getting entry pkg=%p, t=%d, e=%d\n", package, T, E); ssize_t offset = getEntry(package, T, E, &mParams, &type, &entry, &typeClass); - ALOGV("Resulting offset=%d\n", offset); + ALOGV("Resulting offset=%d\n", (int)offset); if (offset <= 0) { // No {entry, appropriate config} pair found in package. If this // package is an overlay package (ip != 0), this simply means the @@ -3777,9 +3855,21 @@ ssize_t ResTable::getBagLocked(uint32_t resID, const bag_entry** outBag, TABLE_NOISY(printf("Creating new bag, entrySize=0x%08x, parent=0x%08x\n", entrySize, parent)); if (parent) { + uint32_t resolvedParent = parent; + + // Bags encode a parent reference without using the standard + // Res_value structure. That means we must always try to + // resolve a parent reference in case it is actually a + // TYPE_DYNAMIC_REFERENCE. + status_t err = grp->dynamicRefTable.lookupResourceId(&resolvedParent); + if (err != NO_ERROR) { + ALOGE("Failed resolving bag parent id 0x%08x", parent); + return UNKNOWN_ERROR; + } + const bag_entry* parentBag; uint32_t parentTypeSpecFlags = 0; - const ssize_t NP = getBagLocked(parent, &parentBag, &parentTypeSpecFlags); + const ssize_t NP = getBagLocked(resolvedParent, &parentBag, &parentTypeSpecFlags); const size_t NT = ((NP >= 0) ? NP : 0) + N; set = (bag_set*)malloc(sizeof(bag_set)+sizeof(bag_entry)*NT); if (set == NULL) { @@ -3874,6 +3964,12 @@ ssize_t ResTable::getBagLocked(uint32_t resID, const bag_entry** outBag, cur->stringBlock = package->header->index; cur->map.name.ident = newName; cur->map.value.copyFrom_dtoh(map->value); + status_t err = grp->dynamicRefTable.lookupResourceValue(&cur->map.value); + if (err != NO_ERROR) { + ALOGE("Reference item(0x%08x) in bag could not be resolved.", cur->map.value.data); + return UNKNOWN_ERROR; + } + TABLE_NOISY(printf("Setting entry #%d %p: block=%d, name=0x%08x, type=%d, data=0x%08x\n", curEntry, cur, cur->stringBlock, cur->map.name.ident, cur->map.value.dataType, cur->map.value.data)); @@ -4571,16 +4667,20 @@ bool ResTable::stringToValue(Res_value* outValue, String16* outString, return false; } } - if (!accessor) { - outValue->data = rid; - return true; + + if (accessor) { + rid = Res_MAKEID( + accessor->getRemappedPackage(Res_GETPACKAGE(rid)), + Res_GETTYPE(rid), Res_GETENTRY(rid)); + TABLE_NOISY(printf("Incl %s:%s/%s: 0x%08x\n", + String8(package).string(), String8(type).string(), + String8(name).string(), rid)); + } + + uint32_t packageId = Res_GETPACKAGE(rid) + 1; + if (packageId != APP_PACKAGE_ID && packageId != SYS_PACKAGE_ID) { + outValue->dataType = Res_value::TYPE_DYNAMIC_REFERENCE; } - rid = Res_MAKEID( - accessor->getRemappedPackage(Res_GETPACKAGE(rid)), - Res_GETTYPE(rid), Res_GETENTRY(rid)); - TABLE_NOISY(printf("Incl %s:%s/%s: 0x%08x\n", - String8(package).string(), String8(type).string(), - String8(name).string(), rid)); outValue->data = rid; return true; } @@ -4592,8 +4692,17 @@ bool ResTable::stringToValue(Res_value* outValue, String16* outString, TABLE_NOISY(printf("Pckg %s:%s/%s: 0x%08x\n", String8(package).string(), String8(type).string(), String8(name).string(), rid)); - outValue->data = rid; - return true; + uint32_t packageId = Res_GETPACKAGE(rid) + 1; + if (packageId == 0x00) { + outValue->data = rid; + outValue->dataType = Res_value::TYPE_DYNAMIC_REFERENCE; + return true; + } else if (packageId == APP_PACKAGE_ID || packageId == SYS_PACKAGE_ID) { + // We accept packageId's generated as 0x01 in order to support + // building the android system resources + outValue->data = rid; + return true; + } } } } @@ -5125,15 +5234,15 @@ size_t ResTable::getBasePackageCount() const return mPackageGroups.size(); } -const char16_t* ResTable::getBasePackageName(size_t idx) const +const String16 ResTable::getBasePackageName(size_t idx) const { if (mError != NO_ERROR) { - return 0; + return String16(); } LOG_FATAL_IF(idx >= mPackageGroups.size(), "Requested package index %d past package count %d", (int)idx, (int)mPackageGroups.size()); - return mPackageGroups[idx]->name.string(); + return mPackageGroups[idx]->name; } uint32_t ResTable::getBasePackageId(size_t idx) const @@ -5162,6 +5271,21 @@ int32_t ResTable::getTableCookie(size_t index) const return mHeaders[index]->cookie; } +const DynamicRefTable* ResTable::getDynamicRefTableForCookie(int32_t cookie) const +{ + const size_t N = mPackageGroups.size(); + for (size_t i = 0; i < N; i++) { + const PackageGroup* pg = mPackageGroups[i]; + size_t M = pg->packages.size(); + for (size_t j = 0; j < M; j++) { + if (pg->packages[j]->header->cookie == cookie) { + return &pg->dynamicRefTable; + } + } + } + return NULL; +} + void ResTable::getConfigurations(Vector<ResTable_config>* configs) const { const size_t I = mPackageGroups.size(); @@ -5303,10 +5427,9 @@ ssize_t ResTable::getEntry( } offset += dtohl(type->entriesStart); - TABLE_NOISY(aout << "Looking in resource table " << package->header->header - << ", typeOff=" - << (void*)(((const char*)type)-((const char*)package->header->header)) - << ", offset=" << (void*)offset << endl); + TABLE_NOISY(ALOGD("Looking in resource table %p, typeOff=%p, offset=%p", + package->header->header, (void*)(((const char*)type)-((const char*)package->header->header)), + (void*)offset)); if (offset > (dtohl(type->header.size)-sizeof(ResTable_entry))) { ALOGW("ResTable_entry at 0x%x is beyond type chunk data 0x%x", @@ -5380,6 +5503,11 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg, if (package == NULL) { return (mError=NO_MEMORY); } + + if (id == 0) { + // This is a library so assign an ID + id = mNextPackageId++; + } size_t idx = mPackageMap[id]; if (idx == 0) { @@ -5416,6 +5544,13 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg, group->basePackage = package; mPackageMap[id] = (uint8_t)idx; + + // Find all packages that reference this package + size_t N = mPackageGroups.size(); + for (size_t i = 0; i < N; i++) { + mPackageGroups[i]->dynamicRefTable.addMapping( + group->name, static_cast<uint8_t>(group->id)); + } } else { group = mPackageGroups.itemAt(idx-1); if (group == NULL) { @@ -5460,7 +5595,7 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg, (void*)(base-(const uint8_t*)chunk), dtohs(typeSpec->header.type), dtohs(typeSpec->header.headerSize), - (void*)typeSize)); + (void*)typeSpecSize)); // look for block overrun or int overflow when multiplying by 4 if ((dtohl(typeSpec->entryCount) > (INT32_MAX/sizeof(uint32_t)) || dtohs(typeSpec->header.headerSize)+(sizeof(uint32_t)*dtohl(typeSpec->entryCount)) @@ -5546,6 +5681,21 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg, ALOGI("Adding config to type %d: %s\n", type->id, thisConfig.toString().string())); t->configs.add(type); + } else if (ctype == RES_TABLE_LIBRARY_TYPE) { + if (group->dynamicRefTable.entries().size() == 0) { + status_t err = group->dynamicRefTable.load((const ResTable_lib_header*) chunk); + if (err != NO_ERROR) { + return (mError=err); + } + + // Fill in the reference table with the entries we already know about. + size_t N = mPackageGroups.size(); + for (size_t i = 0; i < N; i++) { + group->dynamicRefTable.addMapping(mPackageGroups[i]->name, mPackageGroups[i]->id); + } + } else { + ALOGW("Found multiple library tables, ignoring..."); + } } else { status_t err = validate_chunk(chunk, sizeof(ResChunk_header), endPos, "ResTable_package:unknown"); @@ -5564,6 +5714,103 @@ status_t ResTable::parsePackage(const ResTable_package* const pkg, return NO_ERROR; } +DynamicRefTable::DynamicRefTable(uint8_t packageId) + : mAssignedPackageId(packageId) +{ + memset(mLookupTable, 0, sizeof(mLookupTable)); + + // Reserved package ids + mLookupTable[APP_PACKAGE_ID] = APP_PACKAGE_ID; + mLookupTable[SYS_PACKAGE_ID] = SYS_PACKAGE_ID; +} + +status_t DynamicRefTable::load(const ResTable_lib_header* const header) +{ + const uint32_t entryCount = dtohl(header->count); + const uint32_t sizeOfEntries = sizeof(ResTable_lib_entry) * entryCount; + const uint32_t expectedSize = dtohl(header->header.size) - dtohl(header->header.headerSize); + if (sizeOfEntries > expectedSize) { + ALOGE("ResTable_lib_header size %u is too small to fit %u entries (x %u).", + expectedSize, entryCount, (uint32_t)sizeof(ResTable_lib_entry)); + return UNKNOWN_ERROR; + } + + const ResTable_lib_entry* entry = (const ResTable_lib_entry*)(((uint8_t*) header) + + dtohl(header->header.headerSize)); + for (uint32_t entryIndex = 0; entryIndex < entryCount; entryIndex++) { + uint32_t packageId = dtohl(entry->packageId); + char16_t tmpName[sizeof(entry->packageName) / sizeof(char16_t)]; + strcpy16_dtoh(tmpName, entry->packageName, sizeof(entry->packageName) / sizeof(char16_t)); + LIB_NOISY(ALOGV("Found lib entry %s with id %d\n", String8(tmpName).string(), + dtohl(entry->packageId))); + if (packageId >= 256) { + ALOGE("Bad package id 0x%08x", packageId); + return UNKNOWN_ERROR; + } + mEntries.replaceValueFor(String16(tmpName), (uint8_t) packageId); + entry = entry + 1; + } + return NO_ERROR; +} + +status_t DynamicRefTable::addMapping(const String16& packageName, uint8_t packageId) +{ + ssize_t index = mEntries.indexOfKey(packageName); + if (index < 0) { + return UNKNOWN_ERROR; + } + mLookupTable[mEntries.valueAt(index)] = packageId; + return NO_ERROR; +} + +status_t DynamicRefTable::lookupResourceId(uint32_t* resId) const { + uint32_t res = *resId; + size_t packageId = Res_GETPACKAGE(res) + 1; + + if (packageId == APP_PACKAGE_ID) { + // No lookup needs to be done, app package IDs are absolute. + return NO_ERROR; + } + + if (packageId == 0) { + // The package ID is 0x00. That means that a shared library is accessing + // its own local resource, so we fix up the resource with the calling + // package ID. + *resId |= ((uint32_t) mAssignedPackageId) << 24; + return NO_ERROR; + } + + // Do a proper lookup. + uint8_t translatedId = mLookupTable[packageId]; + if (translatedId == 0) { + ALOGV("DynamicRefTable(0x%02x): No mapping for build-time package ID 0x%02x.", + (uint8_t)mAssignedPackageId, (uint8_t)packageId); + for (size_t i = 0; i < 256; i++) { + if (mLookupTable[i] != 0) { + ALOGV("e[0x%02x] -> 0x%02x", (uint8_t)i, mLookupTable[i]); + } + } + return UNKNOWN_ERROR; + } + + *resId = (res & 0x00ffffff) | (((uint32_t) translatedId) << 24); + return NO_ERROR; +} + +status_t DynamicRefTable::lookupResourceValue(Res_value* value) const { + if (value->dataType != Res_value::TYPE_DYNAMIC_REFERENCE) { + return NO_ERROR; + } + + status_t err = lookupResourceId(&value->data); + if (err != NO_ERROR) { + return err; + } + + value->dataType = Res_value::TYPE_REFERENCE; + return NO_ERROR; +} + status_t ResTable::createIdmap(const ResTable& overlay, uint32_t targetCrc, uint32_t overlayCrc, const char* targetPath, const char* overlayPath, @@ -5704,7 +5951,7 @@ status_t ResTable::createIdmap(const ResTable& overlay, continue; } if (N == 1) { // vector expected to hold (offset) + (N > 0 entries) - ALOGW("idmap: type %d supposedly has entries, but no entries found\n", i); + ALOGW("idmap: type %u supposedly has entries, but no entries found\n", (uint32_t)i); return UNKNOWN_ERROR; } *data++ = htodl(N - 1); // do not count the offset (which is vector's first element) @@ -5818,6 +6065,8 @@ void ResTable::print_value(const Package* pkg, const Res_value& value) const printf("(null)\n"); } else if (value.dataType == Res_value::TYPE_REFERENCE) { printf("(reference) 0x%08x\n", value.data); + } else if (value.dataType == Res_value::TYPE_DYNAMIC_REFERENCE) { + printf("(dynamic reference) 0x%08x\n", value.data); } else if (value.dataType == Res_value::TYPE_ATTRIBUTE) { printf("(attribute) 0x%08x\n", value.data); } else if (value.dataType == Res_value::TYPE_STRING) { @@ -5900,6 +6149,11 @@ void ResTable::print(bool inclValues) const uint32_t resID = (0xff000000 & ((pkg->package->id)<<24)) | (0x00ff0000 & ((typeIndex+1)<<16)) | (0x0000ffff & (entryIndex)); + // Since we are creating resID without actually + // iterating over them, we have no idea which is a + // dynamic reference. We must check. + pg->dynamicRefTable.lookupResourceId(&resID); + resource_name resName; if (this->getResourceName(resID, true, &resName)) { String8 type8; @@ -5959,6 +6213,7 @@ void ResTable::print(bool inclValues) const uint32_t resID = (0xff000000 & ((pkg->package->id)<<24)) | (0x00ff0000 & ((typeIndex+1)<<16)) | (0x0000ffff & (entryIndex)); + pg->dynamicRefTable.lookupResourceId(&resID); resource_name resName; if (this->getResourceName(resID, true, &resName)) { String8 type8; @@ -6037,8 +6292,14 @@ void ResTable::print(bool inclValues) const const uint8_t* baseMapPtr = (const uint8_t*)ent; size_t mapOffset = esize; const ResTable_map* mapPtr = (ResTable_map*)(baseMapPtr+mapOffset); - printf(" Parent=0x%08x, Count=%d\n", - dtohl(bagPtr->parent.ident), N); + const uint32_t parent = dtohl(bagPtr->parent.ident); + uint32_t resolvedParent = parent; + status_t err = pg->dynamicRefTable.lookupResourceId(&resolvedParent); + if (err != NO_ERROR) { + resolvedParent = 0; + } + printf(" Parent=0x%08x(Resolved=0x%08x), Count=%d\n", + parent, resolvedParent, N); for (int i=0; i<N && mapOffset < (typeSize-sizeof(ResTable_map)); i++) { printf(" #%i (Key=0x%08x): ", i, dtohl(mapPtr->name.ident)); diff --git a/libs/androidfw/tests/Android.mk b/libs/androidfw/tests/Android.mk index 977ba80..9e9649c 100644 --- a/libs/androidfw/tests/Android.mk +++ b/libs/androidfw/tests/Android.mk @@ -4,6 +4,7 @@ include $(CLEAR_VARS) # Build the unit tests. test_src_files := \ + BackupData_test.cpp \ ObbFile_test.cpp \ ZipUtils_test.cpp \ ResourceTypes_test.cpp diff --git a/libs/androidfw/tests/BackupData_test.cpp b/libs/androidfw/tests/BackupData_test.cpp new file mode 100644 index 0000000..17f91ca --- /dev/null +++ b/libs/androidfw/tests/BackupData_test.cpp @@ -0,0 +1,438 @@ +/* + * Copyright (C) 2010 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. + */ + +#define LOG_TAG "ObbFile_test" +#include <androidfw/BackupHelpers.h> +#include <utils/Log.h> +#include <utils/String8.h> + +#include <gtest/gtest.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <string.h> + +namespace android { + +#define TEST_FILENAME "/test.bd" + +// keys of different lengths to test padding +#define KEY1 "key1" +#define KEY2 "key2a" +#define KEY3 "key3bc" +#define KEY4 "key4def" + +// payloads of different lengths to test padding +#define DATA1 "abcdefg" +#define DATA2 "hijklmnopq" +#define DATA3 "rstuvwxyz" +// KEY4 is only ever deleted + +class BackupDataTest : public testing::Test { +protected: + char* m_external_storage; + char* m_filename; + String8 mKey1; + String8 mKey2; + String8 mKey3; + String8 mKey4; + + virtual void SetUp() { + m_external_storage = getenv("EXTERNAL_STORAGE"); + + const int totalLen = strlen(m_external_storage) + strlen(TEST_FILENAME) + 1; + m_filename = new char[totalLen]; + snprintf(m_filename, totalLen, "%s%s", m_external_storage, TEST_FILENAME); + + ::unlink(m_filename); + int fd = ::open(m_filename, O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); + if (fd < 0) { + FAIL() << "Couldn't create " << m_filename << " for writing"; + } + mKey1 = String8(KEY1); + mKey2 = String8(KEY2); + mKey3 = String8(KEY3); + mKey4 = String8(KEY4); + } + + virtual void TearDown() { + } +}; + +TEST_F(BackupDataTest, WriteAndReadSingle) { + int fd = ::open(m_filename, O_WRONLY); + BackupDataWriter* writer = new BackupDataWriter(fd); + + EXPECT_EQ(NO_ERROR, writer->WriteEntityHeader(mKey1, sizeof(DATA1))) + << "WriteEntityHeader returned an error"; + EXPECT_EQ(NO_ERROR, writer->WriteEntityData(DATA1, sizeof(DATA1))) + << "WriteEntityData returned an error"; + + ::close(fd); + fd = ::open(m_filename, O_RDONLY); + BackupDataReader* reader = new BackupDataReader(fd); + EXPECT_EQ(NO_ERROR, reader->Status()) + << "Reader ctor failed"; + + bool done; + int type; + reader->ReadNextHeader(&done, &type); + EXPECT_EQ(BACKUP_HEADER_ENTITY_V1, type) + << "wrong type from ReadNextHeader"; + + String8 key; + size_t dataSize; + EXPECT_EQ(NO_ERROR, reader->ReadEntityHeader(&key, &dataSize)) + << "ReadEntityHeader returned an error"; + EXPECT_EQ(mKey1, key) + << "wrong key from ReadEntityHeader"; + EXPECT_EQ(sizeof(DATA1), dataSize) + << "wrong size from ReadEntityHeader"; + + char* dataBytes = new char[dataSize]; + EXPECT_EQ((int) dataSize, reader->ReadEntityData(dataBytes, dataSize)) + << "ReadEntityData returned an error"; + for (unsigned int i = 0; i < sizeof(DATA1); i++) { + EXPECT_EQ(DATA1[i], dataBytes[i]) + << "data character " << i << " should be equal"; + } + delete dataBytes; + delete writer; + delete reader; +} + +TEST_F(BackupDataTest, WriteAndReadMultiple) { + int fd = ::open(m_filename, O_WRONLY); + BackupDataWriter* writer = new BackupDataWriter(fd); + writer->WriteEntityHeader(mKey1, sizeof(DATA1)); + writer->WriteEntityData(DATA1, sizeof(DATA1)); + writer->WriteEntityHeader(mKey2, sizeof(DATA2)); + writer->WriteEntityData(DATA2, sizeof(DATA2)); + + ::close(fd); + fd = ::open(m_filename, O_RDONLY); + BackupDataReader* reader = new BackupDataReader(fd); + + bool done; + int type; + String8 key; + size_t dataSize; + char* dataBytes; + // read first entity + reader->ReadNextHeader(&done, &type); + reader->ReadEntityHeader(&key, &dataSize); + dataBytes = new char[dataSize]; + reader->ReadEntityData(dataBytes, dataSize); + delete dataBytes; + + // read and verify second entity + reader->ReadNextHeader(&done, &type); + EXPECT_EQ(BACKUP_HEADER_ENTITY_V1, type) + << "wrong type from ReadNextHeader"; + + EXPECT_EQ(NO_ERROR, reader->ReadEntityHeader(&key, &dataSize)) + << "ReadEntityHeader returned an error on second entity"; + EXPECT_EQ(mKey2, key) + << "wrong key from ReadEntityHeader on second entity"; + EXPECT_EQ(sizeof(DATA2), dataSize) + << "wrong size from ReadEntityHeader on second entity"; + + dataBytes = new char[dataSize]; + EXPECT_EQ((int)dataSize, reader->ReadEntityData(dataBytes, dataSize)) + << "ReadEntityData returned an error on second entity"; + for (unsigned int i = 0; i < sizeof(DATA2); i++) { + EXPECT_EQ(DATA2[i], dataBytes[i]) + << "data character " << i << " should be equal"; + } + delete dataBytes; + delete writer; + delete reader; +} + +TEST_F(BackupDataTest, SkipEntity) { + int fd = ::open(m_filename, O_WRONLY); + BackupDataWriter* writer = new BackupDataWriter(fd); + writer->WriteEntityHeader(mKey1, sizeof(DATA1)); + writer->WriteEntityData(DATA1, sizeof(DATA1)); + writer->WriteEntityHeader(mKey2, sizeof(DATA2)); + writer->WriteEntityData(DATA2, sizeof(DATA2)); + writer->WriteEntityHeader(mKey3, sizeof(DATA3)); + writer->WriteEntityData(DATA3, sizeof(DATA3)); + + ::close(fd); + fd = ::open(m_filename, O_RDONLY); + BackupDataReader* reader = new BackupDataReader(fd); + + bool done; + int type; + String8 key; + size_t dataSize; + char* dataBytes; + // read first entity + reader->ReadNextHeader(&done, &type); + reader->ReadEntityHeader(&key, &dataSize); + dataBytes = new char[dataSize]; + reader->ReadEntityData(dataBytes, dataSize); + delete dataBytes; + + // skip second entity + reader->ReadNextHeader(&done, &type); + reader->ReadEntityHeader(&key, &dataSize); + reader->SkipEntityData(); + + // read and verify third entity + reader->ReadNextHeader(&done, &type); + EXPECT_EQ(BACKUP_HEADER_ENTITY_V1, type) + << "wrong type from ReadNextHeader after skip"; + + EXPECT_EQ(NO_ERROR, reader->ReadEntityHeader(&key, &dataSize)) + << "ReadEntityHeader returned an error on third entity"; + EXPECT_EQ(mKey3, key) + << "wrong key from ReadEntityHeader on third entity"; + EXPECT_EQ(sizeof(DATA3), dataSize) + << "wrong size from ReadEntityHeader on third entity"; + + dataBytes = new char[dataSize]; + EXPECT_EQ((int) dataSize, reader->ReadEntityData(dataBytes, dataSize)) + << "ReadEntityData returned an error on third entity"; + for (unsigned int i = 0; i < sizeof(DATA3); i++) { + EXPECT_EQ(DATA3[i], dataBytes[i]) + << "data character " << i << " should be equal"; + } + delete dataBytes; + delete writer; + delete reader; +} + +TEST_F(BackupDataTest, DeleteEntity) { + int fd = ::open(m_filename, O_WRONLY); + BackupDataWriter* writer = new BackupDataWriter(fd); + writer->WriteEntityHeader(mKey1, sizeof(DATA1)); + writer->WriteEntityData(DATA1, sizeof(DATA1)); + writer->WriteEntityHeader(mKey2, -1); + + ::close(fd); + fd = ::open(m_filename, O_RDONLY); + BackupDataReader* reader = new BackupDataReader(fd); + + bool done; + int type; + String8 key; + size_t dataSize; + char* dataBytes; + // read first entity + reader->ReadNextHeader(&done, &type); + reader->ReadEntityHeader(&key, &dataSize); + dataBytes = new char[dataSize]; + reader->ReadEntityData(dataBytes, dataSize); + delete dataBytes; + + // read and verify deletion + reader->ReadNextHeader(&done, &type); + EXPECT_EQ(BACKUP_HEADER_ENTITY_V1, type) + << "wrong type from ReadNextHeader on deletion"; + + EXPECT_EQ(NO_ERROR, reader->ReadEntityHeader(&key, &dataSize)) + << "ReadEntityHeader returned an error on second entity"; + EXPECT_EQ(mKey2, key) + << "wrong key from ReadEntityHeader on second entity"; + EXPECT_EQ(-1, (int) dataSize) + << "not recognizing deletion on second entity"; + + delete writer; + delete reader; +} + +TEST_F(BackupDataTest, EneityAfterDelete) { + int fd = ::open(m_filename, O_WRONLY); + BackupDataWriter* writer = new BackupDataWriter(fd); + writer->WriteEntityHeader(mKey1, sizeof(DATA1)); + writer->WriteEntityData(DATA1, sizeof(DATA1)); + writer->WriteEntityHeader(mKey2, -1); + writer->WriteEntityHeader(mKey3, sizeof(DATA3)); + writer->WriteEntityData(DATA3, sizeof(DATA3)); + + ::close(fd); + fd = ::open(m_filename, O_RDONLY); + BackupDataReader* reader = new BackupDataReader(fd); + + bool done; + int type; + String8 key; + size_t dataSize; + char* dataBytes; + // read first entity + reader->ReadNextHeader(&done, &type); + reader->ReadEntityHeader(&key, &dataSize); + dataBytes = new char[dataSize]; + reader->ReadEntityData(dataBytes, dataSize); + delete dataBytes; + + // read and verify deletion + reader->ReadNextHeader(&done, &type); + EXPECT_EQ(BACKUP_HEADER_ENTITY_V1, type) + << "wrong type from ReadNextHeader on deletion"; + + EXPECT_EQ(NO_ERROR, reader->ReadEntityHeader(&key, &dataSize)) + << "ReadEntityHeader returned an error on second entity"; + EXPECT_EQ(mKey2, key) + << "wrong key from ReadEntityHeader on second entity"; + EXPECT_EQ(-1, (int)dataSize) + << "not recognizing deletion on second entity"; + + // read and verify third entity + reader->ReadNextHeader(&done, &type); + EXPECT_EQ(BACKUP_HEADER_ENTITY_V1, type) + << "wrong type from ReadNextHeader after deletion"; + + EXPECT_EQ(NO_ERROR, reader->ReadEntityHeader(&key, &dataSize)) + << "ReadEntityHeader returned an error on third entity"; + EXPECT_EQ(mKey3, key) + << "wrong key from ReadEntityHeader on third entity"; + EXPECT_EQ(sizeof(DATA3), dataSize) + << "wrong size from ReadEntityHeader on third entity"; + + dataBytes = new char[dataSize]; + EXPECT_EQ((int) dataSize, reader->ReadEntityData(dataBytes, dataSize)) + << "ReadEntityData returned an error on third entity"; + for (unsigned int i = 0; i < sizeof(DATA3); i++) { + EXPECT_EQ(DATA3[i], dataBytes[i]) + << "data character " << i << " should be equal"; + } + delete dataBytes; + delete writer; + delete reader; +} + +TEST_F(BackupDataTest, OnlyDeleteEntities) { + int fd = ::open(m_filename, O_WRONLY); + BackupDataWriter* writer = new BackupDataWriter(fd); + writer->WriteEntityHeader(mKey1, -1); + writer->WriteEntityHeader(mKey2, -1); + writer->WriteEntityHeader(mKey3, -1); + writer->WriteEntityHeader(mKey4, -1); + + ::close(fd); + fd = ::open(m_filename, O_RDONLY); + BackupDataReader* reader = new BackupDataReader(fd); + + bool done; + int type; + String8 key; + size_t dataSize; + // read and verify first deletion + reader->ReadNextHeader(&done, &type); + EXPECT_EQ(BACKUP_HEADER_ENTITY_V1, type) + << "wrong type from ReadNextHeader first deletion"; + + EXPECT_EQ(NO_ERROR, reader->ReadEntityHeader(&key, &dataSize)) + << "ReadEntityHeader returned an error on first entity"; + EXPECT_EQ(mKey1, key) + << "wrong key from ReadEntityHeader on first entity"; + EXPECT_EQ(-1, (int) dataSize) + << "not recognizing deletion on first entity"; + + // read and verify second deletion + reader->ReadNextHeader(&done, &type); + EXPECT_EQ(BACKUP_HEADER_ENTITY_V1, type) + << "wrong type from ReadNextHeader second deletion"; + + EXPECT_EQ(NO_ERROR, reader->ReadEntityHeader(&key, &dataSize)) + << "ReadEntityHeader returned an error on second entity"; + EXPECT_EQ(mKey2, key) + << "wrong key from ReadEntityHeader on second entity"; + EXPECT_EQ(-1, (int) dataSize) + << "not recognizing deletion on second entity"; + + // read and verify third deletion + reader->ReadNextHeader(&done, &type); + EXPECT_EQ(BACKUP_HEADER_ENTITY_V1, type) + << "wrong type from ReadNextHeader third deletion"; + + EXPECT_EQ(NO_ERROR, reader->ReadEntityHeader(&key, &dataSize)) + << "ReadEntityHeader returned an error on third entity"; + EXPECT_EQ(mKey3, key) + << "wrong key from ReadEntityHeader on third entity"; + EXPECT_EQ(-1, (int) dataSize) + << "not recognizing deletion on third entity"; + + // read and verify fourth deletion + reader->ReadNextHeader(&done, &type); + EXPECT_EQ(BACKUP_HEADER_ENTITY_V1, type) + << "wrong type from ReadNextHeader fourth deletion"; + + EXPECT_EQ(NO_ERROR, reader->ReadEntityHeader(&key, &dataSize)) + << "ReadEntityHeader returned an error on fourth entity"; + EXPECT_EQ(mKey4, key) + << "wrong key from ReadEntityHeader on fourth entity"; + EXPECT_EQ(-1, (int) dataSize) + << "not recognizing deletion on fourth entity"; + + delete writer; + delete reader; +} + +TEST_F(BackupDataTest, ReadDeletedEntityData) { + int fd = ::open(m_filename, O_WRONLY); + BackupDataWriter* writer = new BackupDataWriter(fd); + writer->WriteEntityHeader(mKey1, -1); + writer->WriteEntityHeader(mKey2, -1); + + ::close(fd); + fd = ::open(m_filename, O_RDONLY); + BackupDataReader* reader = new BackupDataReader(fd); + + bool done; + int type; + String8 key; + size_t dataSize; + // read and verify first deletion + reader->ReadNextHeader(&done, &type); + EXPECT_EQ(BACKUP_HEADER_ENTITY_V1, type) + << "wrong type from ReadNextHeader first deletion"; + + EXPECT_EQ(NO_ERROR, reader->ReadEntityHeader(&key, &dataSize)) + << "ReadEntityHeader returned an error on first entity"; + EXPECT_EQ(mKey1, key) + << "wrong key from ReadEntityHeader on first entity"; + EXPECT_EQ(-1, (int) dataSize) + << "not recognizing deletion on first entity"; + + // erroneously try to read first entity data + char* dataBytes = new char[10]; + dataBytes[0] = 'A'; + EXPECT_EQ(NO_ERROR, reader->ReadEntityData(dataBytes, dataSize)); + // expect dataBytes to be unmodofied + EXPECT_EQ('A', dataBytes[0]); + + // read and verify second deletion + reader->ReadNextHeader(&done, &type); + EXPECT_EQ(BACKUP_HEADER_ENTITY_V1, type) + << "wrong type from ReadNextHeader second deletion"; + + EXPECT_EQ(NO_ERROR, reader->ReadEntityHeader(&key, &dataSize)) + << "ReadEntityHeader returned an error on second entity"; + EXPECT_EQ(mKey2, key) + << "wrong key from ReadEntityHeader on second entity"; + EXPECT_EQ(-1, (int) dataSize) + << "not recognizing deletion on second entity"; + + delete writer; + delete reader; +} + +} diff --git a/libs/androidfw/tests/ObbFile_test.cpp b/libs/androidfw/tests/ObbFile_test.cpp index 2c9f650..7a4dd13 100644 --- a/libs/androidfw/tests/ObbFile_test.cpp +++ b/libs/androidfw/tests/ObbFile_test.cpp @@ -91,7 +91,7 @@ TEST_F(ObbFileTest, WriteThenRead) { EXPECT_EQ(sizeof(salt), saltLen) << "salt sizes were not the same"; - for (int i = 0; i < sizeof(salt); i++) { + for (size_t i = 0; i < sizeof(salt); i++) { EXPECT_EQ(salt[i], newSalt[i]) << "salt character " << i << " should be equal"; } |