| /* | 
 |  * Copyright (C) 2006-2007 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 "CursorWindow" | 
 |  | 
 | #include <androidfw/CursorWindow.h> | 
 |  | 
 | #include <sys/mman.h> | 
 |  | 
 | #include "android-base/logging.h" | 
 | #include "cutils/ashmem.h" | 
 |  | 
 | namespace android { | 
 |  | 
 | /** | 
 |  * By default windows are lightweight inline allocations of this size; | 
 |  * they're only inflated to ashmem regions when more space is needed. | 
 |  */ | 
 | static constexpr const size_t kInlineSize = 16384; | 
 |  | 
 | static constexpr const size_t kSlotShift = 4; | 
 | static constexpr const size_t kSlotSizeBytes = 1 << kSlotShift; | 
 |  | 
 | CursorWindow::CursorWindow() { | 
 | } | 
 |  | 
 | CursorWindow::~CursorWindow() { | 
 |     if (mAshmemFd != -1) { | 
 |         ::munmap(mData, mSize); | 
 |         ::close(mAshmemFd); | 
 |     } else { | 
 |         free(mData); | 
 |     } | 
 | } | 
 |  | 
 | status_t CursorWindow::create(const String8 &name, size_t inflatedSize, CursorWindow **outWindow) { | 
 |     *outWindow = nullptr; | 
 |  | 
 |     CursorWindow* window = new CursorWindow(); | 
 |     if (!window) goto fail; | 
 |  | 
 |     window->mName = name; | 
 |     window->mSize = std::min(kInlineSize, inflatedSize); | 
 |     window->mInflatedSize = inflatedSize; | 
 |     window->mData = malloc(window->mSize); | 
 |     if (!window->mData) goto fail; | 
 |     window->mReadOnly = false; | 
 |  | 
 |     window->clear(); | 
 |     window->updateSlotsData(); | 
 |  | 
 |     *outWindow = window; | 
 |     return OK; | 
 |  | 
 | fail: | 
 |     LOG(ERROR) << "Failed create"; | 
 | fail_silent: | 
 |     delete window; | 
 |     return UNKNOWN_ERROR; | 
 | } | 
 |  | 
 | status_t CursorWindow::maybeInflate() { | 
 |     int ashmemFd = 0; | 
 |     void* newData = nullptr; | 
 |  | 
 |     // Bail early when we can't expand any further | 
 |     if (mReadOnly || mSize == mInflatedSize) { | 
 |         return INVALID_OPERATION; | 
 |     } | 
 |  | 
 |     String8 ashmemName("CursorWindow: "); | 
 |     ashmemName.append(mName); | 
 |  | 
 |     ashmemFd = ashmem_create_region(ashmemName.string(), mInflatedSize); | 
 |     if (ashmemFd < 0) { | 
 |         PLOG(ERROR) << "Failed ashmem_create_region"; | 
 |         goto fail_silent; | 
 |     } | 
 |  | 
 |     if (ashmem_set_prot_region(ashmemFd, PROT_READ | PROT_WRITE) < 0) { | 
 |         PLOG(ERROR) << "Failed ashmem_set_prot_region"; | 
 |         goto fail_silent; | 
 |     } | 
 |  | 
 |     newData = ::mmap(nullptr, mInflatedSize, PROT_READ | PROT_WRITE, MAP_SHARED, ashmemFd, 0); | 
 |     if (newData == MAP_FAILED) { | 
 |         PLOG(ERROR) << "Failed mmap"; | 
 |         goto fail_silent; | 
 |     } | 
 |  | 
 |     if (ashmem_set_prot_region(ashmemFd, PROT_READ) < 0) { | 
 |         PLOG(ERROR) << "Failed ashmem_set_prot_region"; | 
 |         goto fail_silent; | 
 |     } | 
 |  | 
 |     { | 
 |         // Migrate existing contents into new ashmem region | 
 |         uint32_t slotsSize = mSize - mSlotsOffset; | 
 |         uint32_t newSlotsOffset = mInflatedSize - slotsSize; | 
 |         memcpy(static_cast<uint8_t*>(newData), | 
 |                 static_cast<uint8_t*>(mData), mAllocOffset); | 
 |         memcpy(static_cast<uint8_t*>(newData) + newSlotsOffset, | 
 |                 static_cast<uint8_t*>(mData) + mSlotsOffset, slotsSize); | 
 |  | 
 |         free(mData); | 
 |         mAshmemFd = ashmemFd; | 
 |         mData = newData; | 
 |         mSize = mInflatedSize; | 
 |         mSlotsOffset = newSlotsOffset; | 
 |  | 
 |         updateSlotsData(); | 
 |     } | 
 |  | 
 |     LOG(DEBUG) << "Inflated: " << this->toString(); | 
 |     return OK; | 
 |  | 
 | fail: | 
 |     LOG(ERROR) << "Failed maybeInflate"; | 
 | fail_silent: | 
 |     ::munmap(newData, mInflatedSize); | 
 |     ::close(ashmemFd); | 
 |     return UNKNOWN_ERROR; | 
 | } | 
 |  | 
 | status_t CursorWindow::createFromParcel(Parcel* parcel, CursorWindow** outWindow) { | 
 |     *outWindow = nullptr; | 
 |  | 
 |     CursorWindow* window = new CursorWindow(); | 
 |     if (!window) goto fail; | 
 |  | 
 |     if (parcel->readString8(&window->mName)) goto fail; | 
 |     if (parcel->readUint32(&window->mNumRows)) goto fail; | 
 |     if (parcel->readUint32(&window->mNumColumns)) goto fail; | 
 |     if (parcel->readUint32(&window->mSize)) goto fail; | 
 |  | 
 |     if ((window->mNumRows * window->mNumColumns * kSlotSizeBytes) > window->mSize) { | 
 |         LOG(ERROR) << "Unexpected size " << window->mSize << " for " << window->mNumRows | 
 |                 << " rows and " << window->mNumColumns << " columns"; | 
 |         goto fail_silent; | 
 |     } | 
 |  | 
 |     bool isAshmem; | 
 |     if (parcel->readBool(&isAshmem)) goto fail; | 
 |     if (isAshmem) { | 
 |         window->mAshmemFd = parcel->readFileDescriptor(); | 
 |         if (window->mAshmemFd < 0) { | 
 |             LOG(ERROR) << "Failed readFileDescriptor"; | 
 |             goto fail_silent; | 
 |         } | 
 |  | 
 |         window->mAshmemFd = ::fcntl(window->mAshmemFd, F_DUPFD_CLOEXEC, 0); | 
 |         if (window->mAshmemFd < 0) { | 
 |             PLOG(ERROR) << "Failed F_DUPFD_CLOEXEC"; | 
 |             goto fail_silent; | 
 |         } | 
 |  | 
 |         window->mData = ::mmap(nullptr, window->mSize, PROT_READ, MAP_SHARED, window->mAshmemFd, 0); | 
 |         if (window->mData == MAP_FAILED) { | 
 |             PLOG(ERROR) << "Failed mmap"; | 
 |             goto fail_silent; | 
 |         } | 
 |     } else { | 
 |         window->mAshmemFd = -1; | 
 |  | 
 |         if (window->mSize > kInlineSize) { | 
 |             LOG(ERROR) << "Unexpected size " << window->mSize << " for inline window"; | 
 |             goto fail_silent; | 
 |         } | 
 |  | 
 |         window->mData = malloc(window->mSize); | 
 |         if (!window->mData) goto fail; | 
 |  | 
 |         if (parcel->read(window->mData, window->mSize)) goto fail; | 
 |     } | 
 |  | 
 |     // We just came from a remote source, so we're read-only | 
 |     // and we can't inflate ourselves | 
 |     window->mInflatedSize = window->mSize; | 
 |     window->mReadOnly = true; | 
 |  | 
 |     window->updateSlotsData(); | 
 |  | 
 |     LOG(DEBUG) << "Created from parcel: " << window->toString(); | 
 |     *outWindow = window; | 
 |     return OK; | 
 |  | 
 | fail: | 
 |     LOG(ERROR) << "Failed createFromParcel"; | 
 | fail_silent: | 
 |     delete window; | 
 |     return UNKNOWN_ERROR; | 
 | } | 
 |  | 
 | status_t CursorWindow::writeToParcel(Parcel* parcel) { | 
 |     LOG(DEBUG) << "Writing to parcel: " << this->toString(); | 
 |  | 
 |     if (parcel->writeString8(mName)) goto fail; | 
 |     if (parcel->writeUint32(mNumRows)) goto fail; | 
 |     if (parcel->writeUint32(mNumColumns)) goto fail; | 
 |     if (mAshmemFd != -1) { | 
 |         if (parcel->writeUint32(mSize)) goto fail; | 
 |         if (parcel->writeBool(true)) goto fail; | 
 |         if (parcel->writeDupFileDescriptor(mAshmemFd)) goto fail; | 
 |     } else { | 
 |         // Since we know we're going to be read-only on the remote side, | 
 |         // we can compact ourselves on the wire, with just enough padding | 
 |         // to ensure our slots stay aligned | 
 |         size_t slotsSize = mSize - mSlotsOffset; | 
 |         size_t compactedSize = mAllocOffset + slotsSize; | 
 |         compactedSize = (compactedSize + 3) & ~3; | 
 |         if (parcel->writeUint32(compactedSize)) goto fail; | 
 |         if (parcel->writeBool(false)) goto fail; | 
 |         void* dest = parcel->writeInplace(compactedSize); | 
 |         if (!dest) goto fail; | 
 |         memcpy(static_cast<uint8_t*>(dest), | 
 |                 static_cast<uint8_t*>(mData), mAllocOffset); | 
 |         memcpy(static_cast<uint8_t*>(dest) + compactedSize - slotsSize, | 
 |                 static_cast<uint8_t*>(mData) + mSlotsOffset, slotsSize); | 
 |     } | 
 |     return OK; | 
 |  | 
 | fail: | 
 |     LOG(ERROR) << "Failed writeToParcel"; | 
 | fail_silent: | 
 |     return UNKNOWN_ERROR; | 
 | } | 
 |  | 
 | status_t CursorWindow::clear() { | 
 |     if (mReadOnly) { | 
 |         return INVALID_OPERATION; | 
 |     } | 
 |     mAllocOffset = 0; | 
 |     mSlotsOffset = mSize; | 
 |     mNumRows = 0; | 
 |     mNumColumns = 0; | 
 |     return OK; | 
 | } | 
 |  | 
 | void CursorWindow::updateSlotsData() { | 
 |     mSlotsStart = static_cast<uint8_t*>(mData) + mSize - kSlotSizeBytes; | 
 |     mSlotsEnd = static_cast<uint8_t*>(mData) + mSlotsOffset; | 
 | } | 
 |  | 
 | void* CursorWindow::offsetToPtr(uint32_t offset, uint32_t bufferSize = 0) { | 
 |     if (offset > mSize) { | 
 |         LOG(ERROR) << "Offset " << offset | 
 |                 << " out of bounds, max value " << mSize; | 
 |         return nullptr; | 
 |     } | 
 |     if (offset + bufferSize > mSize) { | 
 |         LOG(ERROR) << "End offset " << (offset + bufferSize) | 
 |                 << " out of bounds, max value " << mSize; | 
 |         return nullptr; | 
 |     } | 
 |     return static_cast<uint8_t*>(mData) + offset; | 
 | } | 
 |  | 
 | uint32_t CursorWindow::offsetFromPtr(void* ptr) { | 
 |     return static_cast<uint8_t*>(ptr) - static_cast<uint8_t*>(mData); | 
 | } | 
 |  | 
 | status_t CursorWindow::setNumColumns(uint32_t numColumns) { | 
 |     if (mReadOnly) { | 
 |         return INVALID_OPERATION; | 
 |     } | 
 |     uint32_t cur = mNumColumns; | 
 |     if ((cur > 0 || mNumRows > 0) && cur != numColumns) { | 
 |         LOG(ERROR) << "Trying to go from " << cur << " columns to " << numColumns; | 
 |         return INVALID_OPERATION; | 
 |     } | 
 |     mNumColumns = numColumns; | 
 |     return OK; | 
 | } | 
 |  | 
 | status_t CursorWindow::allocRow() { | 
 |     if (mReadOnly) { | 
 |         return INVALID_OPERATION; | 
 |     } | 
 |     size_t size = mNumColumns * kSlotSizeBytes; | 
 |     int32_t newOffset = mSlotsOffset - size; | 
 |     if (newOffset < (int32_t) mAllocOffset) { | 
 |         maybeInflate(); | 
 |         newOffset = mSlotsOffset - size; | 
 |         if (newOffset < (int32_t) mAllocOffset) { | 
 |             return NO_MEMORY; | 
 |         } | 
 |     } | 
 |     memset(offsetToPtr(newOffset), 0, size); | 
 |     mSlotsOffset = newOffset; | 
 |     updateSlotsData(); | 
 |     mNumRows++; | 
 |     return OK; | 
 | } | 
 |  | 
 | status_t CursorWindow::freeLastRow() { | 
 |     if (mReadOnly) { | 
 |         return INVALID_OPERATION; | 
 |     } | 
 |     size_t size = mNumColumns * kSlotSizeBytes; | 
 |     size_t newOffset = mSlotsOffset + size; | 
 |     if (newOffset > mSize) { | 
 |         return NO_MEMORY; | 
 |     } | 
 |     mSlotsOffset = newOffset; | 
 |     updateSlotsData(); | 
 |     mNumRows--; | 
 |     return OK; | 
 | } | 
 |  | 
 | status_t CursorWindow::alloc(size_t size, uint32_t* outOffset) { | 
 |     if (mReadOnly) { | 
 |         return INVALID_OPERATION; | 
 |     } | 
 |     size_t alignedSize = (size + 3) & ~3; | 
 |     size_t newOffset = mAllocOffset + alignedSize; | 
 |     if (newOffset > mSlotsOffset) { | 
 |         maybeInflate(); | 
 |         newOffset = mAllocOffset + alignedSize; | 
 |         if (newOffset > mSlotsOffset) { | 
 |             return NO_MEMORY; | 
 |         } | 
 |     } | 
 |     *outOffset = mAllocOffset; | 
 |     mAllocOffset = newOffset; | 
 |     return OK; | 
 | } | 
 |  | 
 | CursorWindow::FieldSlot* CursorWindow::getFieldSlot(uint32_t row, uint32_t column) { | 
 |     // This is carefully tuned to use as few cycles as | 
 |     // possible, since this is an extremely hot code path; | 
 |     // see CursorWindow_bench.cpp for more details | 
 |     void *result = static_cast<uint8_t*>(mSlotsStart) | 
 |             - (((row * mNumColumns) + column) << kSlotShift); | 
 |     if (result < mSlotsEnd || result > mSlotsStart || column >= mNumColumns) { | 
 |         LOG(ERROR) << "Failed to read row " << row << ", column " << column | 
 |                 << " from a window with " << mNumRows << " rows, " << mNumColumns << " columns"; | 
 |         return nullptr; | 
 |     } else { | 
 |         return static_cast<FieldSlot*>(result); | 
 |     } | 
 | } | 
 |  | 
 | status_t CursorWindow::putBlob(uint32_t row, uint32_t column, const void* value, size_t size) { | 
 |     return putBlobOrString(row, column, value, size, FIELD_TYPE_BLOB); | 
 | } | 
 |  | 
 | status_t CursorWindow::putString(uint32_t row, uint32_t column, const char* value, | 
 |         size_t sizeIncludingNull) { | 
 |     return putBlobOrString(row, column, value, sizeIncludingNull, FIELD_TYPE_STRING); | 
 | } | 
 |  | 
 | status_t CursorWindow::putBlobOrString(uint32_t row, uint32_t column, | 
 |         const void* value, size_t size, int32_t type) { | 
 |     if (mReadOnly) { | 
 |         return INVALID_OPERATION; | 
 |     } | 
 |  | 
 |     FieldSlot* fieldSlot = getFieldSlot(row, column); | 
 |     if (!fieldSlot) { | 
 |         return BAD_VALUE; | 
 |     } | 
 |  | 
 |     uint32_t offset; | 
 |     if (alloc(size, &offset)) { | 
 |         return NO_MEMORY; | 
 |     } | 
 |  | 
 |     memcpy(offsetToPtr(offset), value, size); | 
 |  | 
 |     fieldSlot = getFieldSlot(row, column); | 
 |     fieldSlot->type = type; | 
 |     fieldSlot->data.buffer.offset = offset; | 
 |     fieldSlot->data.buffer.size = size; | 
 |     return OK; | 
 | } | 
 |  | 
 | status_t CursorWindow::putLong(uint32_t row, uint32_t column, int64_t value) { | 
 |     if (mReadOnly) { | 
 |         return INVALID_OPERATION; | 
 |     } | 
 |  | 
 |     FieldSlot* fieldSlot = getFieldSlot(row, column); | 
 |     if (!fieldSlot) { | 
 |         return BAD_VALUE; | 
 |     } | 
 |  | 
 |     fieldSlot->type = FIELD_TYPE_INTEGER; | 
 |     fieldSlot->data.l = value; | 
 |     return OK; | 
 | } | 
 |  | 
 | status_t CursorWindow::putDouble(uint32_t row, uint32_t column, double value) { | 
 |     if (mReadOnly) { | 
 |         return INVALID_OPERATION; | 
 |     } | 
 |  | 
 |     FieldSlot* fieldSlot = getFieldSlot(row, column); | 
 |     if (!fieldSlot) { | 
 |         return BAD_VALUE; | 
 |     } | 
 |  | 
 |     fieldSlot->type = FIELD_TYPE_FLOAT; | 
 |     fieldSlot->data.d = value; | 
 |     return OK; | 
 | } | 
 |  | 
 | status_t CursorWindow::putNull(uint32_t row, uint32_t column) { | 
 |     if (mReadOnly) { | 
 |         return INVALID_OPERATION; | 
 |     } | 
 |  | 
 |     FieldSlot* fieldSlot = getFieldSlot(row, column); | 
 |     if (!fieldSlot) { | 
 |         return BAD_VALUE; | 
 |     } | 
 |  | 
 |     fieldSlot->type = FIELD_TYPE_NULL; | 
 |     fieldSlot->data.buffer.offset = 0; | 
 |     fieldSlot->data.buffer.size = 0; | 
 |     return OK; | 
 | } | 
 |  | 
 | }; // namespace android |