/* * 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 #include #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(newData), static_cast(mData), mAllocOffset); memcpy(static_cast(newData) + newSlotsOffset, static_cast(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(dest), static_cast(mData), mAllocOffset); memcpy(static_cast(dest) + compactedSize - slotsSize, static_cast(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(mData) + mSize - kSlotSizeBytes; mSlotsEnd = static_cast(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(mData) + offset; } uint32_t CursorWindow::offsetFromPtr(void* ptr) { return static_cast(ptr) - static_cast(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(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(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