diff options
author | bigbiff bigbiff <bigbiff@teamw.in> | 2018-12-19 00:39:53 +0100 |
---|---|---|
committer | Ethan Yonker <dees_troy@teamw.in> | 2019-03-20 20:28:21 +0100 |
commit | af32bb9c4f4f06e92de3435ed2db3153c0701094 (patch) | |
tree | 622948fb3167dc17bb436c948d61df581d2e75f7 /mtp/ffs | |
parent | Adding Edl button in reboot menu (diff) | |
download | android_bootable_recovery-af32bb9c4f4f06e92de3435ed2db3153c0701094.tar android_bootable_recovery-af32bb9c4f4f06e92de3435ed2db3153c0701094.tar.gz android_bootable_recovery-af32bb9c4f4f06e92de3435ed2db3153c0701094.tar.bz2 android_bootable_recovery-af32bb9c4f4f06e92de3435ed2db3153c0701094.tar.lz android_bootable_recovery-af32bb9c4f4f06e92de3435ed2db3153c0701094.tar.xz android_bootable_recovery-af32bb9c4f4f06e92de3435ed2db3153c0701094.tar.zst android_bootable_recovery-af32bb9c4f4f06e92de3435ed2db3153c0701094.zip |
Diffstat (limited to 'mtp/ffs')
58 files changed, 12088 insertions, 0 deletions
diff --git a/mtp/ffs/Android.mk b/mtp/ffs/Android.mk new file mode 100644 index 000000000..9e75e0d31 --- /dev/null +++ b/mtp/ffs/Android.mk @@ -0,0 +1,70 @@ +LOCAL_PATH := $(call my-dir) + +# Build libtwrpmtp library + +include $(CLEAR_VARS) +LOCAL_MODULE := libtwrpmtp-ffs +LOCAL_MODULE_TAGS := optional +LOCAL_CFLAGS = -D_FILE_OFFSET_BITS=64 -DMTP_DEVICE -DMTP_HOST -fno-strict-aliasing -Wno-unused-variable -Wno-format -Wno-unused-parameter -Wno-unused-private-field +LOCAL_C_INCLUDES += $(LOCAL_PATH) bionic frameworks/base/include system/core/include bionic/libc/private/ bootable/recovery/twrplibusbhost/include +ifeq ($(shell test $(PLATFORM_SDK_VERSION) -lt 23; echo $$?),0) + LOCAL_C_INCLUDES += external/stlport/stlport + LOCAL_SHARED_LIBRARIES += libstlport +else + LOCAL_SHARED_LIBRARIES += libc++ +endif + +LOCAL_SRC_FILES = \ + MtpDataPacket.cpp \ + MtpDebug.cpp \ + MtpDevice.cpp \ + MtpDevHandle.cpp \ + MtpDeviceInfo.cpp \ + MtpEventPacket.cpp \ + MtpObjectInfo.cpp \ + MtpPacket.cpp \ + MtpProperty.cpp \ + MtpRequestPacket.cpp \ + MtpResponsePacket.cpp \ + MtpServer.cpp \ + MtpStorage.cpp \ + MtpStorageInfo.cpp \ + MtpStringBuffer.cpp \ + MtpUtils.cpp \ + mtp_MtpServer.cpp \ + btree.cpp \ + twrpMtp.cpp \ + mtp_MtpDatabase.cpp \ + node.cpp + +ifeq ($(shell test $(PLATFORM_SDK_VERSION) -gt 25; echo $$?),0) + LOCAL_CFLAGS += -D_FFS_DEVICE + LOCAL_SHARED_LIBRARIES += libasyncio + LOCAL_SRC_FILES += \ + MtpDescriptors.cpp \ + MtpFfsHandle.cpp \ + MtpFfsCompatHandle.cpp \ + PosixAsyncIO.cpp +endif + +LOCAL_SHARED_LIBRARIES += libz \ + libc \ + libusbhost \ + libstdc++ \ + libdl \ + libcutils \ + libutils \ + libaosprecovery \ + libselinux \ + libbase + +LOCAL_C_INCLUDES += bootable/recovery/twrplibusbhost/include + +ifneq ($(TW_MTP_DEVICE),) + LOCAL_CFLAGS += -DUSB_MTP_DEVICE=$(TW_MTP_DEVICE) +endif +ifeq ($(shell test $(PLATFORM_SDK_VERSION) -gt 25; echo $$?),0) + LOCAL_CFLAGS += -DHAS_USBHOST_TIMEOUT +endif + +include $(BUILD_SHARED_LIBRARY) diff --git a/mtp/ffs/AsyncIO.cpp b/mtp/ffs/AsyncIO.cpp new file mode 100644 index 000000000..eb97a98a2 --- /dev/null +++ b/mtp/ffs/AsyncIO.cpp @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2016 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 <android-base/logging.h> +#include <condition_variable> +#include <memory> +#include <mutex> +#include <queue> + +#include "AsyncIO.h" + +void read_func(struct aiocb *aiocbp) { + aiocbp->ret = TEMP_FAILURE_RETRY(pread(aiocbp->aio_fildes, + aiocbp->aio_buf, aiocbp->aio_nbytes, aiocbp->aio_offset)); + if (aiocbp->ret == -1) aiocbp->error = errno; +} + +void write_func(struct aiocb *aiocbp) { + aiocbp->ret = TEMP_FAILURE_RETRY(pwrite(aiocbp->aio_fildes, + aiocbp->aio_buf, aiocbp->aio_nbytes, aiocbp->aio_offset)); + if (aiocbp->ret == -1) aiocbp->error = errno; +} + +void splice_read_func(struct aiocb *aiocbp) { + loff_t long_offset = aiocbp->aio_offset; + aiocbp->ret = TEMP_FAILURE_RETRY(splice(aiocbp->aio_fildes, + &long_offset, aiocbp->aio_sink, + NULL, aiocbp->aio_nbytes, 0)); + if (aiocbp->ret == -1) aiocbp->error = errno; +} + +void splice_write_func(struct aiocb *aiocbp) { + loff_t long_offset = aiocbp->aio_offset; + aiocbp->ret = TEMP_FAILURE_RETRY(splice(aiocbp->aio_fildes, NULL, + aiocbp->aio_sink, &long_offset, + aiocbp->aio_nbytes, 0)); + if (aiocbp->ret == -1) aiocbp->error = errno; +} + +std::queue<std::unique_ptr<struct aiocb>> queue; +std::mutex queue_lock; +std::condition_variable queue_cond; +std::condition_variable write_cond; +int done = 1; +void splice_write_pool_func(int) { + while(1) { + std::unique_lock<std::mutex> lk(queue_lock); + queue_cond.wait(lk, []{return !queue.empty() || done;}); + if (queue.empty() && done) { + return; + } + std::unique_ptr<struct aiocb> aiocbp = std::move(queue.front()); + queue.pop(); + lk.unlock(); + write_cond.notify_one(); + splice_write_func(aiocbp.get()); + close(aiocbp->aio_fildes); + } +} + +void write_pool_func(int) { + while(1) { + std::unique_lock<std::mutex> lk(queue_lock); + queue_cond.wait(lk, []{return !queue.empty() || done;}); + if (queue.empty() && done) { + return; + } + std::unique_ptr<struct aiocb> aiocbp = std::move(queue.front()); + queue.pop(); + lk.unlock(); + write_cond.notify_one(); + aiocbp->ret = TEMP_FAILURE_RETRY(pwrite(aiocbp->aio_fildes, + aiocbp->aio_pool_buf.get(), aiocbp->aio_nbytes, aiocbp->aio_offset)); + if (aiocbp->ret == -1) aiocbp->error = errno; + } +} + +constexpr int NUM_THREADS = 1; +constexpr int MAX_QUEUE_SIZE = 10; +std::thread pool[NUM_THREADS]; + +aiocb::~aiocb() { + CHECK(!thread.joinable()); +} + +void aio_pool_init(void(f)(int)) { + CHECK(done == 1); + done = 0; + for (int i = 0; i < NUM_THREADS; i++) { + pool[i] = std::thread(f, i); + } +} + +void aio_pool_splice_init() { + aio_pool_init(splice_write_pool_func); +} + +void aio_pool_write_init() { + aio_pool_init(write_pool_func); +} + +void aio_pool_end() { + done = 1; + for (int i = 0; i < NUM_THREADS; i++) { + std::unique_lock<std::mutex> lk(queue_lock); + lk.unlock(); + queue_cond.notify_one(); + } + + for (int i = 0; i < NUM_THREADS; i++) { + pool[i].join(); + } +} + +// used for both writes and splices depending on which init was used before. +int aio_pool_write(struct aiocb *aiocbp) { + std::unique_lock<std::mutex> lk(queue_lock); + write_cond.wait(lk, []{return queue.size() < MAX_QUEUE_SIZE;}); + queue.push(std::unique_ptr<struct aiocb>(aiocbp)); + lk.unlock(); + queue_cond.notify_one(); + return 0; +} + +int aio_read(struct aiocb *aiocbp) { + aiocbp->thread = std::thread(read_func, aiocbp); + return 0; +} + +int aio_write(struct aiocb *aiocbp) { + aiocbp->thread = std::thread(write_func, aiocbp); + return 0; +} + +int aio_splice_read(struct aiocb *aiocbp) { + aiocbp->thread = std::thread(splice_read_func, aiocbp); + return 0; +} + +int aio_splice_write(struct aiocb *aiocbp) { + aiocbp->thread = std::thread(splice_write_func, aiocbp); + return 0; +} + +int aio_error(const struct aiocb *aiocbp) { + return aiocbp->error; +} + +ssize_t aio_return(struct aiocb *aiocbp) { + return aiocbp->ret; +} + +int aio_suspend(struct aiocb *aiocbp[], int n, + const struct timespec *) { + for (int i = 0; i < n; i++) { + aiocbp[i]->thread.join(); + } + return 0; +} + +int aio_cancel(int, struct aiocb *) { + // Not implemented + return -1; +} + diff --git a/mtp/ffs/AsyncIO.h b/mtp/ffs/AsyncIO.h new file mode 100644 index 000000000..19e76179c --- /dev/null +++ b/mtp/ffs/AsyncIO.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2016 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. + */ + +#ifndef _ASYNCIO_H +#define _ASYNCIO_H + +#include <fcntl.h> +#include <linux/aio_abi.h> +#include <memory> +#include <signal.h> +#include <sys/cdefs.h> +#include <sys/types.h> +#include <time.h> +#include <thread> +#include <unistd.h> + +/** + * Provides a subset of POSIX aio operations, as well + * as similar operations with splice and threadpools. + */ + +struct aiocb { + int aio_fildes; // Assumed to be the source for splices + void *aio_buf; // Unused for splices + + // Used for threadpool operations only, freed automatically + std::unique_ptr<char[]> aio_pool_buf; + + off_t aio_offset; + size_t aio_nbytes; + + int aio_sink; // Unused for non splice r/w + + // Used internally + std::thread thread; + ssize_t ret; + int error; + + ~aiocb(); +}; + +// Submit a request for IO to be completed +int aio_read(struct aiocb *); +int aio_write(struct aiocb *); +int aio_splice_read(struct aiocb *); +int aio_splice_write(struct aiocb *); + +// Suspend current thread until given IO is complete, at which point +// its return value and any errors can be accessed +// All submitted requests must have a corresponding suspend. +// aiocb->aio_buf must refer to valid memory until after the suspend call +int aio_suspend(struct aiocb *[], int, const struct timespec *); +int aio_error(const struct aiocb *); +ssize_t aio_return(struct aiocb *); + +// (Currently unimplemented) +int aio_cancel(int, struct aiocb *); + +// Initialize a threadpool to perform IO. Only one pool can be +// running at a time. +void aio_pool_write_init(); +void aio_pool_splice_init(); +// Suspend current thread until all queued work is complete, then ends the threadpool +void aio_pool_end(); +// Submit IO work for the threadpool to complete. Memory associated with the work is +// freed automatically when the work is complete. +int aio_pool_write(struct aiocb *); + +#endif // ASYNCIO_H + diff --git a/mtp/ffs/IMtpHandle.h b/mtp/ffs/IMtpHandle.h new file mode 100644 index 000000000..caf445013 --- /dev/null +++ b/mtp/ffs/IMtpHandle.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2016 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. + */ +#ifndef _IMTP_HANDLE_H +#define _IMTP_HANDLE_H + +#include <linux/usb/f_mtp.h> + +class IMtpHandle { +public: + // Return number of bytes read/written, or -1 and errno is set + virtual int read(void *data, size_t len) = 0; + virtual int write(const void *data, size_t len) = 0; + + // Return 0 if send/receive is successful, or -1 and errno is set + virtual int receiveFile(mtp_file_range mfr, bool zero_packet) = 0; + virtual int sendFile(mtp_file_range mfr) = 0; + virtual int sendEvent(mtp_event me) = 0; + + // Return 0 if operation is successful, or -1 else + virtual int start(bool ptp) = 0; + + virtual bool writeDescriptors(bool ptp) = 0; + + virtual void close() = 0; + + virtual ~IMtpHandle() {} +}; + +#endif // _IMTP_HANDLE_H + diff --git a/mtp/ffs/MtpDataPacket.cpp b/mtp/ffs/MtpDataPacket.cpp new file mode 100644 index 000000000..08f57c5e4 --- /dev/null +++ b/mtp/ffs/MtpDataPacket.cpp @@ -0,0 +1,650 @@ +/* + * 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 "MtpDataPacket" + +#include "MtpDataPacket.h" + +#include <algorithm> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <sys/types.h> +#include <usbhost/usbhost.h> +#include "MtpStringBuffer.h" +#include "IMtpHandle.h" +#include "MtpDebug.h" + +namespace { +// Reads the exact |count| bytes from |fd| to |buf|. +// Returns |count| if it succeed to read the bytes. Otherwise returns -1. If it reaches EOF, the +// function regards it as an error. +ssize_t readExactBytes(int fd, void* buf, size_t count) { + if (count > SSIZE_MAX) { + return -1; + } + size_t read_count = 0; + while (read_count < count) { + int result = read(fd, static_cast<int8_t*>(buf) + read_count, count - read_count); + // Assume that EOF is error. + if (result <= 0) { + return -1; + } + read_count += result; + } + return read_count == count ? count : -1; +} +} // namespace + +MtpDataPacket::MtpDataPacket() + : MtpPacket(MTP_BUFFER_SIZE), // MAX_USBFS_BUFFER_SIZE + mOffset(MTP_CONTAINER_HEADER_SIZE) +{ +} + +MtpDataPacket::~MtpDataPacket() { +} + +void MtpDataPacket::reset() { + MtpPacket::reset(); + mOffset = MTP_CONTAINER_HEADER_SIZE; +} + +void MtpDataPacket::setOperationCode(MtpOperationCode code) { + MtpPacket::putUInt16(MTP_CONTAINER_CODE_OFFSET, code); +} + +void MtpDataPacket::setTransactionID(MtpTransactionID id) { + MtpPacket::putUInt32(MTP_CONTAINER_TRANSACTION_ID_OFFSET, id); +} + +bool MtpDataPacket::getUInt8(uint8_t& value) { + if (mPacketSize - mOffset < sizeof(value)) + return false; + value = mBuffer[mOffset++]; + return true; +} + +bool MtpDataPacket::getUInt16(uint16_t& value) { + if (mPacketSize - mOffset < sizeof(value)) + return false; + int offset = mOffset; + value = (uint16_t)mBuffer[offset] | ((uint16_t)mBuffer[offset + 1] << 8); + mOffset += sizeof(value); + return true; +} + +bool MtpDataPacket::getUInt32(uint32_t& value) { + if (mPacketSize - mOffset < sizeof(value)) + return false; + int offset = mOffset; + value = (uint32_t)mBuffer[offset] | ((uint32_t)mBuffer[offset + 1] << 8) | + ((uint32_t)mBuffer[offset + 2] << 16) | ((uint32_t)mBuffer[offset + 3] << 24); + mOffset += sizeof(value); + return true; +} + +bool MtpDataPacket::getUInt64(uint64_t& value) { + if (mPacketSize - mOffset < sizeof(value)) + return false; + int offset = mOffset; + value = (uint64_t)mBuffer[offset] | ((uint64_t)mBuffer[offset + 1] << 8) | + ((uint64_t)mBuffer[offset + 2] << 16) | ((uint64_t)mBuffer[offset + 3] << 24) | + ((uint64_t)mBuffer[offset + 4] << 32) | ((uint64_t)mBuffer[offset + 5] << 40) | + ((uint64_t)mBuffer[offset + 6] << 48) | ((uint64_t)mBuffer[offset + 7] << 56); + mOffset += sizeof(value); + return true; +} + +bool MtpDataPacket::getUInt128(uint128_t& value) { + return getUInt32(value[0]) && getUInt32(value[1]) && getUInt32(value[2]) && getUInt32(value[3]); +} + +bool MtpDataPacket::getString(MtpStringBuffer& string) +{ + return string.readFromPacket(this); +} + +Int8List* MtpDataPacket::getAInt8() { + uint32_t count; + if (!getUInt32(count)) + return NULL; + Int8List* result = new Int8List; + for (uint32_t i = 0; i < count; i++) { + int8_t value; + if (!getInt8(value)) { + delete result; + return NULL; + } + result->push_back(value); + } + return result; +} + +UInt8List* MtpDataPacket::getAUInt8() { + uint32_t count; + if (!getUInt32(count)) + return NULL; + UInt8List* result = new UInt8List; + for (uint32_t i = 0; i < count; i++) { + uint8_t value; + if (!getUInt8(value)) { + delete result; + return NULL; + } + result->push_back(value); + } + return result; +} + +Int16List* MtpDataPacket::getAInt16() { + uint32_t count; + if (!getUInt32(count)) + return NULL; + Int16List* result = new Int16List; + for (uint32_t i = 0; i < count; i++) { + int16_t value; + if (!getInt16(value)) { + delete result; + return NULL; + } + result->push_back(value); + } + return result; +} + +UInt16List* MtpDataPacket::getAUInt16() { + uint32_t count; + if (!getUInt32(count)) + return NULL; + UInt16List* result = new UInt16List; + for (uint32_t i = 0; i < count; i++) { + uint16_t value; + if (!getUInt16(value)) { + delete result; + return NULL; + } + result->push_back(value); + } + return result; +} + +Int32List* MtpDataPacket::getAInt32() { + uint32_t count; + if (!getUInt32(count)) + return NULL; + Int32List* result = new Int32List; + for (uint32_t i = 0; i < count; i++) { + int32_t value; + if (!getInt32(value)) { + delete result; + return NULL; + } + result->push_back(value); + } + return result; +} + +UInt32List* MtpDataPacket::getAUInt32() { + uint32_t count; + if (!getUInt32(count)) + return NULL; + UInt32List* result = new UInt32List; + for (uint32_t i = 0; i < count; i++) { + uint32_t value; + if (!getUInt32(value)) { + delete result; + return NULL; + } + result->push_back(value); + } + return result; +} + +Int64List* MtpDataPacket::getAInt64() { + uint32_t count; + if (!getUInt32(count)) + return NULL; + Int64List* result = new Int64List; + for (uint32_t i = 0; i < count; i++) { + int64_t value; + if (!getInt64(value)) { + delete result; + return NULL; + } + result->push_back(value); + } + return result; +} + +UInt64List* MtpDataPacket::getAUInt64() { + uint32_t count; + if (!getUInt32(count)) + return NULL; + UInt64List* result = new UInt64List; + for (uint32_t i = 0; i < count; i++) { + uint64_t value; + if (!getUInt64(value)) { + delete result; + return NULL; + } + result->push_back(value); + } + return result; +} + +void MtpDataPacket::putInt8(int8_t value) { + allocate(mOffset + 1); + mBuffer[mOffset++] = (uint8_t)value; + if (mPacketSize < mOffset) + mPacketSize = mOffset; +} + +void MtpDataPacket::putUInt8(uint8_t value) { + allocate(mOffset + 1); + mBuffer[mOffset++] = (uint8_t)value; + if (mPacketSize < mOffset) + mPacketSize = mOffset; +} + +void MtpDataPacket::putInt16(int16_t value) { + allocate(mOffset + 2); + mBuffer[mOffset++] = (uint8_t)(value & 0xFF); + mBuffer[mOffset++] = (uint8_t)((value >> 8) & 0xFF); + if (mPacketSize < mOffset) + mPacketSize = mOffset; +} + +void MtpDataPacket::putUInt16(uint16_t value) { + allocate(mOffset + 2); + mBuffer[mOffset++] = (uint8_t)(value & 0xFF); + mBuffer[mOffset++] = (uint8_t)((value >> 8) & 0xFF); + if (mPacketSize < mOffset) + mPacketSize = mOffset; +} + +void MtpDataPacket::putInt32(int32_t value) { + allocate(mOffset + 4); + mBuffer[mOffset++] = (uint8_t)(value & 0xFF); + mBuffer[mOffset++] = (uint8_t)((value >> 8) & 0xFF); + mBuffer[mOffset++] = (uint8_t)((value >> 16) & 0xFF); + mBuffer[mOffset++] = (uint8_t)((value >> 24) & 0xFF); + if (mPacketSize < mOffset) + mPacketSize = mOffset; +} + +void MtpDataPacket::putUInt32(uint32_t value) { + allocate(mOffset + 4); + mBuffer[mOffset++] = (uint8_t)(value & 0xFF); + mBuffer[mOffset++] = (uint8_t)((value >> 8) & 0xFF); + mBuffer[mOffset++] = (uint8_t)((value >> 16) & 0xFF); + mBuffer[mOffset++] = (uint8_t)((value >> 24) & 0xFF); + if (mPacketSize < mOffset) + mPacketSize = mOffset; +} + +void MtpDataPacket::putInt64(int64_t value) { + allocate(mOffset + 8); + mBuffer[mOffset++] = (uint8_t)(value & 0xFF); + mBuffer[mOffset++] = (uint8_t)((value >> 8) & 0xFF); + mBuffer[mOffset++] = (uint8_t)((value >> 16) & 0xFF); + mBuffer[mOffset++] = (uint8_t)((value >> 24) & 0xFF); + mBuffer[mOffset++] = (uint8_t)((value >> 32) & 0xFF); + mBuffer[mOffset++] = (uint8_t)((value >> 40) & 0xFF); + mBuffer[mOffset++] = (uint8_t)((value >> 48) & 0xFF); + mBuffer[mOffset++] = (uint8_t)((value >> 56) & 0xFF); + if (mPacketSize < mOffset) + mPacketSize = mOffset; +} + +void MtpDataPacket::putUInt64(uint64_t value) { + allocate(mOffset + 8); + mBuffer[mOffset++] = (uint8_t)(value & 0xFF); + mBuffer[mOffset++] = (uint8_t)((value >> 8) & 0xFF); + mBuffer[mOffset++] = (uint8_t)((value >> 16) & 0xFF); + mBuffer[mOffset++] = (uint8_t)((value >> 24) & 0xFF); + mBuffer[mOffset++] = (uint8_t)((value >> 32) & 0xFF); + mBuffer[mOffset++] = (uint8_t)((value >> 40) & 0xFF); + mBuffer[mOffset++] = (uint8_t)((value >> 48) & 0xFF); + mBuffer[mOffset++] = (uint8_t)((value >> 56) & 0xFF); + if (mPacketSize < mOffset) + mPacketSize = mOffset; +} + +void MtpDataPacket::putInt128(const int128_t& value) { + putInt32(value[0]); + putInt32(value[1]); + putInt32(value[2]); + putInt32(value[3]); +} + +void MtpDataPacket::putUInt128(const uint128_t& value) { + putUInt32(value[0]); + putUInt32(value[1]); + putUInt32(value[2]); + putUInt32(value[3]); +} + +void MtpDataPacket::putInt128(int64_t value) { + putInt64(value); + putInt64(value < 0 ? -1 : 0); +} + +void MtpDataPacket::putUInt128(uint64_t value) { + putUInt64(value); + putUInt64(0); +} + +void MtpDataPacket::putAInt8(const int8_t* values, int count) { + putUInt32(count); + for (int i = 0; i < count; i++) + putInt8(*values++); +} + +void MtpDataPacket::putAUInt8(const uint8_t* values, int count) { + putUInt32(count); + for (int i = 0; i < count; i++) + putUInt8(*values++); +} + +void MtpDataPacket::putAInt16(const int16_t* values, int count) { + putUInt32(count); + for (int i = 0; i < count; i++) + putInt16(*values++); +} + +void MtpDataPacket::putAUInt16(const uint16_t* values, int count) { + putUInt32(count); + for (int i = 0; i < count; i++) + putUInt16(*values++); +} + +void MtpDataPacket::putAUInt16(const UInt16List* values) { + size_t count = (values ? values->size() : 0); + putUInt32(count); + for (size_t i = 0; i < count; i++) + putUInt16((*values)[i]); +} + +void MtpDataPacket::putAInt32(const int32_t* values, int count) { + putUInt32(count); + for (int i = 0; i < count; i++) + putInt32(*values++); +} + +void MtpDataPacket::putAUInt32(const uint32_t* values, int count) { + putUInt32(count); + for (int i = 0; i < count; i++) + putUInt32(*values++); +} + +void MtpDataPacket::putAUInt32(const UInt32List* list) { + if (!list) { + putEmptyArray(); + } else { + size_t size = list->size(); + putUInt32(size); + for (size_t i = 0; i < size; i++) + putUInt32((*list)[i]); + } +} + +void MtpDataPacket::putAInt64(const int64_t* values, int count) { + putUInt32(count); + for (int i = 0; i < count; i++) + putInt64(*values++); +} + +void MtpDataPacket::putAUInt64(const uint64_t* values, int count) { + putUInt32(count); + for (int i = 0; i < count; i++) + putUInt64(*values++); +} + +void MtpDataPacket::putString(const MtpStringBuffer& string) { + string.writeToPacket(this); +} + +void MtpDataPacket::putString(const char* s) { + MtpStringBuffer string(s); + string.writeToPacket(this); +} + +void MtpDataPacket::putString(const uint16_t* string) { + int count = 0; + for (int i = 0; i <= MTP_STRING_MAX_CHARACTER_NUMBER; i++) { + if (string[i]) + count++; + else + break; + } + putUInt8(count > 0 ? count + 1 : 0); + for (int i = 0; i < count; i++) + putUInt16(string[i]); + // only terminate with zero if string is not empty + if (count > 0) + putUInt16(0); +} + +#ifdef MTP_DEVICE +int MtpDataPacket::read(IMtpHandle *h) { + int ret = h->read(mBuffer, MTP_BUFFER_SIZE); + if (ret < MTP_CONTAINER_HEADER_SIZE) + return -1; + mPacketSize = ret; + mOffset = MTP_CONTAINER_HEADER_SIZE; + return ret; +} + +int MtpDataPacket::write(IMtpHandle *h) { + MtpPacket::putUInt32(MTP_CONTAINER_LENGTH_OFFSET, mPacketSize); + MtpPacket::putUInt16(MTP_CONTAINER_TYPE_OFFSET, MTP_CONTAINER_TYPE_DATA); + int ret = h->write(mBuffer, mPacketSize); + return (ret < 0 ? ret : 0); +} + +int MtpDataPacket::writeData(IMtpHandle *h, void* data, uint32_t length) { + allocate(length + MTP_CONTAINER_HEADER_SIZE); + memcpy(mBuffer + MTP_CONTAINER_HEADER_SIZE, data, length); + length += MTP_CONTAINER_HEADER_SIZE; + MtpPacket::putUInt32(MTP_CONTAINER_LENGTH_OFFSET, length); + MtpPacket::putUInt16(MTP_CONTAINER_TYPE_OFFSET, MTP_CONTAINER_TYPE_DATA); + int ret = h->write(mBuffer, length); + return (ret < 0 ? ret : 0); +} + +#endif // MTP_DEVICE + +#ifdef MTP_HOST +int MtpDataPacket::read(struct usb_request *request) { + // first read the header + request->buffer = mBuffer; + request->buffer_length = mBufferSize; + int length = transfer(request); + if (length >= MTP_CONTAINER_HEADER_SIZE) { + // look at the length field to see if the data spans multiple packets + uint32_t totalLength = MtpPacket::getUInt32(MTP_CONTAINER_LENGTH_OFFSET); + allocate(totalLength); + while (totalLength > static_cast<uint32_t>(length)) { + request->buffer = mBuffer + length; + request->buffer_length = totalLength - length; + int ret = transfer(request); + if (ret >= 0) + length += ret; + else { + length = ret; + break; + } + } + } + if (length >= 0) + mPacketSize = length; + return length; +} + +int MtpDataPacket::readData(struct usb_request *request, void* buffer, int length) { + int read = 0; + while (read < length) { + request->buffer = (char *)buffer + read; + request->buffer_length = length - read; + int ret = transfer(request); + if (ret < 0) { + return ret; + } + read += ret; + } + return read; +} + +// Queue a read request. Call readDataWait to wait for result +int MtpDataPacket::readDataAsync(struct usb_request *req) { + if (usb_request_queue(req)) { + MTPE("usb_endpoint_queue failed, errno: %d", errno); + return -1; + } + return 0; +} + +// Wait for result of readDataAsync +int MtpDataPacket::readDataWait(struct usb_device *device) { + struct usb_request *req = usb_request_wait(device, -1); + return (req ? req->actual_length : -1); +} + +int MtpDataPacket::readDataHeader(struct usb_request *request) { + request->buffer = mBuffer; + request->buffer_length = request->max_packet_size; + int length = transfer(request); + if (length >= 0) + mPacketSize = length; + return length; +} + +int MtpDataPacket::write(struct usb_request *request, UrbPacketDivisionMode divisionMode) { + if (mPacketSize < MTP_CONTAINER_HEADER_SIZE || mPacketSize > MTP_BUFFER_SIZE) { + MTPE("Illegal packet size."); + return -1; + } + + MtpPacket::putUInt32(MTP_CONTAINER_LENGTH_OFFSET, mPacketSize); + MtpPacket::putUInt16(MTP_CONTAINER_TYPE_OFFSET, MTP_CONTAINER_TYPE_DATA); + + size_t processedBytes = 0; + while (processedBytes < mPacketSize) { + const size_t write_size = + processedBytes == 0 && divisionMode == FIRST_PACKET_ONLY_HEADER ? + MTP_CONTAINER_HEADER_SIZE : mPacketSize - processedBytes; + request->buffer = mBuffer + processedBytes; + request->buffer_length = write_size; + const int result = transfer(request); + if (result < 0) { + MTPE("Failed to write bytes to the device."); + return -1; + } + processedBytes += result; + } + + return processedBytes == mPacketSize ? processedBytes : -1; +} + +int MtpDataPacket::write(struct usb_request *request, + UrbPacketDivisionMode divisionMode, + int fd, + size_t payloadSize) { + // Obtain the greatest multiple of minimum packet size that is not greater than + // MTP_BUFFER_SIZE. + if (request->max_packet_size <= 0) { + MTPE("Cannot determine bulk transfer size due to illegal max packet size %d.", + request->max_packet_size); + return -1; + } + const size_t maxBulkTransferSize = + MTP_BUFFER_SIZE - (MTP_BUFFER_SIZE % request->max_packet_size); + const size_t containerLength = payloadSize + MTP_CONTAINER_HEADER_SIZE; + size_t processedBytes = 0; + bool readError = false; + + // Bind the packet with given request. + request->buffer = mBuffer; + allocate(maxBulkTransferSize); + + while (processedBytes < containerLength) { + size_t bulkTransferSize = 0; + + // prepare header. + const bool headerSent = processedBytes != 0; + if (!headerSent) { + MtpPacket::putUInt32(MTP_CONTAINER_LENGTH_OFFSET, containerLength); + MtpPacket::putUInt16(MTP_CONTAINER_TYPE_OFFSET, MTP_CONTAINER_TYPE_DATA); + bulkTransferSize += MTP_CONTAINER_HEADER_SIZE; + } + + // Prepare payload. + if (headerSent || divisionMode == FIRST_PACKET_HAS_PAYLOAD) { + const size_t processedPayloadBytes = + headerSent ? processedBytes - MTP_CONTAINER_HEADER_SIZE : 0; + const size_t maxRead = payloadSize - processedPayloadBytes; + const size_t maxWrite = maxBulkTransferSize - bulkTransferSize; + const size_t bulkTransferPayloadSize = std::min(maxRead, maxWrite); + // prepare payload. + if (!readError) { + const ssize_t result = readExactBytes( + fd, + mBuffer + bulkTransferSize, + bulkTransferPayloadSize); + if (result < 0) { + MTPE("Found an error while reading data from FD. Send 0 data instead."); + readError = true; + } + } + if (readError) { + memset(mBuffer + bulkTransferSize, 0, bulkTransferPayloadSize); + } + bulkTransferSize += bulkTransferPayloadSize; + } + + // Bulk transfer. + mPacketSize = bulkTransferSize; + request->buffer_length = bulkTransferSize; + const int result = transfer(request); + if (result != static_cast<ssize_t>(bulkTransferSize)) { + // Cannot recover writing error. + MTPE("Found an error while write data to MtpDevice."); + return -1; + } + + // Update variables. + processedBytes += bulkTransferSize; + } + + return readError ? -1 : processedBytes; +} + +#endif // MTP_HOST + +void* MtpDataPacket::getData(int* outLength) const { + int length = mPacketSize - MTP_CONTAINER_HEADER_SIZE; + if (length > 0) { + void* result = malloc(length); + if (result) { + memcpy(result, mBuffer + MTP_CONTAINER_HEADER_SIZE, length); + *outLength = length; + return result; + } + } + *outLength = 0; + return NULL; +} diff --git a/mtp/ffs/MtpDataPacket.h b/mtp/ffs/MtpDataPacket.h new file mode 100644 index 000000000..2240a3dd4 --- /dev/null +++ b/mtp/ffs/MtpDataPacket.h @@ -0,0 +1,127 @@ +/* + * 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. + */ + +#ifndef _MTP_DATA_PACKET_H +#define _MTP_DATA_PACKET_H + +#include "MtpPacket.h" +#include "mtp.h" + +struct usb_device; +struct usb_request; + +class IMtpHandle; +class MtpStringBuffer; + +class MtpDataPacket : public MtpPacket { +private: + // current offset for get/put methods + size_t mOffset; + +public: + MtpDataPacket(); + virtual ~MtpDataPacket(); + + virtual void reset(); + + void setOperationCode(MtpOperationCode code); + void setTransactionID(MtpTransactionID id); + + inline const uint8_t* getData() const { return mBuffer + MTP_CONTAINER_HEADER_SIZE; } + + bool getUInt8(uint8_t& value); + inline bool getInt8(int8_t& value) { return getUInt8((uint8_t&)value); } + bool getUInt16(uint16_t& value); + inline bool getInt16(int16_t& value) { return getUInt16((uint16_t&)value); } + bool getUInt32(uint32_t& value); + inline bool getInt32(int32_t& value) { return getUInt32((uint32_t&)value); } + bool getUInt64(uint64_t& value); + inline bool getInt64(int64_t& value) { return getUInt64((uint64_t&)value); } + bool getUInt128(uint128_t& value); + inline bool getInt128(int128_t& value) { return getUInt128((uint128_t&)value); } + bool getString(MtpStringBuffer& string); + + Int8List* getAInt8(); + UInt8List* getAUInt8(); + Int16List* getAInt16(); + UInt16List* getAUInt16(); + Int32List* getAInt32(); + UInt32List* getAUInt32(); + Int64List* getAInt64(); + UInt64List* getAUInt64(); + + void putInt8(int8_t value); + void putUInt8(uint8_t value); + void putInt16(int16_t value); + void putUInt16(uint16_t value); + void putInt32(int32_t value); + void putUInt32(uint32_t value); + void putInt64(int64_t value); + void putUInt64(uint64_t value); + void putInt128(const int128_t& value); + void putUInt128(const uint128_t& value); + void putInt128(int64_t value); + void putUInt128(uint64_t value); + + void putAInt8(const int8_t* values, int count); + void putAUInt8(const uint8_t* values, int count); + void putAInt16(const int16_t* values, int count); + void putAUInt16(const uint16_t* values, int count); + void putAUInt16(const UInt16List* values); + void putAInt32(const int32_t* values, int count); + void putAUInt32(const uint32_t* values, int count); + void putAUInt32(const UInt32List* list); + void putAInt64(const int64_t* values, int count); + void putAUInt64(const uint64_t* values, int count); + void putString(const MtpStringBuffer& string); + void putString(const char* string); + void putString(const uint16_t* string); + inline void putEmptyString() { putUInt8(0); } + inline void putEmptyArray() { putUInt32(0); } + +#ifdef MTP_DEVICE + // fill our buffer with data from the given usb handle + int read(IMtpHandle *h); + + // write our data to the given usb handle + int write(IMtpHandle *h); + int writeData(IMtpHandle *h, void* data, uint32_t length); +#endif + +#ifdef MTP_HOST + int read(struct usb_request *request); + int readData(struct usb_request *request, void* buffer, int length); + int readDataAsync(struct usb_request *req); + int readDataWait(struct usb_device *device); + int readDataHeader(struct usb_request *ep); + + // Write a whole data packet with payload to the end point given by a request. |divisionMode| + // specifies whether to divide header and payload. See |UrbPacketDivisionMode| for meanings of + // each value. Return the number of bytes (including header size) sent to the device on success. + // Otherwise -1. + int write(struct usb_request *request, UrbPacketDivisionMode divisionMode); + // Similar to previous write method but it reads the payload from |fd|. If |size| is larger than + // MTP_BUFFER_SIZE, the data will be sent by multiple bulk transfer requests. + int write(struct usb_request *request, UrbPacketDivisionMode divisionMode, + int fd, size_t size); +#endif + + inline bool hasData() const { return mPacketSize > MTP_CONTAINER_HEADER_SIZE; } + inline uint32_t getContainerLength() const { return MtpPacket::getUInt32(MTP_CONTAINER_LENGTH_OFFSET); } + void* getData(int* outLength) const; +}; + +#endif // _MTP_DATA_PACKET_H diff --git a/mtp/ffs/MtpDatabase.h b/mtp/ffs/MtpDatabase.h new file mode 100755 index 000000000..18aabb8ac --- /dev/null +++ b/mtp/ffs/MtpDatabase.h @@ -0,0 +1,112 @@ +/* + * 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. + */ + +#ifndef _MTP_DATABASE_H +#define _MTP_DATABASE_H + +#include "MtpTypes.h" +#include "MtpStringBuffer.h" + +class MtpDataPacket; +class MtpProperty; +class MtpObjectInfo; + +class MtpDatabase { +public: + virtual ~MtpDatabase() {} + + // called from SendObjectInfo to reserve a database entry for the incoming file + virtual MtpObjectHandle beginSendObject(const char* path, + MtpObjectFormat format, + MtpObjectHandle parent, + MtpStorageID storage, + uint64_t size, + time_t modified) = 0; + + // called to report success or failure of the SendObject file transfer + // success should signal a notification of the new object's creation, + // failure should remove the database entry created in beginSendObject + virtual void endSendObject(const char* path, + MtpObjectHandle handle, + MtpObjectFormat format, + bool succeeded) = 0; + + virtual MtpObjectHandleList* getObjectList(MtpStorageID storageID, + MtpObjectFormat format, + MtpObjectHandle parent) = 0; + + virtual int getNumObjects(MtpStorageID storageID, + MtpObjectFormat format, + MtpObjectHandle parent) = 0; + + // callee should delete[] the results from these + // results can be NULL + virtual MtpObjectFormatList* getSupportedPlaybackFormats() = 0; + virtual MtpObjectFormatList* getSupportedCaptureFormats() = 0; + virtual MtpObjectPropertyList* getSupportedObjectProperties(MtpObjectFormat format) = 0; + virtual MtpDevicePropertyList* getSupportedDeviceProperties() = 0; + + virtual void createDB(MtpStorage* storage, MtpStorageID storageID) = 0; + + virtual MtpResponseCode getObjectPropertyValue(MtpObjectHandle handle, + MtpObjectProperty property, + MtpDataPacket& packet) = 0; + + virtual MtpResponseCode setObjectPropertyValue(MtpObjectHandle handle, + MtpObjectProperty property, + MtpDataPacket& packet) = 0; + + virtual MtpResponseCode getDevicePropertyValue(MtpDeviceProperty property, + MtpDataPacket& packet) = 0; + + virtual MtpResponseCode setDevicePropertyValue(MtpDeviceProperty property, + MtpDataPacket& packet) = 0; + + virtual MtpResponseCode resetDeviceProperty(MtpDeviceProperty property) = 0; + + virtual MtpResponseCode getObjectPropertyList(MtpObjectHandle handle, + uint32_t format, uint32_t property, + int groupCode, int depth, + MtpDataPacket& packet) = 0; + + virtual MtpResponseCode getObjectInfo(MtpObjectHandle handle, + MtpObjectInfo& info) = 0; + + virtual void* getThumbnail(MtpObjectHandle handle, size_t& outThumbSize) = 0; + + virtual MtpResponseCode getObjectFilePath(MtpObjectHandle handle, + MtpStringBuffer& outFilePath, + int64_t& outFileLength, + MtpObjectFormat& outFormat) = 0; + + // virtual MtpResponseCode deleteFile(MtpObjectHandle handle) = 0; + + virtual MtpObjectHandleList* getObjectReferences(MtpObjectHandle handle) = 0; + + virtual MtpResponseCode setObjectReferences(MtpObjectHandle handle, + MtpObjectHandleList* references) = 0; + + virtual MtpProperty* getObjectPropertyDesc(MtpObjectProperty property, + MtpObjectFormat format) = 0; + + virtual MtpProperty* getDevicePropertyDesc(MtpDeviceProperty property) = 0; + + virtual void sessionStarted() = 0; + + virtual void sessionEnded() = 0; +}; + +#endif // _MTP_DATABASE_H diff --git a/mtp/ffs/MtpDebug.cpp b/mtp/ffs/MtpDebug.cpp new file mode 100644 index 000000000..c5f5d43f0 --- /dev/null +++ b/mtp/ffs/MtpDebug.cpp @@ -0,0 +1,424 @@ +/* + * 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. + */ + +#include "MtpDebug.h" + +#define MTP_DEBUG_BUFFER_SIZE 2048 + +static int debug_enabled = 0; + +extern "C" void mtpdebug(const char *fmt, ...) +{ + if (debug_enabled) { + char buf[MTP_DEBUG_BUFFER_SIZE]; // We're going to limit a single request to 512 bytes + + va_list ap; + va_start(ap, fmt); + vsnprintf(buf, MTP_DEBUG_BUFFER_SIZE, fmt, ap); + va_end(ap); + + fputs(buf, stdout); + } +} + +struct CodeEntry { + const char* name; + uint16_t code; +}; + +static const CodeEntry sOperationCodes[] = { + { "MTP_OPERATION_GET_DEVICE_INFO", 0x1001 }, + { "MTP_OPERATION_OPEN_SESSION", 0x1002 }, + { "MTP_OPERATION_CLOSE_SESSION", 0x1003 }, + { "MTP_OPERATION_GET_STORAGE_IDS", 0x1004 }, + { "MTP_OPERATION_GET_STORAGE_INFO", 0x1005 }, + { "MTP_OPERATION_GET_NUM_OBJECTS", 0x1006 }, + { "MTP_OPERATION_GET_OBJECT_HANDLES", 0x1007 }, + { "MTP_OPERATION_GET_OBJECT_INFO", 0x1008 }, + { "MTP_OPERATION_GET_OBJECT", 0x1009 }, + { "MTP_OPERATION_GET_THUMB", 0x100A }, + { "MTP_OPERATION_DELETE_OBJECT", 0x100B }, + { "MTP_OPERATION_SEND_OBJECT_INFO", 0x100C }, + { "MTP_OPERATION_SEND_OBJECT", 0x100D }, + { "MTP_OPERATION_INITIATE_CAPTURE", 0x100E }, + { "MTP_OPERATION_FORMAT_STORE", 0x100F }, + { "MTP_OPERATION_RESET_DEVICE", 0x1010 }, + { "MTP_OPERATION_SELF_TEST", 0x1011 }, + { "MTP_OPERATION_SET_OBJECT_PROTECTION", 0x1012 }, + { "MTP_OPERATION_POWER_DOWN", 0x1013 }, + { "MTP_OPERATION_GET_DEVICE_PROP_DESC", 0x1014 }, + { "MTP_OPERATION_GET_DEVICE_PROP_VALUE", 0x1015 }, + { "MTP_OPERATION_SET_DEVICE_PROP_VALUE", 0x1016 }, + { "MTP_OPERATION_RESET_DEVICE_PROP_VALUE", 0x1017 }, + { "MTP_OPERATION_TERMINATE_OPEN_CAPTURE", 0x1018 }, + { "MTP_OPERATION_MOVE_OBJECT", 0x1019 }, + { "MTP_OPERATION_COPY_OBJECT", 0x101A }, + { "MTP_OPERATION_GET_PARTIAL_OBJECT", 0x101B }, + { "MTP_OPERATION_INITIATE_OPEN_CAPTURE", 0x101C }, + { "MTP_OPERATION_GET_OBJECT_PROPS_SUPPORTED", 0x9801 }, + { "MTP_OPERATION_GET_OBJECT_PROP_DESC", 0x9802 }, + { "MTP_OPERATION_GET_OBJECT_PROP_VALUE", 0x9803 }, + { "MTP_OPERATION_SET_OBJECT_PROP_VALUE", 0x9804 }, + { "MTP_OPERATION_GET_OBJECT_PROP_LIST", 0x9805 }, + { "MTP_OPERATION_SET_OBJECT_PROP_LIST", 0x9806 }, + { "MTP_OPERATION_GET_INTERDEPENDENT_PROP_DESC", 0x9807 }, + { "MTP_OPERATION_SEND_OBJECT_PROP_LIST", 0x9808 }, + { "MTP_OPERATION_GET_OBJECT_REFERENCES", 0x9810 }, + { "MTP_OPERATION_SET_OBJECT_REFERENCES", 0x9811 }, + { "MTP_OPERATION_SKIP", 0x9820 }, + // android extensions + { "MTP_OPERATION_GET_PARTIAL_OBJECT_64", 0x95C1 }, + { "MTP_OPERATION_SEND_PARTIAL_OBJECT", 0x95C2 }, + { "MTP_OPERATION_TRUNCATE_OBJECT", 0x95C3 }, + { "MTP_OPERATION_BEGIN_EDIT_OBJECT", 0x95C4 }, + { "MTP_OPERATION_END_EDIT_OBJECT", 0x95C5 }, + { 0, 0 }, +}; + +static const CodeEntry sFormatCodes[] = { + { "MTP_FORMAT_UNDEFINED", 0x3000 }, + { "MTP_FORMAT_ASSOCIATION", 0x3001 }, + { "MTP_FORMAT_SCRIPT", 0x3002 }, + { "MTP_FORMAT_EXECUTABLE", 0x3003 }, + { "MTP_FORMAT_TEXT", 0x3004 }, + { "MTP_FORMAT_HTML", 0x3005 }, + { "MTP_FORMAT_DPOF", 0x3006 }, + { "MTP_FORMAT_AIFF", 0x3007 }, + { "MTP_FORMAT_WAV", 0x3008 }, + { "MTP_FORMAT_MP3", 0x3009 }, + { "MTP_FORMAT_AVI", 0x300A }, + { "MTP_FORMAT_MPEG", 0x300B }, + { "MTP_FORMAT_ASF", 0x300C }, + { "MTP_FORMAT_DEFINED", 0x3800 }, + { "MTP_FORMAT_EXIF_JPEG", 0x3801 }, + { "MTP_FORMAT_TIFF_EP", 0x3802 }, + { "MTP_FORMAT_FLASHPIX", 0x3803 }, + { "MTP_FORMAT_BMP", 0x3804 }, + { "MTP_FORMAT_CIFF", 0x3805 }, + { "MTP_FORMAT_GIF", 0x3807 }, + { "MTP_FORMAT_JFIF", 0x3808 }, + { "MTP_FORMAT_CD", 0x3809 }, + { "MTP_FORMAT_PICT", 0x380A }, + { "MTP_FORMAT_PNG", 0x380B }, + { "MTP_FORMAT_TIFF", 0x380D }, + { "MTP_FORMAT_TIFF_IT", 0x380E }, + { "MTP_FORMAT_JP2", 0x380F }, + { "MTP_FORMAT_JPX", 0x3810 }, + { "MTP_FORMAT_DNG", 0x3811 }, + { "MTP_FORMAT_HEIF", 0x3812 }, + { "MTP_FORMAT_UNDEFINED_FIRMWARE", 0xB802 }, + { "MTP_FORMAT_WINDOWS_IMAGE_FORMAT", 0xB881 }, + { "MTP_FORMAT_UNDEFINED_AUDIO", 0xB900 }, + { "MTP_FORMAT_WMA", 0xB901 }, + { "MTP_FORMAT_OGG", 0xB902 }, + { "MTP_FORMAT_AAC", 0xB903 }, + { "MTP_FORMAT_AUDIBLE", 0xB904 }, + { "MTP_FORMAT_FLAC", 0xB906 }, + { "MTP_FORMAT_UNDEFINED_VIDEO", 0xB980 }, + { "MTP_FORMAT_WMV", 0xB981 }, + { "MTP_FORMAT_MP4_CONTAINER", 0xB982 }, + { "MTP_FORMAT_MP2", 0xB983 }, + { "MTP_FORMAT_3GP_CONTAINER", 0xB984 }, + { "MTP_FORMAT_UNDEFINED_COLLECTION", 0xBA00 }, + { "MTP_FORMAT_ABSTRACT_MULTIMEDIA_ALBUM", 0xBA01 }, + { "MTP_FORMAT_ABSTRACT_IMAGE_ALBUM", 0xBA02 }, + { "MTP_FORMAT_ABSTRACT_AUDIO_ALBUM", 0xBA03 }, + { "MTP_FORMAT_ABSTRACT_VIDEO_ALBUM", 0xBA04 }, + { "MTP_FORMAT_ABSTRACT_AV_PLAYLIST", 0xBA05 }, + { "MTP_FORMAT_ABSTRACT_CONTACT_GROUP", 0xBA06 }, + { "MTP_FORMAT_ABSTRACT_MESSAGE_FOLDER", 0xBA07 }, + { "MTP_FORMAT_ABSTRACT_CHAPTERED_PRODUCTION", 0xBA08 }, + { "MTP_FORMAT_ABSTRACT_AUDIO_PLAYLIST", 0xBA09 }, + { "MTP_FORMAT_ABSTRACT_VIDEO_PLAYLIST", 0xBA0A }, + { "MTP_FORMAT_ABSTRACT_MEDIACAST", 0xBA0B }, + { "MTP_FORMAT_WPL_PLAYLIST", 0xBA10 }, + { "MTP_FORMAT_M3U_PLAYLIST", 0xBA11 }, + { "MTP_FORMAT_MPL_PLAYLIST", 0xBA12 }, + { "MTP_FORMAT_ASX_PLAYLIST", 0xBA13 }, + { "MTP_FORMAT_PLS_PLAYLIST", 0xBA14 }, + { "MTP_FORMAT_UNDEFINED_DOCUMENT", 0xBA80 }, + { "MTP_FORMAT_ABSTRACT_DOCUMENT", 0xBA81 }, + { "MTP_FORMAT_XML_DOCUMENT", 0xBA82 }, + { "MTP_FORMAT_MS_WORD_DOCUMENT", 0xBA83 }, + { "MTP_FORMAT_MHT_COMPILED_HTML_DOCUMENT", 0xBA84 }, + { "MTP_FORMAT_MS_EXCEL_SPREADSHEET", 0xBA85 }, + { "MTP_FORMAT_MS_POWERPOINT_PRESENTATION", 0xBA86 }, + { "MTP_FORMAT_UNDEFINED_MESSAGE", 0xBB00 }, + { "MTP_FORMAT_ABSTRACT_MESSSAGE", 0xBB01 }, + { "MTP_FORMAT_UNDEFINED_CONTACT", 0xBB80 }, + { "MTP_FORMAT_ABSTRACT_CONTACT", 0xBB81 }, + { "MTP_FORMAT_VCARD_2", 0xBB82 }, + { 0, 0 }, +}; + +static const CodeEntry sObjectPropCodes[] = { + { "MTP_PROPERTY_STORAGE_ID", 0xDC01 }, + { "MTP_PROPERTY_OBJECT_FORMAT", 0xDC02 }, + { "MTP_PROPERTY_PROTECTION_STATUS", 0xDC03 }, + { "MTP_PROPERTY_OBJECT_SIZE", 0xDC04 }, + { "MTP_PROPERTY_ASSOCIATION_TYPE", 0xDC05 }, + { "MTP_PROPERTY_ASSOCIATION_DESC", 0xDC06 }, + { "MTP_PROPERTY_OBJECT_FILE_NAME", 0xDC07 }, + { "MTP_PROPERTY_DATE_CREATED", 0xDC08 }, + { "MTP_PROPERTY_DATE_MODIFIED", 0xDC09 }, + { "MTP_PROPERTY_KEYWORDS", 0xDC0A }, + { "MTP_PROPERTY_PARENT_OBJECT", 0xDC0B }, + { "MTP_PROPERTY_ALLOWED_FOLDER_CONTENTS", 0xDC0C }, + { "MTP_PROPERTY_HIDDEN", 0xDC0D }, + { "MTP_PROPERTY_SYSTEM_OBJECT", 0xDC0E }, + { "MTP_PROPERTY_PERSISTENT_UID", 0xDC41 }, + { "MTP_PROPERTY_SYNC_ID", 0xDC42 }, + { "MTP_PROPERTY_PROPERTY_BAG", 0xDC43 }, + { "MTP_PROPERTY_NAME", 0xDC44 }, + { "MTP_PROPERTY_CREATED_BY", 0xDC45 }, + { "MTP_PROPERTY_ARTIST", 0xDC46 }, + { "MTP_PROPERTY_DATE_AUTHORED", 0xDC47 }, + { "MTP_PROPERTY_DESCRIPTION", 0xDC48 }, + { "MTP_PROPERTY_URL_REFERENCE", 0xDC49 }, + { "MTP_PROPERTY_LANGUAGE_LOCALE", 0xDC4A }, + { "MTP_PROPERTY_COPYRIGHT_INFORMATION", 0xDC4B }, + { "MTP_PROPERTY_SOURCE", 0xDC4C }, + { "MTP_PROPERTY_ORIGIN_LOCATION", 0xDC4D }, + { "MTP_PROPERTY_DATE_ADDED", 0xDC4E }, + { "MTP_PROPERTY_NON_CONSUMABLE", 0xDC4F }, + { "MTP_PROPERTY_CORRUPT_UNPLAYABLE", 0xDC50 }, + { "MTP_PROPERTY_PRODUCER_SERIAL_NUMBER", 0xDC51 }, + { "MTP_PROPERTY_REPRESENTATIVE_SAMPLE_FORMAT", 0xDC81 }, + { "MTP_PROPERTY_REPRESENTATIVE_SAMPLE_SIZE", 0xDC82 }, + { "MTP_PROPERTY_REPRESENTATIVE_SAMPLE_HEIGHT", 0xDC83 }, + { "MTP_PROPERTY_REPRESENTATIVE_SAMPLE_WIDTH", 0xDC84 }, + { "MTP_PROPERTY_REPRESENTATIVE_SAMPLE_DURATION", 0xDC85 }, + { "MTP_PROPERTY_REPRESENTATIVE_SAMPLE_DATA", 0xDC86 }, + { "MTP_PROPERTY_WIDTH", 0xDC87 }, + { "MTP_PROPERTY_HEIGHT", 0xDC88 }, + { "MTP_PROPERTY_DURATION", 0xDC89 }, + { "MTP_PROPERTY_RATING", 0xDC8A }, + { "MTP_PROPERTY_TRACK", 0xDC8B }, + { "MTP_PROPERTY_GENRE", 0xDC8C }, + { "MTP_PROPERTY_CREDITS", 0xDC8D }, + { "MTP_PROPERTY_LYRICS", 0xDC8E }, + { "MTP_PROPERTY_SUBSCRIPTION_CONTENT_ID", 0xDC8F }, + { "MTP_PROPERTY_PRODUCED_BY", 0xDC90 }, + { "MTP_PROPERTY_USE_COUNT", 0xDC91 }, + { "MTP_PROPERTY_SKIP_COUNT", 0xDC92 }, + { "MTP_PROPERTY_LAST_ACCESSED", 0xDC93 }, + { "MTP_PROPERTY_PARENTAL_RATING", 0xDC94 }, + { "MTP_PROPERTY_META_GENRE", 0xDC95 }, + { "MTP_PROPERTY_COMPOSER", 0xDC96 }, + { "MTP_PROPERTY_EFFECTIVE_RATING", 0xDC97 }, + { "MTP_PROPERTY_SUBTITLE", 0xDC98 }, + { "MTP_PROPERTY_ORIGINAL_RELEASE_DATE", 0xDC99 }, + { "MTP_PROPERTY_ALBUM_NAME", 0xDC9A }, + { "MTP_PROPERTY_ALBUM_ARTIST", 0xDC9B }, + { "MTP_PROPERTY_MOOD", 0xDC9C }, + { "MTP_PROPERTY_DRM_STATUS", 0xDC9D }, + { "MTP_PROPERTY_SUB_DESCRIPTION", 0xDC9E }, + { "MTP_PROPERTY_IS_CROPPED", 0xDCD1 }, + { "MTP_PROPERTY_IS_COLOUR_CORRECTED", 0xDCD2 }, + { "MTP_PROPERTY_IMAGE_BIT_DEPTH", 0xDCD3 }, + { "MTP_PROPERTY_F_NUMBER", 0xDCD4 }, + { "MTP_PROPERTY_EXPOSURE_TIME", 0xDCD5 }, + { "MTP_PROPERTY_EXPOSURE_INDEX", 0xDCD6 }, + { "MTP_PROPERTY_TOTAL_BITRATE", 0xDE91 }, + { "MTP_PROPERTY_BITRATE_TYPE", 0xDE92 }, + { "MTP_PROPERTY_SAMPLE_RATE", 0xDE93 }, + { "MTP_PROPERTY_NUMBER_OF_CHANNELS", 0xDE94 }, + { "MTP_PROPERTY_AUDIO_BIT_DEPTH", 0xDE95 }, + { "MTP_PROPERTY_SCAN_TYPE", 0xDE97 }, + { "MTP_PROPERTY_AUDIO_WAVE_CODEC", 0xDE99 }, + { "MTP_PROPERTY_AUDIO_BITRATE", 0xDE9A }, + { "MTP_PROPERTY_VIDEO_FOURCC_CODEC", 0xDE9B }, + { "MTP_PROPERTY_VIDEO_BITRATE", 0xDE9C }, + { "MTP_PROPERTY_FRAMES_PER_THOUSAND_SECONDS", 0xDE9D }, + { "MTP_PROPERTY_KEYFRAME_DISTANCE", 0xDE9E }, + { "MTP_PROPERTY_BUFFER_SIZE", 0xDE9F }, + { "MTP_PROPERTY_ENCODING_QUALITY", 0xDEA0 }, + { "MTP_PROPERTY_ENCODING_PROFILE", 0xDEA1 }, + { "MTP_PROPERTY_DISPLAY_NAME", 0xDCE0 }, + { "MTP_PROPERTY_BODY_TEXT", 0xDCE1 }, + { "MTP_PROPERTY_SUBJECT", 0xDCE2 }, + { "MTP_PROPERTY_PRIORITY", 0xDCE3 }, + { "MTP_PROPERTY_GIVEN_NAME", 0xDD00 }, + { "MTP_PROPERTY_MIDDLE_NAMES", 0xDD01 }, + { "MTP_PROPERTY_FAMILY_NAME", 0xDD02 }, + { "MTP_PROPERTY_PREFIX", 0xDD03 }, + { "MTP_PROPERTY_SUFFIX", 0xDD04 }, + { "MTP_PROPERTY_PHONETIC_GIVEN_NAME", 0xDD05 }, + { "MTP_PROPERTY_PHONETIC_FAMILY_NAME", 0xDD06 }, + { "MTP_PROPERTY_EMAIL_PRIMARY", 0xDD07 }, + { "MTP_PROPERTY_EMAIL_PERSONAL_1", 0xDD08 }, + { "MTP_PROPERTY_EMAIL_PERSONAL_2", 0xDD09 }, + { "MTP_PROPERTY_EMAIL_BUSINESS_1", 0xDD0A }, + { "MTP_PROPERTY_EMAIL_BUSINESS_2", 0xDD0B }, + { "MTP_PROPERTY_EMAIL_OTHERS", 0xDD0C }, + { "MTP_PROPERTY_PHONE_NUMBER_PRIMARY", 0xDD0D }, + { "MTP_PROPERTY_PHONE_NUMBER_PERSONAL", 0xDD0E }, + { "MTP_PROPERTY_PHONE_NUMBER_PERSONAL_2", 0xDD0F }, + { "MTP_PROPERTY_PHONE_NUMBER_BUSINESS", 0xDD10 }, + { "MTP_PROPERTY_PHONE_NUMBER_BUSINESS_2", 0xDD11 }, + { "MTP_PROPERTY_PHONE_NUMBER_MOBILE", 0xDD12 }, + { "MTP_PROPERTY_PHONE_NUMBER_MOBILE_2", 0xDD13 }, + { "MTP_PROPERTY_FAX_NUMBER_PRIMARY", 0xDD14 }, + { "MTP_PROPERTY_FAX_NUMBER_PERSONAL", 0xDD15 }, + { "MTP_PROPERTY_FAX_NUMBER_BUSINESS", 0xDD16 }, + { "MTP_PROPERTY_PAGER_NUMBER", 0xDD17 }, + { "MTP_PROPERTY_PHONE_NUMBER_OTHERS", 0xDD18 }, + { "MTP_PROPERTY_PRIMARY_WEB_ADDRESS", 0xDD19 }, + { "MTP_PROPERTY_PERSONAL_WEB_ADDRESS", 0xDD1A }, + { "MTP_PROPERTY_BUSINESS_WEB_ADDRESS", 0xDD1B }, + { "MTP_PROPERTY_INSTANT_MESSANGER_ADDRESS", 0xDD1C }, + { "MTP_PROPERTY_INSTANT_MESSANGER_ADDRESS_2", 0xDD1D }, + { "MTP_PROPERTY_INSTANT_MESSANGER_ADDRESS_3", 0xDD1E }, + { "MTP_PROPERTY_POSTAL_ADDRESS_PERSONAL_FULL", 0xDD1F }, + { "MTP_PROPERTY_POSTAL_ADDRESS_PERSONAL_LINE_1", 0xDD20 }, + { "MTP_PROPERTY_POSTAL_ADDRESS_PERSONAL_LINE_2", 0xDD21 }, + { "MTP_PROPERTY_POSTAL_ADDRESS_PERSONAL_CITY", 0xDD22 }, + { "MTP_PROPERTY_POSTAL_ADDRESS_PERSONAL_REGION", 0xDD23 }, + { "MTP_PROPERTY_POSTAL_ADDRESS_PERSONAL_POSTAL_CODE", 0xDD24 }, + { "MTP_PROPERTY_POSTAL_ADDRESS_PERSONAL_COUNTRY", 0xDD25 }, + { "MTP_PROPERTY_POSTAL_ADDRESS_BUSINESS_FULL", 0xDD26 }, + { "MTP_PROPERTY_POSTAL_ADDRESS_BUSINESS_LINE_1", 0xDD27 }, + { "MTP_PROPERTY_POSTAL_ADDRESS_BUSINESS_LINE_2", 0xDD28 }, + { "MTP_PROPERTY_POSTAL_ADDRESS_BUSINESS_CITY", 0xDD29 }, + { "MTP_PROPERTY_POSTAL_ADDRESS_BUSINESS_REGION", 0xDD2A }, + { "MTP_PROPERTY_POSTAL_ADDRESS_BUSINESS_POSTAL_CODE", 0xDD2B }, + { "MTP_PROPERTY_POSTAL_ADDRESS_BUSINESS_COUNTRY", 0xDD2C }, + { "MTP_PROPERTY_POSTAL_ADDRESS_OTHER_FULL", 0xDD2D }, + { "MTP_PROPERTY_POSTAL_ADDRESS_OTHER_LINE_1", 0xDD2E }, + { "MTP_PROPERTY_POSTAL_ADDRESS_OTHER_LINE_2", 0xDD2F }, + { "MTP_PROPERTY_POSTAL_ADDRESS_OTHER_CITY", 0xDD30 }, + { "MTP_PROPERTY_POSTAL_ADDRESS_OTHER_REGION", 0xDD31 }, + { "MTP_PROPERTY_POSTAL_ADDRESS_OTHER_POSTAL_CODE", 0xDD32 }, + { "MTP_PROPERTY_POSTAL_ADDRESS_OTHER_COUNTRY", 0xDD33 }, + { "MTP_PROPERTY_ORGANIZATION_NAME", 0xDD34 }, + { "MTP_PROPERTY_PHONETIC_ORGANIZATION_NAME", 0xDD35 }, + { "MTP_PROPERTY_ROLE", 0xDD36 }, + { "MTP_PROPERTY_BIRTHDATE", 0xDD37 }, + { "MTP_PROPERTY_MESSAGE_TO", 0xDD40 }, + { "MTP_PROPERTY_MESSAGE_CC", 0xDD41 }, + { "MTP_PROPERTY_MESSAGE_BCC", 0xDD42 }, + { "MTP_PROPERTY_MESSAGE_READ", 0xDD43 }, + { "MTP_PROPERTY_MESSAGE_RECEIVED_TIME", 0xDD44 }, + { "MTP_PROPERTY_MESSAGE_SENDER", 0xDD45 }, + { "MTP_PROPERTY_ACTIVITY_BEGIN_TIME", 0xDD50 }, + { "MTP_PROPERTY_ACTIVITY_END_TIME", 0xDD51 }, + { "MTP_PROPERTY_ACTIVITY_LOCATION", 0xDD52 }, + { "MTP_PROPERTY_ACTIVITY_REQUIRED_ATTENDEES", 0xDD54 }, + { "MTP_PROPERTY_ACTIVITY_OPTIONAL_ATTENDEES", 0xDD55 }, + { "MTP_PROPERTY_ACTIVITY_RESOURCES", 0xDD56 }, + { "MTP_PROPERTY_ACTIVITY_ACCEPTED", 0xDD57 }, + { "MTP_PROPERTY_ACTIVITY_TENTATIVE", 0xDD58 }, + { "MTP_PROPERTY_ACTIVITY_DECLINED", 0xDD59 }, + { "MTP_PROPERTY_ACTIVITY_REMAINDER_TIME", 0xDD5A }, + { "MTP_PROPERTY_ACTIVITY_OWNER", 0xDD5B }, + { "MTP_PROPERTY_ACTIVITY_STATUS", 0xDD5C }, + { "MTP_PROPERTY_OWNER", 0xDD5D }, + { "MTP_PROPERTY_EDITOR", 0xDD5E }, + { "MTP_PROPERTY_WEBMASTER", 0xDD5F }, + { "MTP_PROPERTY_URL_SOURCE", 0xDD60 }, + { "MTP_PROPERTY_URL_DESTINATION", 0xDD61 }, + { "MTP_PROPERTY_TIME_BOOKMARK", 0xDD62 }, + { "MTP_PROPERTY_OBJECT_BOOKMARK", 0xDD63 }, + { "MTP_PROPERTY_BYTE_BOOKMARK", 0xDD64 }, + { "MTP_PROPERTY_LAST_BUILD_DATE", 0xDD70 }, + { "MTP_PROPERTY_TIME_TO_LIVE", 0xDD71 }, + { "MTP_PROPERTY_MEDIA_GUID", 0xDD72 }, + { 0, 0 }, +}; + +static const CodeEntry sDevicePropCodes[] = { + { "MTP_DEVICE_PROPERTY_UNDEFINED", 0x5000 }, + { "MTP_DEVICE_PROPERTY_BATTERY_LEVEL", 0x5001 }, + { "MTP_DEVICE_PROPERTY_FUNCTIONAL_MODE", 0x5002 }, + { "MTP_DEVICE_PROPERTY_IMAGE_SIZE", 0x5003 }, + { "MTP_DEVICE_PROPERTY_COMPRESSION_SETTING", 0x5004 }, + { "MTP_DEVICE_PROPERTY_WHITE_BALANCE", 0x5005 }, + { "MTP_DEVICE_PROPERTY_RGB_GAIN", 0x5006 }, + { "MTP_DEVICE_PROPERTY_F_NUMBER", 0x5007 }, + { "MTP_DEVICE_PROPERTY_FOCAL_LENGTH", 0x5008 }, + { "MTP_DEVICE_PROPERTY_FOCUS_DISTANCE", 0x5009 }, + { "MTP_DEVICE_PROPERTY_FOCUS_MODE", 0x500A }, + { "MTP_DEVICE_PROPERTY_EXPOSURE_METERING_MODE", 0x500B }, + { "MTP_DEVICE_PROPERTY_FLASH_MODE", 0x500C }, + { "MTP_DEVICE_PROPERTY_EXPOSURE_TIME", 0x500D }, + { "MTP_DEVICE_PROPERTY_EXPOSURE_PROGRAM_MODE", 0x500E }, + { "MTP_DEVICE_PROPERTY_EXPOSURE_INDEX", 0x500F }, + { "MTP_DEVICE_PROPERTY_EXPOSURE_BIAS_COMPENSATION", 0x5010 }, + { "MTP_DEVICE_PROPERTY_DATETIME", 0x5011 }, + { "MTP_DEVICE_PROPERTY_CAPTURE_DELAY", 0x5012 }, + { "MTP_DEVICE_PROPERTY_STILL_CAPTURE_MODE", 0x5013 }, + { "MTP_DEVICE_PROPERTY_CONTRAST", 0x5014 }, + { "MTP_DEVICE_PROPERTY_SHARPNESS", 0x5015 }, + { "MTP_DEVICE_PROPERTY_DIGITAL_ZOOM", 0x5016 }, + { "MTP_DEVICE_PROPERTY_EFFECT_MODE", 0x5017 }, + { "MTP_DEVICE_PROPERTY_BURST_NUMBER", 0x5018 }, + { "MTP_DEVICE_PROPERTY_BURST_INTERVAL", 0x5019 }, + { "MTP_DEVICE_PROPERTY_TIMELAPSE_NUMBER", 0x501A }, + { "MTP_DEVICE_PROPERTY_TIMELAPSE_INTERVAL", 0x501B }, + { "MTP_DEVICE_PROPERTY_FOCUS_METERING_MODE", 0x501C }, + { "MTP_DEVICE_PROPERTY_UPLOAD_URL", 0x501D }, + { "MTP_DEVICE_PROPERTY_ARTIST", 0x501E }, + { "MTP_DEVICE_PROPERTY_COPYRIGHT_INFO", 0x501F }, + { "MTP_DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER", 0xD401 }, + { "MTP_DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME", 0xD402 }, + { "MTP_DEVICE_PROPERTY_VOLUME", 0xD403 }, + { "MTP_DEVICE_PROPERTY_SUPPORTED_FORMATS_ORDERED", 0xD404 }, + { "MTP_DEVICE_PROPERTY_DEVICE_ICON", 0xD405 }, + { "MTP_DEVICE_PROPERTY_PLAYBACK_RATE", 0xD410 }, + { "MTP_DEVICE_PROPERTY_PLAYBACK_OBJECT", 0xD411 }, + { "MTP_DEVICE_PROPERTY_PLAYBACK_CONTAINER_INDEX", 0xD412 }, + { "MTP_DEVICE_PROPERTY_SESSION_INITIATOR_VERSION_INFO", 0xD406 }, + { "MTP_DEVICE_PROPERTY_PERCEIVED_DEVICE_TYPE", 0xD407 }, + { 0, 0 }, +}; + +static const char* getCodeName(uint16_t code, const CodeEntry* table) { + const CodeEntry* entry = table; + while (entry->name) { + if (entry->code == code) + return entry->name; + entry++; + } + return "UNKNOWN"; +} + +const char* MtpDebug::getOperationCodeName(MtpOperationCode code) { + return getCodeName(code, sOperationCodes); +} + +const char* MtpDebug::getFormatCodeName(MtpObjectFormat code) { + if (code == 0) + return "NONE"; + return getCodeName(code, sFormatCodes); +} + +const char* MtpDebug::getObjectPropCodeName(MtpPropertyCode code) { + if (code == 0) + return "NONE"; + return getCodeName(code, sObjectPropCodes); +} + +const char* MtpDebug::getDevicePropCodeName(MtpPropertyCode code) { + if (code == 0) + return "NONE"; + return getCodeName(code, sDevicePropCodes); +} + +void MtpDebug::enableDebug(void) { + debug_enabled = 1; + MTPD("MTP debug logging enabled\n"); +} + diff --git a/mtp/ffs/MtpDebug.h b/mtp/ffs/MtpDebug.h new file mode 100644 index 000000000..61ae4b7ae --- /dev/null +++ b/mtp/ffs/MtpDebug.h @@ -0,0 +1,47 @@ +/* + * 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. + */ + +#ifndef _MTP_DEBUG_H +#define _MTP_DEBUG_H + +// #define LOG_NDEBUG 0 +#include "MtpTypes.h" + +#include <log/log.h> + +#ifdef __cplusplus +extern "C" { +#endif +void mtpdebug(const char *fmt, ...); + +#define MTPI(...) fprintf(stdout, "I:[MTP] " __VA_ARGS__) +#define MTPD(...) mtpdebug("D:[MTP] " __VA_ARGS__) +#define MTPE(...) fprintf(stdout, "E:[MTP] " __VA_ARGS__) + +#ifdef __cplusplus +} +#endif + +class MtpDebug { +public: + static const char* getOperationCodeName(MtpOperationCode code); + static const char* getFormatCodeName(MtpObjectFormat code); + static const char* getObjectPropCodeName(MtpPropertyCode code); + static const char* getDevicePropCodeName(MtpPropertyCode code); + static void enableDebug(); +}; + +#endif // _MTP_DEBUG_H diff --git a/mtp/ffs/MtpDescriptors.cpp b/mtp/ffs/MtpDescriptors.cpp new file mode 100644 index 000000000..54306a997 --- /dev/null +++ b/mtp/ffs/MtpDescriptors.cpp @@ -0,0 +1,282 @@ +/* + * Copyright (C) 2017 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 <android-base/logging.h> +#include <sys/types.h> +#include <cutils/properties.h> + +#include "MtpDescriptors.h" +#include "MtpDebug.h" + +const struct usb_interface_descriptor mtp_interface_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0, + .bNumEndpoints = 3, + .bInterfaceClass = USB_CLASS_STILL_IMAGE, + .bInterfaceSubClass = 1, + .bInterfaceProtocol = 1, + .iInterface = 1, +}; + +const struct usb_interface_descriptor ptp_interface_desc = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0, + .bNumEndpoints = 3, + .bInterfaceClass = USB_CLASS_STILL_IMAGE, + .bInterfaceSubClass = 1, + .bInterfaceProtocol = 1, +}; + +const struct usb_endpoint_descriptor_no_audio fs_sink = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 1 | USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = MAX_PACKET_SIZE_FS, +}; + +const struct usb_endpoint_descriptor_no_audio fs_source = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 2 | USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = MAX_PACKET_SIZE_FS, +}; + +const struct usb_endpoint_descriptor_no_audio intr = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 3 | USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = MAX_PACKET_SIZE_EV, + .bInterval = 6, +}; + +const struct usb_endpoint_descriptor_no_audio hs_sink = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 1 | USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = MAX_PACKET_SIZE_HS, +}; + +const struct usb_endpoint_descriptor_no_audio hs_source = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 2 | USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = MAX_PACKET_SIZE_HS, +}; + +const struct usb_endpoint_descriptor_no_audio ss_sink = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 1 | USB_DIR_IN, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = MAX_PACKET_SIZE_SS, +}; + +const struct usb_endpoint_descriptor_no_audio ss_source = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 2 | USB_DIR_OUT, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + .wMaxPacketSize = MAX_PACKET_SIZE_SS, +}; + +const struct usb_ss_ep_comp_descriptor ss_sink_comp = { + .bLength = sizeof(ss_sink_comp), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + .bMaxBurst = 6, +}; + +const struct usb_ss_ep_comp_descriptor ss_source_comp = { + .bLength = sizeof(ss_source_comp), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, + .bMaxBurst = 6, +}; + +const struct usb_ss_ep_comp_descriptor ss_intr_comp = { + .bLength = sizeof(ss_intr_comp), + .bDescriptorType = USB_DT_SS_ENDPOINT_COMP, +}; + +const struct func_desc mtp_fs_descriptors = { + .intf = mtp_interface_desc, + .sink = fs_sink, + .source = fs_source, + .intr = intr, +}; + +const struct func_desc mtp_hs_descriptors = { + .intf = mtp_interface_desc, + .sink = hs_sink, + .source = hs_source, + .intr = intr, +}; + +const struct ss_func_desc mtp_ss_descriptors = { + .intf = mtp_interface_desc, + .sink = ss_sink, + .sink_comp = ss_sink_comp, + .source = ss_source, + .source_comp = ss_source_comp, + .intr = intr, + .intr_comp = ss_intr_comp, +}; + +const struct func_desc ptp_fs_descriptors = { + .intf = ptp_interface_desc, + .sink = fs_sink, + .source = fs_source, + .intr = intr, +}; + +const struct func_desc ptp_hs_descriptors = { + .intf = ptp_interface_desc, + .sink = hs_sink, + .source = hs_source, + .intr = intr, +}; + +const struct ss_func_desc ptp_ss_descriptors = { + .intf = ptp_interface_desc, + .sink = ss_sink, + .sink_comp = ss_sink_comp, + .source = ss_source, + .source_comp = ss_source_comp, + .intr = intr, + .intr_comp = ss_intr_comp, +}; + +const struct functionfs_strings mtp_strings = { + .header = { + .magic = htole32(FUNCTIONFS_STRINGS_MAGIC), + .length = htole32(sizeof(mtp_strings)), + .str_count = htole32(1), + .lang_count = htole32(1), + }, + .lang0 = { + .code = htole16(0x0409), + .str1 = STR_INTERFACE, + }, +}; + +const struct usb_os_desc_header mtp_os_desc_header = { + .interface = htole32(1), + .dwLength = htole32(sizeof(usb_os_desc_header) + sizeof(usb_ext_compat_desc)), + .bcdVersion = htole16(1), + .wIndex = htole16(4), + .bCount = htole16(1), + .Reserved = htole16(0), +}; + +const struct usb_ext_compat_desc mtp_os_desc_compat = { + .bFirstInterfaceNumber = 0, + .Reserved1 = htole32(1), + .CompatibleID = { 'M', 'T', 'P' }, + .SubCompatibleID = {0}, + .Reserved2 = {0}, +}; + +const struct usb_ext_compat_desc ptp_os_desc_compat = { + .bFirstInterfaceNumber = 0, + .Reserved1 = htole32(1), + .CompatibleID = { 'P', 'T', 'P' }, + .SubCompatibleID = {0}, + .Reserved2 = {0}, +}; + +const struct desc_v2 mtp_desc_v2 = { + .header = { + .magic = htole32(FUNCTIONFS_DESCRIPTORS_MAGIC_V2), + .length = htole32(sizeof(struct desc_v2)), + .flags = FUNCTIONFS_HAS_FS_DESC | FUNCTIONFS_HAS_HS_DESC | + FUNCTIONFS_HAS_SS_DESC | FUNCTIONFS_HAS_MS_OS_DESC, + }, + .fs_count = 4, + .hs_count = 4, + .ss_count = 7, + .os_count = 1, + .fs_descs = mtp_fs_descriptors, + .hs_descs = mtp_hs_descriptors, + .ss_descs = mtp_ss_descriptors, + .os_header = mtp_os_desc_header, + .os_desc = mtp_os_desc_compat, +}; + +const struct desc_v2 ptp_desc_v2 = { + .header = { + .magic = htole32(FUNCTIONFS_DESCRIPTORS_MAGIC_V2), + .length = htole32(sizeof(struct desc_v2)), + .flags = FUNCTIONFS_HAS_FS_DESC | FUNCTIONFS_HAS_HS_DESC | + FUNCTIONFS_HAS_SS_DESC | FUNCTIONFS_HAS_MS_OS_DESC, + }, + .fs_count = 4, + .hs_count = 4, + .ss_count = 7, + .os_count = 1, + .fs_descs = ptp_fs_descriptors, + .hs_descs = ptp_hs_descriptors, + .ss_descs = ptp_ss_descriptors, + .os_header = mtp_os_desc_header, + .os_desc = ptp_os_desc_compat, +}; + +const struct desc_v1 mtp_desc_v1 = { + .header = { + .magic = htole32(FUNCTIONFS_DESCRIPTORS_MAGIC), + .length = htole32(sizeof(struct desc_v1)), + .fs_count = 4, + .hs_count = 4, + }, + .fs_descs = mtp_fs_descriptors, + .hs_descs = mtp_hs_descriptors, +}; + +const struct desc_v1 ptp_desc_v1 = { + .header = { + .magic = htole32(FUNCTIONFS_DESCRIPTORS_MAGIC), + .length = htole32(sizeof(struct desc_v1)), + .fs_count = 4, + .hs_count = 4, + }, + .fs_descs = ptp_fs_descriptors, + .hs_descs = ptp_hs_descriptors, +}; + +bool writeDescriptors(int fd, bool ptp) { + ssize_t ret = TEMP_FAILURE_RETRY(write(fd, + &(ptp ? ptp_desc_v2 : mtp_desc_v2), sizeof(desc_v2))); + if (ret < 0) { + MTPE("Switching to V1 descriptor format\n"); + ret = TEMP_FAILURE_RETRY(write(fd, + &(ptp ? ptp_desc_v1 : mtp_desc_v1), sizeof(desc_v1))); + if (ret < 0) { + MTPE("Writing descriptors failed\n"); + return false; + } + } + ret = TEMP_FAILURE_RETRY(write(fd, &mtp_strings, sizeof(mtp_strings))); + if (ret < 0) { + MTPE("Writing strings failed\n"); + return false; + } + property_set("sys.usb.ffs.mtp.ready", "1"); + return true; +} diff --git a/mtp/ffs/MtpDescriptors.h b/mtp/ffs/MtpDescriptors.h new file mode 100644 index 000000000..f362d80f4 --- /dev/null +++ b/mtp/ffs/MtpDescriptors.h @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2017 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. + */ + +#ifndef MTP_DESCRIPTORS_H +#define MTP_DESCRIPTORS_H + +#include <linux/usb/ch9.h> +#include <linux/usb/functionfs.h> +#include <sys/endian.h> + +constexpr char FFS_MTP_EP0[] = "/dev/usb-ffs/mtp/ep0"; +constexpr char FFS_MTP_EP_IN[] = "/dev/usb-ffs/mtp/ep1"; +constexpr char FFS_MTP_EP_OUT[] = "/dev/usb-ffs/mtp/ep2"; +constexpr char FFS_MTP_EP_INTR[] = "/dev/usb-ffs/mtp/ep3"; + +constexpr char FFS_PTP_EP0[] = "/dev/usb-ffs/ptp/ep0"; +constexpr char FFS_PTP_EP_IN[] = "/dev/usb-ffs/ptp/ep1"; +constexpr char FFS_PTP_EP_OUT[] = "/dev/usb-ffs/ptp/ep2"; +constexpr char FFS_PTP_EP_INTR[] = "/dev/usb-ffs/ptp/ep3"; + +constexpr int MAX_PACKET_SIZE_FS = 64; +constexpr int MAX_PACKET_SIZE_HS = 512; +constexpr int MAX_PACKET_SIZE_SS = 1024; +constexpr int MAX_PACKET_SIZE_EV = 28; + +struct func_desc { + struct usb_interface_descriptor intf; + struct usb_endpoint_descriptor_no_audio sink; + struct usb_endpoint_descriptor_no_audio source; + struct usb_endpoint_descriptor_no_audio intr; +} __attribute__((packed)); + +struct ss_func_desc { + struct usb_interface_descriptor intf; + struct usb_endpoint_descriptor_no_audio sink; + struct usb_ss_ep_comp_descriptor sink_comp; + struct usb_endpoint_descriptor_no_audio source; + struct usb_ss_ep_comp_descriptor source_comp; + struct usb_endpoint_descriptor_no_audio intr; + struct usb_ss_ep_comp_descriptor intr_comp; +} __attribute__((packed)); + +struct desc_v1 { + struct usb_functionfs_descs_head_v1 { + __le32 magic; + __le32 length; + __le32 fs_count; + __le32 hs_count; + } __attribute__((packed)) header; + struct func_desc fs_descs, hs_descs; +} __attribute__((packed)); + +struct desc_v2 { + struct usb_functionfs_descs_head_v2 header; + // The rest of the structure depends on the flags in the header. + __le32 fs_count; + __le32 hs_count; + __le32 ss_count; + __le32 os_count; + struct func_desc fs_descs, hs_descs; + struct ss_func_desc ss_descs; + struct usb_os_desc_header os_header; + struct usb_ext_compat_desc os_desc; +} __attribute__((packed)); + +// OS descriptor contents should not be changed. See b/64790536. +static_assert(sizeof(struct desc_v2) == sizeof(usb_functionfs_descs_head_v2) + + 16 + 2 * sizeof(struct func_desc) + sizeof(struct ss_func_desc) + + sizeof(usb_os_desc_header) + sizeof(usb_ext_compat_desc), + "Size of mtp descriptor is incorrect!"); + +#define STR_INTERFACE "MTP" +struct functionfs_lang { + __le16 code; + char str1[sizeof(STR_INTERFACE)]; +} __attribute__((packed)); + +struct functionfs_strings { + struct usb_functionfs_strings_head header; + struct functionfs_lang lang0; +} __attribute__((packed)); + +extern const struct desc_v2 mtp_desc_v2; +extern const struct desc_v2 ptp_desc_v2; +extern const struct desc_v1 mtp_desc_v1; +extern const struct desc_v1 ptp_desc_v1; +extern const struct functionfs_strings mtp_strings; + +bool writeDescriptors(int fd, bool ptp); + +#endif // MTP_DESCRIPTORS_H diff --git a/mtp/ffs/MtpDevHandle.cpp b/mtp/ffs/MtpDevHandle.cpp new file mode 100644 index 000000000..d6a8b820d --- /dev/null +++ b/mtp/ffs/MtpDevHandle.cpp @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2016 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 <android-base/logging.h> +#include <cutils/properties.h> +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <linux/usb/ch9.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/endian.h> +#include <unistd.h> + +#include "MtpDevHandle.h" + +constexpr char mtp_dev_path[] = "/dev/mtp_usb"; + +MtpDevHandle::MtpDevHandle() + : mFd(-1) {}; + +MtpDevHandle::~MtpDevHandle() {} + +int MtpDevHandle::read(void *data, size_t len) { + return ::read(mFd, data, len); +} + +int MtpDevHandle::write(const void *data, size_t len) { + return ::write(mFd, data, len); +} + +int MtpDevHandle::receiveFile(mtp_file_range mfr, bool) { + return ioctl(mFd, MTP_RECEIVE_FILE, reinterpret_cast<unsigned long>(&mfr)); +} + +int MtpDevHandle::sendFile(mtp_file_range mfr) { + return ioctl(mFd, MTP_SEND_FILE_WITH_HEADER, reinterpret_cast<unsigned long>(&mfr)); +} + +int MtpDevHandle::sendEvent(mtp_event me) { + return ioctl(mFd, MTP_SEND_EVENT, reinterpret_cast<unsigned long>(&me)); +} + +int MtpDevHandle::start(bool /* ptp */) { + mFd.reset(TEMP_FAILURE_RETRY(open(mtp_dev_path, O_RDWR))); + if (mFd == -1) return -1; + return 0; +} + +void MtpDevHandle::close() { + mFd.reset(); +} + +bool MtpDevHandle::writeDescriptors(bool usePtp) { return usePtp; } diff --git a/mtp/ffs/MtpDevHandle.h b/mtp/ffs/MtpDevHandle.h new file mode 100644 index 000000000..4b0692889 --- /dev/null +++ b/mtp/ffs/MtpDevHandle.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2017 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. + */ + +#ifndef _MTP_DEV_HANDLE_H +#define _MTP_DEV_HANDLE_H + +#include <android-base/unique_fd.h> +#include "IMtpHandle.h" + +class MtpDevHandle : public IMtpHandle { +private: + android::base::unique_fd mFd; + +public: + MtpDevHandle(); + ~MtpDevHandle(); + int read(void *data, size_t len); + int write(const void *data, size_t len); + + int receiveFile(mtp_file_range mfr, bool); + int sendFile(mtp_file_range mfr); + int sendEvent(mtp_event me); + + int start(bool ptp); + void close(); + bool writeDescriptors(bool usePtp); +}; + +#endif // _MTP_FFS_HANDLE_H diff --git a/mtp/ffs/MtpDevice.cpp b/mtp/ffs/MtpDevice.cpp new file mode 100644 index 000000000..8a50ef190 --- /dev/null +++ b/mtp/ffs/MtpDevice.cpp @@ -0,0 +1,946 @@ +/* + * 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 "MtpDevice" + +#include "MtpDebug.h" +#include "MtpDevice.h" +#include "MtpDeviceInfo.h" +#include "MtpEventPacket.h" +#include "MtpObjectInfo.h" +#include "MtpProperty.h" +#include "MtpStorageInfo.h" +#include "MtpStringBuffer.h" +#include "MtpUtils.h" + +#include <stdio.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <errno.h> +#include <endian.h> + +#include <usbhost/usbhost.h> + +namespace { + +static constexpr int USB_CONTROL_TRANSFER_TIMEOUT_MS = 200; + +} // namespace + +#if 0 +static bool isMtpDevice(uint16_t vendor, uint16_t product) { + // Sandisk Sansa Fuze + if (vendor == 0x0781 && product == 0x74c2) + return true; + // Samsung YP-Z5 + if (vendor == 0x04e8 && product == 0x503c) + return true; + return false; +} +#endif + +namespace { + +bool writeToFd(void* data, uint32_t /* unused_offset */, uint32_t length, void* clientData) { + const int fd = *static_cast<int*>(clientData); + const ssize_t result = write(fd, data, length); + if (result < 0) { + return false; + } + return static_cast<uint32_t>(result) == length; +} + +} // namespace + +MtpDevice* MtpDevice::open(const char* deviceName, int fd) { + struct usb_device *device = usb_device_new(deviceName, fd); + if (!device) { + MTPE("usb_device_new failed for %s", deviceName); + return NULL; + } + + struct usb_descriptor_header* desc; + struct usb_descriptor_iter iter; + + usb_descriptor_iter_init(device, &iter); + + while ((desc = usb_descriptor_iter_next(&iter)) != NULL) { + if (desc->bDescriptorType == USB_DT_INTERFACE) { + struct usb_interface_descriptor *interface = (struct usb_interface_descriptor *)desc; + + if (interface->bInterfaceClass == USB_CLASS_STILL_IMAGE && + interface->bInterfaceSubClass == 1 && // Still Image Capture + interface->bInterfaceProtocol == 1) // Picture Transfer Protocol (PIMA 15470) + { + char* manufacturerName = usb_device_get_manufacturer_name(device, + USB_CONTROL_TRANSFER_TIMEOUT_MS); + char* productName = usb_device_get_product_name(device, + USB_CONTROL_TRANSFER_TIMEOUT_MS); + MTPD("Found camera: \"%s\" \"%s\"\n", manufacturerName, productName); + free(manufacturerName); + free(productName); + } else if (interface->bInterfaceClass == 0xFF && + interface->bInterfaceSubClass == 0xFF && + interface->bInterfaceProtocol == 0) { + char* interfaceName = usb_device_get_string(device, interface->iInterface, + USB_CONTROL_TRANSFER_TIMEOUT_MS); + if (!interfaceName) { + continue; + } else if (strcmp(interfaceName, "MTP")) { + free(interfaceName); + continue; + } + free(interfaceName); + + // Looks like an android style MTP device + char* manufacturerName = usb_device_get_manufacturer_name(device, + USB_CONTROL_TRANSFER_TIMEOUT_MS); + char* productName = usb_device_get_product_name(device, + USB_CONTROL_TRANSFER_TIMEOUT_MS); + MTPD("Found MTP device: \"%s\" \"%s\"\n", manufacturerName, productName); + free(manufacturerName); + free(productName); + } +#if 0 + else { + // look for special cased devices based on vendor/product ID + // we are doing this mainly for testing purposes + uint16_t vendor = usb_device_get_vendor_id(device); + uint16_t product = usb_device_get_product_id(device); + if (!isMtpDevice(vendor, product)) { + // not an MTP or PTP device + continue; + } + // request MTP OS string and descriptor + // some music players need to see this before entering MTP mode. + char buffer[256]; + memset(buffer, 0, sizeof(buffer)); + int ret = usb_device_control_transfer(device, + USB_DIR_IN|USB_RECIP_DEVICE|USB_TYPE_STANDARD, + USB_REQ_GET_DESCRIPTOR, (USB_DT_STRING << 8) | 0xEE, + 0, buffer, sizeof(buffer), 0); + printf("usb_device_control_transfer returned %d errno: %d\n", ret, errno); + if (ret > 0) { + printf("got MTP string %s\n", buffer); + ret = usb_device_control_transfer(device, + USB_DIR_IN|USB_RECIP_DEVICE|USB_TYPE_VENDOR, 1, + 0, 4, buffer, sizeof(buffer), 0); + printf("OS descriptor got %d\n", ret); + } else { + printf("no MTP string\n"); + } + } +#else + else { + continue; + } +#endif + // if we got here, then we have a likely MTP or PTP device + + // interface should be followed by three endpoints + struct usb_endpoint_descriptor *ep; + struct usb_endpoint_descriptor *ep_in_desc = NULL; + struct usb_endpoint_descriptor *ep_out_desc = NULL; + struct usb_endpoint_descriptor *ep_intr_desc = NULL; + //USB3 add USB_DT_SS_ENDPOINT_COMP as companion descriptor; + struct usb_ss_ep_comp_descriptor *ep_ss_ep_comp_desc = NULL; + for (int i = 0; i < 3; i++) { + ep = (struct usb_endpoint_descriptor *)usb_descriptor_iter_next(&iter); + if (ep && ep->bDescriptorType == USB_DT_SS_ENDPOINT_COMP) { + MTPD("Descriptor type is USB_DT_SS_ENDPOINT_COMP for USB3 \n"); + ep_ss_ep_comp_desc = (usb_ss_ep_comp_descriptor*)ep; + ep = (struct usb_endpoint_descriptor *)usb_descriptor_iter_next(&iter); + } + + if (!ep || ep->bDescriptorType != USB_DT_ENDPOINT) { + MTPE("endpoints not found\n"); + usb_device_close(device); + return NULL; + } + + if (ep->bmAttributes == USB_ENDPOINT_XFER_BULK) { + if (ep->bEndpointAddress & USB_ENDPOINT_DIR_MASK) + ep_in_desc = ep; + else + ep_out_desc = ep; + } else if (ep->bmAttributes == USB_ENDPOINT_XFER_INT && + ep->bEndpointAddress & USB_ENDPOINT_DIR_MASK) { + ep_intr_desc = ep; + } + } + if (!ep_in_desc || !ep_out_desc || !ep_intr_desc) { + MTPE("endpoints not found\n"); + usb_device_close(device); + return NULL; + } + + int ret = usb_device_claim_interface(device, interface->bInterfaceNumber); + if (ret && errno == EBUSY) { + // disconnect kernel driver and try again + usb_device_connect_kernel_driver(device, interface->bInterfaceNumber, false); + ret = usb_device_claim_interface(device, interface->bInterfaceNumber); + } + if (ret) { + MTPE("usb_device_claim_interface failed errno: %d\n", errno); + usb_device_close(device); + return NULL; + } + + MtpDevice* mtpDevice = new MtpDevice(device, interface->bInterfaceNumber, + ep_in_desc, ep_out_desc, ep_intr_desc); + mtpDevice->initialize(); + return mtpDevice; + } + } + + usb_device_close(device); + MTPE("device not found"); + return NULL; +} + +MtpDevice::MtpDevice(struct usb_device* device, int interface, + const struct usb_endpoint_descriptor *ep_in, + const struct usb_endpoint_descriptor *ep_out, + const struct usb_endpoint_descriptor *ep_intr) + : mDevice(device), + mInterface(interface), + mRequestIn1(NULL), + mRequestIn2(NULL), + mRequestOut(NULL), + mRequestIntr(NULL), + mDeviceInfo(NULL), + mSessionID(0), + mTransactionID(0), + mReceivedResponse(false), + mProcessingEvent(false), + mCurrentEventHandle(0), + mLastSendObjectInfoTransactionID(0), + mLastSendObjectInfoObjectHandle(0), + mPacketDivisionMode(FIRST_PACKET_HAS_PAYLOAD) +{ + mRequestIn1 = usb_request_new(device, ep_in); + mRequestIn2 = usb_request_new(device, ep_in); + mRequestOut = usb_request_new(device, ep_out); + mRequestIntr = usb_request_new(device, ep_intr); +} + +MtpDevice::~MtpDevice() { + close(); + for (size_t i = 0; i < mDeviceProperties.size(); i++) + delete mDeviceProperties[i]; + usb_request_free(mRequestIn1); + usb_request_free(mRequestIn2); + usb_request_free(mRequestOut); + usb_request_free(mRequestIntr); +} + +void MtpDevice::initialize() { + openSession(); + mDeviceInfo = getDeviceInfo(); + if (mDeviceInfo) { + if (mDeviceInfo->mDeviceProperties) { + int count = mDeviceInfo->mDeviceProperties->size(); + for (int i = 0; i < count; i++) { + MtpDeviceProperty propCode = (*mDeviceInfo->mDeviceProperties)[i]; + MtpProperty* property = getDevicePropDesc(propCode); + if (property) + mDeviceProperties.push_back(property); + } + } + } +} + +void MtpDevice::close() { + if (mDevice) { + usb_device_release_interface(mDevice, mInterface); + usb_device_close(mDevice); + mDevice = NULL; + } +} + +void MtpDevice::print() { + if (!mDeviceInfo) + return; + + mDeviceInfo->print(); + + if (mDeviceInfo->mDeviceProperties) { + MTPD("***** DEVICE PROPERTIES *****\n"); + int count = mDeviceInfo->mDeviceProperties->size(); + for (int i = 0; i < count; i++) { + MtpDeviceProperty propCode = (*mDeviceInfo->mDeviceProperties)[i]; + MtpProperty* property = getDevicePropDesc(propCode); + if (property) { + property->print(); + delete property; + } + } + } + + if (mDeviceInfo->mPlaybackFormats) { + MTPD("***** OBJECT PROPERTIES *****\n"); + int count = mDeviceInfo->mPlaybackFormats->size(); + for (int i = 0; i < count; i++) { + MtpObjectFormat format = (*mDeviceInfo->mPlaybackFormats)[i]; + MTPD("*** FORMAT: %s\n", MtpDebug::getFormatCodeName(format)); + MtpObjectPropertyList* props = getObjectPropsSupported(format); + if (props) { + for (size_t j = 0; j < props->size(); j++) { + MtpObjectProperty prop = (*props)[j]; + MtpProperty* property = getObjectPropDesc(prop, format); + if (property) { + property->print(); + delete property; + } else { + MTPE("could not fetch property: %s", + MtpDebug::getObjectPropCodeName(prop)); + } + } + } + } + } +} + +const char* MtpDevice::getDeviceName() { + if (mDevice) + return usb_device_get_name(mDevice); + else + return "???"; +} + +bool MtpDevice::openSession() { + std::lock_guard<std::mutex> lg(mMutex); + + mSessionID = 0; + mTransactionID = 0; + MtpSessionID newSession = 1; + mRequest.reset(); + mRequest.setParameter(1, newSession); + if (!sendRequest(MTP_OPERATION_OPEN_SESSION)) + return false; + MtpResponseCode ret = readResponse(); + if (ret == MTP_RESPONSE_SESSION_ALREADY_OPEN) + newSession = mResponse.getParameter(1); + else if (ret != MTP_RESPONSE_OK) + return false; + + mSessionID = newSession; + mTransactionID = 1; + return true; +} + +bool MtpDevice::closeSession() { + // FIXME + return true; +} + +MtpDeviceInfo* MtpDevice::getDeviceInfo() { + std::lock_guard<std::mutex> lg(mMutex); + + mRequest.reset(); + if (!sendRequest(MTP_OPERATION_GET_DEVICE_INFO)) + return NULL; + if (!readData()) + return NULL; + MtpResponseCode ret = readResponse(); + if (ret == MTP_RESPONSE_OK) { + MtpDeviceInfo* info = new MtpDeviceInfo; + if (info->read(mData)) + return info; + else + delete info; + } + return NULL; +} + +MtpStorageIDList* MtpDevice::getStorageIDs() { + std::lock_guard<std::mutex> lg(mMutex); + + mRequest.reset(); + if (!sendRequest(MTP_OPERATION_GET_STORAGE_IDS)) + return NULL; + if (!readData()) + return NULL; + MtpResponseCode ret = readResponse(); + if (ret == MTP_RESPONSE_OK) { + return mData.getAUInt32(); + } + return NULL; +} + +MtpStorageInfo* MtpDevice::getStorageInfo(MtpStorageID storageID) { + std::lock_guard<std::mutex> lg(mMutex); + + mRequest.reset(); + mRequest.setParameter(1, storageID); + if (!sendRequest(MTP_OPERATION_GET_STORAGE_INFO)) + return NULL; + if (!readData()) + return NULL; + MtpResponseCode ret = readResponse(); + if (ret == MTP_RESPONSE_OK) { + MtpStorageInfo* info = new MtpStorageInfo(storageID); + if (info->read(mData)) + return info; + else + delete info; + } + return NULL; +} + +MtpObjectHandleList* MtpDevice::getObjectHandles(MtpStorageID storageID, + MtpObjectFormat format, MtpObjectHandle parent) { + std::lock_guard<std::mutex> lg(mMutex); + + mRequest.reset(); + mRequest.setParameter(1, storageID); + mRequest.setParameter(2, format); + mRequest.setParameter(3, parent); + if (!sendRequest(MTP_OPERATION_GET_OBJECT_HANDLES)) + return NULL; + if (!readData()) + return NULL; + MtpResponseCode ret = readResponse(); + if (ret == MTP_RESPONSE_OK) { + return mData.getAUInt32(); + } + return NULL; +} + +MtpObjectInfo* MtpDevice::getObjectInfo(MtpObjectHandle handle) { + std::lock_guard<std::mutex> lg(mMutex); + + // FIXME - we might want to add some caching here + + mRequest.reset(); + mRequest.setParameter(1, handle); + if (!sendRequest(MTP_OPERATION_GET_OBJECT_INFO)) + return NULL; + if (!readData()) + return NULL; + MtpResponseCode ret = readResponse(); + if (ret == MTP_RESPONSE_OK) { + MtpObjectInfo* info = new MtpObjectInfo(handle); + if (info->read(mData)) + return info; + else + delete info; + } + return NULL; +} + +void* MtpDevice::getThumbnail(MtpObjectHandle handle, int& outLength) { + std::lock_guard<std::mutex> lg(mMutex); + + mRequest.reset(); + mRequest.setParameter(1, handle); + if (sendRequest(MTP_OPERATION_GET_THUMB) && readData()) { + MtpResponseCode ret = readResponse(); + if (ret == MTP_RESPONSE_OK) { + return mData.getData(&outLength); + } + } + outLength = 0; + return NULL; +} + +MtpObjectHandle MtpDevice::sendObjectInfo(MtpObjectInfo* info) { + std::lock_guard<std::mutex> lg(mMutex); + + mRequest.reset(); + MtpObjectHandle parent = info->mParent; + if (parent == 0) + parent = MTP_PARENT_ROOT; + + mRequest.setParameter(1, info->mStorageID); + mRequest.setParameter(2, parent); + + mData.reset(); + mData.putUInt32(info->mStorageID); + mData.putUInt16(info->mFormat); + mData.putUInt16(info->mProtectionStatus); + mData.putUInt32(info->mCompressedSize); + mData.putUInt16(info->mThumbFormat); + mData.putUInt32(info->mThumbCompressedSize); + mData.putUInt32(info->mThumbPixWidth); + mData.putUInt32(info->mThumbPixHeight); + mData.putUInt32(info->mImagePixWidth); + mData.putUInt32(info->mImagePixHeight); + mData.putUInt32(info->mImagePixDepth); + mData.putUInt32(info->mParent); + mData.putUInt16(info->mAssociationType); + mData.putUInt32(info->mAssociationDesc); + mData.putUInt32(info->mSequenceNumber); + mData.putString(info->mName); + + char created[100], modified[100]; + formatDateTime(info->mDateCreated, created, sizeof(created)); + formatDateTime(info->mDateModified, modified, sizeof(modified)); + + mData.putString(created); + mData.putString(modified); + if (info->mKeywords) + mData.putString(info->mKeywords); + else + mData.putEmptyString(); + + if (sendRequest(MTP_OPERATION_SEND_OBJECT_INFO) && sendData()) { + MtpResponseCode ret = readResponse(); + if (ret == MTP_RESPONSE_OK) { + mLastSendObjectInfoTransactionID = mRequest.getTransactionID(); + mLastSendObjectInfoObjectHandle = mResponse.getParameter(3); + info->mStorageID = mResponse.getParameter(1); + info->mParent = mResponse.getParameter(2); + info->mHandle = mResponse.getParameter(3); + return info->mHandle; + } + } + return (MtpObjectHandle)-1; +} + +bool MtpDevice::sendObject(MtpObjectHandle handle, int size, int srcFD) { + std::lock_guard<std::mutex> lg(mMutex); + + if (mLastSendObjectInfoTransactionID + 1 != mTransactionID || + mLastSendObjectInfoObjectHandle != handle) { + MTPE("A sendObject request must follow the sendObjectInfo request."); + return false; + } + + mRequest.reset(); + if (sendRequest(MTP_OPERATION_SEND_OBJECT)) { + mData.setOperationCode(mRequest.getOperationCode()); + mData.setTransactionID(mRequest.getTransactionID()); + const int writeResult = mData.write(mRequestOut, mPacketDivisionMode, srcFD, size); + const MtpResponseCode ret = readResponse(); + return ret == MTP_RESPONSE_OK && writeResult > 0; + } + return false; +} + +bool MtpDevice::deleteObject(MtpObjectHandle handle) { + std::lock_guard<std::mutex> lg(mMutex); + + mRequest.reset(); + mRequest.setParameter(1, handle); + if (sendRequest(MTP_OPERATION_DELETE_OBJECT)) { + MtpResponseCode ret = readResponse(); + if (ret == MTP_RESPONSE_OK) + return true; + } + return false; +} + +MtpObjectHandle MtpDevice::getParent(MtpObjectHandle handle) { + MtpObjectInfo* info = getObjectInfo(handle); + if (info) { + MtpObjectHandle parent = info->mParent; + delete info; + return parent; + } else { + return -1; + } +} + +MtpObjectHandle MtpDevice::getStorageID(MtpObjectHandle handle) { + MtpObjectInfo* info = getObjectInfo(handle); + if (info) { + MtpObjectHandle storageId = info->mStorageID; + delete info; + return storageId; + } else { + return -1; + } +} + +MtpObjectPropertyList* MtpDevice::getObjectPropsSupported(MtpObjectFormat format) { + std::lock_guard<std::mutex> lg(mMutex); + + mRequest.reset(); + mRequest.setParameter(1, format); + if (!sendRequest(MTP_OPERATION_GET_OBJECT_PROPS_SUPPORTED)) + return NULL; + if (!readData()) + return NULL; + MtpResponseCode ret = readResponse(); + if (ret == MTP_RESPONSE_OK) { + return mData.getAUInt16(); + } + return NULL; + +} + +MtpProperty* MtpDevice::getDevicePropDesc(MtpDeviceProperty code) { + std::lock_guard<std::mutex> lg(mMutex); + + mRequest.reset(); + mRequest.setParameter(1, code); + if (!sendRequest(MTP_OPERATION_GET_DEVICE_PROP_DESC)) + return NULL; + if (!readData()) + return NULL; + MtpResponseCode ret = readResponse(); + if (ret == MTP_RESPONSE_OK) { + MtpProperty* property = new MtpProperty; + if (property->read(mData)) + return property; + else + delete property; + } + return NULL; +} + +MtpProperty* MtpDevice::getObjectPropDesc(MtpObjectProperty code, MtpObjectFormat format) { + std::lock_guard<std::mutex> lg(mMutex); + + mRequest.reset(); + mRequest.setParameter(1, code); + mRequest.setParameter(2, format); + if (!sendRequest(MTP_OPERATION_GET_OBJECT_PROP_DESC)) + return NULL; + if (!readData()) + return NULL; + const MtpResponseCode ret = readResponse(); + if (ret == MTP_RESPONSE_OK) { + MtpProperty* property = new MtpProperty; + if (property->read(mData)) + return property; + else + delete property; + } + return NULL; +} + +bool MtpDevice::getObjectPropValue(MtpObjectHandle handle, MtpProperty* property) { + if (property == nullptr) + return false; + + std::lock_guard<std::mutex> lg(mMutex); + + mRequest.reset(); + mRequest.setParameter(1, handle); + mRequest.setParameter(2, property->getPropertyCode()); + if (!sendRequest(MTP_OPERATION_GET_OBJECT_PROP_VALUE)) + return false; + if (!readData()) + return false; + if (readResponse() != MTP_RESPONSE_OK) + return false; + property->setCurrentValue(mData); + return true; +} + +bool MtpDevice::readObject(MtpObjectHandle handle, + ReadObjectCallback callback, + uint32_t expectedLength, + void* clientData) { + return readObjectInternal(handle, callback, &expectedLength, clientData); +} + +// reads the object's data and writes it to the specified file path +bool MtpDevice::readObject(MtpObjectHandle handle, const char* destPath, int group, int perm) { + MTPD("readObject: %s", destPath); + int fd = ::open(destPath, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); + if (fd < 0) { + MTPE("open failed for %s", destPath); + return false; + } + + fchown(fd, getuid(), group); + // set permissions + int mask = umask(0); + fchmod(fd, perm); + umask(mask); + + bool result = readObject(handle, fd); + ::close(fd); + return result; +} + +bool MtpDevice::readObject(MtpObjectHandle handle, int fd) { + MTPD("readObject: %d", fd); + return readObjectInternal(handle, writeToFd, NULL /* expected size */, &fd); +} + +bool MtpDevice::readObjectInternal(MtpObjectHandle handle, + ReadObjectCallback callback, + const uint32_t* expectedLength, + void* clientData) { + std::lock_guard<std::mutex> lg(mMutex); + + mRequest.reset(); + mRequest.setParameter(1, handle); + if (!sendRequest(MTP_OPERATION_GET_OBJECT)) { + MTPE("Failed to send a read request."); + return false; + } + + return readData(callback, expectedLength, nullptr, clientData); +} + +bool MtpDevice::readData(ReadObjectCallback callback, + const uint32_t* expectedLength, + uint32_t* writtenSize, + void* clientData) { + if (!mData.readDataHeader(mRequestIn1)) { + MTPE("Failed to read header."); + return false; + } + + // If object size 0 byte, the remote device may reply a response packet without sending any data + // packets. + if (mData.getContainerType() == MTP_CONTAINER_TYPE_RESPONSE) { + mResponse.copyFrom(mData); + return mResponse.getResponseCode() == MTP_RESPONSE_OK; + } + + const uint32_t fullLength = mData.getContainerLength(); + if (fullLength < MTP_CONTAINER_HEADER_SIZE) { + MTPE("fullLength is too short: %d", fullLength); + return false; + } + const uint32_t length = fullLength - MTP_CONTAINER_HEADER_SIZE; + if (expectedLength && length != *expectedLength) { + MTPE("readObject error length: %d", fullLength); + return false; + } + + uint32_t offset = 0; + bool writingError = false; + + { + int initialDataLength = 0; + void* const initialData = mData.getData(&initialDataLength); + if (fullLength > MTP_CONTAINER_HEADER_SIZE && initialDataLength == 0) { + // According to the MTP spec, the responder (MTP device) can choose two ways of sending + // data. a) The first packet contains the head and as much of the payload as possible + // b) The first packet contains only the header. The initiator (MTP host) needs + // to remember which way the responder used, and send upcoming data in the same way. + MTPD("Found short packet that contains only a header."); + mPacketDivisionMode = FIRST_PACKET_ONLY_HEADER; + } + if (initialData) { + if (initialDataLength > 0) { + if (!callback(initialData, offset, initialDataLength, clientData)) { + MTPE("Failed to write initial data."); + writingError = true; + } + offset += initialDataLength; + } + free(initialData); + } + } + + // USB reads greater than 16K don't work. + char buffer1[MTP_BUFFER_SIZE], buffer2[MTP_BUFFER_SIZE]; + mRequestIn1->buffer = buffer1; + mRequestIn2->buffer = buffer2; + struct usb_request* req = NULL; + + while (offset < length) { + // Wait for previous read to complete. + void* writeBuffer = NULL; + int writeLength = 0; + if (req) { + const int read = mData.readDataWait(mDevice); + if (read < 0) { + MTPE("readDataWait failed."); + return false; + } + writeBuffer = req->buffer; + writeLength = read; + } + + // Request to read next chunk. + const uint32_t nextOffset = offset + writeLength; + if (nextOffset < length) { + // Queue up a read request. + const size_t remaining = length - nextOffset; + req = (req == mRequestIn1 ? mRequestIn2 : mRequestIn1); + req->buffer_length = remaining > MTP_BUFFER_SIZE ? + static_cast<size_t>(MTP_BUFFER_SIZE) : remaining; + if (mData.readDataAsync(req) != 0) { + MTPE("readDataAsync failed"); + return false; + } + } + + // Write previous buffer. + if (writeBuffer && !writingError) { + if (!callback(writeBuffer, offset, writeLength, clientData)) { + MTPE("write failed"); + writingError = true; + } + } + offset = nextOffset; + } + + if (writtenSize) { + *writtenSize = length; + } + + return readResponse() == MTP_RESPONSE_OK; +} + +bool MtpDevice::readPartialObject(MtpObjectHandle handle, + uint32_t offset, + uint32_t size, + uint32_t *writtenSize, + ReadObjectCallback callback, + void* clientData) { + std::lock_guard<std::mutex> lg(mMutex); + + mRequest.reset(); + mRequest.setParameter(1, handle); + mRequest.setParameter(2, offset); + mRequest.setParameter(3, size); + if (!sendRequest(MTP_OPERATION_GET_PARTIAL_OBJECT)) { + MTPE("Failed to send a read request."); + return false; + } + // The expected size is null because it requires the exact number of bytes to read though + // MTP_OPERATION_GET_PARTIAL_OBJECT allows devices to return shorter length of bytes than + // requested. Destination's buffer length should be checked in |callback|. + return readData(callback, nullptr /* expected size */, writtenSize, clientData); +} + +bool MtpDevice::readPartialObject64(MtpObjectHandle handle, + uint64_t offset, + uint32_t size, + uint32_t *writtenSize, + ReadObjectCallback callback, + void* clientData) { + std::lock_guard<std::mutex> lg(mMutex); + + mRequest.reset(); + mRequest.setParameter(1, handle); + mRequest.setParameter(2, 0xffffffff & offset); + mRequest.setParameter(3, 0xffffffff & (offset >> 32)); + mRequest.setParameter(4, size); + if (!sendRequest(MTP_OPERATION_GET_PARTIAL_OBJECT_64)) { + MTPE("Failed to send a read request."); + return false; + } + // The expected size is null because it requires the exact number of bytes to read though + // MTP_OPERATION_GET_PARTIAL_OBJECT_64 allows devices to return shorter length of bytes than + // requested. Destination's buffer length should be checked in |callback|. + return readData(callback, nullptr /* expected size */, writtenSize, clientData); +} + +bool MtpDevice::sendRequest(MtpOperationCode operation) { + MTPD("sendRequest: %s\n", MtpDebug::getOperationCodeName(operation)); + mReceivedResponse = false; + mRequest.setOperationCode(operation); + if (mTransactionID > 0) + mRequest.setTransactionID(mTransactionID++); + int ret = mRequest.write(mRequestOut); + mRequest.dump(); + return (ret > 0); +} + +bool MtpDevice::sendData() { + MTPD("sendData\n"); + mData.setOperationCode(mRequest.getOperationCode()); + mData.setTransactionID(mRequest.getTransactionID()); + int ret = mData.write(mRequestOut, mPacketDivisionMode); + mData.dump(); + return (ret >= 0); +} + +bool MtpDevice::readData() { + mData.reset(); + int ret = mData.read(mRequestIn1); + MTPD("readData returned %d\n", ret); + if (ret >= MTP_CONTAINER_HEADER_SIZE) { + if (mData.getContainerType() == MTP_CONTAINER_TYPE_RESPONSE) { + MTPD("got response packet instead of data packet"); + // we got a response packet rather than data + // copy it to mResponse + mResponse.copyFrom(mData); + mReceivedResponse = true; + return false; + } + mData.dump(); + return true; + } + else { + MTPD("readResponse failed\n"); + return false; + } +} + +MtpResponseCode MtpDevice::readResponse() { + MTPD("readResponse\n"); + if (mReceivedResponse) { + mReceivedResponse = false; + return mResponse.getResponseCode(); + } + int ret = mResponse.read(mRequestIn1); + // handle zero length packets, which might occur if the data transfer + // ends on a packet boundary + if (ret == 0) + ret = mResponse.read(mRequestIn1); + if (ret >= MTP_CONTAINER_HEADER_SIZE) { + mResponse.dump(); + return mResponse.getResponseCode(); + } else { + MTPD("readResponse failed\n"); + return -1; + } +} + +int MtpDevice::submitEventRequest() { + if (!mEventMutex.try_lock()) { + // An event is being reaped on another thread. + return -1; + } + if (mProcessingEvent) { + // An event request was submitted, but no reapEventRequest called so far. + return -1; + } + std::lock_guard<std::mutex> lg(mEventMutexForInterrupt); + mEventPacket.sendRequest(mRequestIntr); + const int currentHandle = ++mCurrentEventHandle; + mProcessingEvent = true; + mEventMutex.unlock(); + return currentHandle; +} + +int MtpDevice::reapEventRequest(int handle, uint32_t (*parameters)[3]) { + std::lock_guard<std::mutex> lg(mEventMutex); + if (!mProcessingEvent || mCurrentEventHandle != handle || !parameters) { + return -1; + } + mProcessingEvent = false; + const int readSize = mEventPacket.readResponse(mRequestIntr->dev); + const int result = mEventPacket.getEventCode(); + // MTP event has three parameters. + (*parameters)[0] = mEventPacket.getParameter(1); + (*parameters)[1] = mEventPacket.getParameter(2); + (*parameters)[2] = mEventPacket.getParameter(3); + return readSize != 0 ? result : 0; +} + +void MtpDevice::discardEventRequest(int handle) { + std::lock_guard<std::mutex> lg(mEventMutexForInterrupt); + if (mCurrentEventHandle != handle) { + return; + } + usb_request_cancel(mRequestIntr); +} diff --git a/mtp/ffs/MtpDevice.h b/mtp/ffs/MtpDevice.h new file mode 100644 index 000000000..da00a81a3 --- /dev/null +++ b/mtp/ffs/MtpDevice.h @@ -0,0 +1,167 @@ +/* + * 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. + */ + +#ifndef _MTP_DEVICE_H +#define _MTP_DEVICE_H + +#include "MtpEventPacket.h" +#include "MtpDataPacket.h" +#include "MtpRequestPacket.h" +#include "MtpResponsePacket.h" +#include "MtpTypes.h" + +#include <mutex> + +struct usb_device; +struct usb_request; +struct usb_endpoint_descriptor; + +class MtpDeviceInfo; +class MtpEventPacket; +class MtpObjectInfo; +class MtpStorageInfo; + +class MtpDevice { +private: + struct usb_device* mDevice; + int mInterface; + struct usb_request* mRequestIn1; + struct usb_request* mRequestIn2; + struct usb_request* mRequestOut; + struct usb_request* mRequestIntr; + MtpDeviceInfo* mDeviceInfo; + MtpPropertyList mDeviceProperties; + + // current session ID + MtpSessionID mSessionID; + // current transaction ID + MtpTransactionID mTransactionID; + + MtpRequestPacket mRequest; + MtpDataPacket mData; + MtpResponsePacket mResponse; + MtpEventPacket mEventPacket; + + // set to true if we received a response packet instead of a data packet + bool mReceivedResponse; + bool mProcessingEvent; + int mCurrentEventHandle; + + // to check if a sendObject request follows the last sendObjectInfo request. + MtpTransactionID mLastSendObjectInfoTransactionID; + MtpObjectHandle mLastSendObjectInfoObjectHandle; + + // to ensure only one MTP transaction at a time + std::mutex mMutex; + std::mutex mEventMutex; + std::mutex mEventMutexForInterrupt; + + // Remember the device's packet division mode. + UrbPacketDivisionMode mPacketDivisionMode; + +public: + typedef bool (*ReadObjectCallback) + (void* data, uint32_t offset, uint32_t length, void* clientData); + + MtpDevice(struct usb_device* device, + int interface, + const struct usb_endpoint_descriptor *ep_in, + const struct usb_endpoint_descriptor *ep_out, + const struct usb_endpoint_descriptor *ep_intr); + + static MtpDevice* open(const char* deviceName, int fd); + + virtual ~MtpDevice(); + + void initialize(); + void close(); + void print(); + const char* getDeviceName(); + + bool openSession(); + bool closeSession(); + + MtpDeviceInfo* getDeviceInfo(); + MtpStorageIDList* getStorageIDs(); + MtpStorageInfo* getStorageInfo(MtpStorageID storageID); + MtpObjectHandleList* getObjectHandles(MtpStorageID storageID, MtpObjectFormat format, + MtpObjectHandle parent); + MtpObjectInfo* getObjectInfo(MtpObjectHandle handle); + void* getThumbnail(MtpObjectHandle handle, int& outLength); + MtpObjectHandle sendObjectInfo(MtpObjectInfo* info); + bool sendObject(MtpObjectHandle handle, int size, int srcFD); + bool deleteObject(MtpObjectHandle handle); + MtpObjectHandle getParent(MtpObjectHandle handle); + MtpStorageID getStorageID(MtpObjectHandle handle); + + MtpObjectPropertyList* getObjectPropsSupported(MtpObjectFormat format); + + MtpProperty* getDevicePropDesc(MtpDeviceProperty code); + MtpProperty* getObjectPropDesc(MtpObjectProperty code, MtpObjectFormat format); + + // Reads value of |property| for |handle|. Returns true on success. + bool getObjectPropValue(MtpObjectHandle handle, MtpProperty* property); + + bool readObject(MtpObjectHandle handle, ReadObjectCallback callback, + uint32_t objectSize, void* clientData); + bool readObject(MtpObjectHandle handle, const char* destPath, int group, + int perm); + bool readObject(MtpObjectHandle handle, int fd); + bool readPartialObject(MtpObjectHandle handle, + uint32_t offset, + uint32_t size, + uint32_t *writtenSize, + ReadObjectCallback callback, + void* clientData); + bool readPartialObject64(MtpObjectHandle handle, + uint64_t offset, + uint32_t size, + uint32_t *writtenSize, + ReadObjectCallback callback, + void* clientData); + // Starts a request to read MTP event from MTP device. It returns a request handle that + // can be used for blocking read or cancel. If other thread has already been processing an + // event returns -1. + int submitEventRequest(); + // Waits for MTP event from the device and returns MTP event code. It blocks the current thread + // until it receives an event from the device. |handle| should be a request handle returned + // by |submitEventRequest|. The function writes event parameters to |parameters|. Returns 0 for + // cancellations. Returns -1 for errors. + int reapEventRequest(int handle, uint32_t (*parameters)[3]); + // Cancels an event request. |handle| should be request handle returned by + // |submitEventRequest|. If there is a thread blocked by |reapEventRequest| with the same + // |handle|, the thread will resume. + void discardEventRequest(int handle); + +private: + // If |objectSize| is not NULL, it checks object size before reading data bytes. + bool readObjectInternal(MtpObjectHandle handle, + ReadObjectCallback callback, + const uint32_t* objectSize, + void* clientData); + // If |objectSize| is not NULL, it checks object size before reading data bytes. + bool readData(ReadObjectCallback callback, + const uint32_t* objectSize, + uint32_t* writtenData, + void* clientData); + bool sendRequest(MtpOperationCode operation); + bool sendData(); + bool readData(); + bool writeDataHeader(MtpOperationCode operation, int dataLength); + MtpResponseCode readResponse(); +}; + +#endif // _MTP_DEVICE_H diff --git a/mtp/ffs/MtpDeviceInfo.cpp b/mtp/ffs/MtpDeviceInfo.cpp new file mode 100644 index 000000000..fa2f95b2d --- /dev/null +++ b/mtp/ffs/MtpDeviceInfo.cpp @@ -0,0 +1,105 @@ +/* + * 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 "MtpDeviceInfo" + +#include "MtpDebug.h" +#include "MtpDataPacket.h" +#include "MtpDeviceInfo.h" +#include "MtpStringBuffer.h" + +MtpDeviceInfo::MtpDeviceInfo() + : mStandardVersion(0), + mVendorExtensionID(0), + mVendorExtensionVersion(0), + mVendorExtensionDesc(NULL), + mFunctionalMode(0), + mOperations(NULL), + mEvents(NULL), + mDeviceProperties(NULL), + mCaptureFormats(NULL), + mPlaybackFormats(NULL), + mManufacturer(NULL), + mModel(NULL), + mVersion(NULL), + mSerial(NULL) +{ +} + +MtpDeviceInfo::~MtpDeviceInfo() { + if (mVendorExtensionDesc) + free(mVendorExtensionDesc); + delete mOperations; + delete mEvents; + delete mDeviceProperties; + delete mCaptureFormats; + delete mPlaybackFormats; + if (mManufacturer) + free(mManufacturer); + if (mModel) + free(mModel); + if (mVersion) + free(mVersion); + if (mSerial) + free(mSerial); +} + +bool MtpDeviceInfo::read(MtpDataPacket& packet) { + MtpStringBuffer string; + + // read the device info + if (!packet.getUInt16(mStandardVersion)) return false; + if (!packet.getUInt32(mVendorExtensionID)) return false; + if (!packet.getUInt16(mVendorExtensionVersion)) return false; + + if (!packet.getString(string)) return false; + mVendorExtensionDesc = strdup((const char *)string); + if (!mVendorExtensionDesc) return false; + + if (!packet.getUInt16(mFunctionalMode)) return false; + mOperations = packet.getAUInt16(); + if (!mOperations) return false; + mEvents = packet.getAUInt16(); + if (!mEvents) return false; + mDeviceProperties = packet.getAUInt16(); + if (!mDeviceProperties) return false; + mCaptureFormats = packet.getAUInt16(); + if (!mCaptureFormats) return false; + mPlaybackFormats = packet.getAUInt16(); + if (!mCaptureFormats) return false; + + if (!packet.getString(string)) return false; + mManufacturer = strdup((const char *)string); + if (!mManufacturer) return false; + if (!packet.getString(string)) return false; + mModel = strdup((const char *)string); + if (!mModel) return false; + if (!packet.getString(string)) return false; + mVersion = strdup((const char *)string); + if (!mVersion) return false; + if (!packet.getString(string)) return false; + mSerial = strdup((const char *)string); + if (!mSerial) return false; + + return true; +} + +void MtpDeviceInfo::print() { + ALOGV("Device Info:\n\tmStandardVersion: %d\n\tmVendorExtensionID: %d\n\tmVendorExtensionVersiony: %d\n", + mStandardVersion, mVendorExtensionID, mVendorExtensionVersion); + ALOGV("\tmVendorExtensionDesc: %s\n\tmFunctionalMode: %d\n\tmManufacturer: %s\n\tmModel: %s\n\tmVersion: %s\n\tmSerial: %s\n", + mVendorExtensionDesc, mFunctionalMode, mManufacturer, mModel, mVersion, mSerial); +} diff --git a/mtp/ffs/MtpDeviceInfo.h b/mtp/ffs/MtpDeviceInfo.h new file mode 100644 index 000000000..1690b619d --- /dev/null +++ b/mtp/ffs/MtpDeviceInfo.h @@ -0,0 +1,50 @@ +/* + * 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. + */ + +#ifndef _MTP_DEVICE_INFO_H +#define _MTP_DEVICE_INFO_H + +struct stat; + +class MtpDataPacket; + +class MtpDeviceInfo { +public: + uint16_t mStandardVersion; + uint32_t mVendorExtensionID; + uint16_t mVendorExtensionVersion; + char* mVendorExtensionDesc; + uint16_t mFunctionalMode; + UInt16List* mOperations; + UInt16List* mEvents; + MtpDevicePropertyList* mDeviceProperties; + MtpObjectFormatList* mCaptureFormats; + MtpObjectFormatList* mPlaybackFormats; + char* mManufacturer; + char* mModel; + char* mVersion; + char* mSerial; + +public: + MtpDeviceInfo(); + virtual ~MtpDeviceInfo(); + + bool read(MtpDataPacket& packet); + + void print(); +}; + +#endif // _MTP_DEVICE_INFO_H diff --git a/mtp/ffs/MtpEventPacket.cpp b/mtp/ffs/MtpEventPacket.cpp new file mode 100644 index 000000000..fa7db8c26 --- /dev/null +++ b/mtp/ffs/MtpEventPacket.cpp @@ -0,0 +1,71 @@ +/* + * 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 "MtpEventPacket" + +#include <stdio.h> +#include <sys/types.h> +#include <fcntl.h> + +#include "IMtpHandle.h" +#include "MtpEventPacket.h" + +#include <usbhost/usbhost.h> + +MtpEventPacket::MtpEventPacket() + : MtpPacket(512) +{ +} + +MtpEventPacket::~MtpEventPacket() { +} + +#ifdef MTP_DEVICE +int MtpEventPacket::write(IMtpHandle *h) { + struct mtp_event event; + + putUInt32(MTP_CONTAINER_LENGTH_OFFSET, mPacketSize); + putUInt16(MTP_CONTAINER_TYPE_OFFSET, MTP_CONTAINER_TYPE_EVENT); + + event.data = mBuffer; + event.length = mPacketSize; + int ret = h->sendEvent(event); + return (ret < 0 ? ret : 0); +} +#endif + +#ifdef MTP_HOST +int MtpEventPacket::sendRequest(struct usb_request *request) { + request->buffer = mBuffer; + request->buffer_length = mBufferSize; + mPacketSize = 0; + if (usb_request_queue(request)) { + MTPE("usb_endpoint_queue failed, errno: %d", errno); + return -1; + } + return 0; +} + +int MtpEventPacket::readResponse(struct usb_device *device) { + struct usb_request* const req = usb_request_wait(device, -1); + if (req) { + mPacketSize = req->actual_length; + return req->actual_length; + } else { + return -1; + } +} +#endif diff --git a/mtp/ffs/MtpEventPacket.h b/mtp/ffs/MtpEventPacket.h new file mode 100644 index 000000000..e8f50f450 --- /dev/null +++ b/mtp/ffs/MtpEventPacket.h @@ -0,0 +1,49 @@ +/* + * 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. + */ + +#ifndef _MTP_EVENT_PACKET_H +#define _MTP_EVENT_PACKET_H + +#include "MtpPacket.h" +#include "mtp.h" + +#include <errno.h> + +class IMtpHandle; + +class MtpEventPacket : public MtpPacket { + +public: + MtpEventPacket(); + virtual ~MtpEventPacket(); + +#ifdef MTP_DEVICE + // write our data to the given usb handle + int write(IMtpHandle *h); +#endif + +#ifdef MTP_HOST + // read our buffer with the given request + int sendRequest(struct usb_request *request); + int readResponse(struct usb_device *device); +#endif + + inline MtpEventCode getEventCode() const { return getContainerCode(); } + inline void setEventCode(MtpEventCode code) + { return setContainerCode(code); } +}; + +#endif // _MTP_EVENT_PACKET_H diff --git a/mtp/ffs/MtpFfsCompatHandle.cpp b/mtp/ffs/MtpFfsCompatHandle.cpp new file mode 100644 index 000000000..4027d601c --- /dev/null +++ b/mtp/ffs/MtpFfsCompatHandle.cpp @@ -0,0 +1,338 @@ +/* + * Copyright (C) 2017 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 <android-base/logging.h> +#include <android-base/properties.h> +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <linux/usb/ch9.h> +#include <linux/usb/functionfs.h> +#include <mutex> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/endian.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include "PosixAsyncIO.h" +#include "MtpFfsCompatHandle.h" +#include "mtp.h" + +#define FUNCTIONFS_ENDPOINT_ALLOC _IOR('g', 231, __u32) + +namespace { + +// Must be divisible by all max packet size values +constexpr int MAX_FILE_CHUNK_SIZE = 3145728; + +// Safe values since some devices cannot handle large DMAs +// To get good performance, override these with +// higher values per device using the properties +// sys.usb.ffs.max_read and sys.usb.ffs.max_write +constexpr int USB_FFS_MAX_WRITE = MTP_BUFFER_SIZE; +constexpr int USB_FFS_MAX_READ = MTP_BUFFER_SIZE; + +static_assert(USB_FFS_MAX_WRITE > 0, "Max r/w values must be > 0!"); +static_assert(USB_FFS_MAX_READ > 0, "Max r/w values must be > 0!"); + +constexpr unsigned int MAX_MTP_FILE_SIZE = 0xFFFFFFFF; + +constexpr size_t ENDPOINT_ALLOC_RETRIES = 10; + +} // anonymous namespace + + +MtpFfsCompatHandle::MtpFfsCompatHandle(int controlFd) : + MtpFfsHandle(controlFd), + mMaxWrite(USB_FFS_MAX_WRITE), + mMaxRead(USB_FFS_MAX_READ) {} + +MtpFfsCompatHandle::~MtpFfsCompatHandle() {} + +int MtpFfsCompatHandle::writeHandle(int fd, const void* data, size_t len) { + int ret = 0; + const char* buf = static_cast<const char*>(data); + while (len > 0) { + int write_len = std::min(mMaxWrite, len); + int n = TEMP_FAILURE_RETRY(::write(fd, buf, write_len)); + + if (n < 0) { + PLOG(ERROR) << "write ERROR: fd = " << fd << ", n = " << n; + return -1; + } else if (n < write_len) { + errno = EIO; + PLOG(ERROR) << "less written than expected"; + return -1; + } + buf += n; + len -= n; + ret += n; + } + return ret; +} + +int MtpFfsCompatHandle::readHandle(int fd, void* data, size_t len) { + int ret = 0; + char* buf = static_cast<char*>(data); + while (len > 0) { + int read_len = std::min(mMaxRead, len); + int n = TEMP_FAILURE_RETRY(::read(fd, buf, read_len)); + if (n < 0) { + PLOG(ERROR) << "read ERROR: fd = " << fd << ", n = " << n; + return -1; + } + ret += n; + if (n < read_len) // done reading early + break; + buf += n; + len -= n; + } + return ret; +} + +int MtpFfsCompatHandle::start(bool ptp) { + if (!openEndpoints(ptp)) + return -1; + + for (unsigned i = 0; i < NUM_IO_BUFS; i++) { + mIobuf[i].bufs.resize(MAX_FILE_CHUNK_SIZE); + posix_madvise(mIobuf[i].bufs.data(), MAX_FILE_CHUNK_SIZE, + POSIX_MADV_SEQUENTIAL | POSIX_MADV_WILLNEED); + } + + // Get device specific r/w size + mMaxWrite = android::base::GetIntProperty("sys.usb.ffs.max_write", USB_FFS_MAX_WRITE); + mMaxRead = android::base::GetIntProperty("sys.usb.ffs.max_read", USB_FFS_MAX_READ); + + size_t attempts = 0; + while (mMaxWrite >= USB_FFS_MAX_WRITE && mMaxRead >= USB_FFS_MAX_READ && + attempts < ENDPOINT_ALLOC_RETRIES) { + // If larger contiguous chunks of memory aren't available, attempt to try + // smaller allocations. + if (ioctl(mBulkIn, FUNCTIONFS_ENDPOINT_ALLOC, static_cast<__u32>(mMaxWrite)) || + ioctl(mBulkOut, FUNCTIONFS_ENDPOINT_ALLOC, static_cast<__u32>(mMaxRead))) { + if (errno == ENODEV) { + // Driver hasn't enabled endpoints yet. + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + attempts += 1; + continue; + } + mMaxWrite /= 2; + mMaxRead /=2; + } else { + return 0; + } + } + // Try to start MtpServer anyway, with the smallest max r/w values + mMaxWrite = USB_FFS_MAX_WRITE; + mMaxRead = USB_FFS_MAX_READ; + PLOG(ERROR) << "Functionfs could not allocate any memory!"; + return 0; +} + +int MtpFfsCompatHandle::read(void* data, size_t len) { + return readHandle(mBulkOut, data, len); +} + +int MtpFfsCompatHandle::write(const void* data, size_t len) { + return writeHandle(mBulkIn, data, len); +} + +int MtpFfsCompatHandle::receiveFile(mtp_file_range mfr, bool zero_packet) { + // When receiving files, the incoming length is given in 32 bits. + // A >4G file is given as 0xFFFFFFFF + uint32_t file_length = mfr.length; + uint64_t offset = mfr.offset; + int packet_size = getPacketSize(mBulkOut); + + unsigned char *data = mIobuf[0].bufs.data(); + unsigned char *data2 = mIobuf[1].bufs.data(); + + struct aiocb aio; + aio.aio_fildes = mfr.fd; + aio.aio_buf = nullptr; + struct aiocb *aiol[] = {&aio}; + int ret = -1; + size_t length; + bool read = false; + bool write = false; + + posix_fadvise(mfr.fd, 0, 0, POSIX_FADV_SEQUENTIAL | POSIX_FADV_NOREUSE); + + // Break down the file into pieces that fit in buffers + while (file_length > 0 || write) { + if (file_length > 0) { + length = std::min(static_cast<uint32_t>(MAX_FILE_CHUNK_SIZE), file_length); + + // Read data from USB, handle errors after waiting for write thread. + ret = readHandle(mBulkOut, data, length); + + if (file_length != MAX_MTP_FILE_SIZE && ret < static_cast<int>(length)) { + ret = -1; + errno = EIO; + } + read = true; + } + + if (write) { + // get the return status of the last write request + aio_suspend(aiol, 1, nullptr); + + int written = aio_return(&aio); + if (written == -1) { + errno = aio_error(&aio); + return -1; + } + if (static_cast<size_t>(written) < aio.aio_nbytes) { + errno = EIO; + return -1; + } + write = false; + } + + // If there was an error reading above + if (ret == -1) { + return -1; + } + + if (read) { + if (file_length == MAX_MTP_FILE_SIZE) { + // For larger files, receive until a short packet is received. + if (static_cast<size_t>(ret) < length) { + file_length = 0; + } + } else { + file_length -= ret; + } + // Enqueue a new write request + aio_prepare(&aio, data, length, offset); + aio_write(&aio); + + offset += ret; + std::swap(data, data2); + + write = true; + read = false; + } + } + // Receive an empty packet if size is a multiple of the endpoint size. + if (ret % packet_size == 0 || zero_packet) { + if (TEMP_FAILURE_RETRY(::read(mBulkOut, data, packet_size)) != 0) { + return -1; + } + } + return 0; +} + +int MtpFfsCompatHandle::sendFile(mtp_file_range mfr) { + uint64_t file_length = mfr.length; + uint32_t given_length = std::min(static_cast<uint64_t>(MAX_MTP_FILE_SIZE), + file_length + sizeof(mtp_data_header)); + uint64_t offset = mfr.offset; + int packet_size = getPacketSize(mBulkIn); + + // If file_length is larger than a size_t, truncating would produce the wrong comparison. + // Instead, promote the left side to 64 bits, then truncate the small result. + int init_read_len = std::min( + static_cast<uint64_t>(packet_size - sizeof(mtp_data_header)), file_length); + + unsigned char *data = mIobuf[0].bufs.data(); + unsigned char *data2 = mIobuf[1].bufs.data(); + + posix_fadvise(mfr.fd, 0, 0, POSIX_FADV_SEQUENTIAL | POSIX_FADV_NOREUSE); + + struct aiocb aio; + aio.aio_fildes = mfr.fd; + struct aiocb *aiol[] = {&aio}; + int ret, length; + int error = 0; + bool read = false; + bool write = false; + + // Send the header data + mtp_data_header *header = reinterpret_cast<mtp_data_header*>(data); + header->length = htole32(given_length); + header->type = htole16(2); /* data packet */ + header->command = htole16(mfr.command); + header->transaction_id = htole32(mfr.transaction_id); + + // Some hosts don't support header/data separation even though MTP allows it + // Handle by filling first packet with initial file data + if (TEMP_FAILURE_RETRY(pread(mfr.fd, reinterpret_cast<char*>(data) + + sizeof(mtp_data_header), init_read_len, offset)) + != init_read_len) return -1; + if (writeHandle(mBulkIn, data, sizeof(mtp_data_header) + init_read_len) == -1) return -1; + file_length -= init_read_len; + offset += init_read_len; + ret = init_read_len + sizeof(mtp_data_header); + + // Break down the file into pieces that fit in buffers + while (file_length > 0) { + if (read) { + // Wait for the previous read to finish + aio_suspend(aiol, 1, nullptr); + ret = aio_return(&aio); + if (ret == -1) { + errno = aio_error(&aio); + return -1; + } + if (static_cast<size_t>(ret) < aio.aio_nbytes) { + errno = EIO; + return -1; + } + + file_length -= ret; + offset += ret; + std::swap(data, data2); + read = false; + write = true; + } + + if (error == -1) { + return -1; + } + + if (file_length > 0) { + length = std::min(static_cast<uint64_t>(MAX_FILE_CHUNK_SIZE), file_length); + // Queue up another read + aio_prepare(&aio, data, length, offset); + aio_read(&aio); + read = true; + } + + if (write) { + if (writeHandle(mBulkIn, data2, ret) == -1) { + error = -1; + } + write = false; + } + } + + if (ret % packet_size == 0) { + // If the last packet wasn't short, send a final empty packet + if (TEMP_FAILURE_RETRY(::write(mBulkIn, data, 0)) != 0) { + return -1; + } + } + + return 0; +} + diff --git a/mtp/ffs/MtpFfsCompatHandle.h b/mtp/ffs/MtpFfsCompatHandle.h new file mode 100644 index 000000000..6f47e60cb --- /dev/null +++ b/mtp/ffs/MtpFfsCompatHandle.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2016 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. + */ + +#ifndef _MTP_FFS_COMPAT_HANDLE_H +#define _MTP_FFS_COMPAT_HANDLE_H + +#include <MtpFfsHandle.h> + +template <class T> class MtpFfsHandleTest; + +class MtpFfsCompatHandle : public MtpFfsHandle { + template <class T> friend class MtpFfsHandleTest; +private: + int writeHandle(int fd, const void *data, size_t len); + int readHandle(int fd, void *data, size_t len); + + size_t mMaxWrite; + size_t mMaxRead; + +public: + int read(void* data, size_t len) override; + int write(const void* data, size_t len) override; + int receiveFile(mtp_file_range mfr, bool zero_packet) override; + int sendFile(mtp_file_range mfr) override; + + /** + * Open ffs endpoints and allocate necessary kernel and user memory. + * Will sleep until endpoints are enabled, for up to 1 second. + */ + int start(bool ptp) override; + + MtpFfsCompatHandle(int controlFd); + ~MtpFfsCompatHandle(); +}; + +#endif // _MTP_FFS_COMPAT_HANDLE_H + diff --git a/mtp/ffs/MtpFfsHandle.cpp b/mtp/ffs/MtpFfsHandle.cpp new file mode 100644 index 000000000..01b6f2edb --- /dev/null +++ b/mtp/ffs/MtpFfsHandle.cpp @@ -0,0 +1,674 @@ +/* + * Copyright (C) 2016 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 <android-base/logging.h> +#include <android-base/properties.h> +#include <asyncio/AsyncIO.h> +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <memory> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/eventfd.h> +#include <sys/ioctl.h> +#include <sys/mman.h> +#include <sys/poll.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include "PosixAsyncIO.h" +#include "MtpDescriptors.h" +#include "MtpFfsHandle.h" +#include "mtp.h" +#include "MtpDebug.h" + +namespace { + +constexpr unsigned AIO_BUFS_MAX = 128; +constexpr unsigned AIO_BUF_LEN = 16384; + +constexpr unsigned FFS_NUM_EVENTS = 5; + +constexpr unsigned MAX_FILE_CHUNK_SIZE = AIO_BUFS_MAX * AIO_BUF_LEN; + +constexpr uint32_t MAX_MTP_FILE_SIZE = 0xFFFFFFFF; + +struct timespec ZERO_TIMEOUT = { 0, 0 }; + +struct mtp_device_status { + uint16_t wLength; + uint16_t wCode; +}; + +} // anonymous namespace + +int MtpFfsHandle::getPacketSize(int ffs_fd) { + struct usb_endpoint_descriptor desc; + if (ioctl(ffs_fd, FUNCTIONFS_ENDPOINT_DESC, reinterpret_cast<unsigned long>(&desc))) { + MTPE("Could not get FFS bulk-in descriptor\n"); + return MAX_PACKET_SIZE_HS; + } else { + return desc.wMaxPacketSize; + } +} + +MtpFfsHandle::MtpFfsHandle(int controlFd) { + mControl.reset(controlFd); +} + +MtpFfsHandle::~MtpFfsHandle() {} + +void MtpFfsHandle::closeEndpoints() { + mIntr.reset(); + mBulkIn.reset(); + mBulkOut.reset(); +} + +bool MtpFfsHandle::openEndpoints(bool ptp) { + if (mBulkIn < 0) { + mBulkIn.reset(TEMP_FAILURE_RETRY(open(ptp ? FFS_PTP_EP_IN : FFS_MTP_EP_IN, O_RDWR))); + if (mBulkIn < 0) { + MTPE("cannot open bulk in ep\n"); + return false; + } + } + + if (mBulkOut < 0) { + mBulkOut.reset(TEMP_FAILURE_RETRY(open(ptp ? FFS_PTP_EP_OUT : FFS_MTP_EP_OUT, O_RDWR))); + if (mBulkOut < 0) { + MTPE("cannot open bulk out ep\n"); + return false; + } + } + + if (mIntr < 0) { + mIntr.reset(TEMP_FAILURE_RETRY(open(ptp ? FFS_PTP_EP_INTR : FFS_MTP_EP_INTR, O_RDWR))); + if (mIntr < 0) { + MTPE("cannot open intr ep\n"); + return false; + } + } + return true; +} + +void MtpFfsHandle::advise(int fd) { + for (unsigned i = 0; i < NUM_IO_BUFS; i++) { + if (posix_madvise(mIobuf[i].bufs.data(), MAX_FILE_CHUNK_SIZE, + POSIX_MADV_SEQUENTIAL | POSIX_MADV_WILLNEED) < 0) + MTPE("Failed to madvise\n"); + } + if (posix_fadvise(fd, 0, 0, + POSIX_FADV_SEQUENTIAL | POSIX_FADV_NOREUSE | POSIX_FADV_WILLNEED) < 0) + MTPE("Failed to fadvise\n"); +} + +bool MtpFfsHandle::writeDescriptors(bool ptp) { + return ::writeDescriptors(mControl, ptp); +} + +void MtpFfsHandle::closeConfig() { + mControl.reset(); +} + +int MtpFfsHandle::doAsync(void* data, size_t len, bool read, bool zero_packet) { + struct io_event ioevs[AIO_BUFS_MAX]; + size_t total = 0; + + while (total < len) { + size_t this_len = std::min(len - total, static_cast<size_t>(AIO_BUF_LEN * AIO_BUFS_MAX)); + int num_bufs = this_len / AIO_BUF_LEN + (this_len % AIO_BUF_LEN == 0 ? 0 : 1); + for (int i = 0; i < num_bufs; i++) { + mIobuf[0].buf[i] = reinterpret_cast<unsigned char*>(data) + total + i * AIO_BUF_LEN; + } + int ret = iobufSubmit(&mIobuf[0], read ? mBulkOut : mBulkIn, this_len, read); + if (ret < 0) return -1; + ret = waitEvents(&mIobuf[0], ret, ioevs, nullptr); + if (ret < 0) return -1; + total += ret; + if (static_cast<size_t>(ret) < this_len) break; + } + + int packet_size = getPacketSize(read ? mBulkOut : mBulkIn); + if (len % packet_size == 0 && zero_packet) { + int ret = iobufSubmit(&mIobuf[0], read ? mBulkOut : mBulkIn, 0, read); + if (ret < 0) return -1; + ret = waitEvents(&mIobuf[0], ret, ioevs, nullptr); + if (ret < 0) return -1; + } + + for (unsigned i = 0; i < AIO_BUFS_MAX; i++) { + mIobuf[0].buf[i] = mIobuf[0].bufs.data() + i * AIO_BUF_LEN; + } + return total; +} + +int MtpFfsHandle::read(void* data, size_t len) { + // Zero packets are handled by receiveFile() + return doAsync(data, len, true, false); +} + +int MtpFfsHandle::write(const void* data, size_t len) { + return doAsync(const_cast<void*>(data), len, false, true); +} + +int MtpFfsHandle::handleEvent() { + + std::vector<usb_functionfs_event> events(FFS_NUM_EVENTS); + usb_functionfs_event *event = events.data(); + int nbytes = TEMP_FAILURE_RETRY(::read(mControl, event, + events.size() * sizeof(usb_functionfs_event))); + if (nbytes == -1) { + return -1; + } + int ret = 0; + for (size_t n = nbytes / sizeof *event; n; --n, ++event) { + switch (event->type) { + case FUNCTIONFS_BIND: + case FUNCTIONFS_ENABLE: + ret = 0; + errno = 0; + break; + case FUNCTIONFS_UNBIND: + case FUNCTIONFS_DISABLE: + errno = ESHUTDOWN; + ret = -1; + break; + case FUNCTIONFS_SETUP: + if (handleControlRequest(&event->u.setup) == -1) + ret = -1; + break; + case FUNCTIONFS_SUSPEND: + case FUNCTIONFS_RESUME: + break; + default: + MTPE("Mtp Event (unknown)\n"); + } + } + return ret; +} + +int MtpFfsHandle::handleControlRequest(const struct usb_ctrlrequest *setup) { + uint8_t type = setup->bRequestType; + uint8_t code = setup->bRequest; + uint16_t length = setup->wLength; + uint16_t index = setup->wIndex; + uint16_t value = setup->wValue; + std::vector<char> buf; + buf.resize(length); + int ret = 0; + + if (!(type & USB_DIR_IN)) { + if (::read(mControl, buf.data(), length) != length) { + MTPE("Mtp error ctrlreq read data"); + } + } + + if ((type & USB_TYPE_MASK) == USB_TYPE_CLASS && index == 0 && value == 0) { + switch(code) { + case MTP_REQ_RESET: + case MTP_REQ_CANCEL: + errno = ECANCELED; + ret = -1; + break; + case MTP_REQ_GET_DEVICE_STATUS: + { + if (length < sizeof(struct mtp_device_status) + 4) { + errno = EINVAL; + return -1; + } + struct mtp_device_status *st = reinterpret_cast<struct mtp_device_status*>(buf.data()); + st->wLength = htole16(sizeof(st)); + if (mCanceled) { + st->wLength += 4; + st->wCode = MTP_RESPONSE_TRANSACTION_CANCELLED; + uint16_t *endpoints = reinterpret_cast<uint16_t*>(st + 1); + endpoints[0] = ioctl(mBulkIn, FUNCTIONFS_ENDPOINT_REVMAP); + endpoints[1] = ioctl(mBulkOut, FUNCTIONFS_ENDPOINT_REVMAP); + mCanceled = false; + } else { + st->wCode = MTP_RESPONSE_OK; + } + length = st->wLength; + break; + } + default: + MTPE("Unrecognized Mtp class request!\n"); + } + } else { + MTPE("Unrecognized request type\n"); + } + + if (type & USB_DIR_IN) { + if (::write(mControl, buf.data(), length) != length) { + MTPE("Mtp error ctrlreq write data"); + } + } + return 0; +} + +int MtpFfsHandle::start(bool ptp) { + if (!openEndpoints(ptp)) + return -1; + + for (unsigned i = 0; i < NUM_IO_BUFS; i++) { + mIobuf[i].bufs.resize(MAX_FILE_CHUNK_SIZE); + mIobuf[i].iocb.resize(AIO_BUFS_MAX); + mIobuf[i].iocbs.resize(AIO_BUFS_MAX); + mIobuf[i].buf.resize(AIO_BUFS_MAX); + for (unsigned j = 0; j < AIO_BUFS_MAX; j++) { + mIobuf[i].buf[j] = mIobuf[i].bufs.data() + j * AIO_BUF_LEN; + mIobuf[i].iocb[j] = &mIobuf[i].iocbs[j]; + } + } + + memset(&mCtx, 0, sizeof(mCtx)); + if (io_setup(AIO_BUFS_MAX, &mCtx) < 0) { + MTPE("unable to setup aio"); + return -1; + } + mEventFd.reset(eventfd(0, EFD_NONBLOCK)); + mPollFds[0].fd = mControl; + mPollFds[0].events = POLLIN; + mPollFds[1].fd = mEventFd; + mPollFds[1].events = POLLIN; + + mCanceled = false; + return 0; +} + +void MtpFfsHandle::close() { + io_destroy(mCtx); + closeEndpoints(); + closeConfig(); +} + +int MtpFfsHandle::waitEvents(__attribute__((unused)) struct io_buffer *buf, int min_events, struct io_event *events, + int *counter) { + int num_events = 0; + int ret = 0; + int error = 0; + + while (num_events < min_events) { + if (poll(mPollFds, 2, 0) == -1) { + MTPE("Mtp error during poll()\n"); + return -1; + } + if (mPollFds[0].revents & POLLIN) { + mPollFds[0].revents = 0; + if (handleEvent() == -1) { + error = errno; + } + } + if (mPollFds[1].revents & POLLIN) { + mPollFds[1].revents = 0; + uint64_t ev_cnt = 0; + + if (::read(mEventFd, &ev_cnt, sizeof(ev_cnt)) == -1) { + MTPE("Mtp unable to read eventfd\n"); + error = errno; + continue; + } + + // It's possible that io_getevents will return more events than the eventFd reported, + // since events may appear in the time between the calls. In this case, the eventFd will + // show up as readable next iteration, but there will be fewer or no events to actually + // wait for. Thus we never want io_getevents to block. + int this_events = TEMP_FAILURE_RETRY(io_getevents(mCtx, 0, AIO_BUFS_MAX, events, &ZERO_TIMEOUT)); + if (this_events == -1) { + MTPE("Mtp error getting events"); + error = errno; + } + // Add up the total amount of data and find errors on the way. + for (unsigned j = 0; j < static_cast<unsigned>(this_events); j++) { + if (events[j].res < 0) { + errno = -events[j].res; + MTPE("Mtp got error event\n"); + error = errno; + } + ret += events[j].res; + } + num_events += this_events; + if (counter) + *counter += this_events; + } + if (error) { + errno = error; + ret = -1; + break; + } + } + return ret; +} + +void MtpFfsHandle::cancelTransaction() { + // Device cancels by stalling both bulk endpoints. + if (::read(mBulkIn, nullptr, 0) != -1 || errno != EBADMSG) + MTPE("Mtp stall failed on bulk in\n"); + if (::write(mBulkOut, nullptr, 0) != -1 || errno != EBADMSG) + MTPE("Mtp stall failed on bulk out\n"); + mCanceled = true; + errno = ECANCELED; +} + +int MtpFfsHandle::cancelEvents(struct iocb **iocb, struct io_event *events, unsigned start, + unsigned end) { + // Some manpages for io_cancel are out of date and incorrect. + // io_cancel will return -EINPROGRESS on success and does + // not place the event in the given memory. We have to use + // io_getevents to wait for all the events we cancelled. + int ret = 0; + unsigned num_events = 0; + int save_errno = errno; + errno = 0; + + for (unsigned j = start; j < end; j++) { + if (io_cancel(mCtx, iocb[j], nullptr) != -1 || errno != EINPROGRESS) { + MTPE("Mtp couldn't cancel request\n"); + } else { + num_events++; + } + } + if (num_events != end - start) { + ret = -1; + errno = EIO; + } + int evs = TEMP_FAILURE_RETRY(io_getevents(mCtx, num_events, AIO_BUFS_MAX, events, nullptr)); + if (static_cast<unsigned>(evs) != num_events) { + MTPE("Mtp couldn't cancel all requests\n"); + ret = -1; + } + + uint64_t ev_cnt = 0; + if (num_events && ::read(mEventFd, &ev_cnt, sizeof(ev_cnt)) == -1) + MTPE("Mtp Unable to read event fd\n"); + + if (ret == 0) { + // Restore errno since it probably got overriden with EINPROGRESS. + errno = save_errno; + } + return ret; +} + +int MtpFfsHandle::iobufSubmit(struct io_buffer *buf, int fd, unsigned length, bool read) { + int ret = 0; + buf->actual = AIO_BUFS_MAX; + for (unsigned j = 0; j < AIO_BUFS_MAX; j++) { + unsigned rq_length = std::min(AIO_BUF_LEN, length - AIO_BUF_LEN * j); + io_prep(buf->iocb[j], fd, buf->buf[j], rq_length, 0, read); + buf->iocb[j]->aio_flags |= IOCB_FLAG_RESFD; + buf->iocb[j]->aio_resfd = mEventFd; + + // Not enough data, so table is truncated. + if (rq_length < AIO_BUF_LEN || length == AIO_BUF_LEN * (j + 1)) { + buf->actual = j + 1; + break; + } + } + + ret = io_submit(mCtx, buf->actual, buf->iocb.data()); + if (ret != static_cast<int>(buf->actual)) { + MTPE("Mtp io_submit\n"); + if (ret != -1) { + errno = EIO; + } + ret = -1; + } + return ret; +} + +int MtpFfsHandle::receiveFile(mtp_file_range mfr, bool zero_packet) { + // When receiving files, the incoming length is given in 32 bits. + // A >=4G file is given as 0xFFFFFFFF + uint32_t file_length = mfr.length; + uint64_t offset = mfr.offset; + + struct aiocb aio; + aio.aio_fildes = mfr.fd; + aio.aio_buf = nullptr; + struct aiocb *aiol[] = {&aio}; + + int ret = -1; + unsigned i = 0; + size_t length; + struct io_event ioevs[AIO_BUFS_MAX]; + bool has_write = false; + bool error = false; + bool write_error = false; + int packet_size = getPacketSize(mBulkOut); + bool short_packet = false; + advise(mfr.fd); + + // Break down the file into pieces that fit in buffers + while (file_length > 0 || has_write) { + // Queue an asynchronous read from USB. + if (file_length > 0) { + length = std::min(static_cast<uint32_t>(MAX_FILE_CHUNK_SIZE), file_length); + if (iobufSubmit(&mIobuf[i], mBulkOut, length, true) == -1) + error = true; + } + + // Get the return status of the last write request. + if (has_write) { + aio_suspend(aiol, 1, nullptr); + int written = aio_return(&aio); + if (static_cast<size_t>(written) < aio.aio_nbytes) { + errno = written == -1 ? aio_error(&aio) : EIO; + MTPE("Mtp error writing to disk\n"); + write_error = true; + } + has_write = false; + } + + if (error) { + return -1; + } + + // Get the result of the read request, and queue a write to disk. + if (file_length > 0) { + unsigned num_events = 0; + ret = 0; + unsigned short_i = mIobuf[i].actual; + while (num_events < short_i) { + // Get all events up to the short read, if there is one. + // We must wait for each event since data transfer could end at any time. + int this_events = 0; + int event_ret = waitEvents(&mIobuf[i], 1, ioevs, &this_events); + num_events += this_events; + + if (event_ret == -1) { + cancelEvents(mIobuf[i].iocb.data(), ioevs, num_events, mIobuf[i].actual); + return -1; + } + ret += event_ret; + for (int j = 0; j < this_events; j++) { + // struct io_event contains a pointer to the associated struct iocb as a __u64. + if (static_cast<__u64>(ioevs[j].res) < + reinterpret_cast<struct iocb*>(ioevs[j].obj)->aio_nbytes) { + // We've found a short event. Store the index since + // events won't necessarily arrive in the order they are queued. + short_i = (ioevs[j].obj - reinterpret_cast<uint64_t>(mIobuf[i].iocbs.data())) + / sizeof(struct iocb) + 1; + short_packet = true; + } + } + } + if (short_packet) { + if (cancelEvents(mIobuf[i].iocb.data(), ioevs, short_i, mIobuf[i].actual)) { + write_error = true; + } + } + if (file_length == MAX_MTP_FILE_SIZE) { + // For larger files, receive until a short packet is received. + if (static_cast<size_t>(ret) < length) { + file_length = 0; + } + } else if (ret < static_cast<int>(length)) { + // If file is less than 4G and we get a short packet, it's an error. + errno = EIO; + MTPE("Mtp got unexpected short packet\n"); + return -1; + } else { + file_length -= ret; + } + + if (write_error) { + cancelTransaction(); + return -1; + } + + // Enqueue a new write request + aio_prepare(&aio, mIobuf[i].bufs.data(), ret, offset); + aio_write(&aio); + + offset += ret; + i = (i + 1) % NUM_IO_BUFS; + has_write = true; + } + } + if ((ret % packet_size == 0 && !short_packet) || zero_packet) { + // Receive an empty packet if size is a multiple of the endpoint size + // and we didn't already get an empty packet from the header or large file. + if (read(mIobuf[0].bufs.data(), packet_size) != 0) { + return -1; + } + } + return 0; +} + +int MtpFfsHandle::sendFile(mtp_file_range mfr) { + uint64_t file_length = mfr.length; + uint32_t given_length = std::min(static_cast<uint64_t>(MAX_MTP_FILE_SIZE), + file_length + sizeof(mtp_data_header)); + uint64_t offset = mfr.offset; + int packet_size = getPacketSize(mBulkIn); + + // If file_length is larger than a size_t, truncating would produce the wrong comparison. + // Instead, promote the left side to 64 bits, then truncate the small result. + int init_read_len = std::min( + static_cast<uint64_t>(packet_size - sizeof(mtp_data_header)), file_length); + + advise(mfr.fd); + + struct aiocb aio; + aio.aio_fildes = mfr.fd; + struct aiocb *aiol[] = {&aio}; + int ret = 0; + int length, num_read; + unsigned i = 0; + struct io_event ioevs[AIO_BUFS_MAX]; + bool error = false; + bool has_write = false; + + // Send the header data + mtp_data_header *header = reinterpret_cast<mtp_data_header*>(mIobuf[0].bufs.data()); + header->length = htole32(given_length); + header->type = htole16(2); // data packet + header->command = htole16(mfr.command); + header->transaction_id = htole32(mfr.transaction_id); + + // Some hosts don't support header/data separation even though MTP allows it + // Handle by filling first packet with initial file data + if (TEMP_FAILURE_RETRY(pread(mfr.fd, mIobuf[0].bufs.data() + + sizeof(mtp_data_header), init_read_len, offset)) + != init_read_len) return -1; + if (doAsync(mIobuf[0].bufs.data(), sizeof(mtp_data_header) + init_read_len, + false, false /* zlps are handled below */) == -1) + return -1; + file_length -= init_read_len; + offset += init_read_len; + ret = init_read_len + sizeof(mtp_data_header); + + // Break down the file into pieces that fit in buffers + while(file_length > 0 || has_write) { + if (file_length > 0) { + // Queue up a read from disk. + length = std::min(static_cast<uint64_t>(MAX_FILE_CHUNK_SIZE), file_length); + aio_prepare(&aio, mIobuf[i].bufs.data(), length, offset); + aio_read(&aio); + } + + if (has_write) { + // Wait for usb write. Cancel unwritten portion if there's an error. + int num_events = 0; + if (waitEvents(&mIobuf[(i-1)%NUM_IO_BUFS], mIobuf[(i-1)%NUM_IO_BUFS].actual, ioevs, + &num_events) != ret) { + error = true; + cancelEvents(mIobuf[(i-1)%NUM_IO_BUFS].iocb.data(), ioevs, num_events, + mIobuf[(i-1)%NUM_IO_BUFS].actual); + } + has_write = false; + } + + if (file_length > 0) { + // Wait for the previous read to finish + aio_suspend(aiol, 1, nullptr); + num_read = aio_return(&aio); + if (static_cast<size_t>(num_read) < aio.aio_nbytes) { + errno = num_read == -1 ? aio_error(&aio) : EIO; + MTPE("Mtp error reading from disk\n"); + cancelTransaction(); + return -1; + } + + file_length -= num_read; + offset += num_read; + + if (error) { + return -1; + } + + // Queue up a write to usb. + if (iobufSubmit(&mIobuf[i], mBulkIn, num_read, false) == -1) { + return -1; + } + has_write = true; + ret = num_read; + } + + i = (i + 1) % NUM_IO_BUFS; + } + + if (ret % packet_size == 0) { + // If the last packet wasn't short, send a final empty packet + if (write(mIobuf[0].bufs.data(), 0) != 0) { + return -1; + } + } + return 0; +} + +int MtpFfsHandle::sendEvent(mtp_event me) { + // Mimic the behavior of f_mtp by sending the event async. + // Events aren't critical to the connection, so we don't need to check the return value. + char *temp = new char[me.length]; + memcpy(temp, me.data, me.length); + me.data = temp; + std::thread t([this, me]() { return this->doSendEvent(me); }); + t.detach(); + return 0; +} + +void MtpFfsHandle::doSendEvent(mtp_event me) { + unsigned length = me.length; + int ret = ::write(mIntr, me.data, length); + if (static_cast<unsigned>(ret) != length) + MTPE("Mtp error sending event thread!\n"); + delete[] reinterpret_cast<char*>(me.data); +} + diff --git a/mtp/ffs/MtpFfsHandle.h b/mtp/ffs/MtpFfsHandle.h new file mode 100644 index 000000000..20f74fa29 --- /dev/null +++ b/mtp/ffs/MtpFfsHandle.h @@ -0,0 +1,112 @@ +/* + * Copyright (C) 2016 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. + */ + +#ifndef _MTP_FFS_HANDLE_H +#define _MTP_FFS_HANDLE_H + +#include <android-base/unique_fd.h> +#include <linux/aio_abi.h> +#include <mutex> +#include <sys/poll.h> +#include <time.h> +#include <thread> +#include <vector> + +#include <IMtpHandle.h> + +constexpr int NUM_IO_BUFS = 2; + +struct io_buffer { + std::vector<struct iocb> iocbs; // Holds memory for all iocbs. Not used directly. + std::vector<struct iocb*> iocb; // Pointers to individual iocbs, for syscalls + std::vector<unsigned char> bufs; // A large buffer, used with filesystem io + std::vector<unsigned char*> buf; // Pointers within the larger buffer, for syscalls + unsigned actual; // The number of buffers submitted for this request +}; + +template <class T> class MtpFfsHandleTest; + +class MtpFfsHandle : public IMtpHandle { + template <class T> friend class MtpFfsHandleTest; +protected: + void closeConfig(); + void closeEndpoints(); + void advise(int fd); + int handleControlRequest(const struct usb_ctrlrequest *request); + int doAsync(void* data, size_t len, bool read, bool zero_packet); + int handleEvent(); + void cancelTransaction(); + void doSendEvent(mtp_event me); + bool openEndpoints(bool ptp); + + static int getPacketSize(int ffs_fd); + + bool mCanceled; + + android::base::unique_fd mControl; + // "in" from the host's perspective => sink for mtp server + android::base::unique_fd mBulkIn; + // "out" from the host's perspective => source for mtp server + android::base::unique_fd mBulkOut; + android::base::unique_fd mIntr; + + aio_context_t mCtx; + + android::base::unique_fd mEventFd; + struct pollfd mPollFds[2]; + + struct io_buffer mIobuf[NUM_IO_BUFS]; + + // Submit an io request of given length. Return amount submitted or -1. + int iobufSubmit(struct io_buffer *buf, int fd, unsigned length, bool read); + + // Cancel submitted requests from start to end in the given array. Return 0 or -1. + int cancelEvents(struct iocb **iocb, struct io_event *events, unsigned start, unsigned end); + + // Wait for at minimum the given number of events. Returns the amount of data in the returned + // events. Increments counter by the number of events returned. + int waitEvents(struct io_buffer *buf, int min_events, struct io_event *events, int *counter); + +public: + int read(void *data, size_t len) override; + int write(const void *data, size_t len) override; + + int receiveFile(mtp_file_range mfr, bool zero_packet) override; + int sendFile(mtp_file_range mfr) override; + int sendEvent(mtp_event me) override; + + int start(bool ptp) override; + void close() override; + + bool writeDescriptors(bool ptp); + + MtpFfsHandle(int controlFd); + ~MtpFfsHandle(); +}; + +struct mtp_data_header { + /* length of packet, including this header */ + __le32 length; + /* container type (2 for data packet) */ + __le16 type; + /* MTP command code */ + __le16 command; + /* MTP transaction ID */ + __le32 transaction_id; +}; + +#endif // _MTP_FFS_HANDLE_H + diff --git a/mtp/ffs/MtpMessage.hpp b/mtp/ffs/MtpMessage.hpp new file mode 100644 index 000000000..31465d8c6 --- /dev/null +++ b/mtp/ffs/MtpMessage.hpp @@ -0,0 +1,33 @@ +/* + * 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. + * + * Copyright (C) 2014 TeamWin - bigbiff and Dees_Troy mtp database conversion to C++ + */ + +#ifndef _MTPMESSAGE_HPP +#define _MTPMESSAGE_HPP + +#define MTP_MESSAGE_ADD_STORAGE 1 +#define MTP_MESSAGE_REMOVE_STORAGE 2 + +struct mtpmsg { + int message_type; // 1 is add, 2 is remove, see above + unsigned int storage_id; + char display[1024]; + char path[1024]; + uint64_t maxFileSize; +}; + +#endif //_MTPMESSAGE_HPP diff --git a/mtp/ffs/MtpObjectInfo.cpp b/mtp/ffs/MtpObjectInfo.cpp new file mode 100644 index 000000000..3b4d80ccb --- /dev/null +++ b/mtp/ffs/MtpObjectInfo.cpp @@ -0,0 +1,108 @@ +/* + * 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 "MtpObjectInfo" + +#include "MtpDebug.h" +#include "MtpDataPacket.h" +#include "MtpObjectInfo.h" +#include "MtpStringBuffer.h" +#include "MtpUtils.h" + +MtpObjectInfo::MtpObjectInfo(MtpObjectHandle handle) + : mHandle(handle), + mStorageID(0), + mFormat(0), + mProtectionStatus(0), + mCompressedSize(0), + mThumbFormat(0), + mThumbCompressedSize(0), + mThumbPixWidth(0), + mThumbPixHeight(0), + mImagePixWidth(0), + mImagePixHeight(0), + mImagePixDepth(0), + mParent(0), + mAssociationType(0), + mAssociationDesc(0), + mSequenceNumber(0), + mName(NULL), + mDateCreated(0), + mDateModified(0), + mKeywords(NULL) +{ +} + +MtpObjectInfo::~MtpObjectInfo() { + if (mName) + free(mName); + if (mKeywords) + free(mKeywords); +} + +bool MtpObjectInfo::read(MtpDataPacket& packet) { + MtpStringBuffer string; + time_t time; + + if (!packet.getUInt32(mStorageID)) return false; + if (!packet.getUInt16(mFormat)) return false; + if (!packet.getUInt16(mProtectionStatus)) return false; + if (!packet.getUInt32(mCompressedSize)) return false; + if (!packet.getUInt16(mThumbFormat)) return false; + if (!packet.getUInt32(mThumbCompressedSize)) return false; + if (!packet.getUInt32(mThumbPixWidth)) return false; + if (!packet.getUInt32(mThumbPixHeight)) return false; + if (!packet.getUInt32(mImagePixWidth)) return false; + if (!packet.getUInt32(mImagePixHeight)) return false; + if (!packet.getUInt32(mImagePixDepth)) return false; + if (!packet.getUInt32(mParent)) return false; + if (!packet.getUInt16(mAssociationType)) return false; + if (!packet.getUInt32(mAssociationDesc)) return false; + if (!packet.getUInt32(mSequenceNumber)) return false; + + if (!packet.getString(string)) return false; + mName = strdup((const char *)string); + if (!mName) return false; + + if (!packet.getString(string)) return false; + if (parseDateTime((const char*)string, time)) + mDateCreated = time; + + if (!packet.getString(string)) return false; + if (parseDateTime((const char*)string, time)) + mDateModified = time; + + if (!packet.getString(string)) return false; + mKeywords = strdup((const char *)string); + if (!mKeywords) return false; + + return true; +} + +void MtpObjectInfo::print() { + MTPD("MtpObject Info %08X: %s\n", mHandle, mName); + MTPD(" mStorageID: %08X mFormat: %04X mProtectionStatus: %d\n", + mStorageID, mFormat, mProtectionStatus); + MTPD(" mCompressedSize: %d mThumbFormat: %04X mThumbCompressedSize: %d\n", + mCompressedSize, mFormat, mThumbCompressedSize); + MTPD(" mThumbPixWidth: %d mThumbPixHeight: %d\n", mThumbPixWidth, mThumbPixHeight); + MTPD(" mImagePixWidth: %d mImagePixHeight: %d mImagePixDepth: %d\n", + mImagePixWidth, mImagePixHeight, mImagePixDepth); + MTPD(" mParent: %08X mAssociationType: %04X mAssociationDesc: %04X\n", + mParent, mAssociationType, mAssociationDesc); + MTPD(" mSequenceNumber: %d mDateCreated: %ld mDateModified: %ld mKeywords: %s\n", + mSequenceNumber, mDateCreated, mDateModified, mKeywords); +} diff --git a/mtp/ffs/MtpObjectInfo.h b/mtp/ffs/MtpObjectInfo.h new file mode 100644 index 000000000..74e371913 --- /dev/null +++ b/mtp/ffs/MtpObjectInfo.h @@ -0,0 +1,56 @@ +/* + * 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. + */ + +#ifndef _MTP_OBJECT_INFO_H +#define _MTP_OBJECT_INFO_H + +#include "MtpTypes.h" + +class MtpDataPacket; + +class MtpObjectInfo { +public: + MtpObjectHandle mHandle; + MtpStorageID mStorageID; + MtpObjectFormat mFormat; + uint16_t mProtectionStatus; + uint32_t mCompressedSize; + MtpObjectFormat mThumbFormat; + uint32_t mThumbCompressedSize; + uint32_t mThumbPixWidth; + uint32_t mThumbPixHeight; + uint32_t mImagePixWidth; + uint32_t mImagePixHeight; + uint32_t mImagePixDepth; + MtpObjectHandle mParent; + uint16_t mAssociationType; + uint32_t mAssociationDesc; + uint32_t mSequenceNumber; + char* mName; + time_t mDateCreated; + time_t mDateModified; + char* mKeywords; + +public: + explicit MtpObjectInfo(MtpObjectHandle handle); + virtual ~MtpObjectInfo(); + + bool read(MtpDataPacket& packet); + + void print(); +}; + +#endif // _MTP_OBJECT_INFO_H diff --git a/mtp/ffs/MtpPacket.cpp b/mtp/ffs/MtpPacket.cpp new file mode 100644 index 000000000..9d6f875c6 --- /dev/null +++ b/mtp/ffs/MtpPacket.cpp @@ -0,0 +1,162 @@ +/* + * 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 "MtpPacket" + +#include "MtpDebug.h" +#include "MtpPacket.h" +#include "mtp.h" + +#include <stdio.h> +#include <stdlib.h> +#include <stdio.h> + +#include <usbhost/usbhost.h> + +MtpPacket::MtpPacket(int bufferSize) + : mBuffer(NULL), + mBufferSize(bufferSize), + mAllocationIncrement(bufferSize), + mPacketSize(0) +{ + mBuffer = (uint8_t *)malloc(bufferSize); + if (!mBuffer) { + MTPE("out of memory!"); + abort(); + } +} + +MtpPacket::~MtpPacket() { + if (mBuffer) + free(mBuffer); +} + +void MtpPacket::reset() { + allocate(MTP_CONTAINER_HEADER_SIZE); + mPacketSize = MTP_CONTAINER_HEADER_SIZE; + memset(mBuffer, 0, mBufferSize); +} + +void MtpPacket::allocate(size_t length) { + if (length > mBufferSize) { + int newLength = length + mAllocationIncrement; + mBuffer = (uint8_t *)realloc(mBuffer, newLength); + if (!mBuffer) { + MTPE("out of memory!"); + abort(); + } + mBufferSize = newLength; + } +} + +void MtpPacket::dump() { +#define DUMP_BYTES_PER_ROW 16 + char buffer[500]; + char* bufptr = buffer; + + for (size_t i = 0; i < mPacketSize; i++) { + bufptr += snprintf(bufptr, sizeof(buffer) - (bufptr - buffer), "%02X ", + mBuffer[i]); + if (i % DUMP_BYTES_PER_ROW == (DUMP_BYTES_PER_ROW - 1)) { + ALOGV("%s", buffer); + bufptr = buffer; + } + } + if (bufptr != buffer) { + // print last line + ALOGV("%s", buffer); + } + ALOGV("\n"); +} + +void MtpPacket::copyFrom(const MtpPacket& src) { + int length = src.mPacketSize; + allocate(length); + mPacketSize = length; + memcpy(mBuffer, src.mBuffer, length); +} + +uint16_t MtpPacket::getUInt16(int offset) const { + return ((uint16_t)mBuffer[offset + 1] << 8) | (uint16_t)mBuffer[offset]; +} + +uint32_t MtpPacket::getUInt32(int offset) const { + return ((uint32_t)mBuffer[offset + 3] << 24) | ((uint32_t)mBuffer[offset + 2] << 16) | + ((uint32_t)mBuffer[offset + 1] << 8) | (uint32_t)mBuffer[offset]; +} + +void MtpPacket::putUInt16(int offset, uint16_t value) { + mBuffer[offset++] = (uint8_t)(value & 0xFF); + mBuffer[offset++] = (uint8_t)((value >> 8) & 0xFF); +} + +void MtpPacket::putUInt32(int offset, uint32_t value) { + mBuffer[offset++] = (uint8_t)(value & 0xFF); + mBuffer[offset++] = (uint8_t)((value >> 8) & 0xFF); + mBuffer[offset++] = (uint8_t)((value >> 16) & 0xFF); + mBuffer[offset++] = (uint8_t)((value >> 24) & 0xFF); +} + +uint16_t MtpPacket::getContainerCode() const { + return getUInt16(MTP_CONTAINER_CODE_OFFSET); +} + +void MtpPacket::setContainerCode(uint16_t code) { + putUInt16(MTP_CONTAINER_CODE_OFFSET, code); +} + +uint16_t MtpPacket::getContainerType() const { + return getUInt16(MTP_CONTAINER_TYPE_OFFSET); +} + +MtpTransactionID MtpPacket::getTransactionID() const { + return getUInt32(MTP_CONTAINER_TRANSACTION_ID_OFFSET); +} + +void MtpPacket::setTransactionID(MtpTransactionID id) { + putUInt32(MTP_CONTAINER_TRANSACTION_ID_OFFSET, id); +} + +uint32_t MtpPacket::getParameter(int index) const { + if (index < 1 || index > 5) { + MTPE("index %d out of range in MtpPacket::getParameter", index); + return 0; + } + return getUInt32(MTP_CONTAINER_PARAMETER_OFFSET + (index - 1) * sizeof(uint32_t)); +} + +void MtpPacket::setParameter(int index, uint32_t value) { + if (index < 1 || index > 5) { + MTPE("index %d out of range in MtpPacket::setParameter", index); + return; + } + int offset = MTP_CONTAINER_PARAMETER_OFFSET + (index - 1) * sizeof(uint32_t); + if (mPacketSize < offset + sizeof(uint32_t)) + mPacketSize = offset + sizeof(uint32_t); + putUInt32(offset, value); +} + +#ifdef MTP_HOST +int MtpPacket::transfer(struct usb_request* request) { + int result = usb_device_bulk_transfer(request->dev, + request->endpoint, + request->buffer, + request->buffer_length, + 0); + request->actual_length = result; + return result; +} +#endif diff --git a/mtp/ffs/MtpPacket.h b/mtp/ffs/MtpPacket.h new file mode 100644 index 000000000..b7570917f --- /dev/null +++ b/mtp/ffs/MtpPacket.h @@ -0,0 +1,74 @@ +/* + * 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. + */ + +#ifndef _MTP_PACKET_H +#define _MTP_PACKET_H + +#include <android-base/macros.h> + +#include "MtpDebug.h" +#include "MtpTypes.h" + +struct usb_device; +struct usb_request; + +class MtpPacket { + +protected: + uint8_t* mBuffer; + // current size of the buffer + size_t mBufferSize; + // number of bytes to add when resizing the buffer + size_t mAllocationIncrement; + // size of the data in the packet + size_t mPacketSize; + +public: + explicit MtpPacket(int bufferSize); + virtual ~MtpPacket(); + + // sets packet size to the default container size and sets buffer to zero + virtual void reset(); + + void allocate(size_t length); + void dump(); + void copyFrom(const MtpPacket& src); + + uint16_t getContainerCode() const; + void setContainerCode(uint16_t code); + + uint16_t getContainerType() const; + + MtpTransactionID getTransactionID() const; + void setTransactionID(MtpTransactionID id); + + uint32_t getParameter(int index) const; + void setParameter(int index, uint32_t value); + +#ifdef MTP_HOST + int transfer(struct usb_request* request); +#endif + +protected: + uint16_t getUInt16(int offset) const; + uint32_t getUInt32(int offset) const; + void putUInt16(int offset, uint16_t value); + void putUInt32(int offset, uint32_t value); + + DISALLOW_COPY_AND_ASSIGN(MtpPacket); +}; + +#endif // _MTP_PACKET_H diff --git a/mtp/ffs/MtpProperty.cpp b/mtp/ffs/MtpProperty.cpp new file mode 100644 index 000000000..126cb7905 --- /dev/null +++ b/mtp/ffs/MtpProperty.cpp @@ -0,0 +1,570 @@ +/* + * 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 "MtpProperty" + +#include <inttypes.h> +#include <cutils/compiler.h> +#include <iomanip> +#include <sstream> +#include <string> + +#include "MtpDataPacket.h" +#include "MtpDebug.h" +#include "MtpProperty.h" +#include "MtpStringBuffer.h" +#include "MtpUtils.h" + +MtpProperty::MtpProperty() + : mCode(0), + mType(0), + mWriteable(false), + mDefaultArrayLength(0), + mDefaultArrayValues(NULL), + mCurrentArrayLength(0), + mCurrentArrayValues(NULL), + mGroupCode(0), + mFormFlag(kFormNone), + mEnumLength(0), + mEnumValues(NULL) +{ + memset(&mDefaultValue, 0, sizeof(mDefaultValue)); + memset(&mCurrentValue, 0, sizeof(mCurrentValue)); + memset(&mMinimumValue, 0, sizeof(mMinimumValue)); + memset(&mMaximumValue, 0, sizeof(mMaximumValue)); +} + +MtpProperty::MtpProperty(MtpPropertyCode propCode, + MtpDataType type, + bool writeable, + int defaultValue) + : mCode(propCode), + mType(type), + mWriteable(writeable), + mDefaultArrayLength(0), + mDefaultArrayValues(NULL), + mCurrentArrayLength(0), + mCurrentArrayValues(NULL), + mGroupCode(0), + mFormFlag(kFormNone), + mEnumLength(0), + mEnumValues(NULL) +{ + memset(&mDefaultValue, 0, sizeof(mDefaultValue)); + memset(&mCurrentValue, 0, sizeof(mCurrentValue)); + memset(&mMinimumValue, 0, sizeof(mMinimumValue)); + memset(&mMaximumValue, 0, sizeof(mMaximumValue)); + + if (defaultValue) { + switch (type) { + case MTP_TYPE_INT8: + mDefaultValue.u.i8 = defaultValue; + break; + case MTP_TYPE_UINT8: + mDefaultValue.u.u8 = defaultValue; + break; + case MTP_TYPE_INT16: + mDefaultValue.u.i16 = defaultValue; + break; + case MTP_TYPE_UINT16: + mDefaultValue.u.u16 = defaultValue; + break; + case MTP_TYPE_INT32: + mDefaultValue.u.i32 = defaultValue; + break; + case MTP_TYPE_UINT32: + mDefaultValue.u.u32 = defaultValue; + break; + case MTP_TYPE_INT64: + mDefaultValue.u.i64 = defaultValue; + break; + case MTP_TYPE_UINT64: + mDefaultValue.u.u64 = defaultValue; + break; + default: + MTPE("unknown type %04X in MtpProperty::MtpProperty", type); + } + } +} + +MtpProperty::~MtpProperty() { + if (mType == MTP_TYPE_STR) { + // free all strings + free(mDefaultValue.str); + free(mCurrentValue.str); + free(mMinimumValue.str); + free(mMaximumValue.str); + if (mDefaultArrayValues) { + for (uint32_t i = 0; i < mDefaultArrayLength; i++) + free(mDefaultArrayValues[i].str); + } + if (mCurrentArrayValues) { + for (uint32_t i = 0; i < mCurrentArrayLength; i++) + free(mCurrentArrayValues[i].str); + } + if (mEnumValues) { + for (uint16_t i = 0; i < mEnumLength; i++) + free(mEnumValues[i].str); + } + } + delete[] mDefaultArrayValues; + delete[] mCurrentArrayValues; + delete[] mEnumValues; +} + +bool MtpProperty::read(MtpDataPacket& packet) { + uint8_t temp8; + + if (!packet.getUInt16(mCode)) return false; + bool deviceProp = isDeviceProperty(); + if (!packet.getUInt16(mType)) return false; + if (!packet.getUInt8(temp8)) return false; + mWriteable = (temp8 == 1); + switch (mType) { + case MTP_TYPE_AINT8: + case MTP_TYPE_AUINT8: + case MTP_TYPE_AINT16: + case MTP_TYPE_AUINT16: + case MTP_TYPE_AINT32: + case MTP_TYPE_AUINT32: + case MTP_TYPE_AINT64: + case MTP_TYPE_AUINT64: + case MTP_TYPE_AINT128: + case MTP_TYPE_AUINT128: + mDefaultArrayValues = readArrayValues(packet, mDefaultArrayLength); + if (!mDefaultArrayValues) return false; + if (deviceProp) { + mCurrentArrayValues = readArrayValues(packet, mCurrentArrayLength); + if (!mCurrentArrayValues) return false; + } + break; + default: + if (!readValue(packet, mDefaultValue)) return false; + if (deviceProp) { + if (!readValue(packet, mCurrentValue)) return false; + } + } + if (!deviceProp) { + if (!packet.getUInt32(mGroupCode)) return false; + } + if (!packet.getUInt8(mFormFlag)) return false; + + if (mFormFlag == kFormRange) { + if (!readValue(packet, mMinimumValue)) return false; + if (!readValue(packet, mMaximumValue)) return false; + if (!readValue(packet, mStepSize)) return false; + } else if (mFormFlag == kFormEnum) { + if (!packet.getUInt16(mEnumLength)) return false; + mEnumValues = new MtpPropertyValue[mEnumLength]; + for (int i = 0; i < mEnumLength; i++) { + if (!readValue(packet, mEnumValues[i])) return false; + } + } + + return true; +} + +void MtpProperty::write(MtpDataPacket& packet) { + bool deviceProp = isDeviceProperty(); + + packet.putUInt16(mCode); + packet.putUInt16(mType); + packet.putUInt8(mWriteable ? 1 : 0); + + switch (mType) { + case MTP_TYPE_AINT8: + case MTP_TYPE_AUINT8: + case MTP_TYPE_AINT16: + case MTP_TYPE_AUINT16: + case MTP_TYPE_AINT32: + case MTP_TYPE_AUINT32: + case MTP_TYPE_AINT64: + case MTP_TYPE_AUINT64: + case MTP_TYPE_AINT128: + case MTP_TYPE_AUINT128: + writeArrayValues(packet, mDefaultArrayValues, mDefaultArrayLength); + if (deviceProp) + writeArrayValues(packet, mCurrentArrayValues, mCurrentArrayLength); + break; + default: + writeValue(packet, mDefaultValue); + if (deviceProp) + writeValue(packet, mCurrentValue); + } + if (!deviceProp) + packet.putUInt32(mGroupCode); + packet.putUInt8(mFormFlag); + if (mFormFlag == kFormRange) { + writeValue(packet, mMinimumValue); + writeValue(packet, mMaximumValue); + writeValue(packet, mStepSize); + } else if (mFormFlag == kFormEnum) { + packet.putUInt16(mEnumLength); + for (int i = 0; i < mEnumLength; i++) + writeValue(packet, mEnumValues[i]); + } +} + +void MtpProperty::setDefaultValue(const uint16_t* string) { + free(mDefaultValue.str); + if (string) { + MtpStringBuffer buffer(string); + mDefaultValue.str = strdup(buffer); + } + else + mDefaultValue.str = NULL; +} + +void MtpProperty::setCurrentValue(const uint16_t* string) { + free(mCurrentValue.str); + if (string) { + MtpStringBuffer buffer(string); + mCurrentValue.str = strdup(buffer); + } + else + mCurrentValue.str = NULL; +} + +void MtpProperty::setCurrentValue(MtpDataPacket& packet) { + free(mCurrentValue.str); + mCurrentValue.str = NULL; + readValue(packet, mCurrentValue); +} + +void MtpProperty::setFormRange(int min, int max, int step) { + mFormFlag = kFormRange; + switch (mType) { + case MTP_TYPE_INT8: + mMinimumValue.u.i8 = min; + mMaximumValue.u.i8 = max; + mStepSize.u.i8 = step; + break; + case MTP_TYPE_UINT8: + mMinimumValue.u.u8 = min; + mMaximumValue.u.u8 = max; + mStepSize.u.u8 = step; + break; + case MTP_TYPE_INT16: + mMinimumValue.u.i16 = min; + mMaximumValue.u.i16 = max; + mStepSize.u.i16 = step; + break; + case MTP_TYPE_UINT16: + mMinimumValue.u.u16 = min; + mMaximumValue.u.u16 = max; + mStepSize.u.u16 = step; + break; + case MTP_TYPE_INT32: + mMinimumValue.u.i32 = min; + mMaximumValue.u.i32 = max; + mStepSize.u.i32 = step; + break; + case MTP_TYPE_UINT32: + mMinimumValue.u.u32 = min; + mMaximumValue.u.u32 = max; + mStepSize.u.u32 = step; + break; + case MTP_TYPE_INT64: + mMinimumValue.u.i64 = min; + mMaximumValue.u.i64 = max; + mStepSize.u.i64 = step; + break; + case MTP_TYPE_UINT64: + mMinimumValue.u.u64 = min; + mMaximumValue.u.u64 = max; + mStepSize.u.u64 = step; + break; + default: + MTPE("unsupported type for MtpProperty::setRange"); + break; + } +} + +void MtpProperty::setFormEnum(const int* values, int count) { + mFormFlag = kFormEnum; + delete[] mEnumValues; + mEnumValues = new MtpPropertyValue[count]; + mEnumLength = count; + + for (int i = 0; i < count; i++) { + int value = *values++; + switch (mType) { + case MTP_TYPE_INT8: + mEnumValues[i].u.i8 = value; + break; + case MTP_TYPE_UINT8: + mEnumValues[i].u.u8 = value; + break; + case MTP_TYPE_INT16: + mEnumValues[i].u.i16 = value; + break; + case MTP_TYPE_UINT16: + mEnumValues[i].u.u16 = value; + break; + case MTP_TYPE_INT32: + mEnumValues[i].u.i32 = value; + break; + case MTP_TYPE_UINT32: + mEnumValues[i].u.u32 = value; + break; + case MTP_TYPE_INT64: + mEnumValues[i].u.i64 = value; + break; + case MTP_TYPE_UINT64: + mEnumValues[i].u.u64 = value; + break; + default: + MTPE("unsupported type for MtpProperty::setEnum"); + break; + } + } +} + +void MtpProperty::setFormDateTime() { + mFormFlag = kFormDateTime; +} + +void MtpProperty::print() { + std::string buffer; + bool deviceProp = isDeviceProperty(); + if (deviceProp) + MTPD(" %s (%04X)", MtpDebug::getDevicePropCodeName(mCode), mCode); + else + MTPD(" %s (%04X)", MtpDebug::getObjectPropCodeName(mCode), mCode); + MTPD(" type %04X", mType); + MTPD(" writeable %s", (mWriteable ? "true" : "false")); + buffer = " default value: "; + print(mDefaultValue, buffer); + MTPD("%s", buffer.c_str()); + if (deviceProp) { + buffer = " current value: "; + print(mCurrentValue, buffer); + MTPD("%s", buffer.c_str()); + } + switch (mFormFlag) { + case kFormNone: + break; + case kFormRange: + buffer = " Range ("; + print(mMinimumValue, buffer); + buffer += ", "; + print(mMaximumValue, buffer); + buffer += ", "; + print(mStepSize, buffer); + buffer += ")"; + MTPD("%s", buffer.c_str()); + break; + case kFormEnum: + buffer = " Enum { "; + for (int i = 0; i < mEnumLength; i++) { + print(mEnumValues[i], buffer); + buffer += " "; + } + buffer += "}"; + MTPD("%s", buffer.c_str()); + break; + case kFormDateTime: + MTPD(" DateTime\n"); + break; + default: + MTPD(" form %d\n", mFormFlag); + break; + } +} + +void MtpProperty::print(MtpPropertyValue& value, std::string& buffer) { + std::ostringstream s; + switch (mType) { + case MTP_TYPE_INT8: + buffer += std::to_string(value.u.i8); + break; + case MTP_TYPE_UINT8: + buffer += std::to_string(value.u.u8); + break; + case MTP_TYPE_INT16: + buffer += std::to_string(value.u.i16); + break; + case MTP_TYPE_UINT16: + buffer += std::to_string(value.u.u16); + break; + case MTP_TYPE_INT32: + buffer += std::to_string(value.u.i32); + break; + case MTP_TYPE_UINT32: + buffer += std::to_string(value.u.u32); + break; + case MTP_TYPE_INT64: + buffer += std::to_string(value.u.i64); + break; + case MTP_TYPE_UINT64: + buffer += std::to_string(value.u.u64); + break; + case MTP_TYPE_INT128: + for (auto i : value.u.i128) { + s << std::hex << std::setfill('0') << std::uppercase << i; + } + buffer += s.str(); + break; + case MTP_TYPE_UINT128: + for (auto i : value.u.u128) { + s << std::hex << std::setfill('0') << std::uppercase << i; + } + buffer += s.str(); + break; + case MTP_TYPE_STR: + buffer += value.str; + break; + default: + MTPE("unsupported type for MtpProperty::print\n"); + break; + } +} + +bool MtpProperty::readValue(MtpDataPacket& packet, MtpPropertyValue& value) { + MtpStringBuffer stringBuffer; + + switch (mType) { + case MTP_TYPE_INT8: + case MTP_TYPE_AINT8: + if (!packet.getInt8(value.u.i8)) return false; + break; + case MTP_TYPE_UINT8: + case MTP_TYPE_AUINT8: + if (!packet.getUInt8(value.u.u8)) return false; + break; + case MTP_TYPE_INT16: + case MTP_TYPE_AINT16: + if (!packet.getInt16(value.u.i16)) return false; + break; + case MTP_TYPE_UINT16: + case MTP_TYPE_AUINT16: + if (!packet.getUInt16(value.u.u16)) return false; + break; + case MTP_TYPE_INT32: + case MTP_TYPE_AINT32: + if (!packet.getInt32(value.u.i32)) return false; + break; + case MTP_TYPE_UINT32: + case MTP_TYPE_AUINT32: + if (!packet.getUInt32(value.u.u32)) return false; + break; + case MTP_TYPE_INT64: + case MTP_TYPE_AINT64: + if (!packet.getInt64(value.u.i64)) return false; + break; + case MTP_TYPE_UINT64: + case MTP_TYPE_AUINT64: + if (!packet.getUInt64(value.u.u64)) return false; + break; + case MTP_TYPE_INT128: + case MTP_TYPE_AINT128: + if (!packet.getInt128(value.u.i128)) return false; + break; + case MTP_TYPE_UINT128: + case MTP_TYPE_AUINT128: + if (!packet.getUInt128(value.u.u128)) return false; + break; + case MTP_TYPE_STR: + if (!packet.getString(stringBuffer)) return false; + value.str = strdup(stringBuffer); + break; + default: + MTPE("unknown type %04X in MtpProperty::readValue", mType); + return false; + } + return true; +} + +void MtpProperty::writeValue(MtpDataPacket& packet, MtpPropertyValue& value) { + MtpStringBuffer stringBuffer; + + switch (mType) { + case MTP_TYPE_INT8: + case MTP_TYPE_AINT8: + packet.putInt8(value.u.i8); + break; + case MTP_TYPE_UINT8: + case MTP_TYPE_AUINT8: + packet.putUInt8(value.u.u8); + break; + case MTP_TYPE_INT16: + case MTP_TYPE_AINT16: + packet.putInt16(value.u.i16); + break; + case MTP_TYPE_UINT16: + case MTP_TYPE_AUINT16: + packet.putUInt16(value.u.u16); + break; + case MTP_TYPE_INT32: + case MTP_TYPE_AINT32: + packet.putInt32(value.u.i32); + break; + case MTP_TYPE_UINT32: + case MTP_TYPE_AUINT32: + packet.putUInt32(value.u.u32); + break; + case MTP_TYPE_INT64: + case MTP_TYPE_AINT64: + packet.putInt64(value.u.i64); + break; + case MTP_TYPE_UINT64: + case MTP_TYPE_AUINT64: + packet.putUInt64(value.u.u64); + break; + case MTP_TYPE_INT128: + case MTP_TYPE_AINT128: + packet.putInt128(value.u.i128); + break; + case MTP_TYPE_UINT128: + case MTP_TYPE_AUINT128: + packet.putUInt128(value.u.u128); + break; + case MTP_TYPE_STR: + if (value.str) + packet.putString(value.str); + else + packet.putEmptyString(); + break; + default: + MTPE("unknown type %04X in MtpProperty::writeValue", mType); + } +} + +MtpPropertyValue* MtpProperty::readArrayValues(MtpDataPacket& packet, uint32_t& length) { + if (!packet.getUInt32(length)) return NULL; + + // Fail if resulting array is over 2GB. This is because the maximum array + // size may be less than SIZE_MAX on some platforms. + if ( CC_UNLIKELY( + length == 0 || + length >= INT32_MAX / sizeof(MtpPropertyValue)) ) { + length = 0; + return NULL; + } + MtpPropertyValue* result = new MtpPropertyValue[length]; + for (uint32_t i = 0; i < length; i++) + if (!readValue(packet, result[i])) { + delete [] result; + return NULL; + } + return result; +} + +void MtpProperty::writeArrayValues(MtpDataPacket& packet, MtpPropertyValue* values, uint32_t length) { + packet.putUInt32(length); + for (uint32_t i = 0; i < length; i++) + writeValue(packet, values[i]); +} diff --git a/mtp/ffs/MtpProperty.h b/mtp/ffs/MtpProperty.h new file mode 100644 index 000000000..43ec7c3f6 --- /dev/null +++ b/mtp/ffs/MtpProperty.h @@ -0,0 +1,115 @@ +/* + * 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. + */ + +#ifndef _MTP_PROPERTY_H +#define _MTP_PROPERTY_H + +#include "MtpTypes.h" + +#include <string> + +class MtpDataPacket; + +struct MtpPropertyValue { + union { + int8_t i8; + uint8_t u8; + int16_t i16; + uint16_t u16; + int32_t i32; + uint32_t u32; + int64_t i64; + uint64_t u64; + int128_t i128; + uint128_t u128; + } u; + // string in UTF8 format + char* str; +}; + +class MtpProperty { +public: + MtpPropertyCode mCode; + MtpDataType mType; + bool mWriteable; + MtpPropertyValue mDefaultValue; + MtpPropertyValue mCurrentValue; + + // for array types + uint32_t mDefaultArrayLength; + MtpPropertyValue* mDefaultArrayValues; + uint32_t mCurrentArrayLength; + MtpPropertyValue* mCurrentArrayValues; + + enum { + kFormNone = 0, + kFormRange = 1, + kFormEnum = 2, + kFormDateTime = 3, + }; + + uint32_t mGroupCode; + uint8_t mFormFlag; + + // for range form + MtpPropertyValue mMinimumValue; + MtpPropertyValue mMaximumValue; + MtpPropertyValue mStepSize; + + // for enum form + uint16_t mEnumLength; + MtpPropertyValue* mEnumValues; + +public: + MtpProperty(); + MtpProperty(MtpPropertyCode propCode, + MtpDataType type, + bool writeable = false, + int defaultValue = 0); + virtual ~MtpProperty(); + + MtpPropertyCode getPropertyCode() const { return mCode; } + MtpDataType getDataType() const { return mType; } + + bool read(MtpDataPacket& packet); + void write(MtpDataPacket& packet); + + void setDefaultValue(const uint16_t* string); + void setCurrentValue(const uint16_t* string); + void setCurrentValue(MtpDataPacket& packet); + const MtpPropertyValue& getCurrentValue() { return mCurrentValue; } + + void setFormRange(int min, int max, int step); + void setFormEnum(const int* values, int count); + void setFormDateTime(); + + void print(); + + inline bool isDeviceProperty() const { + return ( ((mCode & 0xF000) == 0x5000) + || ((mCode & 0xF800) == 0xD000)); + } + +private: + bool readValue(MtpDataPacket& packet, MtpPropertyValue& value); + void writeValue(MtpDataPacket& packet, MtpPropertyValue& value); + MtpPropertyValue* readArrayValues(MtpDataPacket& packet, uint32_t& length); + void writeArrayValues(MtpDataPacket& packet, + MtpPropertyValue* values, uint32_t length); + void print(MtpPropertyValue& value, std::string& buffer); +}; + +#endif // _MTP_PROPERTY_H diff --git a/mtp/ffs/MtpRequestPacket.cpp b/mtp/ffs/MtpRequestPacket.cpp new file mode 100644 index 000000000..8ef1f3c3d --- /dev/null +++ b/mtp/ffs/MtpRequestPacket.cpp @@ -0,0 +1,71 @@ +/* + * 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 "MtpRequestPacket" + +#include <stdio.h> +#include <sys/types.h> +#include <fcntl.h> + +#include "IMtpHandle.h" +#include "MtpDebug.h" +#include "MtpRequestPacket.h" + +#include <usbhost/usbhost.h> + +MtpRequestPacket::MtpRequestPacket() + : MtpPacket(512), + mParameterCount(0) +{ +} + +MtpRequestPacket::~MtpRequestPacket() { +} + +#ifdef MTP_DEVICE +int MtpRequestPacket::read(IMtpHandle *h) { + int ret = h->read(mBuffer, mBufferSize); + if (ret < 0) { + // file read error + return ret; + } + + // request packet should have 12 byte header followed by 0 to 5 32-bit arguments + const size_t read_size = static_cast<size_t>(ret); + if (read_size >= MTP_CONTAINER_HEADER_SIZE + && read_size <= MTP_CONTAINER_HEADER_SIZE + 5 * sizeof(uint32_t) + && ((read_size - MTP_CONTAINER_HEADER_SIZE) & 3) == 0) { + mPacketSize = read_size; + mParameterCount = (read_size - MTP_CONTAINER_HEADER_SIZE) / sizeof(uint32_t); + } else { + MTPE("Malformed MTP request packet"); + ret = -1; + } + return ret; +} +#endif + +#ifdef MTP_HOST + // write our buffer to the given endpoint (host mode) +int MtpRequestPacket::write(struct usb_request *request) +{ + putUInt32(MTP_CONTAINER_LENGTH_OFFSET, mPacketSize); + putUInt16(MTP_CONTAINER_TYPE_OFFSET, MTP_CONTAINER_TYPE_COMMAND); + request->buffer = mBuffer; + request->buffer_length = mPacketSize; + return transfer(request); +} +#endif diff --git a/mtp/ffs/MtpRequestPacket.h b/mtp/ffs/MtpRequestPacket.h new file mode 100644 index 000000000..f05335b21 --- /dev/null +++ b/mtp/ffs/MtpRequestPacket.h @@ -0,0 +1,51 @@ +/* + * 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. + */ + +#ifndef _MTP_REQUEST_PACKET_H +#define _MTP_REQUEST_PACKET_H + +#include "MtpPacket.h" +#include "mtp.h" + +class IMtpHandle; +struct usb_request; + +class MtpRequestPacket : public MtpPacket { + +public: + MtpRequestPacket(); + virtual ~MtpRequestPacket(); + +#ifdef MTP_DEVICE + // fill our buffer with data from the given usb handle + int read(IMtpHandle *h); +#endif + +#ifdef MTP_HOST + // write our buffer to the given endpoint + int write(struct usb_request *request); +#endif + + inline MtpOperationCode getOperationCode() const { return getContainerCode(); } + inline void setOperationCode(MtpOperationCode code) + { return setContainerCode(code); } + inline int getParameterCount() const { return mParameterCount; } + +private: + int mParameterCount; +}; + +#endif // _MTP_REQUEST_PACKET_H diff --git a/mtp/ffs/MtpResponsePacket.cpp b/mtp/ffs/MtpResponsePacket.cpp new file mode 100644 index 000000000..641a4fc8d --- /dev/null +++ b/mtp/ffs/MtpResponsePacket.cpp @@ -0,0 +1,57 @@ +/* + * 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 "MtpResponsePacket" + +#include <stdio.h> +#include <sys/types.h> +#include <fcntl.h> + +#include "IMtpHandle.h" +#include "MtpResponsePacket.h" + +#include <usbhost/usbhost.h> + +MtpResponsePacket::MtpResponsePacket() + : MtpPacket(512) +{ +} + +MtpResponsePacket::~MtpResponsePacket() { +} + +#ifdef MTP_DEVICE +int MtpResponsePacket::write(IMtpHandle *h) { + putUInt32(MTP_CONTAINER_LENGTH_OFFSET, mPacketSize); + putUInt16(MTP_CONTAINER_TYPE_OFFSET, MTP_CONTAINER_TYPE_RESPONSE); + int ret = h->write(mBuffer, mPacketSize); + return (ret < 0 ? ret : 0); +} +#endif + +#ifdef MTP_HOST +int MtpResponsePacket::read(struct usb_request *request) { + request->buffer = mBuffer; + request->buffer_length = mBufferSize; + int ret = transfer(request); + if (ret >= 0) + mPacketSize = ret; + else + mPacketSize = 0; + return ret; +} +#endif + diff --git a/mtp/ffs/MtpResponsePacket.h b/mtp/ffs/MtpResponsePacket.h new file mode 100644 index 000000000..4bde1cac9 --- /dev/null +++ b/mtp/ffs/MtpResponsePacket.h @@ -0,0 +1,46 @@ +/* + * 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. + */ + +#ifndef _MTP_RESPONSE_PACKET_H +#define _MTP_RESPONSE_PACKET_H + +#include "MtpPacket.h" +#include "mtp.h" + +class IMtpHandle; + +class MtpResponsePacket : public MtpPacket { + +public: + MtpResponsePacket(); + virtual ~MtpResponsePacket(); + +#ifdef MTP_DEVICE + // write our data to the given usb handle + int write(IMtpHandle *h); +#endif + +#ifdef MTP_HOST + // read our buffer with the given request + int read(struct usb_request *request); +#endif + + inline MtpResponseCode getResponseCode() const { return getContainerCode(); } + inline void setResponseCode(MtpResponseCode code) + { return setContainerCode(code); } +}; + +#endif // _MTP_RESPONSE_PACKET_H diff --git a/mtp/ffs/MtpServer.cpp b/mtp/ffs/MtpServer.cpp new file mode 100755 index 000000000..5f17ff2ff --- /dev/null +++ b/mtp/ffs/MtpServer.cpp @@ -0,0 +1,1459 @@ +/* + * 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. + */ + +#include <algorithm> +#include <android-base/logging.h> +#include <android-base/properties.h> +#include <chrono> +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <inttypes.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/stat.h> +#include <sys/time.h> + +#define LOG_TAG "MtpServer" + +#include "MtpDebug.h" +#include "mtp_MtpDatabase.hpp" +#include "MtpDescriptors.h" +#include "MtpDevHandle.h" +#include "MtpFfsCompatHandle.h" +#include "MtpFfsHandle.h" +#include "MtpObjectInfo.h" +#include "MtpProperty.h" +#include "MtpServer.h" +#include "MtpStorage.h" +#include "MtpStringBuffer.h" + +static const MtpOperationCode kSupportedOperationCodes[] = { + MTP_OPERATION_GET_DEVICE_INFO, + MTP_OPERATION_OPEN_SESSION, + MTP_OPERATION_CLOSE_SESSION, + MTP_OPERATION_GET_STORAGE_IDS, + MTP_OPERATION_GET_STORAGE_INFO, + MTP_OPERATION_GET_NUM_OBJECTS, + MTP_OPERATION_GET_OBJECT_HANDLES, + MTP_OPERATION_GET_OBJECT_INFO, + MTP_OPERATION_GET_OBJECT, + MTP_OPERATION_GET_THUMB, + MTP_OPERATION_DELETE_OBJECT, + MTP_OPERATION_SEND_OBJECT_INFO, + MTP_OPERATION_SEND_OBJECT, +// MTP_OPERATION_INITIATE_CAPTURE, +// MTP_OPERATION_FORMAT_STORE, + MTP_OPERATION_RESET_DEVICE, +// MTP_OPERATION_SELF_TEST, +// MTP_OPERATION_SET_OBJECT_PROTECTION, +// MTP_OPERATION_POWER_DOWN, + MTP_OPERATION_GET_DEVICE_PROP_DESC, + MTP_OPERATION_GET_DEVICE_PROP_VALUE, + MTP_OPERATION_SET_DEVICE_PROP_VALUE, + MTP_OPERATION_RESET_DEVICE_PROP_VALUE, +// MTP_OPERATION_TERMINATE_OPEN_CAPTURE, + MTP_OPERATION_MOVE_OBJECT, + MTP_OPERATION_COPY_OBJECT, + MTP_OPERATION_GET_PARTIAL_OBJECT, +// MTP_OPERATION_INITIATE_OPEN_CAPTURE, + MTP_OPERATION_GET_OBJECT_PROPS_SUPPORTED, + MTP_OPERATION_GET_OBJECT_PROP_DESC, + MTP_OPERATION_GET_OBJECT_PROP_VALUE, + MTP_OPERATION_SET_OBJECT_PROP_VALUE, + MTP_OPERATION_GET_OBJECT_PROP_LIST, +// MTP_OPERATION_SET_OBJECT_PROP_LIST, +// MTP_OPERATION_GET_INTERDEPENDENT_PROP_DESC, +// MTP_OPERATION_SEND_OBJECT_PROP_LIST, + MTP_OPERATION_GET_OBJECT_REFERENCES, + MTP_OPERATION_SET_OBJECT_REFERENCES, +// MTP_OPERATION_SKIP, + // Android extension for direct file IO + MTP_OPERATION_GET_PARTIAL_OBJECT_64, + MTP_OPERATION_SEND_PARTIAL_OBJECT, + MTP_OPERATION_TRUNCATE_OBJECT, + MTP_OPERATION_BEGIN_EDIT_OBJECT, + MTP_OPERATION_END_EDIT_OBJECT, +}; + +static const MtpEventCode kSupportedEventCodes[] = { + MTP_EVENT_OBJECT_ADDED, + MTP_EVENT_OBJECT_REMOVED, + MTP_EVENT_STORE_ADDED, + MTP_EVENT_STORE_REMOVED, + MTP_EVENT_DEVICE_PROP_CHANGED, +}; + +MtpServer::MtpServer(IMtpDatabase* database, int controlFd, bool ptp, + const char *deviceInfoManufacturer, + const char *deviceInfoModel, + const char *deviceInfoDeviceVersion, + const char *deviceInfoSerialNumber) + : mDatabase(database), + mPtp(ptp), + mDeviceInfoManufacturer(deviceInfoManufacturer), + mDeviceInfoModel(deviceInfoModel), + mDeviceInfoDeviceVersion(deviceInfoDeviceVersion), + mDeviceInfoSerialNumber(deviceInfoSerialNumber), + mSessionID(0), + mSessionOpen(false), + mSendObjectHandle(kInvalidObjectHandle), + mSendObjectFormat(0), + mSendObjectFileSize(0), + mSendObjectModifiedTime(0) +{ + bool ffs_ok = access(FFS_MTP_EP0, W_OK) == 0; + if (ffs_ok) { + bool aio_compat = android::base::GetBoolProperty("sys.usb.ffs.aio_compat", false); + mHandle = aio_compat ? new MtpFfsCompatHandle(controlFd) : new MtpFfsHandle(controlFd); + mHandle->writeDescriptors(mPtp); + } else { + mHandle = new MtpDevHandle(); + } +} + +MtpServer::~MtpServer() { +} + +void MtpServer::addStorage(MtpStorage* storage) { + std::lock_guard<std::mutex> lg(mMutex); + mDatabase->createDB(storage, storage->getStorageID()); + mStorages.push_back(storage); + sendStoreAdded(storage->getStorageID()); +} + +void MtpServer::removeStorage(MtpStorage* storage) { + std::lock_guard<std::mutex> lg(mMutex); + auto iter = std::find(mStorages.begin(), mStorages.end(), storage); + if (iter != mStorages.end()) { + sendStoreRemoved(storage->getStorageID()); + mStorages.erase(iter); + } +} + +MtpStorage* MtpServer::getStorage(MtpStorageID id) { + if (id == 0) + return mStorages[0]; + for (MtpStorage *storage : mStorages) { + if (storage->getStorageID() == id) + return storage; + } + return nullptr; +} + +bool MtpServer::hasStorage(MtpStorageID id) { + if (id == 0 || id == 0xFFFFFFFF) + return mStorages.size() > 0; + return (getStorage(id) != nullptr); +} + +void MtpServer::run() { + if (mHandle->start(mPtp)) { + MTPE("Failed to start usb driver!"); + mHandle->close(); + return; + } + + while (1) { + int ret = mRequest.read(mHandle); + if (ret < 0) { + MTPE("request read returned %d, errno: %d", ret, errno); + if (errno == ECANCELED) { + // return to top of loop and wait for next command + continue; + } + break; + } + MtpOperationCode operation = mRequest.getOperationCode(); + MtpTransactionID transaction = mRequest.getTransactionID(); + + MTPD("operation: %s\n", MtpDebug::getOperationCodeName(operation)); + // FIXME need to generalize this + bool dataIn = (operation == MTP_OPERATION_SEND_OBJECT_INFO + || operation == MTP_OPERATION_SET_OBJECT_REFERENCES + || operation == MTP_OPERATION_SET_OBJECT_PROP_VALUE + || operation == MTP_OPERATION_SET_DEVICE_PROP_VALUE); + if (dataIn) { + int ret = mData.read(mHandle); + if (ret < 0) { + MTPE("data read returned %d, errno: %d", ret, errno); + if (errno == ECANCELED) { + // return to top of loop and wait for next command + continue; + } + break; + } + MTPD("received data:"); + } else { + mData.reset(); + } + + if (handleRequest()) { + if (!dataIn && mData.hasData()) { + mData.setOperationCode(operation); + mData.setTransactionID(transaction); + MTPD("sending data:"); + ret = mData.write(mHandle); + if (ret < 0) { + MTPE("request write returned %d, errno: %d", ret, errno); + if (errno == ECANCELED) { + // return to top of loop and wait for next command + continue; + } + break; + } + } + + mResponse.setTransactionID(transaction); + MTPD("sending response %04X", mResponse.getResponseCode()); + ret = mResponse.write(mHandle); + const int savedErrno = errno; + if (ret < 0) { + MTPE("request write returned %d, errno: %d", ret, errno); + if (savedErrno == ECANCELED) { + // return to top of loop and wait for next command + continue; + } + break; + } + } else { + MTPD("skipping response\n"); + } + } + + // commit any open edits + int count = mObjectEditList.size(); + for (int i = 0; i < count; i++) { + ObjectEdit* edit = mObjectEditList[i]; + commitEdit(edit); + delete edit; + } + mObjectEditList.clear(); + + mHandle->close(); +} + +void MtpServer::sendObjectAdded(MtpObjectHandle handle) { + MTPD("MtpServer::sendObjectAdded %d\n", handle); + sendEvent(MTP_EVENT_OBJECT_ADDED, handle); +} + +void MtpServer::sendObjectRemoved(MtpObjectHandle handle) { + MTPD("MtpServer::sendObjectRemoved %d\n", handle); + sendEvent(MTP_EVENT_OBJECT_REMOVED, handle); +} + +void MtpServer::sendStoreRemoved(MtpStorageID id) { + MTPD("MtpServer::sendStoreRemoved %08X\n", id); + sendEvent(MTP_EVENT_STORE_REMOVED, id); +} + +void MtpServer::sendStoreAdded(MtpStorageID id) { + MTPD("MtpServer::sendStoreAdded %08X\n", id); + sendEvent(MTP_EVENT_STORE_ADDED, id); +} + +void MtpServer::sendObjectUpdated(MtpObjectHandle handle) { + MTPD("MtpServer::sendObjectUpdated %d\n", handle); + sendEvent(MTP_EVENT_OBJECT_PROP_CHANGED, handle); +} + +void MtpServer::sendDevicePropertyChanged(MtpDeviceProperty property) { + MTPD("MtpServer::sendDevicePropertyChanged %d\n", property); + sendEvent(MTP_EVENT_DEVICE_PROP_CHANGED, property); +} + +void MtpServer::sendObjectInfoChanged(MtpObjectHandle handle) { + MTPD("MtpServer::sendObjectInfoChanged %d\n", handle); + sendEvent(MTP_EVENT_OBJECT_INFO_CHANGED, handle); +} + +void MtpServer::sendEvent(MtpEventCode code, uint32_t param1) { + if (mSessionOpen) { + mEvent.setEventCode(code); + mEvent.setTransactionID(mRequest.getTransactionID()); + mEvent.setParameter(1, param1); + if (mEvent.write(mHandle)) + MTPE("Mtp send event failed: %s\n", strerror(errno)); + } +} + +void MtpServer::addEditObject(MtpObjectHandle handle, MtpStringBuffer& path, + uint64_t size, MtpObjectFormat format, int fd) { + ObjectEdit* edit = new ObjectEdit(handle, path, size, format, fd); + mObjectEditList.push_back(edit); +} + +MtpServer::ObjectEdit* MtpServer::getEditObject(MtpObjectHandle handle) { + int count = mObjectEditList.size(); + for (int i = 0; i < count; i++) { + ObjectEdit* edit = mObjectEditList[i]; + if (edit->mHandle == handle) return edit; + } + return nullptr; +} + +void MtpServer::removeEditObject(MtpObjectHandle handle) { + int count = mObjectEditList.size(); + for (int i = 0; i < count; i++) { + ObjectEdit* edit = mObjectEditList[i]; + if (edit->mHandle == handle) { + delete edit; + mObjectEditList.erase(mObjectEditList.begin() + i); + return; + } + } + MTPE("ObjectEdit not found in removeEditObject"); +} + +void MtpServer::commitEdit(ObjectEdit* edit) { + mDatabase->rescanFile((const char *)edit->mPath, edit->mHandle, edit->mFormat); +} + + +bool MtpServer::handleRequest() { + std::lock_guard<std::mutex> lg(mMutex); + + MtpOperationCode operation = mRequest.getOperationCode(); + MtpResponseCode response; + + mResponse.reset(); + + if (mSendObjectHandle != kInvalidObjectHandle && operation != MTP_OPERATION_SEND_OBJECT) { + mSendObjectHandle = kInvalidObjectHandle; + mSendObjectFormat = 0; + mSendObjectModifiedTime = 0; + } + + int containertype = mRequest.getContainerType(); + if (containertype != MTP_CONTAINER_TYPE_COMMAND) { + MTPE("wrong container type %d", containertype); + return false; + } + + MTPD("got command %s (%x)\n", MtpDebug::getOperationCodeName(operation), operation); + + switch (operation) { + case MTP_OPERATION_GET_DEVICE_INFO: + response = doGetDeviceInfo(); + break; + case MTP_OPERATION_OPEN_SESSION: + response = doOpenSession(); + break; + case MTP_OPERATION_RESET_DEVICE: + case MTP_OPERATION_CLOSE_SESSION: + response = doCloseSession(); + break; + case MTP_OPERATION_GET_STORAGE_IDS: + response = doGetStorageIDs(); + break; + case MTP_OPERATION_GET_STORAGE_INFO: + response = doGetStorageInfo(); + break; + case MTP_OPERATION_GET_OBJECT_PROPS_SUPPORTED: + response = doGetObjectPropsSupported(); + break; + case MTP_OPERATION_GET_OBJECT_HANDLES: + response = doGetObjectHandles(); + break; + case MTP_OPERATION_GET_NUM_OBJECTS: + response = doGetNumObjects(); + break; + case MTP_OPERATION_GET_OBJECT_REFERENCES: + response = doGetObjectReferences(); + break; + case MTP_OPERATION_SET_OBJECT_REFERENCES: + response = doSetObjectReferences(); + break; + case MTP_OPERATION_GET_OBJECT_PROP_VALUE: + response = doGetObjectPropValue(); + break; + case MTP_OPERATION_SET_OBJECT_PROP_VALUE: + response = doSetObjectPropValue(); + break; + case MTP_OPERATION_GET_DEVICE_PROP_VALUE: + response = doGetDevicePropValue(); + break; + case MTP_OPERATION_SET_DEVICE_PROP_VALUE: + response = doSetDevicePropValue(); + break; + case MTP_OPERATION_RESET_DEVICE_PROP_VALUE: + response = doResetDevicePropValue(); + break; + case MTP_OPERATION_GET_OBJECT_PROP_LIST: + response = doGetObjectPropList(); + break; + case MTP_OPERATION_GET_OBJECT_INFO: + response = doGetObjectInfo(); + break; + case MTP_OPERATION_GET_OBJECT: + response = doGetObject(); + break; + case MTP_OPERATION_GET_THUMB: + response = doGetThumb(); + break; + case MTP_OPERATION_GET_PARTIAL_OBJECT: + case MTP_OPERATION_GET_PARTIAL_OBJECT_64: + response = doGetPartialObject(operation); + break; + case MTP_OPERATION_SEND_OBJECT_INFO: + response = doSendObjectInfo(); + break; + case MTP_OPERATION_SEND_OBJECT: + response = doSendObject(); + break; + case MTP_OPERATION_DELETE_OBJECT: + response = doDeleteObject(); + break; + case MTP_OPERATION_COPY_OBJECT: + response = doCopyObject(); + break; + case MTP_OPERATION_MOVE_OBJECT: + response = doMoveObject(); + break; + case MTP_OPERATION_GET_OBJECT_PROP_DESC: + response = doGetObjectPropDesc(); + break; + case MTP_OPERATION_GET_DEVICE_PROP_DESC: + response = doGetDevicePropDesc(); + break; + case MTP_OPERATION_SEND_PARTIAL_OBJECT: + response = doSendPartialObject(); + break; + case MTP_OPERATION_TRUNCATE_OBJECT: + response = doTruncateObject(); + break; + case MTP_OPERATION_BEGIN_EDIT_OBJECT: + response = doBeginEditObject(); + break; + case MTP_OPERATION_END_EDIT_OBJECT: + response = doEndEditObject(); + break; + default: + MTPE("got unsupported command %s (%x)", + MtpDebug::getOperationCodeName(operation), operation); + response = MTP_RESPONSE_OPERATION_NOT_SUPPORTED; + break; + } + + if (response == MTP_RESPONSE_TRANSACTION_CANCELLED) + return false; + mResponse.setResponseCode(response); + return true; +} + +MtpResponseCode MtpServer::doGetDeviceInfo() { + MtpStringBuffer string; + + MtpObjectFormatList* playbackFormats = mDatabase->getSupportedPlaybackFormats(); + MtpObjectFormatList* captureFormats = mDatabase->getSupportedCaptureFormats(); + MtpDevicePropertyList* deviceProperties = mDatabase->getSupportedDeviceProperties(); + + // fill in device info + mData.putUInt16(MTP_STANDARD_VERSION); + if (mPtp) { + mData.putUInt32(0); + } else { + // MTP Vendor Extension ID + mData.putUInt32(6); + } + mData.putUInt16(MTP_STANDARD_VERSION); + if (mPtp) { + // no extensions + string.set(""); + } else { + // MTP extensions + string.set("microsoft.com: 1.0; android.com: 1.0;"); + } + mData.putString(string); // MTP Extensions + mData.putUInt16(0); //Functional Mode + mData.putAUInt16(kSupportedOperationCodes, + sizeof(kSupportedOperationCodes) / sizeof(uint16_t)); // Operations Supported + mData.putAUInt16(kSupportedEventCodes, + sizeof(kSupportedEventCodes) / sizeof(uint16_t)); // Events Supported + mData.putAUInt16(deviceProperties); // Device Properties Supported + mData.putAUInt16(captureFormats); // Capture Formats + mData.putAUInt16(playbackFormats); // Playback Formats + + mData.putString(mDeviceInfoManufacturer); // Manufacturer + mData.putString(mDeviceInfoModel); // Model + mData.putString(mDeviceInfoDeviceVersion); // Device Version + mData.putString(mDeviceInfoSerialNumber); // Serial Number + + delete playbackFormats; + delete captureFormats; + delete deviceProperties; + + return MTP_RESPONSE_OK; +} + +MtpResponseCode MtpServer::doOpenSession() { + if (mSessionOpen) { + mResponse.setParameter(1, mSessionID); + return MTP_RESPONSE_SESSION_ALREADY_OPEN; + } + if (mRequest.getParameterCount() < 1) + return MTP_RESPONSE_INVALID_PARAMETER; + + mSessionID = mRequest.getParameter(1); + mSessionOpen = true; + + return MTP_RESPONSE_OK; +} + +MtpResponseCode MtpServer::doCloseSession() { + if (!mSessionOpen) + return MTP_RESPONSE_SESSION_NOT_OPEN; + mSessionID = 0; + mSessionOpen = false; + return MTP_RESPONSE_OK; +} + +MtpResponseCode MtpServer::doGetStorageIDs() { + if (!mSessionOpen) + return MTP_RESPONSE_SESSION_NOT_OPEN; + + int count = mStorages.size(); + mData.putUInt32(count); + for (int i = 0; i < count; i++) + mData.putUInt32(mStorages[i]->getStorageID()); + + return MTP_RESPONSE_OK; +} + +MtpResponseCode MtpServer::doGetStorageInfo() { + MtpStringBuffer string; + + if (!mSessionOpen) + return MTP_RESPONSE_SESSION_NOT_OPEN; + if (mRequest.getParameterCount() < 1) + return MTP_RESPONSE_INVALID_PARAMETER; + + MtpStorageID id = mRequest.getParameter(1); + MtpStorage* storage = getStorage(id); + if (!storage) + return MTP_RESPONSE_INVALID_STORAGE_ID; + + mData.putUInt16(storage->getType()); + mData.putUInt16(storage->getFileSystemType()); + mData.putUInt16(storage->getAccessCapability()); + mData.putUInt64(storage->getMaxCapacity()); + mData.putUInt64(storage->getFreeSpace()); + mData.putUInt32(1024*1024*1024); // Free Space in Objects + string.set(storage->getDescription()); + mData.putString(string); + mData.putEmptyString(); // Volume Identifier + + return MTP_RESPONSE_OK; +} + +MtpResponseCode MtpServer::doGetObjectPropsSupported() { + if (!mSessionOpen) + return MTP_RESPONSE_SESSION_NOT_OPEN; + if (mRequest.getParameterCount() < 1) + return MTP_RESPONSE_INVALID_PARAMETER; + MtpObjectFormat format = mRequest.getParameter(1); + MtpObjectPropertyList* properties = mDatabase->getSupportedObjectProperties(format); + mData.putAUInt16(properties); + delete properties; + return MTP_RESPONSE_OK; +} + +MtpResponseCode MtpServer::doGetObjectHandles() { + if (!mSessionOpen) + return MTP_RESPONSE_SESSION_NOT_OPEN; + if (mRequest.getParameterCount() < 3) + return MTP_RESPONSE_INVALID_PARAMETER; + MtpStorageID storageID = mRequest.getParameter(1); // 0xFFFFFFFF for all storage + MtpObjectFormat format = mRequest.getParameter(2); // 0 for all formats + MtpObjectHandle parent = mRequest.getParameter(3); // 0xFFFFFFFF for objects with no parent + // 0x00000000 for all objects + + if (!hasStorage(storageID)) + return MTP_RESPONSE_INVALID_STORAGE_ID; + + MtpObjectHandleList* handles = mDatabase->getObjectList(storageID, format, parent); + if (handles == NULL) + return MTP_RESPONSE_INVALID_OBJECT_HANDLE; + mData.putAUInt32(handles); + delete handles; + return MTP_RESPONSE_OK; +} + +MtpResponseCode MtpServer::doGetNumObjects() { + if (!mSessionOpen) + return MTP_RESPONSE_SESSION_NOT_OPEN; + if (mRequest.getParameterCount() < 3) + return MTP_RESPONSE_INVALID_PARAMETER; + MtpStorageID storageID = mRequest.getParameter(1); // 0xFFFFFFFF for all storage + MtpObjectFormat format = mRequest.getParameter(2); // 0 for all formats + MtpObjectHandle parent = mRequest.getParameter(3); // 0xFFFFFFFF for objects with no parent + // 0x00000000 for all objects + if (!hasStorage(storageID)) + return MTP_RESPONSE_INVALID_STORAGE_ID; + + int count = mDatabase->getNumObjects(storageID, format, parent); + if (count >= 0) { + mResponse.setParameter(1, count); + return MTP_RESPONSE_OK; + } else { + mResponse.setParameter(1, 0); + return MTP_RESPONSE_INVALID_OBJECT_HANDLE; + } +} + +MtpResponseCode MtpServer::doGetObjectReferences() { + if (!mSessionOpen) + return MTP_RESPONSE_SESSION_NOT_OPEN; + if (!hasStorage()) + return MTP_RESPONSE_INVALID_OBJECT_HANDLE; + if (mRequest.getParameterCount() < 1) + return MTP_RESPONSE_INVALID_PARAMETER; + MtpObjectHandle handle = mRequest.getParameter(1); + + // FIXME - check for invalid object handle + MtpObjectHandleList* handles = mDatabase->getObjectReferences(handle); + if (handles) { + mData.putAUInt32(handles); + delete handles; + } else { + mData.putEmptyArray(); + } + return MTP_RESPONSE_OK; +} + +MtpResponseCode MtpServer::doSetObjectReferences() { + if (!mSessionOpen) + return MTP_RESPONSE_SESSION_NOT_OPEN; + if (!hasStorage()) + return MTP_RESPONSE_INVALID_OBJECT_HANDLE; + if (mRequest.getParameterCount() < 1) + return MTP_RESPONSE_INVALID_PARAMETER; + MtpStorageID handle = mRequest.getParameter(1); + + MtpObjectHandleList* references = mData.getAUInt32(); + if (!references) + return MTP_RESPONSE_INVALID_PARAMETER; + MtpResponseCode result = mDatabase->setObjectReferences(handle, references); + delete references; + return result; +} + +MtpResponseCode MtpServer::doGetObjectPropValue() { + if (!hasStorage()) + return MTP_RESPONSE_INVALID_OBJECT_HANDLE; + if (mRequest.getParameterCount() < 2) + return MTP_RESPONSE_INVALID_PARAMETER; + MtpObjectHandle handle = mRequest.getParameter(1); + MtpObjectProperty property = mRequest.getParameter(2); + MTPD("GetObjectPropValue %d %s\n", handle, + MtpDebug::getObjectPropCodeName(property)); + + return mDatabase->getObjectPropertyValue(handle, property, mData); +} + +MtpResponseCode MtpServer::doSetObjectPropValue() { + if (!hasStorage()) + return MTP_RESPONSE_INVALID_OBJECT_HANDLE; + if (mRequest.getParameterCount() < 2) + return MTP_RESPONSE_INVALID_PARAMETER; + MtpObjectHandle handle = mRequest.getParameter(1); + MtpObjectProperty property = mRequest.getParameter(2); + MTPD("SetObjectPropValue %d %s\n", handle, + MtpDebug::getObjectPropCodeName(property)); + + return mDatabase->setObjectPropertyValue(handle, property, mData); +} + +MtpResponseCode MtpServer::doGetDevicePropValue() { + if (mRequest.getParameterCount() < 1) + return MTP_RESPONSE_INVALID_PARAMETER; + MtpDeviceProperty property = mRequest.getParameter(1); + MTPD("GetDevicePropValue %s\n", + MtpDebug::getDevicePropCodeName(property)); + + return mDatabase->getDevicePropertyValue(property, mData); +} + +MtpResponseCode MtpServer::doSetDevicePropValue() { + if (mRequest.getParameterCount() < 1) + return MTP_RESPONSE_INVALID_PARAMETER; + MtpDeviceProperty property = mRequest.getParameter(1); + MTPD("SetDevicePropValue %s\n", + MtpDebug::getDevicePropCodeName(property)); + + return mDatabase->setDevicePropertyValue(property, mData); +} + +MtpResponseCode MtpServer::doResetDevicePropValue() { + if (mRequest.getParameterCount() < 1) + return MTP_RESPONSE_INVALID_PARAMETER; + MtpDeviceProperty property = mRequest.getParameter(1); + MTPD("ResetDevicePropValue %s\n", + MtpDebug::getDevicePropCodeName(property)); + + return mDatabase->resetDeviceProperty(property); +} + +MtpResponseCode MtpServer::doGetObjectPropList() { + if (!hasStorage()) + return MTP_RESPONSE_INVALID_OBJECT_HANDLE; + if (mRequest.getParameterCount() < 5) + return MTP_RESPONSE_INVALID_PARAMETER; + + MtpObjectHandle handle = mRequest.getParameter(1); + // use uint32_t so we can support 0xFFFFFFFF + uint32_t format = mRequest.getParameter(2); + uint32_t property = mRequest.getParameter(3); + int groupCode = mRequest.getParameter(4); + int depth = mRequest.getParameter(5); + MTPD("GetObjectPropList %d format: %s property: %s group: %d depth: %d\n", + handle, MtpDebug::getFormatCodeName(format), + MtpDebug::getObjectPropCodeName(property), groupCode, depth); + + return mDatabase->getObjectPropertyList(handle, format, property, groupCode, depth, mData); +} + +MtpResponseCode MtpServer::doGetObjectInfo() { + if (!hasStorage()) + return MTP_RESPONSE_INVALID_OBJECT_HANDLE; + if (mRequest.getParameterCount() < 1) + return MTP_RESPONSE_INVALID_PARAMETER; + MtpObjectHandle handle = mRequest.getParameter(1); + MtpObjectInfo info(handle); + MtpResponseCode result = mDatabase->getObjectInfo(handle, info); + if (result == MTP_RESPONSE_OK) { + char date[20]; + + mData.putUInt32(info.mStorageID); + mData.putUInt16(info.mFormat); + mData.putUInt16(info.mProtectionStatus); + + // if object is being edited the database size may be out of date + uint32_t size = info.mCompressedSize; + ObjectEdit* edit = getEditObject(handle); + if (edit) + size = (edit->mSize > 0xFFFFFFFFLL ? 0xFFFFFFFF : (uint32_t)edit->mSize); + mData.putUInt32(size); + + mData.putUInt16(info.mThumbFormat); + mData.putUInt32(info.mThumbCompressedSize); + mData.putUInt32(info.mThumbPixWidth); + mData.putUInt32(info.mThumbPixHeight); + mData.putUInt32(info.mImagePixWidth); + mData.putUInt32(info.mImagePixHeight); + mData.putUInt32(info.mImagePixDepth); + mData.putUInt32(info.mParent); + mData.putUInt16(info.mAssociationType); + mData.putUInt32(info.mAssociationDesc); + mData.putUInt32(info.mSequenceNumber); + mData.putString(info.mName); + formatDateTime(info.mDateCreated, date, sizeof(date)); + mData.putString(date); // date created + formatDateTime(info.mDateModified, date, sizeof(date)); + mData.putString(date); // date modified + mData.putEmptyString(); // keywords + } + return result; +} + +MtpResponseCode MtpServer::doGetObject() { + if (!hasStorage()) + return MTP_RESPONSE_INVALID_OBJECT_HANDLE; + if (mRequest.getParameterCount() < 1) + return MTP_RESPONSE_INVALID_PARAMETER; + MtpObjectHandle handle = mRequest.getParameter(1); + MtpStringBuffer pathBuf; + int64_t fileLength; + MtpObjectFormat format; + int result = mDatabase->getObjectFilePath(handle, pathBuf, fileLength, format); + if (result != MTP_RESPONSE_OK) + return result; + + auto start = std::chrono::steady_clock::now(); + + const char* filePath = (const char *)pathBuf; + mtp_file_range mfr; + mfr.fd = open(filePath, O_RDONLY); + if (mfr.fd < 0) { + return MTP_RESPONSE_GENERAL_ERROR; + } + mfr.offset = 0; + mfr.length = fileLength; + mfr.command = mRequest.getOperationCode(); + mfr.transaction_id = mRequest.getTransactionID(); + + // then transfer the file + int ret = mHandle->sendFile(mfr); + if (ret < 0) { + MTPE("Mtp send file got error %s", strerror(errno)); + if (errno == ECANCELED) { + result = MTP_RESPONSE_TRANSACTION_CANCELLED; + } else { + result = MTP_RESPONSE_GENERAL_ERROR; + } + } else { + result = MTP_RESPONSE_OK; + } + + auto end = std::chrono::steady_clock::now(); + std::chrono::duration<double> diff = end - start; + struct stat sstat; + fstat(mfr.fd, &sstat); + uint64_t finalsize = sstat.st_size; + MTPD("Sent a file over MTP. Time: %f s, Size: %" PRIu64 ", Rate: %f bytes/s", + diff.count(), finalsize, ((double) finalsize) / diff.count()); + closeObjFd(mfr.fd, filePath); + return result; +} + +MtpResponseCode MtpServer::doGetThumb() { + if (mRequest.getParameterCount() < 1) + return MTP_RESPONSE_INVALID_PARAMETER; + MtpObjectHandle handle = mRequest.getParameter(1); + size_t thumbSize; + void* thumb = mDatabase->getThumbnail(handle, thumbSize); + if (thumb) { + // send data + mData.setOperationCode(mRequest.getOperationCode()); + mData.setTransactionID(mRequest.getTransactionID()); + mData.writeData(mHandle, thumb, thumbSize); + free(thumb); + return MTP_RESPONSE_OK; + } else { + return MTP_RESPONSE_GENERAL_ERROR; + } +} + +MtpResponseCode MtpServer::doGetPartialObject(MtpOperationCode operation) { + if (!hasStorage()) + return MTP_RESPONSE_INVALID_OBJECT_HANDLE; + MtpObjectHandle handle = mRequest.getParameter(1); + uint64_t offset; + uint32_t length; + offset = mRequest.getParameter(2); + if (operation == MTP_OPERATION_GET_PARTIAL_OBJECT_64) { + // MTP_OPERATION_GET_PARTIAL_OBJECT_64 takes 4 arguments + if (mRequest.getParameterCount() < 4) + return MTP_RESPONSE_INVALID_PARAMETER; + + // android extension with 64 bit offset + uint64_t offset2 = mRequest.getParameter(3); + offset = offset | (offset2 << 32); + length = mRequest.getParameter(4); + } else { + // MTP_OPERATION_GET_PARTIAL_OBJECT takes 3 arguments + if (mRequest.getParameterCount() < 3) + return MTP_RESPONSE_INVALID_PARAMETER; + + // standard GetPartialObject + length = mRequest.getParameter(3); + } + MtpStringBuffer pathBuf; + int64_t fileLength; + MtpObjectFormat format; + int result = mDatabase->getObjectFilePath(handle, pathBuf, fileLength, format); + if (result != MTP_RESPONSE_OK) + return result; + if (offset + length > (uint64_t)fileLength) + length = fileLength - offset; + + const char* filePath = (const char *)pathBuf; + MTPD("sending partial %s\n %" PRIu64 " %" PRIu32, filePath, offset, length); + mtp_file_range mfr; + mfr.fd = open(filePath, O_RDONLY); + if (mfr.fd < 0) { + return MTP_RESPONSE_GENERAL_ERROR; + } + mfr.offset = offset; + mfr.length = length; + mfr.command = mRequest.getOperationCode(); + mfr.transaction_id = mRequest.getTransactionID(); + mResponse.setParameter(1, length); + + // transfer the file + int ret = mHandle->sendFile(mfr); + MTPD("MTP_SEND_FILE_WITH_HEADER returned %d\n", ret); + result = MTP_RESPONSE_OK; + if (ret < 0) { + if (errno == ECANCELED) + result = MTP_RESPONSE_TRANSACTION_CANCELLED; + else + result = MTP_RESPONSE_GENERAL_ERROR; + } + closeObjFd(mfr.fd, filePath); + return result; +} + +MtpResponseCode MtpServer::doSendObjectInfo() { + MtpStringBuffer path; + uint16_t temp16; + uint32_t temp32; + + if (mRequest.getParameterCount() < 2) + return MTP_RESPONSE_INVALID_PARAMETER; + MtpStorageID storageID = mRequest.getParameter(1); + MtpStorage* storage = getStorage(storageID); + MtpObjectHandle parent = mRequest.getParameter(2); + if (!storage) + return MTP_RESPONSE_INVALID_STORAGE_ID; + + // special case the root + if (parent == MTP_PARENT_ROOT) { + path.set(storage->getPath()); + parent = 0; + } else { + int64_t length; + MtpObjectFormat format; + int result = mDatabase->getObjectFilePath(parent, path, length, format); + if (result != MTP_RESPONSE_OK) + return result; + if (format != MTP_FORMAT_ASSOCIATION) + return MTP_RESPONSE_INVALID_PARENT_OBJECT; + } + + // read only the fields we need + if (!mData.getUInt32(temp32)) return MTP_RESPONSE_INVALID_PARAMETER; // storage ID + if (!mData.getUInt16(temp16)) return MTP_RESPONSE_INVALID_PARAMETER; + MtpObjectFormat format = temp16; + if (!mData.getUInt16(temp16)) return MTP_RESPONSE_INVALID_PARAMETER; // protection status + if (!mData.getUInt32(temp32)) return MTP_RESPONSE_INVALID_PARAMETER; + mSendObjectFileSize = temp32; + if (!mData.getUInt16(temp16)) return MTP_RESPONSE_INVALID_PARAMETER; // thumb format + if (!mData.getUInt32(temp32)) return MTP_RESPONSE_INVALID_PARAMETER; // thumb compressed size + if (!mData.getUInt32(temp32)) return MTP_RESPONSE_INVALID_PARAMETER; // thumb pix width + if (!mData.getUInt32(temp32)) return MTP_RESPONSE_INVALID_PARAMETER; // thumb pix height + if (!mData.getUInt32(temp32)) return MTP_RESPONSE_INVALID_PARAMETER; // image pix width + if (!mData.getUInt32(temp32)) return MTP_RESPONSE_INVALID_PARAMETER; // image pix height + if (!mData.getUInt32(temp32)) return MTP_RESPONSE_INVALID_PARAMETER; // image bit depth + if (!mData.getUInt32(temp32)) return MTP_RESPONSE_INVALID_PARAMETER; // parent + if (!mData.getUInt16(temp16)) return MTP_RESPONSE_INVALID_PARAMETER; + if (!mData.getUInt32(temp32)) return MTP_RESPONSE_INVALID_PARAMETER; + if (!mData.getUInt32(temp32)) return MTP_RESPONSE_INVALID_PARAMETER; // sequence number + MtpStringBuffer name, created, modified; + if (!mData.getString(name)) return MTP_RESPONSE_INVALID_PARAMETER; // file name + if (name.isEmpty()) { + MTPE("empty name"); + return MTP_RESPONSE_INVALID_PARAMETER; + } + if (!mData.getString(created)) return MTP_RESPONSE_INVALID_PARAMETER; // date created + if (!mData.getString(modified)) return MTP_RESPONSE_INVALID_PARAMETER; // date modified + // keywords follow + + MTPD("name: %s format: %04X\n", (const char *)name, format); + time_t modifiedTime; + if (!parseDateTime(modified, modifiedTime)) + modifiedTime = 0; + + if (path[path.size() - 1] != '/') + path.append("/"); + path.append(name); + + // check space first + if (mSendObjectFileSize > storage->getFreeSpace()) + return MTP_RESPONSE_STORAGE_FULL; + uint64_t maxFileSize = storage->getMaxFileSize(); + // check storage max file size + if (maxFileSize != 0) { + // if mSendObjectFileSize is 0xFFFFFFFF, then all we know is the file size + // is >= 0xFFFFFFFF + if (mSendObjectFileSize > maxFileSize || mSendObjectFileSize == 0xFFFFFFFF) + return MTP_RESPONSE_OBJECT_TOO_LARGE; + } + + MTPD("path: %s parent: %d storageID: %08X", (const char*)path, parent, storageID); + uint64_t size = 0; // TODO: this needs to be implemented + time_t modified_time = 0; // TODO: this needs to be implemented + MtpObjectHandle handle = mDatabase->beginSendObject((const char*)path, format, + parent, storageID, size, modified_time); + if (handle == kInvalidObjectHandle) { + return MTP_RESPONSE_GENERAL_ERROR; + } + + if (format == MTP_FORMAT_ASSOCIATION) { + int ret = makeFolder((const char *)path); + if (ret) + return MTP_RESPONSE_GENERAL_ERROR; + + // SendObject does not get sent for directories, so call endSendObject here instead + mDatabase->endSendObject((const char*)path, handle, format, MTP_RESPONSE_OK); + } + mSendObjectFilePath = path; + // save the handle for the SendObject call, which should follow + mSendObjectHandle = handle; + mSendObjectFormat = format; + mSendObjectModifiedTime = modifiedTime; + + mResponse.setParameter(1, storageID); + mResponse.setParameter(2, parent); + mResponse.setParameter(3, handle); + + return MTP_RESPONSE_OK; +} + +MtpResponseCode MtpServer::doMoveObject() { + if (!hasStorage()) + return MTP_RESPONSE_GENERAL_ERROR; + if (mRequest.getParameterCount() < 3) + return MTP_RESPONSE_INVALID_PARAMETER; + MtpObjectHandle objectHandle = mRequest.getParameter(1); + MtpStorageID storageID = mRequest.getParameter(2); + MtpStorage* storage = getStorage(storageID); + MtpObjectHandle parent = mRequest.getParameter(3); + if (!storage) + return MTP_RESPONSE_INVALID_STORAGE_ID; + MtpStringBuffer path; + MtpResponseCode result; + + MtpStringBuffer fromPath; + int64_t fileLength; + MtpObjectFormat format; + MtpObjectInfo info(objectHandle); + result = mDatabase->getObjectInfo(objectHandle, info); + if (result != MTP_RESPONSE_OK) + return result; + result = mDatabase->getObjectFilePath(objectHandle, fromPath, fileLength, format); + if (result != MTP_RESPONSE_OK) + return result; + + // special case the root + if (parent == 0) { + path.set(storage->getPath()); + } else { + int64_t parentLength; + MtpObjectFormat parentFormat; + result = mDatabase->getObjectFilePath(parent, path, parentLength, parentFormat); + if (result != MTP_RESPONSE_OK) + return result; + if (parentFormat != MTP_FORMAT_ASSOCIATION) + return MTP_RESPONSE_INVALID_PARENT_OBJECT; + } + + if (path[path.size() - 1] != '/') + path.append("/"); + path.append(info.mName); + + result = mDatabase->beginMoveObject(objectHandle, parent, storageID); + if (result != MTP_RESPONSE_OK) + return result; + + if (info.mStorageID == storageID) { + MTPD("Moving file from %s to %s", (const char*)fromPath, (const char*)path); + if (renameTo(fromPath, path)) { + PLOG(ERROR) << "rename() failed from " << fromPath << " to " << path; + result = MTP_RESPONSE_GENERAL_ERROR; + } + } else { + MTPD("Moving across storages from %s to %s", (const char*)fromPath, (const char*)path); + if (format == MTP_FORMAT_ASSOCIATION) { + int ret = makeFolder((const char *)path); + ret += copyRecursive(fromPath, path); + if (ret) { + result = MTP_RESPONSE_GENERAL_ERROR; + } else { + deletePath(fromPath); + } + } else { + if (copyFile(fromPath, path)) { + result = MTP_RESPONSE_GENERAL_ERROR; + } else { + deletePath(fromPath); + } + } + } + + // If the move failed, undo the database change + mDatabase->endMoveObject(info.mParent, parent, info.mStorageID, storageID, objectHandle, + result == MTP_RESPONSE_OK); + + return result; +} + +MtpResponseCode MtpServer::doCopyObject() { + if (!hasStorage()) + return MTP_RESPONSE_GENERAL_ERROR; + MtpResponseCode result = MTP_RESPONSE_OK; + if (mRequest.getParameterCount() < 3) + return MTP_RESPONSE_INVALID_PARAMETER; + MtpObjectHandle objectHandle = mRequest.getParameter(1); + MtpStorageID storageID = mRequest.getParameter(2); + MtpStorage* storage = getStorage(storageID); + MtpObjectHandle parent = mRequest.getParameter(3); + if (!storage) + return MTP_RESPONSE_INVALID_STORAGE_ID; + MtpStringBuffer path; + + MtpStringBuffer fromPath; + int64_t fileLength; + MtpObjectFormat format; + MtpObjectInfo info(objectHandle); + result = mDatabase->getObjectInfo(objectHandle, info); + if (result != MTP_RESPONSE_OK) + return result; + result = mDatabase->getObjectFilePath(objectHandle, fromPath, fileLength, format); + if (result != MTP_RESPONSE_OK) + return result; + + // special case the root + if (parent == 0) { + path.set(storage->getPath()); + } else { + int64_t parentLength; + MtpObjectFormat parentFormat; + result = mDatabase->getObjectFilePath(parent, path, parentLength, parentFormat); + if (result != MTP_RESPONSE_OK) + return result; + if (parentFormat != MTP_FORMAT_ASSOCIATION) + return MTP_RESPONSE_INVALID_PARENT_OBJECT; + } + + // check space first + if ((uint64_t) fileLength > storage->getFreeSpace()) + return MTP_RESPONSE_STORAGE_FULL; + + if (path[path.size() - 1] != '/') + path.append("/"); + path.append(info.mName); + + MtpObjectHandle handle = mDatabase->beginCopyObject(objectHandle, parent, storageID); + if (handle == kInvalidObjectHandle) { + return MTP_RESPONSE_GENERAL_ERROR; + } + + MTPD("Copying file from %s to %s", (const char*)fromPath, (const char*)path); + if (format == MTP_FORMAT_ASSOCIATION) { + int ret = makeFolder((const char *)path); + ret += copyRecursive(fromPath, path); + if (ret) { + result = MTP_RESPONSE_GENERAL_ERROR; + } + } else { + if (copyFile(fromPath, path)) { + result = MTP_RESPONSE_GENERAL_ERROR; + } + } + + mDatabase->endCopyObject(handle, result); + mResponse.setParameter(1, handle); + return result; +} + +MtpResponseCode MtpServer::doSendObject() { + if (!hasStorage()) + return MTP_RESPONSE_GENERAL_ERROR; + MtpResponseCode result = MTP_RESPONSE_OK; + mode_t mask; + int ret, initialData; + bool isCanceled = false; + struct stat sstat = {}; + + auto start = std::chrono::steady_clock::now(); + + if (mSendObjectHandle == kInvalidObjectHandle) { + MTPE("Expected SendObjectInfo before SendObject"); + result = MTP_RESPONSE_NO_VALID_OBJECT_INFO; + goto done; + } + + // read the header, and possibly some data + ret = mData.read(mHandle); + if (ret < MTP_CONTAINER_HEADER_SIZE) { + result = MTP_RESPONSE_GENERAL_ERROR; + goto done; + } + initialData = ret - MTP_CONTAINER_HEADER_SIZE; + + if (mSendObjectFormat == MTP_FORMAT_ASSOCIATION) { + if (initialData != 0) + MTPE("Expected folder size to be 0!"); + mSendObjectHandle = kInvalidObjectHandle; + mSendObjectFormat = 0; + mSendObjectModifiedTime = 0; + return result; + } + + mtp_file_range mfr; + mfr.fd = open(mSendObjectFilePath, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); + if (mfr.fd < 0) { + result = MTP_RESPONSE_GENERAL_ERROR; + goto done; + } + fchown(mfr.fd, getuid(), FILE_GROUP); + // set permissions + mask = umask(0); + fchmod(mfr.fd, FILE_PERM); + umask(mask); + + if (initialData > 0) { + ret = write(mfr.fd, mData.getData(), initialData); + } + + if (ret < 0) { + MTPE("failed to write initial data"); + result = MTP_RESPONSE_GENERAL_ERROR; + } else { + mfr.offset = initialData; + if (mSendObjectFileSize == 0xFFFFFFFF) { + // tell driver to read until it receives a short packet + mfr.length = 0xFFFFFFFF; + } else { + mfr.length = mSendObjectFileSize - initialData; + } + + mfr.command = 0; + mfr.transaction_id = 0; + + // transfer the file + ret = mHandle->receiveFile(mfr, mfr.length == 0 && + initialData == MTP_BUFFER_SIZE - MTP_CONTAINER_HEADER_SIZE); + if ((ret < 0) && (errno == ECANCELED)) { + isCanceled = true; + } + } + + if (mSendObjectModifiedTime) { + struct timespec newTime[2]; + newTime[0].tv_nsec = UTIME_NOW; + newTime[1].tv_sec = mSendObjectModifiedTime; + newTime[1].tv_nsec = 0; + if (futimens(mfr.fd, newTime) < 0) { + MTPE("changing modified time failed, %s", strerror(errno)); + } + } + + fstat(mfr.fd, &sstat); + closeObjFd(mfr.fd, mSendObjectFilePath); + + if (ret < 0) { + MTPE("Mtp receive file got error %s", strerror(errno)); + unlink(mSendObjectFilePath); + if (isCanceled) + result = MTP_RESPONSE_TRANSACTION_CANCELLED; + else + result = MTP_RESPONSE_GENERAL_ERROR; + } + +done: + // reset so we don't attempt to send the data back + mData.reset(); + + mDatabase->endSendObject(mSendObjectFilePath, mSendObjectHandle, mSendObjectFormat, result == MTP_RESPONSE_OK); + mSendObjectHandle = kInvalidObjectHandle; + mSendObjectFormat = 0; + mSendObjectModifiedTime = 0; + + auto end = std::chrono::steady_clock::now(); + std::chrono::duration<double> diff = end - start; + uint64_t finalsize = sstat.st_size; + MTPD("Got a file over MTP. Time: %fs, Size: %" PRIu64 ", Rate: %f bytes/s", + diff.count(), finalsize, ((double) finalsize) / diff.count()); + return result; +} + +MtpResponseCode MtpServer::doDeleteObject() { + MTPD("In MtpServer::doDeleteObject\n"); + if (!hasStorage()) + return MTP_RESPONSE_INVALID_OBJECT_HANDLE; + if (mRequest.getParameterCount() < 1) + return MTP_RESPONSE_INVALID_PARAMETER; + MtpObjectHandle handle = mRequest.getParameter(1); + MtpObjectFormat format; + // FIXME - support deleting all objects if handle is 0xFFFFFFFF + // FIXME - implement deleting objects by format + + MtpStringBuffer filePath; + int64_t fileLength; + int result = mDatabase->getObjectFilePath(handle, filePath, fileLength, format); + if (result != MTP_RESPONSE_OK) + return result; + + // Don't delete the actual files unless the database deletion is allowed + result = mDatabase->beginDeleteObject(handle); + if (result != MTP_RESPONSE_OK) + return result; + + bool success = deletePath((const char *)filePath); + + mDatabase->endDeleteObject(handle, success); + return success ? result : MTP_RESPONSE_PARTIAL_DELETION; +} + +MtpResponseCode MtpServer::doGetObjectPropDesc() { + if (mRequest.getParameterCount() < 2) + return MTP_RESPONSE_INVALID_PARAMETER; + MtpObjectProperty propCode = mRequest.getParameter(1); + MtpObjectFormat format = mRequest.getParameter(2); + MTPD("GetObjectPropDesc %s %s\n", MtpDebug::getObjectPropCodeName(propCode), + MtpDebug::getFormatCodeName(format)); + MtpProperty* property = mDatabase->getObjectPropertyDesc(propCode, format); + if (!property) + return MTP_RESPONSE_OBJECT_PROP_NOT_SUPPORTED; + property->write(mData); + delete property; + return MTP_RESPONSE_OK; +} + +MtpResponseCode MtpServer::doGetDevicePropDesc() { + if (mRequest.getParameterCount() < 1) + return MTP_RESPONSE_INVALID_PARAMETER; + MtpDeviceProperty propCode = mRequest.getParameter(1); + MTPD("GetDevicePropDesc %s\n", MtpDebug::getDevicePropCodeName(propCode)); + MtpProperty* property = mDatabase->getDevicePropertyDesc(propCode); + if (!property) + return MTP_RESPONSE_DEVICE_PROP_NOT_SUPPORTED; + property->write(mData); + delete property; + return MTP_RESPONSE_OK; +} + +MtpResponseCode MtpServer::doSendPartialObject() { + if (!hasStorage()) + return MTP_RESPONSE_INVALID_OBJECT_HANDLE; + if (mRequest.getParameterCount() < 4) + return MTP_RESPONSE_INVALID_PARAMETER; + MtpObjectHandle handle = mRequest.getParameter(1); + uint64_t offset = mRequest.getParameter(2); + uint64_t offset2 = mRequest.getParameter(3); + offset = offset | (offset2 << 32); + uint32_t length = mRequest.getParameter(4); + + ObjectEdit* edit = getEditObject(handle); + if (!edit) { + MTPE("object not open for edit in doSendPartialObject"); + return MTP_RESPONSE_GENERAL_ERROR; + } + + // can't start writing past the end of the file + if (offset > edit->mSize) { + MTPD("writing past end of object, offset: %" PRIu64 ", edit->mSize: %" PRIu64, + offset, edit->mSize); + return MTP_RESPONSE_GENERAL_ERROR; + } + + const char* filePath = (const char *)edit->mPath; + MTPD("receiving partial %s %" PRIu64 " %" PRIu32, filePath, offset, length); + + // read the header, and possibly some data + int ret = mData.read(mHandle); + if (ret < MTP_CONTAINER_HEADER_SIZE) + return MTP_RESPONSE_GENERAL_ERROR; + int initialData = ret - MTP_CONTAINER_HEADER_SIZE; + + if (initialData > 0) { + ret = pwrite(edit->mFD, mData.getData(), initialData, offset); + offset += initialData; + length -= initialData; + } + + bool isCanceled = false; + if (ret < 0) { + MTPE("failed to write initial data"); + } else { + mtp_file_range mfr; + mfr.fd = edit->mFD; + mfr.offset = offset; + mfr.length = length; + mfr.command = 0; + mfr.transaction_id = 0; + + // transfer the file + ret = mHandle->receiveFile(mfr, mfr.length == 0 && + initialData == MTP_BUFFER_SIZE - MTP_CONTAINER_HEADER_SIZE); + if ((ret < 0) && (errno == ECANCELED)) { + isCanceled = true; + } + } + if (ret < 0) { + mResponse.setParameter(1, 0); + if (isCanceled) + return MTP_RESPONSE_TRANSACTION_CANCELLED; + else + return MTP_RESPONSE_GENERAL_ERROR; + } + + // reset so we don't attempt to send this back + mData.reset(); + mResponse.setParameter(1, length); + uint64_t end = offset + length; + if (end > edit->mSize) { + edit->mSize = end; + } + return MTP_RESPONSE_OK; +} + +MtpResponseCode MtpServer::doTruncateObject() { + if (mRequest.getParameterCount() < 3) + return MTP_RESPONSE_INVALID_PARAMETER; + MtpObjectHandle handle = mRequest.getParameter(1); + ObjectEdit* edit = getEditObject(handle); + if (!edit) { + MTPE("object not open for edit in doTruncateObject"); + return MTP_RESPONSE_GENERAL_ERROR; + } + + uint64_t offset = mRequest.getParameter(2); + uint64_t offset2 = mRequest.getParameter(3); + offset |= (offset2 << 32); + if (ftruncate(edit->mFD, offset) != 0) { + return MTP_RESPONSE_GENERAL_ERROR; + } else { + edit->mSize = offset; + return MTP_RESPONSE_OK; + } +} + +MtpResponseCode MtpServer::doBeginEditObject() { + if (mRequest.getParameterCount() < 1) + return MTP_RESPONSE_INVALID_PARAMETER; + MtpObjectHandle handle = mRequest.getParameter(1); + if (getEditObject(handle)) { + MTPE("object already open for edit in doBeginEditObject"); + return MTP_RESPONSE_GENERAL_ERROR; + } + + MtpStringBuffer path; + int64_t fileLength; + MtpObjectFormat format; + int result = mDatabase->getObjectFilePath(handle, path, fileLength, format); + if (result != MTP_RESPONSE_OK) + return result; + + int fd = open((const char *)path, O_RDWR | O_EXCL); + if (fd < 0) { + MTPE("open failed for %s in doBeginEditObject (%d)", (const char *)path, errno); + return MTP_RESPONSE_GENERAL_ERROR; + } + + addEditObject(handle, path, fileLength, format, fd); + return MTP_RESPONSE_OK; +} + +MtpResponseCode MtpServer::doEndEditObject() { + if (mRequest.getParameterCount() < 1) + return MTP_RESPONSE_INVALID_PARAMETER; + MtpObjectHandle handle = mRequest.getParameter(1); + ObjectEdit* edit = getEditObject(handle); + if (!edit) { + MTPE("object not open for edit in doEndEditObject"); + return MTP_RESPONSE_GENERAL_ERROR; + } + + commitEdit(edit); + removeEditObject(handle); + return MTP_RESPONSE_OK; +} diff --git a/mtp/ffs/MtpServer.h b/mtp/ffs/MtpServer.h new file mode 100755 index 000000000..4bc07cdef --- /dev/null +++ b/mtp/ffs/MtpServer.h @@ -0,0 +1,167 @@ +/* + * 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. + */ + +#ifndef _MTP_SERVER_H +#define _MTP_SERVER_H + +#include "MtpRequestPacket.h" +#include "MtpDataPacket.h" +#include "MtpResponsePacket.h" +#include "MtpEventPacket.h" +#include "MtpStringBuffer.h" +#include "mtp.h" +#include "MtpUtils.h" +#include "IMtpHandle.h" + +#include <memory> +#include <mutex> +#include <queue> + +class IMtpDatabase; +class MtpStorage; + +class MtpServer { + +private: + IMtpDatabase* mDatabase; + + // appear as a PTP device + bool mPtp; + + // Manufacturer to report in DeviceInfo + MtpStringBuffer mDeviceInfoManufacturer; + // Model to report in DeviceInfo + MtpStringBuffer mDeviceInfoModel; + // Device version to report in DeviceInfo + MtpStringBuffer mDeviceInfoDeviceVersion; + // Serial number to report in DeviceInfo + MtpStringBuffer mDeviceInfoSerialNumber; + + // current session ID + MtpSessionID mSessionID; + // true if we have an open session and mSessionID is valid + bool mSessionOpen; + + MtpRequestPacket mRequest; + MtpDataPacket mData; + MtpResponsePacket mResponse; + + MtpEventPacket mEvent; + + MtpStorageList mStorages; + + IMtpHandle* mHandle; + + // handle for new object, set by SendObjectInfo and used by SendObject + MtpObjectHandle mSendObjectHandle; + MtpObjectFormat mSendObjectFormat; + MtpStringBuffer mSendObjectFilePath; + size_t mSendObjectFileSize; + time_t mSendObjectModifiedTime; + + std::mutex mMutex; + + // represents an MTP object that is being edited using the android extensions + // for direct editing (BeginEditObject, SendPartialObject, TruncateObject and EndEditObject) + class ObjectEdit { + public: + MtpObjectHandle mHandle; + MtpStringBuffer mPath; + uint64_t mSize; + MtpObjectFormat mFormat; + int mFD; + + ObjectEdit(MtpObjectHandle handle, const char* path, uint64_t size, + MtpObjectFormat format, int fd) + : mHandle(handle), mPath(path), mSize(size), mFormat(format), mFD(fd) { + } + + virtual ~ObjectEdit() { + close(mFD); + } + }; + std::vector<ObjectEdit*> mObjectEditList; + +public: + MtpServer(IMtpDatabase* database, int controlFd, bool ptp, + const char *deviceInfoManufacturer, + const char *deviceInfoModel, + const char *deviceInfoDeviceVersion, + const char *deviceInfoSerialNumber); + virtual ~MtpServer(); + + MtpStorage* getStorage(MtpStorageID id); + inline bool hasStorage() { return mStorages.size() > 0; } + bool hasStorage(MtpStorageID id); + void addStorage(MtpStorage* storage); + void removeStorage(MtpStorage* storage); + + void run(); + + void sendObjectAdded(MtpObjectHandle handle); + void sendObjectRemoved(MtpObjectHandle handle); + void sendObjectUpdated(MtpObjectHandle handle); + void sendDevicePropertyChanged(MtpDeviceProperty property); + void sendObjectInfoChanged(MtpObjectHandle handle); + + +private: + void sendStoreAdded(MtpStorageID id); + void sendStoreRemoved(MtpStorageID id); + void sendEvent(MtpEventCode code, uint32_t param1); + + void addEditObject(MtpObjectHandle handle, MtpStringBuffer& path, + uint64_t size, MtpObjectFormat format, int fd); + ObjectEdit* getEditObject(MtpObjectHandle handle); + void removeEditObject(MtpObjectHandle handle); + void commitEdit(ObjectEdit* edit); + + bool handleRequest(); + + MtpResponseCode doGetDeviceInfo(); + MtpResponseCode doOpenSession(); + MtpResponseCode doCloseSession(); + MtpResponseCode doGetStorageIDs(); + MtpResponseCode doGetStorageInfo(); + MtpResponseCode doGetObjectPropsSupported(); + MtpResponseCode doGetObjectHandles(); + MtpResponseCode doGetNumObjects(); + MtpResponseCode doGetObjectReferences(); + MtpResponseCode doSetObjectReferences(); + MtpResponseCode doGetObjectPropValue(); + MtpResponseCode doSetObjectPropValue(); + MtpResponseCode doGetDevicePropValue(); + MtpResponseCode doSetDevicePropValue(); + MtpResponseCode doResetDevicePropValue(); + MtpResponseCode doGetObjectPropList(); + MtpResponseCode doGetObjectInfo(); + MtpResponseCode doGetObject(); + MtpResponseCode doGetThumb(); + MtpResponseCode doGetPartialObject(MtpOperationCode operation); + MtpResponseCode doSendObjectInfo(); + MtpResponseCode doSendObject(); + MtpResponseCode doDeleteObject(); + MtpResponseCode doMoveObject(); + MtpResponseCode doCopyObject(); + MtpResponseCode doGetObjectPropDesc(); + MtpResponseCode doGetDevicePropDesc(); + MtpResponseCode doSendPartialObject(); + MtpResponseCode doTruncateObject(); + MtpResponseCode doBeginEditObject(); + MtpResponseCode doEndEditObject(); +}; + +#endif // _MTP_SERVER_H diff --git a/mtp/ffs/MtpStorage.cpp b/mtp/ffs/MtpStorage.cpp new file mode 100755 index 000000000..8c67b5bd6 --- /dev/null +++ b/mtp/ffs/MtpStorage.cpp @@ -0,0 +1,810 @@ +/* + * 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 "MtpStorage" + +#include "MtpDebug.h" +#include "MtpStorage.h" +#include "btree.hpp" + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/statfs.h> +#include <unistd.h> +#include <dirent.h> +#include <errno.h> +#include <string.h> +#include <stdio.h> +#include <limits.h> +#include <iterator> +#include <sys/inotify.h> + +#define WATCH_FLAGS ( IN_CREATE | IN_DELETE | IN_MOVE | IN_MODIFY ) + +MtpStorage::MtpStorage(MtpStorageID id, const char* filePath, + const char* description, bool removable, uint64_t maxFileSize, MtpServer* refserver) + : mStorageID(id), + mFilePath(filePath), + mDescription(description), + mMaxCapacity(0), + mMaxFileSize(maxFileSize), + mRemovable(removable), + mServer(refserver) +{ + MTPD("MtpStorage id: %d path: %s\n", id, filePath); + inotify_thread = 0; + inotify_fd = -1; + // Threading has not started yet so we should be safe to set these directly instead of using atomics + inotify_thread_kill.set_value(0); + sendEvents = false; + handleCurrentlySending = 0; + use_mutex = true; + if (pthread_mutex_init(&mtpMutex, NULL) != 0) { + MTPE("Failed to init mtpMutex\n"); + use_mutex = false; + } + if (pthread_mutex_init(&inMutex, NULL) != 0) { + MTPE("Failed to init inMutex\n"); + pthread_mutex_destroy(&mtpMutex); + use_mutex = false; + } +} + +MtpStorage::~MtpStorage() { + if (inotify_thread) { + inotify_thread_kill.set_value(1); + MTPD("joining inotify_thread after sending the kill notification.\n"); + pthread_join(inotify_thread, NULL); // There's not much we can do if there's an error here + inotify_thread = 0; + MTPD("~MtpStorage removing inotify watches and closing inotify_fd\n"); + for (std::map<int, Tree*>::iterator i = inotifymap.begin(); i != inotifymap.end(); i++) { + inotify_rm_watch(inotify_fd, i->first); + } + close(inotify_fd); + inotifymap.clear(); + } + // Deleting the root tree causes a cascade in btree.cpp that ends up + // deleting all of the trees and nodes. + delete mtpmap[0]; + mtpmap.clear(); + if (use_mutex) { + use_mutex = false; + MTPD("~MtpStorage destroying mutexes\n"); + pthread_mutex_destroy(&mtpMutex); + pthread_mutex_destroy(&inMutex); + } + +} + +int MtpStorage::getType() const { + return (mRemovable ? MTP_STORAGE_REMOVABLE_RAM : MTP_STORAGE_FIXED_RAM); +} + +int MtpStorage::getFileSystemType() const { + return MTP_STORAGE_FILESYSTEM_HIERARCHICAL; +} + +int MtpStorage::getAccessCapability() const { + return MTP_STORAGE_READ_WRITE; +} + +uint64_t MtpStorage::getMaxCapacity() { + if (mMaxCapacity == 0) { + struct statfs stat; + if (statfs(getPath(), &stat)) + return -1; + mMaxCapacity = (uint64_t)stat.f_blocks * (uint64_t)stat.f_bsize; + } + return mMaxCapacity; +} + +uint64_t MtpStorage::getFreeSpace() { + struct statfs stat; + if (statfs(getPath(), &stat)) + return -1; + return (uint64_t)stat.f_bavail * (uint64_t)stat.f_bsize; +} + +const char* MtpStorage::getDescription() const { + return (const char *)mDescription; +} + +int MtpStorage::renameObject(MtpObjectHandle handle, std::string newName) { + MTPD("MtpStorage::renameObject, handle: %u, new name: '%s'\n", handle, newName.c_str()); + if (handle == MTP_PARENT_ROOT) { + MTPE("parent == MTP_PARENT_ROOT, cannot rename root\n"); + return -1; + } else { + for (iter i = mtpmap.begin(); i != mtpmap.end(); i++) { + Node* node = i->second->findNode(handle); + if (node != NULL) { + std::string oldName = getNodePath(node); + std::string parentdir = oldName.substr(0, oldName.find_last_of('/')); + std::string newFullName = parentdir + "/" + newName; + MTPD("old: '%s', new: '%s'\n", oldName.c_str(), newFullName.c_str()); + if (rename(oldName.c_str(), newFullName.c_str()) == 0) { + node->rename(newName); + return 0; + } else { + MTPE("MtpStorage::renameObject failed, handle: %u, new name: '%s'\n", handle, newName.c_str()); + return -1; + } + } + } + } + // handle not found on this storage + return -1; +} + +MtpObjectHandle MtpStorage::beginSendObject(const char* path, + MtpObjectFormat format, + MtpObjectHandle parent, + __attribute__((unused)) uint64_t size, + __attribute__((unused)) time_t modified) { + MTPD("MtpStorage::beginSendObject(), path: '%s', parent: %u, format: %04x\n", path, parent, format); + iter it = mtpmap.find(parent); + if (it == mtpmap.end()) { + MTPE("parent node not found, returning error\n"); + return kInvalidObjectHandle; + } + Tree* tree = it->second; + + std::string pathstr(path); + size_t slashpos = pathstr.find_last_of('/'); + if (slashpos == std::string::npos) { + MTPE("path has no slash, returning error\n"); + return kInvalidObjectHandle; + } + std::string parentdir = pathstr.substr(0, slashpos); + std::string basename = pathstr.substr(slashpos + 1); + if (parent != 0 && parentdir != getNodePath(tree)) { + MTPE("beginSendObject into path '%s' but parent tree has path '%s', returning error\n", parentdir.c_str(), getNodePath(tree).c_str()); + return kInvalidObjectHandle; + } + + MTPD("MtpStorage::beginSendObject() parentdir: %s basename: %s\n", parentdir.c_str(), basename.c_str()); + // note: for directories, the mkdir call is done later in MtpServer, here we just reserve a handle + bool isDir = format == MTP_FORMAT_ASSOCIATION; + Node* node = addNewNode(isDir, tree, basename); + handleCurrentlySending = node->Mtpid(); // suppress inotify for this node while sending + + return node->Mtpid(); +} + +int MtpStorage::createDB() { + std::string mtpParent = ""; + mtpstorageparent = getPath(); + // root directory is special: handle 0, parent 0, and empty path + mtpmap[0] = new Tree(0, 0, ""); + if (use_mutex) { + sendEvents = true; + MTPD("inotify_init\n"); + inotify_fd = inotify_init(); + if (inotify_fd < 0) { + MTPE("Can't run inotify_init for mtp server: %s\n", strerror(errno)); + } else { + MTPD("Starting inotify thread\n"); + inotify_thread = inotify(); + } + } else { + MTPD("NOT starting inotify thread\n"); + } + // for debugging and caching purposes, read the root dir already now + readDir(mtpstorageparent, mtpmap[0]); + // all other dirs are read on demand + // + MTPD("MtpStorage::createDB DONE\n"); + return 0; +} + +Node* MtpStorage::findNode(MtpObjectHandle handle) { + for (iter i = mtpmap.begin(); i != mtpmap.end(); i++) { + Node* node = i->second->findNode(handle); + if (node != NULL) { + MTPD("findNode: found node %p for handle %u, name: %s\n", node, handle, node->getName().c_str()); + if (node->Mtpid() != handle) + { + MTPE("BUG: entry for handle %u points to node with handle %u\n", handle, node->Mtpid()); + } + return node; + } + } + // Item is not on this storage device + MTPD("MtpStorage::findNode: no node found for handle %u on storage %u, searched %u trees\n", handle, mStorageID, mtpmap.size()); + return NULL; +} + +std::string MtpStorage::getNodePath(Node* node) { + std::string path; + MTPD("getNodePath: node %p, handle %u\n", node, node->Mtpid()); + while (node) + { + path = "/" + node->getName() + path; + MtpObjectHandle parent = node->getMtpParentId(); + if (parent == 0) // root + break; + node = findNode(parent); + } + path = mtpstorageparent + path; + MTPD("getNodePath: path %s\n", path.c_str()); + return path; +} + +MtpObjectHandleList* MtpStorage::getObjectList(__attribute__((unused)) MtpStorageID storageID, MtpObjectHandle parent) { + MTPD("MtpStorage::getObjectList, parent: %u\n", parent); + //append object id (numerical #s) of database to int array + MtpObjectHandleList* list = new MtpObjectHandleList(); + if (parent == MTP_PARENT_ROOT) { + MTPD("parent == MTP_PARENT_ROOT\n"); + parent = 0; + } + + if (mtpmap.find(parent) == mtpmap.end()) { + MTPE("parent handle not found, returning empty list\n"); + return list; + } + + Tree* tree = mtpmap[parent]; + if (!tree->wasAlreadyRead()) + { + std::string path = getNodePath(tree); + MTPD("reading directory on demand for tree %p (%u), path: %s\n", tree, tree->Mtpid(), path.c_str()); + readDir(path, tree); + } + + mtpmap[parent]->getmtpids(list); + MTPD("returning %u objects in %s.\n", list->size(), tree->getName().c_str()); + return list; +} + +Node* MtpStorage::addNewNode(bool isDir, Tree* tree, const std::string& name) +{ + // global counter for new object handles + static MtpObjectHandle mtpid = 0; + + ++mtpid; + MTPD("adding new %s node for %s, new handle: %u\n", isDir ? "dir" : "file", name.c_str(), mtpid); + MtpObjectHandle parent = tree->Mtpid(); + MTPD("parent tree: %x, handle: %u, name: %s\n", tree, parent, tree->getName().c_str()); + Node* node; + if (isDir) + node = mtpmap[mtpid] = new Tree(mtpid, parent, name); + else + node = new Node(mtpid, parent, name); + tree->addEntry(node); + return node; +} + +int MtpStorage::readDir(const std::string& path, Tree* tree) +{ + struct dirent *de; + int storageID = getStorageID(); + MtpObjectHandle parent = tree->Mtpid(); + + DIR *d = opendir(path.c_str()); + MTPD("reading dir '%s', parent handle %u\n", path.c_str(), parent); + if (d == NULL) { + MTPE("error opening '%s' -- error: %s\n", path.c_str(), strerror(errno)); + return -1; + } + // TODO: for refreshing dirs: capture old entries here + while ((de = readdir(d)) != NULL) { + // Because exfat-fuse causes issues with dirent, we will use stat + // for some things that dirent should be able to do + std::string item = path + "/" + de->d_name; + struct stat st; + if (lstat(item.c_str(), &st)) { + MTPE("Error running lstat on '%s'\n", item.c_str()); + return -1; + } + // TODO: if we want to use this for refreshing dirs too, first find existing name and overwrite + if (strcmp(de->d_name, ".") == 0) + continue; + if (strcmp(de->d_name, "..") == 0) + continue; + Node* node = addNewNode(st.st_mode & S_IFDIR, tree, de->d_name); + node->addProperties(item, storageID); + //if (sendEvents) + // mServer->sendObjectAdded(node->Mtpid()); + // sending events here makes simple-mtpfs very slow, and it is probably the wrong thing to do anyway + } + closedir(d); + // TODO: for refreshing dirs: remove entries that no longer exist (with their nodes) + tree->setAlreadyRead(true); + addInotify(tree); + return 0; +} + +int MtpStorage::addInotify(Tree* tree) { + if (inotify_fd < 0) { + MTPE("inotify_fd not set or error: %i\n", inotify_fd); + return -1; + } + std::string path = getNodePath(tree); + MTPD("adding inotify for tree %x, dir: %s\n", tree, path.c_str()); + int wd = inotify_add_watch(inotify_fd, path.c_str(), WATCH_FLAGS); + if (wd < 0) { + MTPE("inotify_add_watch failed: %s\n", strerror(errno)); + return -1; + } + inotifymap[wd] = tree; + return 0; +} + +pthread_t MtpStorage::inotify(void) { + pthread_t thread; + pthread_attr_t tattr; + + if (pthread_attr_init(&tattr)) { + MTPE("Unable to pthread_attr_init\n"); + return 0; + } + if (pthread_attr_setdetachstate(&tattr, PTHREAD_CREATE_JOINABLE)) { + MTPE("Error setting pthread_attr_setdetachstate\n"); + return 0; + } + ThreadPtr inotifyptr = &MtpStorage::inotify_t; + PThreadPtr p = *(PThreadPtr*)&inotifyptr; + pthread_create(&thread, &tattr, p, this); + if (pthread_attr_destroy(&tattr)) { + MTPE("Failed to pthread_attr_destroy\n"); + } + return thread; +} + +int MtpStorage::inotify_t(void) { + #define EVENT_SIZE ( sizeof(struct inotify_event) ) + #define EVENT_BUF_LEN ( 1024 * ( EVENT_SIZE + 16) ) + char buf[EVENT_BUF_LEN]; + fd_set fdset; + struct timeval seltmout; + int sel_ret; + + MTPD("inotify thread starting.\n"); + + while (inotify_thread_kill.get_value() == 0) { + FD_ZERO(&fdset); + FD_SET(inotify_fd, &fdset); + seltmout.tv_sec = 0; + seltmout.tv_usec = 25000; + sel_ret = select(inotify_fd + 1, &fdset, NULL, NULL, &seltmout); + if (sel_ret == 0) + continue; + int i = 0; + int len = read(inotify_fd, buf, EVENT_BUF_LEN); + + if (len < 0) { + if (errno == EINTR) + continue; + MTPE("inotify_t Can't read inotify events\n"); + } + + while (i < len && inotify_thread_kill.get_value() == 0) { + struct inotify_event *event = (struct inotify_event *) &buf[i]; + if (event->len) { + MTPD("inotify event: wd: %i, mask: %x, name: %s\n", event->wd, event->mask, event->name); + lockMutex(1); + handleInotifyEvent(event); + unlockMutex(1); + } + i += EVENT_SIZE + event->len; + } + } + MTPD("inotify_thread_kill received!\n"); + // This cleanup is handled in the destructor. + /*for (std::map<int, Tree*>::iterator i = inotifymap.begin(); i != inotifymap.end(); i++) { + inotify_rm_watch(inotify_fd, i->first); + } + close(inotify_fd);*/ + return 0; +} + +void MtpStorage::handleInotifyEvent(struct inotify_event* event) +{ + std::map<int, Tree*>::iterator it = inotifymap.find(event->wd); + if (it == inotifymap.end()) { + MTPE("Unable to locate inotify_wd: %i\n", event->wd); + return; + } + Tree* tree = it->second; + MTPD("inotify_t tree: %x '%s'\n", tree, tree->getName().c_str()); + Node* node = tree->findEntryByName(basename(event->name)); + if (node && node->Mtpid() == handleCurrentlySending) { + MTPD("ignoring inotify event for currently uploading file, handle: %u\n", node->Mtpid()); + return; + } + if (event->mask & IN_CREATE || event->mask & IN_MOVED_TO) { + if (event->mask & IN_ISDIR) { + MTPD("inotify_t create is dir\n"); + } else { + MTPD("inotify_t create is file\n"); + } + if (node == NULL) { + node = addNewNode(event->mask & IN_ISDIR, tree, event->name); + std::string item = getNodePath(tree) + "/" + event->name; + node->addProperties(item, getStorageID()); + mServer->sendObjectAdded(node->Mtpid()); + } else { + MTPD("inotify_t item already exists.\n"); + } + if (event->mask & IN_ISDIR) { + // TODO: do we need to do anything here? probably not until someone reads from the dir... + } + } else if (event->mask & IN_DELETE || event->mask & IN_MOVED_FROM) { + if (event->mask & IN_ISDIR) { + MTPD("inotify_t Directory %s deleted\n", event->name); + } else { + MTPD("inotify_t File %s deleted\n", event->name); + } + if (node) + { + if (event->mask & IN_ISDIR) { + for (std::map<int, Tree*>::iterator it = inotifymap.begin(); it != inotifymap.end(); ++it) { + if (it->second == node) { + inotify_rm_watch(inotify_fd, it->first); + MTPD("inotify_t removing watch on '%s'\n", getNodePath(it->second).c_str()); + inotifymap.erase(it->first); + break; + } + + } + } + MtpObjectHandle handle = node->Mtpid(); + deleteFile(handle); + mServer->sendObjectRemoved(handle); + } else { + MTPD("inotify_t already removed.\n"); + } + } else if (event->mask & IN_MODIFY) { + MTPD("inotify_t item %s modified.\n", event->name); + if (node != NULL) { + uint64_t orig_size = node->getProperty(MTP_PROPERTY_OBJECT_SIZE).valueInt; + struct stat st; + uint64_t new_size = 0; + if (lstat(getNodePath(node).c_str(), &st) == 0) + new_size = (uint64_t)st.st_size; + if (orig_size != new_size) { + MTPD("size changed from %llu to %llu on mtpid: %u\n", orig_size, new_size, node->Mtpid()); + node->updateProperty(MTP_PROPERTY_OBJECT_SIZE, new_size, "", MTP_TYPE_UINT64); + mServer->sendObjectUpdated(node->Mtpid()); + } + } else { + MTPE("inotify_t modified item not found\n"); + } + } else if (event->mask & IN_DELETE_SELF || event->mask & IN_MOVE_SELF) { + // TODO: is this always already handled by IN_DELETE for the parent dir? + } +} + +void MtpStorage::lockMutex(int thread_type) { + if (!use_mutex) + return; // mutex is disabled + if (thread_type) { + // inotify thread + pthread_mutex_lock(&inMutex); + while (pthread_mutex_trylock(&mtpMutex)) { + pthread_mutex_unlock(&inMutex); + usleep(32000); + pthread_mutex_lock(&inMutex); + } + } else { + // main mtp thread + pthread_mutex_lock(&mtpMutex); + while (pthread_mutex_trylock(&inMutex)) { + pthread_mutex_unlock(&mtpMutex); + usleep(13000); + pthread_mutex_lock(&mtpMutex); + } + } +} + +void MtpStorage::unlockMutex( __attribute__((unused)) int thread_type) { + if (!use_mutex) + return; // mutex is disabled + pthread_mutex_unlock(&inMutex); + pthread_mutex_unlock(&mtpMutex); +} + +int MtpStorage::getObjectPropertyValue(MtpObjectHandle handle, MtpObjectProperty property, MtpStorage::PropEntry& pe) { + Node *node; + for (iter i = mtpmap.begin(); i != mtpmap.end(); i++) { + node = i->second->findNode(handle); + if (node != NULL) { + const Node::mtpProperty& prop = node->getProperty(property); + if (prop.property != property) { + MTPD("getObjectPropertyValue: unknown property %x for handle %u\n", property, handle); + return -1; + } + pe.datatype = prop.dataType; + pe.intvalue = prop.valueInt; + pe.strvalue = prop.valueStr; + pe.handle = handle; + pe.property = property; + return 0; + } + } + // handle not found on this storage + return -1; +} + +void MtpStorage::endSendObject(const char* path, MtpObjectHandle handle, __attribute__((unused)) MtpObjectFormat format, __attribute__((unused)) bool succeeded) +{ + Node* node = findNode(handle); + if (!node) + return; // just ignore if this is for another storage + + node->addProperties(path, mStorageID); + handleCurrentlySending = 0; + // TODO: are we supposed to send an event about an upload by the initiator? + if (sendEvents) + mServer->sendObjectAdded(node->Mtpid()); +} + +int MtpStorage::getObjectPropertyList(MtpObjectHandle handle, uint32_t format, uint32_t property, int groupCode, __attribute__((unused)) int depth, MtpDataPacket& packet) { + MTPD("MtpStorage::getObjectPropertyList handle: %u, format: %x, property: %x\n", handle, format, property); + if (groupCode != 0) + { + MTPE("getObjectPropertyList: groupCode unsupported\n"); + return -1; // TODO: RESPONSE_SPECIFICATION_BY_GROUP_UNSUPPORTED + } + // TODO: support all the special stuff, like: + // handle == 0 -> all objects at the root level + // handle == 0xffffffff -> all objects (on all storages? how could we support that?) + // format == 0 -> all formats, otherwise filter by ObjectFormatCode + // property == 0xffffffff -> all properties except those with group code 0xffffffff + // if property == 0 then use groupCode + // groupCode == 0 -> return Specification_By_Group_Unsupported + // depth == 0xffffffff -> all objects incl. and below handle + + std::vector<PropEntry> results; + + if (handle == 0xffffffff) { + // TODO: all object on all storages (needs a different design, result packet needs to be built by server instead of storage) + } else if (handle == 0) { + // all objects at the root level + Tree* root = mtpmap[0]; + MtpObjectHandleList list; + root->getmtpids(&list); + for (MtpObjectHandleList::iterator it = list.begin(); it != list.end(); ++it) { + Node* node = root->findNode(*it); + if (!node) { + MTPE("BUG: node not found for root entry with handle %u\n", *it); + break; + } + queryNodeProperties(results, node, property, groupCode, mStorageID); + } + } else { + // single object + Node* node = findNode(handle); + if (!node) { + // Item is not on this storage device + return -1; + } + queryNodeProperties(results, node, property, groupCode, mStorageID); + } + + MTPD("MtpStorage::getObjectPropertyList::count: %u\n", results.size()); + packet.putUInt32(results.size()); + + for (size_t i = 0; i < results.size(); ++i) { + PropEntry& p = results[i]; + MTPD("handle: %u, propertyCode: %x = %s, datatype: %x, value: %llu\n", + p.handle, p.property, MtpDebug::getObjectPropCodeName(p.property), + p.datatype, p.intvalue); + packet.putUInt32(p.handle); + packet.putUInt16(p.property); + packet.putUInt16(p.datatype); + switch (p.datatype) { + case MTP_TYPE_INT8: + MTPD("MtpStorage::getObjectPropertyList::MTP_TYPE_INT8\n"); + packet.putInt8(p.intvalue); + break; + case MTP_TYPE_UINT8: + MTPD("MtpStorage::getObjectPropertyList::MTP_TYPE_UINT8\n"); + packet.putUInt8(p.intvalue); + break; + case MTP_TYPE_INT16: + MTPD("MtpStorage::getObjectPropertyList::MTP_TYPE_INT16\n"); + packet.putInt16(p.intvalue); + break; + case MTP_TYPE_UINT16: + MTPD("MtpStorage::getObjectPropertyList::MTP_TYPE_UINT16\n"); + packet.putUInt16(p.intvalue); + break; + case MTP_TYPE_INT32: + MTPD("MtpStorage::getObjectPropertyList::MTP_TYPE_INT32\n"); + packet.putInt32(p.intvalue); + break; + case MTP_TYPE_UINT32: + MTPD("MtpStorage::getObjectPropertyList::MTP_TYPE_UINT32\n"); + packet.putUInt32(p.intvalue); + break; + case MTP_TYPE_INT64: + MTPD("MtpStorage::getObjectPropertyList::MTP_TYPE_INT64\n"); + packet.putInt64(p.intvalue); + break; + case MTP_TYPE_UINT64: + MTPD("MtpStorage::getObjectPropertyList::MTP_TYPE_UINT64\n"); + packet.putUInt64(p.intvalue); + break; + case MTP_TYPE_INT128: + MTPD("MtpStorage::getObjectPropertyList::MTP_TYPE_INT128\n"); + packet.putInt128(p.intvalue); + break; + case MTP_TYPE_UINT128: + MTPD("MtpStorage::getObjectPropertyList::MTP_TYPE_UINT128\n"); + packet.putUInt128(p.intvalue); + break; + case MTP_TYPE_STR: + MTPD("MtpStorage::getObjectPropertyList::MTP_TYPE_STR: %s\n", p.strvalue.c_str()); + packet.putString(p.strvalue.c_str()); + break; + default: + MTPE("bad or unsupported data type: %x in MyMtpDatabase::getObjectPropertyList", p.datatype); + break; + } + } + return 0; +} + +int MtpStorage::getObjectInfo(MtpObjectHandle handle, MtpObjectInfo& info) { + struct stat st; + uint64_t size = 0; + MTPD("MtpStorage::getObjectInfo, handle: %u\n", handle); + Node* node = findNode(handle); + if (!node) { + // Item is not on this storage device + return -1; + } + + info.mStorageID = getStorageID(); + MTPD("info.mStorageID: %u\n", info.mStorageID); + info.mParent = node->getMtpParentId(); + MTPD("mParent: %u\n", info.mParent); + // TODO: do we want to lstat again here, or read from the node properties? + if (lstat(getNodePath(node).c_str(), &st) == 0) + size = st.st_size; + MTPD("size is: %llu\n", size); + info.mCompressedSize = (size > 0xFFFFFFFFLL ? 0xFFFFFFFF : size); + info.mDateModified = st.st_mtime; + if (S_ISDIR(st.st_mode)) { + info.mFormat = MTP_FORMAT_ASSOCIATION; + } + else { + info.mFormat = MTP_FORMAT_UNDEFINED; + } + info.mName = strdup(node->getName().c_str()); + MTPD("MtpStorage::getObjectInfo found, Exiting getObjectInfo()\n"); + return 0; +} + +int MtpStorage::getObjectFilePath(MtpObjectHandle handle, MtpStringBuffer& outFilePath, int64_t& outFileLength, MtpObjectFormat& outFormat) { + MTPD("MtpStorage::getObjectFilePath handle: %u\n", handle); + Node* node = findNode(handle); + if (!node) + { + // Item is not on this storage device + return -1; + } + // TODO: do we want to lstat here, or just read the info from the node? + struct stat st; + if (lstat(getNodePath(node).c_str(), &st) == 0) + outFileLength = st.st_size; + else + outFileLength = 0; + outFilePath.set(getNodePath(node).c_str()); + MTPD("outFilePath: %s\n", (const char*) outFilePath); + outFormat = node->isDir() ? MTP_FORMAT_ASSOCIATION : MTP_FORMAT_UNDEFINED; + return 0; +} + +int MtpStorage::deleteFile(MtpObjectHandle handle) { + MTPD("MtpStorage::deleteFile handle: %u\n", handle); + Node* node = findNode(handle); + if (!node) { + // Item is not on this storage device + return -1; + } + MtpObjectHandle parent = node->getMtpParentId(); + Tree* tree = mtpmap[parent]; + if (!tree) { + MTPE("parent tree for handle %u not found\n", parent); + return -1; + } + if (node->isDir()) { + MTPD("deleting tree from mtpmap: %u\n", handle); + mtpmap.erase(handle); + } + + MTPD("deleting handle: %u\n", handle); + tree->deleteNode(handle); + MTPD("deleted\n"); + return 0; +} + +void MtpStorage::queryNodeProperties(std::vector<MtpStorage::PropEntry>& results, Node* node, uint32_t property, __attribute__((unused)) int groupCode, MtpStorageID storageID) +{ + MTPD("queryNodeProperties handle %u, path: %s\n", node->Mtpid(), getNodePath(node).c_str()); + PropEntry pe; + pe.handle = node->Mtpid(); + pe.property = property; + + if (property == 0xffffffff) + { + // add all properties + MTPD("MtpStorage::queryNodeProperties for all properties\n"); + std::vector<Node::mtpProperty> mtpprop = node->getMtpProps(); + for (size_t i = 0; i < mtpprop.size(); ++i) { + pe.property = mtpprop[i].property; + pe.datatype = mtpprop[i].dataType; + pe.intvalue = mtpprop[i].valueInt; + pe.strvalue = mtpprop[i].valueStr; + results.push_back(pe); + } + return; + } + else if (property == 0) + { + // TODO: use groupCode + } + + // single property + // TODO: this should probably be moved to the Node class and/or merged with getObjectPropertyValue + switch (property) { +// case MTP_PROPERTY_OBJECT_FORMAT: +// pe.datatype = MTP_TYPE_UINT16; +// pe.intvalue = node->getIntProperty(MTP_PROPERTY_OBJECT_FORMAT); +// break; + + case MTP_PROPERTY_STORAGE_ID: + pe.datatype = MTP_TYPE_UINT32; + pe.intvalue = storageID; + break; + + case MTP_PROPERTY_PROTECTION_STATUS: + pe.datatype = MTP_TYPE_UINT16; + pe.intvalue = 0; + break; + + case MTP_PROPERTY_OBJECT_SIZE: + { + pe.datatype = MTP_TYPE_UINT64; + struct stat st; + pe.intvalue = 0; + if (lstat(getNodePath(node).c_str(), &st) == 0) + pe.intvalue = st.st_size; + break; + } + + default: + { + const Node::mtpProperty& prop = node->getProperty(property); + if (prop.property != property) + { + MTPD("queryNodeProperties: unknown property %x\n", property); + return; + } + pe.datatype = prop.dataType; + pe.intvalue = prop.valueInt; + pe.strvalue = prop.valueStr; + // TODO: all the special case stuff in MyMtpDatabase::getObjectPropertyValue is missing here + } + + } + results.push_back(pe); +} + + diff --git a/mtp/ffs/MtpStorage.h b/mtp/ffs/MtpStorage.h new file mode 100755 index 000000000..9d6d29115 --- /dev/null +++ b/mtp/ffs/MtpStorage.h @@ -0,0 +1,103 @@ +/* + * 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. + */ + +#ifndef _MTP_STORAGE_H +#define _MTP_STORAGE_H + +#include "MtpObjectInfo.h" +#include "MtpServer.h" +#include "MtpStringBuffer.h" +#include "MtpTypes.h" +#include "mtp.h" +#include "btree.hpp" +#include "../../tw_atomic.hpp" + +class MtpDatabase; + +class MtpStorage { + +public: + struct PropEntry { + MtpObjectHandle handle; + uint16_t property; + uint16_t datatype; + uint64_t intvalue; + std::string strvalue; + }; + +private: + MtpStorageID mStorageID; + MtpStringBuffer mFilePath; + MtpStringBuffer mDescription; + uint64_t mMaxCapacity; + uint64_t mMaxFileSize; + bool mRemovable; + typedef std::map<int, Tree*> maptree; + typedef maptree::iterator iter; + maptree mtpmap; + std::string mtpstorageparent; + MtpObjectHandle handleCurrentlySending; + int inotify_fd; + std::map<int, Tree*> inotifymap; // inotify wd -> tree + bool sendEvents; + MtpServer* mServer; + typedef int (MtpStorage::*ThreadPtr)(void); + typedef void* (*PThreadPtr)(void *); + bool use_mutex; + pthread_mutex_t inMutex; // inotify mutex + pthread_mutex_t mtpMutex; // main mtp mutex + TWAtomicInt inotify_thread_kill; + pthread_t inotify_thread; + Node* findNode(MtpObjectHandle handle); + std::string getNodePath(Node* node); + Node* addNewNode(bool isDir, Tree* tree, const std::string& name); + void queryNodeProperties(std::vector<PropEntry>& results, Node* node, uint32_t property, int groupCode, MtpStorageID storageID); + int addInotify(Tree* tree); + void handleInotifyEvent(struct inotify_event* event); + +public: + MtpStorage(MtpStorageID id, const char* filePath, + const char* description, + bool removable, uint64_t maxFileSize, MtpServer* refserver); + virtual ~MtpStorage(); + inline MtpStorageID getStorageID() const { return mStorageID; } + int getType() const; + int getFileSystemType() const; + int getAccessCapability() const; + uint64_t getMaxCapacity(); + uint64_t getFreeSpace(); + const char* getDescription() const; + inline const char* getPath() const { return (const char *)mFilePath; } + inline bool isRemovable() const { return mRemovable; } + inline uint64_t getMaxFileSize() const { return mMaxFileSize; } + int renameObject(MtpObjectHandle handle, std::string newName); + MtpObjectHandle beginSendObject(const char* path, MtpObjectFormat format, MtpObjectHandle parent, uint64_t size, time_t modified); + MtpObjectHandleList* getObjectList(MtpStorageID storageID, MtpObjectHandle parent); + int getObjectPropertyList(MtpObjectHandle handle, uint32_t format, uint32_t property, int groupCode, int depth, MtpDataPacket& packet); + int readDir(const std::string& path, Tree* tree); + int getObjectPropertyValue(MtpObjectHandle handle, MtpObjectProperty property, PropEntry& prop); + int getObjectInfo(MtpObjectHandle handle, MtpObjectInfo& info); + void endSendObject(const char* path, MtpObjectHandle handle, MtpObjectFormat format, bool succeeded); + int getObjectFilePath(MtpObjectHandle handle, MtpStringBuffer& outFilePath, int64_t& outFileLength, MtpObjectFormat& outFormat); + int deleteFile(MtpObjectHandle handle); + int createDB(); + pthread_t inotify(); + int inotify_t(); + void lockMutex(int thread_type); + void unlockMutex(int thread_type); +}; + +#endif // _MTP_STORAGE_H diff --git a/mtp/ffs/MtpStorageInfo.cpp b/mtp/ffs/MtpStorageInfo.cpp new file mode 100644 index 000000000..21a8322da --- /dev/null +++ b/mtp/ffs/MtpStorageInfo.cpp @@ -0,0 +1,74 @@ +/* + * 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 "MtpStorageInfo" + +#include <inttypes.h> + +#include "MtpDebug.h" +#include "MtpDataPacket.h" +#include "MtpStorageInfo.h" +#include "MtpStringBuffer.h" + +MtpStorageInfo::MtpStorageInfo(MtpStorageID id) + : mStorageID(id), + mStorageType(0), + mFileSystemType(0), + mAccessCapability(0), + mMaxCapacity(0), + mFreeSpaceBytes(0), + mFreeSpaceObjects(0), + mStorageDescription(NULL), + mVolumeIdentifier(NULL) +{ +} + +MtpStorageInfo::~MtpStorageInfo() { + if (mStorageDescription) + free(mStorageDescription); + if (mVolumeIdentifier) + free(mVolumeIdentifier); +} + +bool MtpStorageInfo::read(MtpDataPacket& packet) { + MtpStringBuffer string; + + // read the device info + if (!packet.getUInt16(mStorageType)) return false; + if (!packet.getUInt16(mFileSystemType)) return false; + if (!packet.getUInt16(mAccessCapability)) return false; + if (!packet.getUInt64(mMaxCapacity)) return false; + if (!packet.getUInt64(mFreeSpaceBytes)) return false; + if (!packet.getUInt32(mFreeSpaceObjects)) return false; + + if (!packet.getString(string)) return false; + mStorageDescription = strdup((const char *)string); + if (!mStorageDescription) return false; + if (!packet.getString(string)) return false; + mVolumeIdentifier = strdup((const char *)string); + if (!mVolumeIdentifier) return false; + + return true; +} + +void MtpStorageInfo::print() { + MTPD("Storage Info %08X:\n\tmStorageType: %d\n\tmFileSystemType: %d\n\tmAccessCapability: %d\n", + mStorageID, mStorageType, mFileSystemType, mAccessCapability); + MTPD("\tmMaxCapacity: %" PRIu64 "\n\tmFreeSpaceBytes: %" PRIu64 "\n\tmFreeSpaceObjects: %d\n", + mMaxCapacity, mFreeSpaceBytes, mFreeSpaceObjects); + MTPD("\tmStorageDescription: %s\n\tmVolumeIdentifier: %s\n", + mStorageDescription, mVolumeIdentifier); +} diff --git a/mtp/ffs/MtpStorageInfo.h b/mtp/ffs/MtpStorageInfo.h new file mode 100644 index 000000000..08e05716e --- /dev/null +++ b/mtp/ffs/MtpStorageInfo.h @@ -0,0 +1,45 @@ +/* + * 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. + */ + +#ifndef _MTP_STORAGE_INFO_H +#define _MTP_STORAGE_INFO_H + +#include "MtpTypes.h" + +class MtpDataPacket; + +class MtpStorageInfo { +public: + MtpStorageID mStorageID; + uint16_t mStorageType; + uint16_t mFileSystemType; + uint16_t mAccessCapability; + uint64_t mMaxCapacity; + uint64_t mFreeSpaceBytes; + uint32_t mFreeSpaceObjects; + char* mStorageDescription; + char* mVolumeIdentifier; + +public: + explicit MtpStorageInfo(MtpStorageID id); + virtual ~MtpStorageInfo(); + + bool read(MtpDataPacket& packet); + + void print(); +}; + +#endif // _MTP_STORAGE_INFO_H diff --git a/mtp/ffs/MtpStringBuffer.cpp b/mtp/ffs/MtpStringBuffer.cpp new file mode 100644 index 000000000..e2302df75 --- /dev/null +++ b/mtp/ffs/MtpStringBuffer.cpp @@ -0,0 +1,108 @@ +/* + * 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 "MtpStringBuffer" + +#include <codecvt> +#include <locale> +#include <string> +#include <vector> + +#include "MtpDataPacket.h" +#include "MtpStringBuffer.h" + +namespace { + +std::wstring_convert<std::codecvt_utf8_utf16<char16_t>,char16_t> gConvert; + +static std::string utf16ToUtf8(std::u16string input_str) { + return gConvert.to_bytes(input_str); +} + +static std::u16string utf8ToUtf16(std::string input_str) { + return gConvert.from_bytes(input_str); +} + +} // namespace + +MtpStringBuffer::MtpStringBuffer(const char* src) +{ + set(src); +} + +MtpStringBuffer::MtpStringBuffer(const uint16_t* src) +{ + set(src); +} + +MtpStringBuffer::MtpStringBuffer(const MtpStringBuffer& src) +{ + mString = src.mString; +} + +void MtpStringBuffer::set(const char* src) { + mString = std::string(src); +} + +void MtpStringBuffer::set(const uint16_t* src) { + mString = utf16ToUtf8(std::u16string((const char16_t*)src)); +} + +bool MtpStringBuffer::readFromPacket(MtpDataPacket* packet) { + uint8_t count; + if (!packet->getUInt8(count)) + return false; + if (count == 0) + return true; + + std::vector<char16_t> buffer(count); + for (int i = 0; i < count; i++) { + uint16_t ch; + if (!packet->getUInt16(ch)) + return false; + buffer[i] = ch; + } + if (buffer[count-1] != '\0') { + MTPE("Mtp string not null terminated\n"); + return false; + } + mString = utf16ToUtf8(std::u16string(buffer.data())); + return true; +} + +void MtpStringBuffer::writeToPacket(MtpDataPacket* packet) const { + std::u16string src16 = utf8ToUtf16(mString); + int count = src16.length(); + + if (count == 0) { + packet->putUInt8(0); + return; + } + packet->putUInt8(std::min(count + 1, MTP_STRING_MAX_CHARACTER_NUMBER)); + + int i = 0; + for (char16_t &c : src16) { + if (i == MTP_STRING_MAX_CHARACTER_NUMBER - 1) { + // Leave a slot for null termination. + MTPD("Mtp truncating long string\n"); + break; + } + packet->putUInt16(c); + i++; + } + // only terminate with zero if string is not empty + packet->putUInt16(0); +} diff --git a/mtp/ffs/MtpStringBuffer.h b/mtp/ffs/MtpStringBuffer.h new file mode 100644 index 000000000..0006cdbca --- /dev/null +++ b/mtp/ffs/MtpStringBuffer.h @@ -0,0 +1,66 @@ +/* + * 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. + */ + +#ifndef _MTP_STRING_BUFFER_H +#define _MTP_STRING_BUFFER_H + +#include <log/log.h> +#include <stdint.h> +#include <string> + +// Max Character number of a MTP String +#define MTP_STRING_MAX_CHARACTER_NUMBER 255 + +class MtpDataPacket; + +// Represents a utf8 string, with a maximum of 255 characters +class MtpStringBuffer { + +private: + std::string mString; + +public: + MtpStringBuffer() {}; + ~MtpStringBuffer() {}; + + explicit MtpStringBuffer(const char* src); + explicit MtpStringBuffer(const uint16_t* src); + MtpStringBuffer(const MtpStringBuffer& src); + + void set(const char* src); + void set(const uint16_t* src); + + inline void append(const char* other); + inline void append(MtpStringBuffer &other); + + bool readFromPacket(MtpDataPacket* packet); + void writeToPacket(MtpDataPacket* packet) const; + + inline bool isEmpty() const { return mString.empty(); } + inline int size() const { return mString.length(); } + + inline operator const char*() const { return mString.c_str(); } +}; + +inline void MtpStringBuffer::append(const char* other) { + mString += other; +} + +inline void MtpStringBuffer::append(MtpStringBuffer &other) { + mString += other.mString; +} + +#endif // _MTP_STRING_BUFFER_H diff --git a/mtp/ffs/MtpTypes.h b/mtp/ffs/MtpTypes.h new file mode 100644 index 000000000..9c37b8c1a --- /dev/null +++ b/mtp/ffs/MtpTypes.h @@ -0,0 +1,78 @@ +/* + * 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. + */ + +#ifndef _MTP_TYPES_H +#define _MTP_TYPES_H + +#include <stdint.h> +#include <vector> + +typedef int32_t int128_t[4]; +typedef uint32_t uint128_t[4]; + +typedef uint16_t MtpOperationCode; +typedef uint16_t MtpResponseCode; +typedef uint16_t MtpEventCode; +typedef uint32_t MtpSessionID; +typedef uint32_t MtpStorageID; +typedef uint32_t MtpTransactionID; +typedef uint16_t MtpPropertyCode; +typedef uint16_t MtpDataType; +typedef uint16_t MtpObjectFormat; +typedef MtpPropertyCode MtpDeviceProperty; +typedef MtpPropertyCode MtpObjectProperty; + +// object handles are unique across all storage but only within a single session. +// object handles cannot be reused after an object is deleted. +// values 0x00000000 and 0xFFFFFFFF are reserved for special purposes. +typedef uint32_t MtpObjectHandle; + +// Special values +#define MTP_PARENT_ROOT 0xFFFFFFFF // parent is root of the storage +#define kInvalidObjectHandle 0xFFFFFFFF + +class MtpStorage; +class MtpDevice; +class MtpProperty; + +typedef std::vector<MtpStorage *> MtpStorageList; +typedef std::vector<MtpDevice*> MtpDeviceList; +typedef std::vector<MtpProperty*> MtpPropertyList; + +typedef std::vector<uint8_t> UInt8List; +typedef std::vector<uint16_t> UInt16List; +typedef std::vector<uint32_t> UInt32List; +typedef std::vector<uint64_t> UInt64List; +typedef std::vector<int8_t> Int8List; +typedef std::vector<int16_t> Int16List; +typedef std::vector<int32_t> Int32List; +typedef std::vector<int64_t> Int64List; + +typedef UInt16List MtpObjectPropertyList; +typedef UInt16List MtpDevicePropertyList; +typedef UInt16List MtpObjectFormatList; +typedef UInt32List MtpObjectHandleList; +typedef UInt16List MtpObjectPropertyList; +typedef UInt32List MtpStorageIDList; + +enum UrbPacketDivisionMode { + // First packet only contains a header. + FIRST_PACKET_ONLY_HEADER, + // First packet contains payload much as possible. + FIRST_PACKET_HAS_PAYLOAD +}; + +#endif // _MTP_TYPES_H diff --git a/mtp/ffs/MtpUtils.cpp b/mtp/ffs/MtpUtils.cpp new file mode 100644 index 000000000..80c01bf16 --- /dev/null +++ b/mtp/ffs/MtpUtils.cpp @@ -0,0 +1,263 @@ +/* + * 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 "MtpUtils" + +#include <android-base/logging.h> +#include <android-base/unique_fd.h> +#include <dirent.h> +#include <fcntl.h> +#include <string> +#include <sys/sendfile.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <stdio.h> +#include <time.h> +#include <unistd.h> + +#include "MtpUtils.h" + +using namespace std; + +constexpr unsigned long FILE_COPY_SIZE = 262144; + +static void access_ok(const char *path) { + if (access(path, F_OK) == -1) { + // Ignore. Failure could be common in cases of delete where + // the metadata was updated through other paths. + } +} + +/* +DateTime strings follow a compatible subset of the definition found in ISO 8601, and +take the form of a Unicode string formatted as: "YYYYMMDDThhmmss.s". In this +representation, YYYY shall be replaced by the year, MM replaced by the month (01-12), +DD replaced by the day (01-31), T is a constant character 'T' delimiting time from date, +hh is replaced by the hour (00-23), mm is replaced by the minute (00-59), and ss by the +second (00-59). The ".s" is optional, and represents tenths of a second. +This is followed by a UTC offset given as "[+-]zzzz" or the literal "Z", meaning UTC. +*/ + +bool parseDateTime(const char* dateTime, time_t& outSeconds) { + int year, month, day, hour, minute, second; + if (sscanf(dateTime, "%04d%02d%02dT%02d%02d%02d", + &year, &month, &day, &hour, &minute, &second) != 6) + return false; + + // skip optional tenth of second + const char* tail = dateTime + 15; + if (tail[0] == '.' && tail[1]) tail += 2; + + // FIXME: "Z" means UTC, but non-"Z" doesn't mean local time. + // It might be that you're in Asia/Seoul on vacation and your Android + // device has noticed this via the network, but your camera was set to + // America/Los_Angeles once when you bought it and doesn't know where + // it is right now, so the camera says "20160106T081700-0800" but we + // just ignore the "-0800" and assume local time which is actually "+0900". + // I think to support this (without switching to Java or using icu4c) + // you'd want to always use timegm(3) and then manually add/subtract + // the UTC offset parsed from the string (taking care of wrapping). + // mktime(3) ignores the tm_gmtoff field, so you can't let it do the work. + bool useUTC = (tail[0] == 'Z'); + + struct tm tm = {}; + tm.tm_sec = second; + tm.tm_min = minute; + tm.tm_hour = hour; + tm.tm_mday = day; + tm.tm_mon = month - 1; // mktime uses months in 0 - 11 range + tm.tm_year = year - 1900; + tm.tm_isdst = -1; + outSeconds = useUTC ? timegm(&tm) : mktime(&tm); + + return true; +} + +void formatDateTime(time_t seconds, char* buffer, int bufferLength) { + struct tm tm; + + localtime_r(&seconds, &tm); + snprintf(buffer, bufferLength, "%04d%02d%02dT%02d%02d%02d", + tm.tm_year + 1900, + tm.tm_mon + 1, // localtime_r uses months in 0 - 11 range + tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); +} + +int makeFolder(const char *path) { + mode_t mask = umask(0); + int ret = mkdir((const char *)path, DIR_PERM); + umask(mask); + if (ret && ret != -EEXIST) { + PLOG(ERROR) << "Failed to create folder " << path; + ret = -1; + } else { + chown((const char *)path, getuid(), FILE_GROUP); + } + access_ok(path); + return ret; +} + +/** + * Copies target path and all children to destination path. + * + * Returns 0 on success or a negative value indicating number of failures + */ +int copyRecursive(const char *fromPath, const char *toPath) { + int ret = 0; + string fromPathStr(fromPath); + string toPathStr(toPath); + + DIR* dir = opendir(fromPath); + if (!dir) { + PLOG(ERROR) << "opendir " << fromPath << " failed"; + return -1; + } + if (fromPathStr[fromPathStr.size()-1] != '/') + fromPathStr += '/'; + if (toPathStr[toPathStr.size()-1] != '/') + toPathStr += '/'; + + struct dirent* entry; + while ((entry = readdir(dir))) { + const char* name = entry->d_name; + + // ignore "." and ".." + if (name[0] == '.' && (name[1] == 0 || (name[1] == '.' && name[2] == 0))) { + continue; + } + string oldFile = fromPathStr + name; + string newFile = toPathStr + name; + + if (entry->d_type == DT_DIR) { + ret += makeFolder(newFile.c_str()); + ret += copyRecursive(oldFile.c_str(), newFile.c_str()); + } else { + ret += copyFile(oldFile.c_str(), newFile.c_str()); + } + } + return ret; +} + +int copyFile(const char *fromPath, const char *toPath) { + auto start = std::chrono::steady_clock::now(); + + android::base::unique_fd fromFd(open(fromPath, O_RDONLY)); + if (fromFd == -1) { + PLOG(ERROR) << "Failed to open copy from " << fromPath; + return -1; + } + android::base::unique_fd toFd(open(toPath, O_CREAT | O_WRONLY, FILE_PERM)); + if (toFd == -1) { + PLOG(ERROR) << "Failed to open copy to " << toPath; + return -1; + } + off_t offset = 0; + + struct stat sstat = {}; + if (stat(fromPath, &sstat) == -1) + return -1; + + off_t length = sstat.st_size; + int ret = 0; + + while (offset < length) { + ssize_t transfer_length = std::min(length - offset, (off_t) FILE_COPY_SIZE); + ret = sendfile(toFd, fromFd, &offset, transfer_length); + if (ret != transfer_length) { + ret = -1; + PLOG(ERROR) << "Copying failed!"; + break; + } + } + auto end = std::chrono::steady_clock::now(); + std::chrono::duration<double> diff = end - start; + LOG(DEBUG) << "Copied a file with MTP. Time: " << diff.count() << " s, Size: " << length << + ", Rate: " << ((double) length) / diff.count() << " bytes/s"; + chown(toPath, getuid(), FILE_GROUP); + access_ok(toPath); + return ret == -1 ? -1 : 0; +} + +void deleteRecursive(const char* path) { + string pathStr(path); + if (pathStr[pathStr.size()-1] != '/') { + pathStr += '/'; + } + + DIR* dir = opendir(path); + if (!dir) { + PLOG(ERROR) << "opendir " << path << " failed"; + return; + } + + struct dirent* entry; + while ((entry = readdir(dir))) { + const char* name = entry->d_name; + + // ignore "." and ".." + if (name[0] == '.' && (name[1] == 0 || (name[1] == '.' && name[2] == 0))) { + continue; + } + string childPath = pathStr + name; + int success; + if (entry->d_type == DT_DIR) { + deleteRecursive(childPath.c_str()); + success = rmdir(childPath.c_str()); + } else { + success = unlink(childPath.c_str()); + } + access_ok(childPath.c_str()); + if (success == -1) + PLOG(ERROR) << "Deleting path " << childPath << " failed"; + } + closedir(dir); +} + +bool deletePath(const char* path) { + struct stat statbuf; + int success; + if (stat(path, &statbuf) == 0) { + if (S_ISDIR(statbuf.st_mode)) { + // rmdir will fail if the directory is non empty, so + // there is no need to keep errors from deleteRecursive + deleteRecursive(path); + success = rmdir(path); + } else { + success = unlink(path); + } + } else { + PLOG(ERROR) << "deletePath stat failed for " << path; + return false; + } + if (success == -1) + PLOG(ERROR) << "Deleting path " << path << " failed"; + access_ok(path); + return success == 0; +} + +int renameTo(const char *oldPath, const char *newPath) { + int ret = rename(oldPath, newPath); + access_ok(oldPath); + access_ok(newPath); + return ret; +} + +// Calls access(2) on the path to update underlying filesystems, +// then closes the fd. +void closeObjFd(int fd, const char *path) { + close(fd); + access_ok(path); +} diff --git a/mtp/ffs/MtpUtils.h b/mtp/ffs/MtpUtils.h new file mode 100644 index 000000000..4eae95e67 --- /dev/null +++ b/mtp/ffs/MtpUtils.h @@ -0,0 +1,38 @@ +/* + * 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. + */ + +#ifndef _MTP_UTILS_H +#define _MTP_UTILS_H + +#include "private/android_filesystem_config.h" + +#include <stdint.h> + +constexpr int FILE_GROUP = AID_MEDIA_RW; +constexpr int FILE_PERM = 0664; +constexpr int DIR_PERM = 0775; + +bool parseDateTime(const char* dateTime, time_t& outSeconds); +void formatDateTime(time_t seconds, char* buffer, int bufferLength); + +int makeFolder(const char *path); +int copyRecursive(const char *fromPath, const char *toPath); +int copyFile(const char *fromPath, const char *toPath); +bool deletePath(const char* path); +int renameTo(const char *oldPath, const char *newPath); + +void closeObjFd(int fd, const char *path); +#endif // _MTP_UTILS_H diff --git a/mtp/ffs/NOTICE b/mtp/ffs/NOTICE new file mode 100644 index 000000000..c5b1efa7a --- /dev/null +++ b/mtp/ffs/NOTICE @@ -0,0 +1,190 @@ + + Copyright (c) 2005-2008, 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. + + 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. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/mtp/ffs/PosixAsyncIO.cpp b/mtp/ffs/PosixAsyncIO.cpp new file mode 100644 index 000000000..435000afc --- /dev/null +++ b/mtp/ffs/PosixAsyncIO.cpp @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2016 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 <android-base/logging.h> +#include <condition_variable> +#include <memory> +#include <mutex> +#include <queue> +#include <unistd.h> + +#include "PosixAsyncIO.h" + +namespace { + +void read_func(struct aiocb *aiocbp) { + aiocbp->ret = TEMP_FAILURE_RETRY(pread(aiocbp->aio_fildes, + aiocbp->aio_buf, aiocbp->aio_nbytes, aiocbp->aio_offset)); + if (aiocbp->ret == -1) aiocbp->error = errno; +} + +void write_func(struct aiocb *aiocbp) { + aiocbp->ret = TEMP_FAILURE_RETRY(pwrite(aiocbp->aio_fildes, + aiocbp->aio_buf, aiocbp->aio_nbytes, aiocbp->aio_offset)); + if (aiocbp->ret == -1) aiocbp->error = errno; +} + +} // end anonymous namespace + +aiocb::~aiocb() { + CHECK(!thread.joinable()); +} + +int aio_read(struct aiocb *aiocbp) { + aiocbp->thread = std::thread(read_func, aiocbp); + return 0; +} + +int aio_write(struct aiocb *aiocbp) { + aiocbp->thread = std::thread(write_func, aiocbp); + return 0; +} + +int aio_error(const struct aiocb *aiocbp) { + return aiocbp->error; +} + +ssize_t aio_return(struct aiocb *aiocbp) { + return aiocbp->ret; +} + +int aio_suspend(struct aiocb *aiocbp[], int n, + const struct timespec *) { + for (int i = 0; i < n; i++) { + aiocbp[i]->thread.join(); + } + return 0; +} + +void aio_prepare(struct aiocb *aiocbp, void* buf, size_t count, off_t offset) { + aiocbp->aio_buf = buf; + aiocbp->aio_offset = offset; + aiocbp->aio_nbytes = count; +} diff --git a/mtp/ffs/PosixAsyncIO.h b/mtp/ffs/PosixAsyncIO.h new file mode 100644 index 000000000..69ab9a5bc --- /dev/null +++ b/mtp/ffs/PosixAsyncIO.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2016 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. + */ + +#ifndef _POSIXASYNCIO_H +#define _POSIXASYNCIO_H + +#include <sys/cdefs.h> +#include <sys/types.h> +#include <time.h> +#include <thread> +#include <unistd.h> + +/** + * Provides a subset of POSIX aio operations. + */ + +struct aiocb { + int aio_fildes; + void *aio_buf; + + off_t aio_offset; + size_t aio_nbytes; + + // Used internally + std::thread thread; + ssize_t ret; + int error; + + ~aiocb(); +}; + +// Submit a request for IO to be completed +int aio_read(struct aiocb *); +int aio_write(struct aiocb *); + +// Suspend current thread until given IO is complete, at which point +// its return value and any errors can be accessed +// All submitted requests must have a corresponding suspend. +// aiocb->aio_buf must refer to valid memory until after the suspend call +int aio_suspend(struct aiocb *[], int, const struct timespec *); +int aio_error(const struct aiocb *); +ssize_t aio_return(struct aiocb *); + +// Helper method for setting aiocb members +void aio_prepare(struct aiocb *, void*, size_t, off_t); + +#endif // POSIXASYNCIO_H + diff --git a/mtp/ffs/btree.cpp b/mtp/ffs/btree.cpp new file mode 100644 index 000000000..78b39a779 --- /dev/null +++ b/mtp/ffs/btree.cpp @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2014 TeamWin - bigbiff and Dees_Troy mtp database conversion to C++ + * + * 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 <utils/threads.h> +#include "btree.hpp" +#include "MtpDebug.h" + +Tree::Tree(MtpObjectHandle handle, MtpObjectHandle parent, const std::string& name) + : Node(handle, parent, name), alreadyRead(false) { +} + +Tree::~Tree() { + for (std::map<MtpObjectHandle, Node*>::iterator it = entries.begin(); it != entries.end(); ++it) + delete it->second; + entries.clear(); +} + +int Tree::getCount(void) { + int count = entries.size(); + MTPD("Tree::getCount::node count: %d\n", count); + return count; +} + +void Tree::addEntry(Node* node) { + if (node->Mtpid() == 0) { + MTPE("Tree::addEntry: not adding node with 0 handle.\n"); + return; + } + if (node->Mtpid() == node->getMtpParentId()) { + MTPE("Tree::addEntry: not adding node with handle %u == parent.\n", node->Mtpid()); + return; + } + entries[node->Mtpid()] = node; +} + +Node* Tree::findEntryByName(std::string name) { + for (std::map<MtpObjectHandle, Node*>::iterator it = entries.begin(); it != entries.end(); ++it) + { + Node* node = it->second; + if (node->getName().compare(name) == 0 && node->Mtpid() > 0) + return node; + } + return NULL; +} + +Node* Tree::findNode(MtpObjectHandle handle) { + std::map<MtpObjectHandle, Node*>::iterator it = entries.find(handle); + if (it != entries.end()) + return it->second; + return NULL; +} + +void Tree::getmtpids(MtpObjectHandleList* mtpids) { + for (std::map<MtpObjectHandle, Node*>::iterator it = entries.begin(); it != entries.end(); ++it) + mtpids->push_back(it->second->Mtpid()); +} + +void Tree::deleteNode(MtpObjectHandle handle) { + std::map<MtpObjectHandle, Node*>::iterator it = entries.find(handle); + if (it != entries.end()) { + delete it->second; + entries.erase(it); + } +} diff --git a/mtp/ffs/btree.hpp b/mtp/ffs/btree.hpp new file mode 100644 index 000000000..e1aad3636 --- /dev/null +++ b/mtp/ffs/btree.hpp @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2014 TeamWin - bigbiff and Dees_Troy mtp database conversion to C++ + * + * 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. + */ + +#ifndef BTREE_HPP +#define BTREE_HPP + +#include <vector> +#include <string> +#include <map> +#include "MtpTypes.h" + +// A directory entry +class Node { + MtpObjectHandle handle; + MtpObjectHandle parent; + std::string name; // name only without path + +public: + Node(); + Node(MtpObjectHandle handle, MtpObjectHandle parent, const std::string& name); + virtual ~Node() {} + + virtual bool isDir() const { return false; } + + void rename(const std::string& newName); + MtpObjectHandle Mtpid() const; + MtpObjectHandle getMtpParentId() const; + const std::string& getName() const; + + void addProperty(MtpPropertyCode property, uint64_t valueInt, std::string valueStr, MtpDataType dataType); + void updateProperty(MtpPropertyCode property, uint64_t valueInt, std::string valueStr, MtpDataType dataType); + void addProperties(const std::string& path, int storageID); + uint64_t getIntProperty(MtpPropertyCode property); + struct mtpProperty { + MtpPropertyCode property; + MtpDataType dataType; + uint64_t valueInt; + std::string valueStr; + mtpProperty() : property(0), dataType(0), valueInt(0) {} + }; + std::vector<mtpProperty>& getMtpProps(); + std::vector<mtpProperty> mtpProp; + const mtpProperty& getProperty(MtpPropertyCode property); +}; + +// A directory +class Tree : public Node { + std::map<MtpObjectHandle, Node*> entries; + bool alreadyRead; +public: + Tree(MtpObjectHandle handle, MtpObjectHandle parent, const std::string& name); + ~Tree(); + + virtual bool isDir() const { return true; } + + void addEntry(Node* node); + Node* findNode(MtpObjectHandle handle); + void getmtpids(MtpObjectHandleList* mtpids); + void deleteNode(MtpObjectHandle handle); + std::string getPath(Node* node); + int getMtpParentId() { return Node::getMtpParentId(); } + int getMtpParentId(Node* node); + Node* findEntryByName(std::string name); + int getCount(); + bool wasAlreadyRead() const { return alreadyRead; } + void setAlreadyRead(bool b) { alreadyRead = b; } +}; + +#endif diff --git a/mtp/ffs/mtp.h b/mtp/ffs/mtp.h new file mode 100644 index 000000000..9f6c3239a --- /dev/null +++ b/mtp/ffs/mtp.h @@ -0,0 +1,616 @@ +/* + * 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. + */ + +#ifndef _MTP_H +#define _MTP_H + +#include <stdint.h> +#include <stdlib.h> + +#define MTP_STANDARD_VERSION 100 + +// Container Types +#define MTP_CONTAINER_TYPE_UNDEFINED 0 +#define MTP_CONTAINER_TYPE_COMMAND 1 +#define MTP_CONTAINER_TYPE_DATA 2 +#define MTP_CONTAINER_TYPE_RESPONSE 3 +#define MTP_CONTAINER_TYPE_EVENT 4 + +// Container Offsets +#define MTP_CONTAINER_LENGTH_OFFSET 0 +#define MTP_CONTAINER_TYPE_OFFSET 4 +#define MTP_CONTAINER_CODE_OFFSET 6 +#define MTP_CONTAINER_TRANSACTION_ID_OFFSET 8 +#define MTP_CONTAINER_PARAMETER_OFFSET 12 +#define MTP_CONTAINER_HEADER_SIZE 12 + +// Maximum buffer size for a MTP packet. +#define MTP_BUFFER_SIZE 16384 + +// MTP Data Types +#define MTP_TYPE_UNDEFINED 0x0000 // Undefined +#define MTP_TYPE_INT8 0x0001 // Signed 8-bit integer +#define MTP_TYPE_UINT8 0x0002 // Unsigned 8-bit integer +#define MTP_TYPE_INT16 0x0003 // Signed 16-bit integer +#define MTP_TYPE_UINT16 0x0004 // Unsigned 16-bit integer +#define MTP_TYPE_INT32 0x0005 // Signed 32-bit integer +#define MTP_TYPE_UINT32 0x0006 // Unsigned 32-bit integer +#define MTP_TYPE_INT64 0x0007 // Signed 64-bit integer +#define MTP_TYPE_UINT64 0x0008 // Unsigned 64-bit integer +#define MTP_TYPE_INT128 0x0009 // Signed 128-bit integer +#define MTP_TYPE_UINT128 0x000A // Unsigned 128-bit integer +#define MTP_TYPE_AINT8 0x4001 // Array of signed 8-bit integers +#define MTP_TYPE_AUINT8 0x4002 // Array of unsigned 8-bit integers +#define MTP_TYPE_AINT16 0x4003 // Array of signed 16-bit integers +#define MTP_TYPE_AUINT16 0x4004 // Array of unsigned 16-bit integers +#define MTP_TYPE_AINT32 0x4005 // Array of signed 32-bit integers +#define MTP_TYPE_AUINT32 0x4006 // Array of unsigned 32-bit integers +#define MTP_TYPE_AINT64 0x4007 // Array of signed 64-bit integers +#define MTP_TYPE_AUINT64 0x4008 // Array of unsigned 64-bit integers +#define MTP_TYPE_AINT128 0x4009 // Array of signed 128-bit integers +#define MTP_TYPE_AUINT128 0x400A // Array of unsigned 128-bit integers +#define MTP_TYPE_STR 0xFFFF // Variable-length Unicode string + +// MTP Format Codes +#define MTP_FORMAT_UNDEFINED 0x3000 // Undefined object +#define MTP_FORMAT_ASSOCIATION 0x3001 // Association (for example, a folder) +#define MTP_FORMAT_SCRIPT 0x3002 // Device model-specific script +#define MTP_FORMAT_EXECUTABLE 0x3003 // Device model-specific binary executable +#define MTP_FORMAT_TEXT 0x3004 // Text file +#define MTP_FORMAT_HTML 0x3005 // Hypertext Markup Language file (text) +#define MTP_FORMAT_DPOF 0x3006 // Digital Print Order Format file (text) +#define MTP_FORMAT_AIFF 0x3007 // Audio clip +#define MTP_FORMAT_WAV 0x3008 // Audio clip +#define MTP_FORMAT_MP3 0x3009 // Audio clip +#define MTP_FORMAT_AVI 0x300A // Video clip +#define MTP_FORMAT_MPEG 0x300B // Video clip +#define MTP_FORMAT_ASF 0x300C // Microsoft Advanced Streaming Format (video) +#define MTP_FORMAT_DEFINED 0x3800 // Unknown image object +#define MTP_FORMAT_EXIF_JPEG 0x3801 // Exchangeable File Format, JEIDA standard +#define MTP_FORMAT_TIFF_EP 0x3802 // Tag Image File Format for Electronic Photography +#define MTP_FORMAT_FLASHPIX 0x3803 // Structured Storage Image Format +#define MTP_FORMAT_BMP 0x3804 // Microsoft Windows Bitmap file +#define MTP_FORMAT_CIFF 0x3805 // Canon Camera Image File Format +#define MTP_FORMAT_GIF 0x3807 // Graphics Interchange Format +#define MTP_FORMAT_JFIF 0x3808 // JPEG File Interchange Format +#define MTP_FORMAT_CD 0x3809 // PhotoCD Image Pac +#define MTP_FORMAT_PICT 0x380A // Quickdraw Image Format +#define MTP_FORMAT_PNG 0x380B // Portable Network Graphics +#define MTP_FORMAT_TIFF 0x380D // Tag Image File Format +#define MTP_FORMAT_TIFF_IT 0x380E // Tag Image File Format for Information Technology (graphic arts) +#define MTP_FORMAT_JP2 0x380F // JPEG2000 Baseline File Format +#define MTP_FORMAT_JPX 0x3810 // JPEG2000 Extended File Format +#define MTP_FORMAT_DNG 0x3811 // Digital Negative +#define MTP_FORMAT_HEIF 0x3812 // HEIF images +#define MTP_FORMAT_UNDEFINED_FIRMWARE 0xB802 +#define MTP_FORMAT_WINDOWS_IMAGE_FORMAT 0xB881 +#define MTP_FORMAT_UNDEFINED_AUDIO 0xB900 +#define MTP_FORMAT_WMA 0xB901 +#define MTP_FORMAT_OGG 0xB902 +#define MTP_FORMAT_AAC 0xB903 +#define MTP_FORMAT_AUDIBLE 0xB904 +#define MTP_FORMAT_FLAC 0xB906 +#define MTP_FORMAT_UNDEFINED_VIDEO 0xB980 +#define MTP_FORMAT_WMV 0xB981 +#define MTP_FORMAT_MP4_CONTAINER 0xB982 // ISO 14496-1 +#define MTP_FORMAT_MP2 0xB983 +#define MTP_FORMAT_3GP_CONTAINER 0xB984 // 3GPP file format. Details: http://www.3gpp.org/ftp/Specs/html-info/26244.htm (page title - \u201cTransparent end-to-end packet switched streaming service, 3GPP file format\u201d). +#define MTP_FORMAT_UNDEFINED_COLLECTION 0xBA00 +#define MTP_FORMAT_ABSTRACT_MULTIMEDIA_ALBUM 0xBA01 +#define MTP_FORMAT_ABSTRACT_IMAGE_ALBUM 0xBA02 +#define MTP_FORMAT_ABSTRACT_AUDIO_ALBUM 0xBA03 +#define MTP_FORMAT_ABSTRACT_VIDEO_ALBUM 0xBA04 +#define MTP_FORMAT_ABSTRACT_AV_PLAYLIST 0xBA05 +#define MTP_FORMAT_ABSTRACT_CONTACT_GROUP 0xBA06 +#define MTP_FORMAT_ABSTRACT_MESSAGE_FOLDER 0xBA07 +#define MTP_FORMAT_ABSTRACT_CHAPTERED_PRODUCTION 0xBA08 +#define MTP_FORMAT_ABSTRACT_AUDIO_PLAYLIST 0xBA09 +#define MTP_FORMAT_ABSTRACT_VIDEO_PLAYLIST 0xBA0A +#define MTP_FORMAT_ABSTRACT_MEDIACAST 0xBA0B // For use with mediacasts; references multimedia enclosures of RSS feeds or episodic content +#define MTP_FORMAT_WPL_PLAYLIST 0xBA10 +#define MTP_FORMAT_M3U_PLAYLIST 0xBA11 +#define MTP_FORMAT_MPL_PLAYLIST 0xBA12 +#define MTP_FORMAT_ASX_PLAYLIST 0xBA13 +#define MTP_FORMAT_PLS_PLAYLIST 0xBA14 +#define MTP_FORMAT_UNDEFINED_DOCUMENT 0xBA80 +#define MTP_FORMAT_ABSTRACT_DOCUMENT 0xBA81 +#define MTP_FORMAT_XML_DOCUMENT 0xBA82 +#define MTP_FORMAT_MS_WORD_DOCUMENT 0xBA83 +#define MTP_FORMAT_MHT_COMPILED_HTML_DOCUMENT 0xBA84 +#define MTP_FORMAT_MS_EXCEL_SPREADSHEET 0xBA85 +#define MTP_FORMAT_MS_POWERPOINT_PRESENTATION 0xBA86 +#define MTP_FORMAT_UNDEFINED_MESSAGE 0xBB00 +#define MTP_FORMAT_ABSTRACT_MESSSAGE 0xBB01 +#define MTP_FORMAT_UNDEFINED_CONTACT 0xBB80 +#define MTP_FORMAT_ABSTRACT_CONTACT 0xBB81 +#define MTP_FORMAT_VCARD_2 0xBB82 + +// MTP Object Property Codes +#define MTP_PROPERTY_STORAGE_ID 0xDC01 +#define MTP_PROPERTY_OBJECT_FORMAT 0xDC02 +#define MTP_PROPERTY_PROTECTION_STATUS 0xDC03 +#define MTP_PROPERTY_OBJECT_SIZE 0xDC04 +#define MTP_PROPERTY_ASSOCIATION_TYPE 0xDC05 +#define MTP_PROPERTY_ASSOCIATION_DESC 0xDC06 +#define MTP_PROPERTY_OBJECT_FILE_NAME 0xDC07 +#define MTP_PROPERTY_DATE_CREATED 0xDC08 +#define MTP_PROPERTY_DATE_MODIFIED 0xDC09 +#define MTP_PROPERTY_KEYWORDS 0xDC0A +#define MTP_PROPERTY_PARENT_OBJECT 0xDC0B +#define MTP_PROPERTY_ALLOWED_FOLDER_CONTENTS 0xDC0C +#define MTP_PROPERTY_HIDDEN 0xDC0D +#define MTP_PROPERTY_SYSTEM_OBJECT 0xDC0E +#define MTP_PROPERTY_PERSISTENT_UID 0xDC41 +#define MTP_PROPERTY_SYNC_ID 0xDC42 +#define MTP_PROPERTY_PROPERTY_BAG 0xDC43 +#define MTP_PROPERTY_NAME 0xDC44 +#define MTP_PROPERTY_CREATED_BY 0xDC45 +#define MTP_PROPERTY_ARTIST 0xDC46 +#define MTP_PROPERTY_DATE_AUTHORED 0xDC47 +#define MTP_PROPERTY_DESCRIPTION 0xDC48 +#define MTP_PROPERTY_URL_REFERENCE 0xDC49 +#define MTP_PROPERTY_LANGUAGE_LOCALE 0xDC4A +#define MTP_PROPERTY_COPYRIGHT_INFORMATION 0xDC4B +#define MTP_PROPERTY_SOURCE 0xDC4C +#define MTP_PROPERTY_ORIGIN_LOCATION 0xDC4D +#define MTP_PROPERTY_DATE_ADDED 0xDC4E +#define MTP_PROPERTY_NON_CONSUMABLE 0xDC4F +#define MTP_PROPERTY_CORRUPT_UNPLAYABLE 0xDC50 +#define MTP_PROPERTY_PRODUCER_SERIAL_NUMBER 0xDC51 +#define MTP_PROPERTY_REPRESENTATIVE_SAMPLE_FORMAT 0xDC81 +#define MTP_PROPERTY_REPRESENTATIVE_SAMPLE_SIZE 0xDC82 +#define MTP_PROPERTY_REPRESENTATIVE_SAMPLE_HEIGHT 0xDC83 +#define MTP_PROPERTY_REPRESENTATIVE_SAMPLE_WIDTH 0xDC84 +#define MTP_PROPERTY_REPRESENTATIVE_SAMPLE_DURATION 0xDC85 +#define MTP_PROPERTY_REPRESENTATIVE_SAMPLE_DATA 0xDC86 +#define MTP_PROPERTY_WIDTH 0xDC87 +#define MTP_PROPERTY_HEIGHT 0xDC88 +#define MTP_PROPERTY_DURATION 0xDC89 +#define MTP_PROPERTY_RATING 0xDC8A +#define MTP_PROPERTY_TRACK 0xDC8B +#define MTP_PROPERTY_GENRE 0xDC8C +#define MTP_PROPERTY_CREDITS 0xDC8D +#define MTP_PROPERTY_LYRICS 0xDC8E +#define MTP_PROPERTY_SUBSCRIPTION_CONTENT_ID 0xDC8F +#define MTP_PROPERTY_PRODUCED_BY 0xDC90 +#define MTP_PROPERTY_USE_COUNT 0xDC91 +#define MTP_PROPERTY_SKIP_COUNT 0xDC92 +#define MTP_PROPERTY_LAST_ACCESSED 0xDC93 +#define MTP_PROPERTY_PARENTAL_RATING 0xDC94 +#define MTP_PROPERTY_META_GENRE 0xDC95 +#define MTP_PROPERTY_COMPOSER 0xDC96 +#define MTP_PROPERTY_EFFECTIVE_RATING 0xDC97 +#define MTP_PROPERTY_SUBTITLE 0xDC98 +#define MTP_PROPERTY_ORIGINAL_RELEASE_DATE 0xDC99 +#define MTP_PROPERTY_ALBUM_NAME 0xDC9A +#define MTP_PROPERTY_ALBUM_ARTIST 0xDC9B +#define MTP_PROPERTY_MOOD 0xDC9C +#define MTP_PROPERTY_DRM_STATUS 0xDC9D +#define MTP_PROPERTY_SUB_DESCRIPTION 0xDC9E +#define MTP_PROPERTY_IS_CROPPED 0xDCD1 +#define MTP_PROPERTY_IS_COLOUR_CORRECTED 0xDCD2 +#define MTP_PROPERTY_IMAGE_BIT_DEPTH 0xDCD3 +#define MTP_PROPERTY_F_NUMBER 0xDCD4 +#define MTP_PROPERTY_EXPOSURE_TIME 0xDCD5 +#define MTP_PROPERTY_EXPOSURE_INDEX 0xDCD6 +#define MTP_PROPERTY_TOTAL_BITRATE 0xDE91 +#define MTP_PROPERTY_BITRATE_TYPE 0xDE92 +#define MTP_PROPERTY_SAMPLE_RATE 0xDE93 +#define MTP_PROPERTY_NUMBER_OF_CHANNELS 0xDE94 +#define MTP_PROPERTY_AUDIO_BIT_DEPTH 0xDE95 +#define MTP_PROPERTY_SCAN_TYPE 0xDE97 +#define MTP_PROPERTY_AUDIO_WAVE_CODEC 0xDE99 +#define MTP_PROPERTY_AUDIO_BITRATE 0xDE9A +#define MTP_PROPERTY_VIDEO_FOURCC_CODEC 0xDE9B +#define MTP_PROPERTY_VIDEO_BITRATE 0xDE9C +#define MTP_PROPERTY_FRAMES_PER_THOUSAND_SECONDS 0xDE9D +#define MTP_PROPERTY_KEYFRAME_DISTANCE 0xDE9E +#define MTP_PROPERTY_BUFFER_SIZE 0xDE9F +#define MTP_PROPERTY_ENCODING_QUALITY 0xDEA0 +#define MTP_PROPERTY_ENCODING_PROFILE 0xDEA1 +#define MTP_PROPERTY_DISPLAY_NAME 0xDCE0 +#define MTP_PROPERTY_BODY_TEXT 0xDCE1 +#define MTP_PROPERTY_SUBJECT 0xDCE2 +#define MTP_PROPERTY_PRIORITY 0xDCE3 +#define MTP_PROPERTY_GIVEN_NAME 0xDD00 +#define MTP_PROPERTY_MIDDLE_NAMES 0xDD01 +#define MTP_PROPERTY_FAMILY_NAME 0xDD02 +#define MTP_PROPERTY_PREFIX 0xDD03 +#define MTP_PROPERTY_SUFFIX 0xDD04 +#define MTP_PROPERTY_PHONETIC_GIVEN_NAME 0xDD05 +#define MTP_PROPERTY_PHONETIC_FAMILY_NAME 0xDD06 +#define MTP_PROPERTY_EMAIL_PRIMARY 0xDD07 +#define MTP_PROPERTY_EMAIL_PERSONAL_1 0xDD08 +#define MTP_PROPERTY_EMAIL_PERSONAL_2 0xDD09 +#define MTP_PROPERTY_EMAIL_BUSINESS_1 0xDD0A +#define MTP_PROPERTY_EMAIL_BUSINESS_2 0xDD0B +#define MTP_PROPERTY_EMAIL_OTHERS 0xDD0C +#define MTP_PROPERTY_PHONE_NUMBER_PRIMARY 0xDD0D +#define MTP_PROPERTY_PHONE_NUMBER_PERSONAL 0xDD0E +#define MTP_PROPERTY_PHONE_NUMBER_PERSONAL_2 0xDD0F +#define MTP_PROPERTY_PHONE_NUMBER_BUSINESS 0xDD10 +#define MTP_PROPERTY_PHONE_NUMBER_BUSINESS_2 0xDD11 +#define MTP_PROPERTY_PHONE_NUMBER_MOBILE 0xDD12 +#define MTP_PROPERTY_PHONE_NUMBER_MOBILE_2 0xDD13 +#define MTP_PROPERTY_FAX_NUMBER_PRIMARY 0xDD14 +#define MTP_PROPERTY_FAX_NUMBER_PERSONAL 0xDD15 +#define MTP_PROPERTY_FAX_NUMBER_BUSINESS 0xDD16 +#define MTP_PROPERTY_PAGER_NUMBER 0xDD17 +#define MTP_PROPERTY_PHONE_NUMBER_OTHERS 0xDD18 +#define MTP_PROPERTY_PRIMARY_WEB_ADDRESS 0xDD19 +#define MTP_PROPERTY_PERSONAL_WEB_ADDRESS 0xDD1A +#define MTP_PROPERTY_BUSINESS_WEB_ADDRESS 0xDD1B +#define MTP_PROPERTY_INSTANT_MESSANGER_ADDRESS 0xDD1C +#define MTP_PROPERTY_INSTANT_MESSANGER_ADDRESS_2 0xDD1D +#define MTP_PROPERTY_INSTANT_MESSANGER_ADDRESS_3 0xDD1E +#define MTP_PROPERTY_POSTAL_ADDRESS_PERSONAL_FULL 0xDD1F +#define MTP_PROPERTY_POSTAL_ADDRESS_PERSONAL_LINE_1 0xDD20 +#define MTP_PROPERTY_POSTAL_ADDRESS_PERSONAL_LINE_2 0xDD21 +#define MTP_PROPERTY_POSTAL_ADDRESS_PERSONAL_CITY 0xDD22 +#define MTP_PROPERTY_POSTAL_ADDRESS_PERSONAL_REGION 0xDD23 +#define MTP_PROPERTY_POSTAL_ADDRESS_PERSONAL_POSTAL_CODE 0xDD24 +#define MTP_PROPERTY_POSTAL_ADDRESS_PERSONAL_COUNTRY 0xDD25 +#define MTP_PROPERTY_POSTAL_ADDRESS_BUSINESS_FULL 0xDD26 +#define MTP_PROPERTY_POSTAL_ADDRESS_BUSINESS_LINE_1 0xDD27 +#define MTP_PROPERTY_POSTAL_ADDRESS_BUSINESS_LINE_2 0xDD28 +#define MTP_PROPERTY_POSTAL_ADDRESS_BUSINESS_CITY 0xDD29 +#define MTP_PROPERTY_POSTAL_ADDRESS_BUSINESS_REGION 0xDD2A +#define MTP_PROPERTY_POSTAL_ADDRESS_BUSINESS_POSTAL_CODE 0xDD2B +#define MTP_PROPERTY_POSTAL_ADDRESS_BUSINESS_COUNTRY 0xDD2C +#define MTP_PROPERTY_POSTAL_ADDRESS_OTHER_FULL 0xDD2D +#define MTP_PROPERTY_POSTAL_ADDRESS_OTHER_LINE_1 0xDD2E +#define MTP_PROPERTY_POSTAL_ADDRESS_OTHER_LINE_2 0xDD2F +#define MTP_PROPERTY_POSTAL_ADDRESS_OTHER_CITY 0xDD30 +#define MTP_PROPERTY_POSTAL_ADDRESS_OTHER_REGION 0xDD31 +#define MTP_PROPERTY_POSTAL_ADDRESS_OTHER_POSTAL_CODE 0xDD32 +#define MTP_PROPERTY_POSTAL_ADDRESS_OTHER_COUNTRY 0xDD33 +#define MTP_PROPERTY_ORGANIZATION_NAME 0xDD34 +#define MTP_PROPERTY_PHONETIC_ORGANIZATION_NAME 0xDD35 +#define MTP_PROPERTY_ROLE 0xDD36 +#define MTP_PROPERTY_BIRTHDATE 0xDD37 +#define MTP_PROPERTY_MESSAGE_TO 0xDD40 +#define MTP_PROPERTY_MESSAGE_CC 0xDD41 +#define MTP_PROPERTY_MESSAGE_BCC 0xDD42 +#define MTP_PROPERTY_MESSAGE_READ 0xDD43 +#define MTP_PROPERTY_MESSAGE_RECEIVED_TIME 0xDD44 +#define MTP_PROPERTY_MESSAGE_SENDER 0xDD45 +#define MTP_PROPERTY_ACTIVITY_BEGIN_TIME 0xDD50 +#define MTP_PROPERTY_ACTIVITY_END_TIME 0xDD51 +#define MTP_PROPERTY_ACTIVITY_LOCATION 0xDD52 +#define MTP_PROPERTY_ACTIVITY_REQUIRED_ATTENDEES 0xDD54 +#define MTP_PROPERTY_ACTIVITY_OPTIONAL_ATTENDEES 0xDD55 +#define MTP_PROPERTY_ACTIVITY_RESOURCES 0xDD56 +#define MTP_PROPERTY_ACTIVITY_ACCEPTED 0xDD57 +#define MTP_PROPERTY_ACTIVITY_TENTATIVE 0xDD58 +#define MTP_PROPERTY_ACTIVITY_DECLINED 0xDD59 +#define MTP_PROPERTY_ACTIVITY_REMAINDER_TIME 0xDD5A +#define MTP_PROPERTY_ACTIVITY_OWNER 0xDD5B +#define MTP_PROPERTY_ACTIVITY_STATUS 0xDD5C +#define MTP_PROPERTY_OWNER 0xDD5D +#define MTP_PROPERTY_EDITOR 0xDD5E +#define MTP_PROPERTY_WEBMASTER 0xDD5F +#define MTP_PROPERTY_URL_SOURCE 0xDD60 +#define MTP_PROPERTY_URL_DESTINATION 0xDD61 +#define MTP_PROPERTY_TIME_BOOKMARK 0xDD62 +#define MTP_PROPERTY_OBJECT_BOOKMARK 0xDD63 +#define MTP_PROPERTY_BYTE_BOOKMARK 0xDD64 +#define MTP_PROPERTY_LAST_BUILD_DATE 0xDD70 +#define MTP_PROPERTY_TIME_TO_LIVE 0xDD71 +#define MTP_PROPERTY_MEDIA_GUID 0xDD72 + +// MTP Device Property Codes +#define MTP_DEVICE_PROPERTY_UNDEFINED 0x5000 +#define MTP_DEVICE_PROPERTY_BATTERY_LEVEL 0x5001 +#define MTP_DEVICE_PROPERTY_FUNCTIONAL_MODE 0x5002 +#define MTP_DEVICE_PROPERTY_IMAGE_SIZE 0x5003 +#define MTP_DEVICE_PROPERTY_COMPRESSION_SETTING 0x5004 +#define MTP_DEVICE_PROPERTY_WHITE_BALANCE 0x5005 +#define MTP_DEVICE_PROPERTY_RGB_GAIN 0x5006 +#define MTP_DEVICE_PROPERTY_F_NUMBER 0x5007 +#define MTP_DEVICE_PROPERTY_FOCAL_LENGTH 0x5008 +#define MTP_DEVICE_PROPERTY_FOCUS_DISTANCE 0x5009 +#define MTP_DEVICE_PROPERTY_FOCUS_MODE 0x500A +#define MTP_DEVICE_PROPERTY_EXPOSURE_METERING_MODE 0x500B +#define MTP_DEVICE_PROPERTY_FLASH_MODE 0x500C +#define MTP_DEVICE_PROPERTY_EXPOSURE_TIME 0x500D +#define MTP_DEVICE_PROPERTY_EXPOSURE_PROGRAM_MODE 0x500E +#define MTP_DEVICE_PROPERTY_EXPOSURE_INDEX 0x500F +#define MTP_DEVICE_PROPERTY_EXPOSURE_BIAS_COMPENSATION 0x5010 +#define MTP_DEVICE_PROPERTY_DATETIME 0x5011 +#define MTP_DEVICE_PROPERTY_CAPTURE_DELAY 0x5012 +#define MTP_DEVICE_PROPERTY_STILL_CAPTURE_MODE 0x5013 +#define MTP_DEVICE_PROPERTY_CONTRAST 0x5014 +#define MTP_DEVICE_PROPERTY_SHARPNESS 0x5015 +#define MTP_DEVICE_PROPERTY_DIGITAL_ZOOM 0x5016 +#define MTP_DEVICE_PROPERTY_EFFECT_MODE 0x5017 +#define MTP_DEVICE_PROPERTY_BURST_NUMBER 0x5018 +#define MTP_DEVICE_PROPERTY_BURST_INTERVAL 0x5019 +#define MTP_DEVICE_PROPERTY_TIMELAPSE_NUMBER 0x501A +#define MTP_DEVICE_PROPERTY_TIMELAPSE_INTERVAL 0x501B +#define MTP_DEVICE_PROPERTY_FOCUS_METERING_MODE 0x501C +#define MTP_DEVICE_PROPERTY_UPLOAD_URL 0x501D +#define MTP_DEVICE_PROPERTY_ARTIST 0x501E +#define MTP_DEVICE_PROPERTY_COPYRIGHT_INFO 0x501F +#define MTP_DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER 0xD401 +#define MTP_DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME 0xD402 +#define MTP_DEVICE_PROPERTY_VOLUME 0xD403 +#define MTP_DEVICE_PROPERTY_SUPPORTED_FORMATS_ORDERED 0xD404 +#define MTP_DEVICE_PROPERTY_DEVICE_ICON 0xD405 +#define MTP_DEVICE_PROPERTY_PLAYBACK_RATE 0xD410 +#define MTP_DEVICE_PROPERTY_PLAYBACK_OBJECT 0xD411 +#define MTP_DEVICE_PROPERTY_PLAYBACK_CONTAINER_INDEX 0xD412 +#define MTP_DEVICE_PROPERTY_SESSION_INITIATOR_VERSION_INFO 0xD406 +#define MTP_DEVICE_PROPERTY_PERCEIVED_DEVICE_TYPE 0xD407 + +// MTP Operation Codes +#define MTP_OPERATION_GET_DEVICE_INFO 0x1001 +#define MTP_OPERATION_OPEN_SESSION 0x1002 +#define MTP_OPERATION_CLOSE_SESSION 0x1003 +#define MTP_OPERATION_GET_STORAGE_IDS 0x1004 +#define MTP_OPERATION_GET_STORAGE_INFO 0x1005 +#define MTP_OPERATION_GET_NUM_OBJECTS 0x1006 +#define MTP_OPERATION_GET_OBJECT_HANDLES 0x1007 +#define MTP_OPERATION_GET_OBJECT_INFO 0x1008 +#define MTP_OPERATION_GET_OBJECT 0x1009 +#define MTP_OPERATION_GET_THUMB 0x100A +#define MTP_OPERATION_DELETE_OBJECT 0x100B +#define MTP_OPERATION_SEND_OBJECT_INFO 0x100C +#define MTP_OPERATION_SEND_OBJECT 0x100D +#define MTP_OPERATION_INITIATE_CAPTURE 0x100E +#define MTP_OPERATION_FORMAT_STORE 0x100F +#define MTP_OPERATION_RESET_DEVICE 0x1010 +#define MTP_OPERATION_SELF_TEST 0x1011 +#define MTP_OPERATION_SET_OBJECT_PROTECTION 0x1012 +#define MTP_OPERATION_POWER_DOWN 0x1013 +#define MTP_OPERATION_GET_DEVICE_PROP_DESC 0x1014 +#define MTP_OPERATION_GET_DEVICE_PROP_VALUE 0x1015 +#define MTP_OPERATION_SET_DEVICE_PROP_VALUE 0x1016 +#define MTP_OPERATION_RESET_DEVICE_PROP_VALUE 0x1017 +#define MTP_OPERATION_TERMINATE_OPEN_CAPTURE 0x1018 +#define MTP_OPERATION_MOVE_OBJECT 0x1019 +#define MTP_OPERATION_COPY_OBJECT 0x101A +#define MTP_OPERATION_GET_PARTIAL_OBJECT 0x101B +#define MTP_OPERATION_INITIATE_OPEN_CAPTURE 0x101C +#define MTP_OPERATION_GET_OBJECT_PROPS_SUPPORTED 0x9801 +#define MTP_OPERATION_GET_OBJECT_PROP_DESC 0x9802 +#define MTP_OPERATION_GET_OBJECT_PROP_VALUE 0x9803 +#define MTP_OPERATION_SET_OBJECT_PROP_VALUE 0x9804 +#define MTP_OPERATION_GET_OBJECT_PROP_LIST 0x9805 +#define MTP_OPERATION_SET_OBJECT_PROP_LIST 0x9806 +#define MTP_OPERATION_GET_INTERDEPENDENT_PROP_DESC 0x9807 +#define MTP_OPERATION_SEND_OBJECT_PROP_LIST 0x9808 +#define MTP_OPERATION_GET_OBJECT_REFERENCES 0x9810 +#define MTP_OPERATION_SET_OBJECT_REFERENCES 0x9811 +#define MTP_OPERATION_SKIP 0x9820 + +// Android extensions for direct file IO + +// Same as GetPartialObject, but with 64 bit offset +#define MTP_OPERATION_GET_PARTIAL_OBJECT_64 0x95C1 +// Same as GetPartialObject64, but copying host to device +#define MTP_OPERATION_SEND_PARTIAL_OBJECT 0x95C2 +// Truncates file to 64 bit length +#define MTP_OPERATION_TRUNCATE_OBJECT 0x95C3 +// Must be called before using SendPartialObject and TruncateObject +#define MTP_OPERATION_BEGIN_EDIT_OBJECT 0x95C4 +// Called to commit changes made by SendPartialObject and TruncateObject +#define MTP_OPERATION_END_EDIT_OBJECT 0x95C5 + +// MTP Response Codes +#define MTP_RESPONSE_UNDEFINED 0x2000 +#define MTP_RESPONSE_OK 0x2001 +#define MTP_RESPONSE_GENERAL_ERROR 0x2002 +#define MTP_RESPONSE_SESSION_NOT_OPEN 0x2003 +#define MTP_RESPONSE_INVALID_TRANSACTION_ID 0x2004 +#define MTP_RESPONSE_OPERATION_NOT_SUPPORTED 0x2005 +#define MTP_RESPONSE_PARAMETER_NOT_SUPPORTED 0x2006 +#define MTP_RESPONSE_INCOMPLETE_TRANSFER 0x2007 +#define MTP_RESPONSE_INVALID_STORAGE_ID 0x2008 +#define MTP_RESPONSE_INVALID_OBJECT_HANDLE 0x2009 +#define MTP_RESPONSE_DEVICE_PROP_NOT_SUPPORTED 0x200A +#define MTP_RESPONSE_INVALID_OBJECT_FORMAT_CODE 0x200B +#define MTP_RESPONSE_STORAGE_FULL 0x200C +#define MTP_RESPONSE_OBJECT_WRITE_PROTECTED 0x200D +#define MTP_RESPONSE_STORE_READ_ONLY 0x200E +#define MTP_RESPONSE_ACCESS_DENIED 0x200F +#define MTP_RESPONSE_NO_THUMBNAIL_PRESENT 0x2010 +#define MTP_RESPONSE_SELF_TEST_FAILED 0x2011 +#define MTP_RESPONSE_PARTIAL_DELETION 0x2012 +#define MTP_RESPONSE_STORE_NOT_AVAILABLE 0x2013 +#define MTP_RESPONSE_SPECIFICATION_BY_FORMAT_UNSUPPORTED 0x2014 +#define MTP_RESPONSE_NO_VALID_OBJECT_INFO 0x2015 +#define MTP_RESPONSE_INVALID_CODE_FORMAT 0x2016 +#define MTP_RESPONSE_UNKNOWN_VENDOR_CODE 0x2017 +#define MTP_RESPONSE_CAPTURE_ALREADY_TERMINATED 0x2018 +#define MTP_RESPONSE_DEVICE_BUSY 0x2019 +#define MTP_RESPONSE_INVALID_PARENT_OBJECT 0x201A +#define MTP_RESPONSE_INVALID_DEVICE_PROP_FORMAT 0x201B +#define MTP_RESPONSE_INVALID_DEVICE_PROP_VALUE 0x201C +#define MTP_RESPONSE_INVALID_PARAMETER 0x201D +#define MTP_RESPONSE_SESSION_ALREADY_OPEN 0x201E +#define MTP_RESPONSE_TRANSACTION_CANCELLED 0x201F +#define MTP_RESPONSE_SPECIFICATION_OF_DESTINATION_UNSUPPORTED 0x2020 +#define MTP_RESPONSE_INVALID_OBJECT_PROP_CODE 0xA801 +#define MTP_RESPONSE_INVALID_OBJECT_PROP_FORMAT 0xA802 +#define MTP_RESPONSE_INVALID_OBJECT_PROP_VALUE 0xA803 +#define MTP_RESPONSE_INVALID_OBJECT_REFERENCE 0xA804 +#define MTP_RESPONSE_GROUP_NOT_SUPPORTED 0xA805 +#define MTP_RESPONSE_INVALID_DATASET 0xA806 +#define MTP_RESPONSE_SPECIFICATION_BY_GROUP_UNSUPPORTED 0xA807 +#define MTP_RESPONSE_SPECIFICATION_BY_DEPTH_UNSUPPORTED 0xA808 +#define MTP_RESPONSE_OBJECT_TOO_LARGE 0xA809 +#define MTP_RESPONSE_OBJECT_PROP_NOT_SUPPORTED 0xA80A + +// Supported Playback Formats +#define SUPPORTED_PLAYBACK_FORMAT_UNDEFINED 0x3000 +/** Format code for associations (folders and directories) */ +#define SUPPORTED_PLAYBACK_FORMAT_ASSOCIATION 0x3001 +/** Format code for script files */ +#define SUPPORTED_PLAYBACK_FORMAT_SCRIPT 0x3002 +/** Format code for executable files */ +#define SUPPORTED_PLAYBACK_FORMAT_EXECUTABLE 0x3003 +/** Format code for text files */ +#define SUPPORTED_PLAYBACK_FORMAT_TEXT 0x3004 +/** Format code for HTML files */ +#define SUPPORTED_PLAYBACK_FORMAT_HTML 0x3005 +/** Format code for DPOF files */ +#define SUPPORTED_PLAYBACK_FORMAT_DPOF 0x3006 +/** Format code for AIFF audio files */ +#define SUPPORTED_PLAYBACK_FORMAT_AIFF 0x3007 +/** Format code for WAV audio files */ +#define SUPPORTED_PLAYBACK_FORMAT_WAV 0x3008 +/** Format code for MP3 audio files */ +#define SUPPORTED_PLAYBACK_FORMAT_MP3 0x3009 +/** Format code for AVI video files */ +#define SUPPORTED_PLAYBACK_FORMAT_AVI 0x300A +/** Format code for MPEG video files */ +#define SUPPORTED_PLAYBACK_FORMAT_MPEG 0x300B +/** Format code for ASF files */ +#define SUPPORTED_PLAYBACK_FORMAT_ASF 0x300C +/** Format code for JPEG image files */ +#define SUPPORTED_PLAYBACK_FORMAT_EXIF_JPEG 0x3801 +/** Format code for TIFF EP image files */ +#define SUPPORTED_PLAYBACK_FORMAT_TIFF_EP 0x3802 +/** Format code for BMP image files */ +#define SUPPORTED_PLAYBACK_FORMAT_BMP 0x3804 +/** Format code for GIF image files */ +#define SUPPORTED_PLAYBACK_FORMAT_GIF 0x3807 +/** Format code for JFIF image files */ +#define SUPPORTED_PLAYBACK_FORMAT_JFIF 0x3808 +/** Format code for PICT image files */ +#define SUPPORTED_PLAYBACK_FORMAT_PICT 0x380A +/** Format code for PNG image files */ +#define SUPPORTED_PLAYBACK_FORMAT_PNG 0x380B +/** Format code for TIFF image files */ +#define SUPPORTED_PLAYBACK_FORMAT_TIFF 0x380D +/** Format code for JP2 files */ +#define SUPPORTED_PLAYBACK_FORMAT_JP2 0x380F +/** Format code for JPX files */ +#define SUPPORTED_PLAYBACK_FORMAT_JPX 0x3810 +/** Format code for firmware files */ +#define SUPPORTED_PLAYBACK_FORMAT_UNDEFINED_FIRMWARE 0xB802 +/** Format code for Windows image files */ +#define SUPPORTED_PLAYBACK_FORMAT_WINDOWS_IMAGE_FORMAT 0xB881 +/** Format code for undefined audio files files */ +#define SUPPORTED_PLAYBACK_FORMAT_UNDEFINED_AUDIO 0xB900 +/** Format code for WMA audio files */ +#define SUPPORTED_PLAYBACK_FORMAT_WMA 0xB901 +/** Format code for OGG audio files */ +#define SUPPORTED_PLAYBACK_FORMAT_OGG 0xB902 +/** Format code for AAC audio files */ +#define SUPPORTED_PLAYBACK_FORMAT_AAC 0xB903 +/** Format code for Audible audio files */ +#define SUPPORTED_PLAYBACK_FORMAT_AUDIBLE 0xB904 +/** Format code for FLAC audio files */ +#define SUPPORTED_PLAYBACK_FORMAT_FLAC 0xB906 +/** Format code for undefined video files */ +#define SUPPORTED_PLAYBACK_FORMAT_UNDEFINED_VIDEO 0xB980 +/** Format code for WMV video files */ +#define SUPPORTED_PLAYBACK_FORMAT_WMV 0xB981 +/** Format code for MP4 files */ +#define SUPPORTED_PLAYBACK_FORMAT_MP4_CONTAINER 0xB982 +/** Format code for MP2 files */ +#define SUPPORTED_PLAYBACK_FORMAT_MP2 0xB983 +/** Format code for 3GP files */ +#define SUPPORTED_PLAYBACK_FORMAT_3GP_CONTAINER 0xB984 +/** Format code for undefined collections */ +#define SUPPORTED_PLAYBACK_FORMAT_UNDEFINED_COLLECTION 0xBA00 +/** Format code for multimedia albums */ +#define SUPPORTED_PLAYBACK_FORMAT_ABSTRACT_MULTIMEDIA_ALBUM 0xBA01 +/** Format code for image albums */ +#define SUPPORTED_PLAYBACK_FORMAT_ABSTRACT_IMAGE_ALBUM 0xBA02 +/** Format code for audio albums */ +#define SUPPORTED_PLAYBACK_FORMAT_ABSTRACT_AUDIO_ALBUM 0xBA03 +/** Format code for video albums */ +#define SUPPORTED_PLAYBACK_FORMAT_ABSTRACT_VIDEO_ALBUM 0xBA04 +/** Format code for abstract AV playlists */ +#define SUPPORTED_PLAYBACK_FORMAT_ABSTRACT_AV_PLAYLIST 0xBA05 +/** Format code for abstract audio playlists */ +#define SUPPORTED_PLAYBACK_FORMAT_ABSTRACT_AUDIO_PLAYLIST 0xBA09 +/** Format code for abstract video playlists */ +#define SUPPORTED_PLAYBACK_FORMAT_ABSTRACT_VIDEO_PLAYLIST 0xBA0A +/** Format code for abstract mediacasts */ +#define SUPPORTED_PLAYBACK_FORMAT_ABSTRACT_MEDIACAST 0xBA0B +/** Format code for WPL playlist files */ +#define SUPPORTED_PLAYBACK_FORMAT_WPL_PLAYLIST 0xBA10 +/** Format code for M3u playlist files */ +#define SUPPORTED_PLAYBACK_FORMAT_M3U_PLAYLIST 0xBA11 +/** Format code for MPL playlist files */ +#define SUPPORTED_PLAYBACK_FORMAT_MPL_PLAYLIST 0xBA12 +/** Format code for ASX playlist files */ +#define SUPPORTED_PLAYBACK_FORMAT_ASX_PLAYLIST 0xBA13 +/** Format code for PLS playlist files */ +#define SUPPORTED_PLAYBACK_FORMAT_PLS_PLAYLIST 0xBA14 +/** Format code for undefined document files */ +#define SUPPORTED_PLAYBACK_FORMAT_UNDEFINED_DOCUMENT 0xBA80 +/** Format code for abstract documents */ +#define SUPPORTED_PLAYBACK_FORMAT_ABSTRACT_DOCUMENT 0xBA81 +/** Format code for XML documents */ +#define SUPPORTED_PLAYBACK_FORMAT_XML_DOCUMENT 0xBA82 +/** Format code for MS Word documents */ +#define SUPPORTED_PLAYBACK_FORMAT_MS_WORD_DOCUMENT 0xBA83 +/** Format code for MS Excel spreadsheets */ +#define SUPPORTED_PLAYBACK_FORMAT_MS_EXCEL_SPREADSHEET 0xBA85 +/** Format code for MS PowerPoint presentatiosn */ +#define SUPPORTED_PLAYBACK_FORMAT_MS_POWERPOINT_PRESENTATION 0xBA86 + +// MTP Event Codes +#define MTP_EVENT_UNDEFINED 0x4000 +#define MTP_EVENT_CANCEL_TRANSACTION 0x4001 +#define MTP_EVENT_OBJECT_ADDED 0x4002 +#define MTP_EVENT_OBJECT_REMOVED 0x4003 +#define MTP_EVENT_STORE_ADDED 0x4004 +#define MTP_EVENT_STORE_REMOVED 0x4005 +#define MTP_EVENT_DEVICE_PROP_CHANGED 0x4006 +#define MTP_EVENT_OBJECT_INFO_CHANGED 0x4007 +#define MTP_EVENT_DEVICE_INFO_CHANGED 0x4008 +#define MTP_EVENT_REQUEST_OBJECT_TRANSFER 0x4009 +#define MTP_EVENT_STORE_FULL 0x400A +#define MTP_EVENT_DEVICE_RESET 0x400B +#define MTP_EVENT_STORAGE_INFO_CHANGED 0x400C +#define MTP_EVENT_CAPTURE_COMPLETE 0x400D +#define MTP_EVENT_UNREPORTED_STATUS 0x400E +#define MTP_EVENT_OBJECT_PROP_CHANGED 0xC801 +#define MTP_EVENT_OBJECT_PROP_DESC_CHANGED 0xC802 +#define MTP_EVENT_OBJECT_REFERENCES_CHANGED 0xC803 + +// Storage Type +#define MTP_STORAGE_FIXED_ROM 0x0001 +#define MTP_STORAGE_REMOVABLE_ROM 0x0002 +#define MTP_STORAGE_FIXED_RAM 0x0003 +#define MTP_STORAGE_REMOVABLE_RAM 0x0004 + +// Storage File System +#define MTP_STORAGE_FILESYSTEM_FLAT 0x0001 +#define MTP_STORAGE_FILESYSTEM_HIERARCHICAL 0x0002 +#define MTP_STORAGE_FILESYSTEM_DCF 0x0003 + +// Storage Access Capability +#define MTP_STORAGE_READ_WRITE 0x0000 +#define MTP_STORAGE_READ_ONLY_WITHOUT_DELETE 0x0001 +#define MTP_STORAGE_READ_ONLY_WITH_DELETE 0x0002 + +// Association Type +#define MTP_ASSOCIATION_TYPE_UNDEFINED 0x0000 +#define MTP_ASSOCIATION_TYPE_GENERIC_FOLDER 0x0001 + +// MTP class reqeusts +#define MTP_REQ_CANCEL 0x64 +#define MTP_REQ_GET_EXT_EVENT_DATA 0x65 +#define MTP_REQ_RESET 0x66 +#define MTP_REQ_GET_DEVICE_STATUS 0x67 + +#endif // _MTP_H diff --git a/mtp/ffs/mtp_MtpDatabase.cpp b/mtp/ffs/mtp_MtpDatabase.cpp new file mode 100755 index 000000000..30076982b --- /dev/null +++ b/mtp/ffs/mtp_MtpDatabase.cpp @@ -0,0 +1,850 @@ +/* + * 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. + * + * Copyright (C) 2014 TeamWin - bigbiff and Dees_Troy mtp database conversion to C++ + */ + +#include <utils/Log.h> + +#include <assert.h> +#include <cutils/properties.h> +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <libgen.h> +#include <limits.h> +#include <stdio.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> +#include <map> +#include <string> + +#include "MtpDataPacket.h" +#include "MtpDatabase.h" +#include "MtpDebug.h" +#include "MtpObjectInfo.h" +#include "MtpProperty.h" +#include "MtpStorage.h" +#include "MtpStringBuffer.h" +#include "MtpUtils.h" +#include "mtp.h" +#include "mtp_MtpDatabase.hpp" + +IMtpDatabase::IMtpDatabase() { + storagenum = 0; + count = -1; +} + +IMtpDatabase::~IMtpDatabase() { + std::map<int, MtpStorage*>::iterator i; + for (i = storagemap.begin(); i != storagemap.end(); i++) { + delete i->second; + } +} + +int IMtpDatabase::DEVICE_PROPERTIES[3] = { MTP_DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER, + MTP_DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME, + MTP_DEVICE_PROPERTY_IMAGE_SIZE }; + +int IMtpDatabase::FILE_PROPERTIES[10] = { + // NOTE must match beginning of AUDIO_PROPERTIES, VIDEO_PROPERTIES + // and IMAGE_PROPERTIES below + MTP_PROPERTY_STORAGE_ID, MTP_PROPERTY_OBJECT_FORMAT, MTP_PROPERTY_PROTECTION_STATUS, + MTP_PROPERTY_OBJECT_SIZE, MTP_PROPERTY_OBJECT_FILE_NAME, MTP_PROPERTY_DATE_MODIFIED, + MTP_PROPERTY_PARENT_OBJECT, MTP_PROPERTY_PERSISTENT_UID, MTP_PROPERTY_NAME, + // TODO: why is DISPLAY_NAME not here? + MTP_PROPERTY_DATE_ADDED +}; + +int IMtpDatabase::AUDIO_PROPERTIES[19] = { + // NOTE must match FILE_PROPERTIES above + MTP_PROPERTY_STORAGE_ID, MTP_PROPERTY_OBJECT_FORMAT, MTP_PROPERTY_PROTECTION_STATUS, + MTP_PROPERTY_OBJECT_SIZE, MTP_PROPERTY_OBJECT_FILE_NAME, MTP_PROPERTY_DATE_MODIFIED, + MTP_PROPERTY_PARENT_OBJECT, MTP_PROPERTY_PERSISTENT_UID, MTP_PROPERTY_NAME, + MTP_PROPERTY_DISPLAY_NAME, MTP_PROPERTY_DATE_ADDED, + + // audio specific properties + MTP_PROPERTY_ARTIST, MTP_PROPERTY_ALBUM_NAME, MTP_PROPERTY_ALBUM_ARTIST, MTP_PROPERTY_TRACK, + MTP_PROPERTY_ORIGINAL_RELEASE_DATE, MTP_PROPERTY_DURATION, MTP_PROPERTY_GENRE, + MTP_PROPERTY_COMPOSER +}; + +int IMtpDatabase::VIDEO_PROPERTIES[15] = { + // NOTE must match FILE_PROPERTIES above + MTP_PROPERTY_STORAGE_ID, MTP_PROPERTY_OBJECT_FORMAT, MTP_PROPERTY_PROTECTION_STATUS, + MTP_PROPERTY_OBJECT_SIZE, MTP_PROPERTY_OBJECT_FILE_NAME, MTP_PROPERTY_DATE_MODIFIED, + MTP_PROPERTY_PARENT_OBJECT, MTP_PROPERTY_PERSISTENT_UID, MTP_PROPERTY_NAME, + MTP_PROPERTY_DISPLAY_NAME, MTP_PROPERTY_DATE_ADDED, + + // video specific properties + MTP_PROPERTY_ARTIST, MTP_PROPERTY_ALBUM_NAME, MTP_PROPERTY_DURATION, MTP_PROPERTY_DESCRIPTION +}; + +int IMtpDatabase::IMAGE_PROPERTIES[12] = { + // NOTE must match FILE_PROPERTIES above + MTP_PROPERTY_STORAGE_ID, MTP_PROPERTY_OBJECT_FORMAT, MTP_PROPERTY_PROTECTION_STATUS, + MTP_PROPERTY_OBJECT_SIZE, MTP_PROPERTY_OBJECT_FILE_NAME, MTP_PROPERTY_DATE_MODIFIED, + MTP_PROPERTY_PARENT_OBJECT, MTP_PROPERTY_PERSISTENT_UID, MTP_PROPERTY_NAME, + MTP_PROPERTY_DISPLAY_NAME, MTP_PROPERTY_DATE_ADDED, + + // image specific properties + MTP_PROPERTY_DESCRIPTION +}; + +int IMtpDatabase::ALL_PROPERTIES[25] = { + // NOTE must match FILE_PROPERTIES above + MTP_PROPERTY_STORAGE_ID, MTP_PROPERTY_OBJECT_FORMAT, MTP_PROPERTY_PROTECTION_STATUS, + MTP_PROPERTY_OBJECT_SIZE, MTP_PROPERTY_OBJECT_FILE_NAME, MTP_PROPERTY_DATE_MODIFIED, + MTP_PROPERTY_PARENT_OBJECT, MTP_PROPERTY_PERSISTENT_UID, MTP_PROPERTY_NAME, + MTP_PROPERTY_DISPLAY_NAME, MTP_PROPERTY_DATE_ADDED, + + // image specific properties + MTP_PROPERTY_DESCRIPTION, + + // audio specific properties + MTP_PROPERTY_ARTIST, MTP_PROPERTY_ALBUM_NAME, MTP_PROPERTY_ALBUM_ARTIST, MTP_PROPERTY_TRACK, + MTP_PROPERTY_ORIGINAL_RELEASE_DATE, MTP_PROPERTY_DURATION, MTP_PROPERTY_GENRE, + MTP_PROPERTY_COMPOSER, + + // video specific properties + MTP_PROPERTY_ARTIST, MTP_PROPERTY_ALBUM_NAME, MTP_PROPERTY_DURATION, MTP_PROPERTY_DESCRIPTION, + + // image specific properties + MTP_PROPERTY_DESCRIPTION +}; + +int IMtpDatabase::SUPPORTED_PLAYBACK_FORMATS[26] = { SUPPORTED_PLAYBACK_FORMAT_UNDEFINED, + SUPPORTED_PLAYBACK_FORMAT_ASSOCIATION, + SUPPORTED_PLAYBACK_FORMAT_TEXT, + SUPPORTED_PLAYBACK_FORMAT_HTML, + SUPPORTED_PLAYBACK_FORMAT_WAV, + SUPPORTED_PLAYBACK_FORMAT_MP3, + SUPPORTED_PLAYBACK_FORMAT_MPEG, + SUPPORTED_PLAYBACK_FORMAT_EXIF_JPEG, + SUPPORTED_PLAYBACK_FORMAT_TIFF_EP, + SUPPORTED_PLAYBACK_FORMAT_BMP, + SUPPORTED_PLAYBACK_FORMAT_GIF, + SUPPORTED_PLAYBACK_FORMAT_JFIF, + SUPPORTED_PLAYBACK_FORMAT_PNG, + SUPPORTED_PLAYBACK_FORMAT_TIFF, + SUPPORTED_PLAYBACK_FORMAT_WMA, + SUPPORTED_PLAYBACK_FORMAT_OGG, + SUPPORTED_PLAYBACK_FORMAT_AAC, + SUPPORTED_PLAYBACK_FORMAT_MP4_CONTAINER, + SUPPORTED_PLAYBACK_FORMAT_MP2, + SUPPORTED_PLAYBACK_FORMAT_3GP_CONTAINER, + SUPPORTED_PLAYBACK_FORMAT_ABSTRACT_AV_PLAYLIST, + SUPPORTED_PLAYBACK_FORMAT_WPL_PLAYLIST, + SUPPORTED_PLAYBACK_FORMAT_M3U_PLAYLIST, + SUPPORTED_PLAYBACK_FORMAT_PLS_PLAYLIST, + SUPPORTED_PLAYBACK_FORMAT_XML_DOCUMENT, + SUPPORTED_PLAYBACK_FORMAT_FLAC }; + +MtpObjectHandle IMtpDatabase::beginSendObject(const char* path, MtpObjectFormat format, + MtpObjectHandle parent, MtpStorageID storageID, + uint64_t size, time_t modified) { + if (storagemap.find(storageID) == storagemap.end()) return kInvalidObjectHandle; + return storagemap[storageID]->beginSendObject(path, format, parent, size, modified); +} + +void IMtpDatabase::endSendObject(const char* path, MtpObjectHandle handle, MtpObjectFormat format, + bool succeeded) { + MTPD("endSendObject() %s\n", path); + if (!succeeded) { + MTPE("endSendObject() failed, unlinking %s\n", path); + unlink(path); + } + std::map<int, MtpStorage*>::iterator storit; + for (storit = storagemap.begin(); storit != storagemap.end(); storit++) + storit->second->endSendObject(path, handle, format, succeeded); +} + +void IMtpDatabase::createDB(MtpStorage* storage, MtpStorageID storageID) { + storagemap[storageID] = storage; + storage->createDB(); +} + +void IMtpDatabase::destroyDB(MtpStorageID storageID) { + MtpStorage* storage = storagemap[storageID]; + storagemap.erase(storageID); + delete storage; +} + +MtpObjectHandleList* IMtpDatabase::getObjectList(MtpStorageID storageID, + __attribute__((unused)) MtpObjectFormat format, + MtpObjectHandle parent) { + MTPD("IMtpDatabase::getObjectList::storageID: %d\n", storageID); + MtpObjectHandleList* list = storagemap[storageID]->getObjectList(storageID, parent); + MTPD("IMtpDatabase::getObjectList::list size: %d\n", list->size()); + return list; +} + +int IMtpDatabase::getNumObjects(MtpStorageID storageID, + __attribute__((unused)) MtpObjectFormat format, + MtpObjectHandle parent) { + MtpObjectHandleList* list = storagemap[storageID]->getObjectList(storageID, parent); + int size = list->size(); + delete list; + return size; +} + +MtpObjectFormatList* IMtpDatabase::getSupportedPlaybackFormats() { + // This function tells the host PC which file formats the device supports + MtpObjectFormatList* list = new MtpObjectFormatList(); + int length = sizeof(SUPPORTED_PLAYBACK_FORMATS) / sizeof(SUPPORTED_PLAYBACK_FORMATS[0]); + MTPD("IMtpDatabase::getSupportedPlaybackFormats length: %i\n", length); + for (int i = 0; i < length; i++) { + MTPD("supported playback format: %x\n", SUPPORTED_PLAYBACK_FORMATS[i]); + list->push_back(SUPPORTED_PLAYBACK_FORMATS[i]); + } + return list; +} + +MtpObjectFormatList* IMtpDatabase::getSupportedCaptureFormats() { + // Android OS implementation of this function returns NULL + // so we are not implementing this function either. + MTPD( + "IMtpDatabase::getSupportedCaptureFormats returning NULL (This is what Android does as " + "well).\n"); + return NULL; +} + +MtpObjectPropertyList* IMtpDatabase::getSupportedObjectProperties(MtpObjectFormat format) { + int* properties; + MtpObjectPropertyList* list = new MtpObjectPropertyList(); + int length = 0; + switch (format) { + case MTP_FORMAT_MP3: + case MTP_FORMAT_WAV: + case MTP_FORMAT_WMA: + case MTP_FORMAT_OGG: + case MTP_FORMAT_AAC: + properties = AUDIO_PROPERTIES; + length = sizeof(AUDIO_PROPERTIES) / sizeof(AUDIO_PROPERTIES[0]); + break; + case MTP_FORMAT_MPEG: + case MTP_FORMAT_3GP_CONTAINER: + case MTP_FORMAT_WMV: + properties = VIDEO_PROPERTIES; + length = sizeof(VIDEO_PROPERTIES) / sizeof(VIDEO_PROPERTIES[0]); + break; + case MTP_FORMAT_EXIF_JPEG: + case MTP_FORMAT_GIF: + case MTP_FORMAT_PNG: + case MTP_FORMAT_BMP: + properties = IMAGE_PROPERTIES; + length = sizeof(IMAGE_PROPERTIES) / sizeof(IMAGE_PROPERTIES[0]); + break; + case 0: + properties = ALL_PROPERTIES; + length = sizeof(ALL_PROPERTIES) / sizeof(ALL_PROPERTIES[0]); + break; + default: + properties = FILE_PROPERTIES; + length = sizeof(FILE_PROPERTIES) / sizeof(FILE_PROPERTIES[0]); + } + MTPD("IMtpDatabase::getSupportedObjectProperties length is: %i, format: %x", length, format); + for (int i = 0; i < length; i++) { + MTPD("supported object property: %x\n", properties[i]); + list->push_back(properties[i]); + } + return list; +} + +MtpDevicePropertyList* IMtpDatabase::getSupportedDeviceProperties() { + MtpDevicePropertyList* list = new MtpDevicePropertyList(); + int length = sizeof(DEVICE_PROPERTIES) / sizeof(DEVICE_PROPERTIES[0]); + MTPD("IMtpDatabase::getSupportedDeviceProperties length was: %i\n", length); + for (int i = 0; i < length; i++) list->push_back(DEVICE_PROPERTIES[i]); + return list; +} + +MtpResponseCode IMtpDatabase::getObjectPropertyValue(MtpObjectHandle handle, + MtpObjectProperty property, + MtpDataPacket& packet) { + MTPD("IMtpDatabase::getObjectPropertyValue mtpid: %u, property: %x\n", handle, property); + int type; + MtpResponseCode result = MTP_RESPONSE_INVALID_OBJECT_HANDLE; + MtpStorage::PropEntry prop; + if (!getObjectPropertyInfo(property, type)) { + MTPE("IMtpDatabase::getObjectPropertyValue returning MTP_RESPONSE_OBJECT_PROP_NOT_SUPPORTED\n"); + return MTP_RESPONSE_OBJECT_PROP_NOT_SUPPORTED; + } + std::map<int, MtpStorage*>::iterator storit; + for (storit = storagemap.begin(); storit != storagemap.end(); storit++) { + if (storit->second->getObjectPropertyValue(handle, property, prop) == 0) { + result = MTP_RESPONSE_OK; + break; + } + } + + if (result != MTP_RESPONSE_OK) { + MTPE("IMtpDatabase::getObjectPropertyValue unable to locate handle: %u\n", handle); + return MTP_RESPONSE_INVALID_OBJECT_HANDLE; + } + + uint64_t longValue = prop.intvalue; + // special case date properties, which are strings to MTP + // but stored internally as a uint64 + if (property == MTP_PROPERTY_DATE_MODIFIED || property == MTP_PROPERTY_DATE_ADDED) { + char date[20]; + formatDateTime(longValue, date, sizeof(date)); + packet.putString(date); + goto out; + } + + switch (type) { + case MTP_TYPE_INT8: + packet.putInt8(longValue); + break; + case MTP_TYPE_UINT8: + packet.putUInt8(longValue); + break; + case MTP_TYPE_INT16: + packet.putInt16(longValue); + break; + case MTP_TYPE_UINT16: + packet.putUInt16(longValue); + break; + case MTP_TYPE_INT32: + packet.putInt32(longValue); + break; + case MTP_TYPE_UINT32: + packet.putUInt32(longValue); + break; + case MTP_TYPE_INT64: + packet.putInt64(longValue); + break; + case MTP_TYPE_UINT64: + packet.putUInt64(longValue); + break; + case MTP_TYPE_INT128: + packet.putInt128(longValue); + break; + case MTP_TYPE_UINT128: + packet.putUInt128(longValue); + break; + case MTP_TYPE_STR: { + /*std::string stringValue = (string)stringValuesArray[0]; + if (stringValue) { + const char* str = stringValue.c_str(); + if (str == NULL) { + return MTP_RESPONSE_GENERAL_ERROR; + } + packet.putString(str); + } else { + packet.putEmptyString(); + }*/ + packet.putString(prop.strvalue.c_str()); + MTPD("MTP_TYPE_STR: %x = %s\n", prop.property, prop.strvalue.c_str()); + // MTPE("STRING unsupported type in getObjectPropertyValue\n"); + // result = MTP_RESPONSE_INVALID_OBJECT_PROP_FORMAT; + break; + } + default: + MTPE("unsupported type in getObjectPropertyValue\n"); + result = MTP_RESPONSE_INVALID_OBJECT_PROP_FORMAT; + } +out: + return result; +} + +MtpResponseCode IMtpDatabase::setObjectPropertyValue(MtpObjectHandle handle, + MtpObjectProperty property, + MtpDataPacket& packet) { + int type; + MTPD("IMtpDatabase::setObjectPropertyValue start\n"); + if (!getObjectPropertyInfo(property, type)) { + MTPE("IMtpDatabase::setObjectPropertyValue returning MTP_RESPONSE_OBJECT_PROP_NOT_SUPPORTED\n"); + return MTP_RESPONSE_OBJECT_PROP_NOT_SUPPORTED; + } + MTPD("IMtpDatabase::setObjectPropertyValue continuing\n"); + + int8_t int8_t_value; + uint8_t uint8_t_value; + int16_t int16_t_value; + uint16_t uint16_t_value; + int32_t int32_t_value; + uint32_t uint32_t_value; + int64_t int64_t_value; + uint64_t uint64_t_value; + std::string stringValue; + + switch (type) { + case MTP_TYPE_INT8: + MTPD("int8\n"); + packet.getInt8(int8_t_value); + break; + case MTP_TYPE_UINT8: + MTPD("uint8\n"); + packet.getUInt8(uint8_t_value); + break; + case MTP_TYPE_INT16: + MTPD("int16\n"); + packet.getInt16(int16_t_value); + break; + case MTP_TYPE_UINT16: + MTPD("uint16\n"); + packet.getUInt16(uint16_t_value); + break; + case MTP_TYPE_INT32: + MTPD("int32\n"); + packet.getInt32(int32_t_value); + break; + case MTP_TYPE_UINT32: + MTPD("uint32\n"); + packet.getUInt32(uint32_t_value); + break; + case MTP_TYPE_INT64: + MTPD("int64\n"); + packet.getInt64(int64_t_value); + break; + case MTP_TYPE_UINT64: + MTPD("uint64\n"); + packet.getUInt64(uint64_t_value); + break; + case MTP_TYPE_STR: { + MTPD("string\n"); + MtpStringBuffer buffer; + packet.getString(buffer); + stringValue = buffer; + break; + } + default: + MTPE("IMtpDatabase::setObjectPropertyValue unsupported type %i in getObjectPropertyValue\n", + type); + return MTP_RESPONSE_INVALID_OBJECT_PROP_FORMAT; + } + + int result = MTP_RESPONSE_OBJECT_PROP_NOT_SUPPORTED; + + switch (property) { + case MTP_PROPERTY_OBJECT_FILE_NAME: { + MTPD("IMtpDatabase::setObjectPropertyValue renaming file, handle: %d, new name: '%s'\n", + handle, stringValue.c_str()); + std::map<int, MtpStorage*>::iterator storit; + for (storit = storagemap.begin(); storit != storagemap.end(); storit++) { + if (storit->second->renameObject(handle, stringValue) == 0) { + MTPD("MTP_RESPONSE_OK\n"); + result = MTP_RESPONSE_OK; + break; + } + } + } break; + + default: + MTPE("IMtpDatabase::setObjectPropertyValue property %x not supported.\n", property); + result = MTP_RESPONSE_OBJECT_PROP_NOT_SUPPORTED; + } + MTPD("IMtpDatabase::setObjectPropertyValue returning %d\n", result); + return result; +} + +MtpResponseCode IMtpDatabase::getDevicePropertyValue(MtpDeviceProperty property, + MtpDataPacket& packet) { + int type, result = 0; + char prop_value[PROPERTY_VALUE_MAX]; + MTPD("property %s\n", MtpDebug::getDevicePropCodeName(property)); + if (!getDevicePropertyInfo(property, type)) { + MTPE("IMtpDatabase::getDevicePropertyValue MTP_RESPONSE_DEVICE_PROP_NOT_SUPPORTED\n"); + return MTP_RESPONSE_DEVICE_PROP_NOT_SUPPORTED; + } + MTPD("property %s\n", MtpDebug::getDevicePropCodeName(property)); + MTPD("property %x\n", property); + MTPD("MTP_DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME %x\n", MTP_DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME); + switch (property) { + case MTP_DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER: + case MTP_DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME: + result = MTP_RESPONSE_OK; + break; + default: { + MTPE("IMtpDatabase::getDevicePropertyValue property %x not supported\n", property); + result = MTP_RESPONSE_DEVICE_PROP_NOT_SUPPORTED; + break; + } + } + + if (result != MTP_RESPONSE_OK) { + MTPD("MTP_REPONSE_OK NOT OK\n"); + return result; + } + + long longValue = 0; + property_get("ro.build.product", prop_value, "unknown manufacturer"); + switch (type) { + case MTP_TYPE_INT8: { + MTPD("MTP_TYPE_INT8\n"); + packet.putInt8(longValue); + break; + } + case MTP_TYPE_UINT8: { + MTPD("MTP_TYPE_UINT8\n"); + packet.putUInt8(longValue); + break; + } + case MTP_TYPE_INT16: { + MTPD("MTP_TYPE_INT16\n"); + packet.putInt16(longValue); + break; + } + case MTP_TYPE_UINT16: { + MTPD("MTP_TYPE_UINT16\n"); + packet.putUInt16(longValue); + break; + } + case MTP_TYPE_INT32: { + MTPD("MTP_TYPE_INT32\n"); + packet.putInt32(longValue); + break; + } + case MTP_TYPE_UINT32: { + MTPD("MTP_TYPE_UINT32\n"); + packet.putUInt32(longValue); + break; + } + case MTP_TYPE_INT64: { + MTPD("MTP_TYPE_INT64\n"); + packet.putInt64(longValue); + break; + } + case MTP_TYPE_UINT64: { + MTPD("MTP_TYPE_UINT64\n"); + packet.putUInt64(longValue); + break; + } + case MTP_TYPE_INT128: { + MTPD("MTP_TYPE_INT128\n"); + packet.putInt128(longValue); + break; + } + case MTP_TYPE_UINT128: { + MTPD("MTP_TYPE_UINT128\n"); + packet.putInt128(longValue); + break; + } + case MTP_TYPE_STR: { + MTPD("MTP_TYPE_STR\n"); + char* str = prop_value; + packet.putString(str); + break; + } + default: + MTPE("IMtpDatabase::getDevicePropertyValue unsupported type %i in getDevicePropertyValue\n", + type); + return MTP_RESPONSE_INVALID_DEVICE_PROP_FORMAT; + } + + return MTP_RESPONSE_OK; +} + +MtpResponseCode IMtpDatabase::setDevicePropertyValue(__attribute__((unused)) + MtpDeviceProperty property, + __attribute__((unused)) + MtpDataPacket& packet) { + MTPE("IMtpDatabase::setDevicePropertyValue not implemented, returning 0\n"); + return 0; +} + +MtpResponseCode IMtpDatabase::resetDeviceProperty(__attribute__((unused)) + MtpDeviceProperty property) { + MTPE("IMtpDatabase::resetDeviceProperty not implemented, returning -1\n"); + return -1; +} + +MtpResponseCode IMtpDatabase::getObjectPropertyList(MtpObjectHandle handle, uint32_t format, + uint32_t property, int groupCode, int depth, + MtpDataPacket& packet) { + MTPD("getObjectPropertyList()\n"); + MTPD("property: %x\n", property); + std::map<int, MtpStorage*>::iterator storit; + for (storit = storagemap.begin(); storit != storagemap.end(); storit++) { + MTPD("IMtpDatabase::getObjectPropertyList calling getObjectPropertyList\n"); + if (storit->second->getObjectPropertyList(handle, format, property, groupCode, depth, packet) == + 0) { + MTPD("MTP_RESPONSE_OK\n"); + return MTP_RESPONSE_OK; + } + } + MTPE("IMtpDatabase::getObjectPropertyList MTP_RESPOSNE_INVALID_OBJECT_HANDLE %i\n", handle); + return MTP_RESPONSE_INVALID_OBJECT_HANDLE; +} + +MtpResponseCode IMtpDatabase::getObjectInfo(MtpObjectHandle handle, MtpObjectInfo& info) { + std::map<int, MtpStorage*>::iterator storit; + for (storit = storagemap.begin(); storit != storagemap.end(); storit++) { + if (storit->second->getObjectInfo(handle, info) == 0) { + MTPD("MTP_RESPONSE_OK\n"); + return MTP_RESPONSE_OK; + } + } + MTPE("IMtpDatabase::getObjectInfo MTP_RESPONSE_INVALID_OBJECT_HANDLE %i\n", handle); + return MTP_RESPONSE_INVALID_OBJECT_HANDLE; +} + +void* IMtpDatabase::getThumbnail(__attribute__((unused)) MtpObjectHandle handle, + __attribute__((unused)) size_t& outThumbSize) { + MTPE("IMtpDatabase::getThumbnail not implemented, returning 0\n"); + return 0; +} + +MtpResponseCode IMtpDatabase::getObjectFilePath(MtpObjectHandle handle, + MtpStringBuffer& outFilePath, + int64_t& outFileLength, + MtpObjectFormat& outFormat) { + std::map<int, MtpStorage*>::iterator storit; + for (storit = storagemap.begin(); storit != storagemap.end(); storit++) { + MTPD("IMtpDatabase::getObjectFilePath calling getObjectFilePath\n"); + if (storit->second->getObjectFilePath(handle, outFilePath, outFileLength, outFormat) == 0) { + MTPD("MTP_RESPONSE_OK\n"); + return MTP_RESPONSE_OK; + } + } + MTPE("IMtpDatabase::getObjectFilePath MTP_RESPOSNE_INVALID_OBJECT_HANDLE %i\n", handle); + return MTP_RESPONSE_INVALID_OBJECT_HANDLE; +} + +// MtpResponseCode IMtpDatabase::deleteFile(MtpObjectHandle handle) { +// MTPD("IMtpDatabase::deleteFile\n"); +// std::map<int, MtpStorage*>::iterator storit; +// for (storit = storagemap.begin(); storit != storagemap.end(); storit++) { +// if (storit->second->deleteFile(handle) == 0) { +// MTPD("MTP_RESPONSE_OK\n"); +// return MTP_RESPONSE_OK; +// } +// } +// MTPE("IMtpDatabase::deleteFile MTP_RESPONSE_INVALID_OBJECT_HANDLE %i\n", handle); +// return MTP_RESPONSE_INVALID_OBJECT_HANDLE; +// } + +struct PropertyTableEntry { + MtpObjectProperty property; + int type; +}; + +static const PropertyTableEntry kObjectPropertyTable[] = { + { MTP_PROPERTY_STORAGE_ID, MTP_TYPE_UINT32 }, + { MTP_PROPERTY_OBJECT_FORMAT, MTP_TYPE_UINT16 }, + { MTP_PROPERTY_PROTECTION_STATUS, MTP_TYPE_UINT16 }, + { MTP_PROPERTY_OBJECT_SIZE, MTP_TYPE_UINT64 }, + { MTP_PROPERTY_OBJECT_FILE_NAME, MTP_TYPE_STR }, + { MTP_PROPERTY_DATE_MODIFIED, MTP_TYPE_STR }, + { MTP_PROPERTY_PARENT_OBJECT, MTP_TYPE_UINT32 }, + { MTP_PROPERTY_PERSISTENT_UID, MTP_TYPE_UINT128 }, + { MTP_PROPERTY_NAME, MTP_TYPE_STR }, + { MTP_PROPERTY_DISPLAY_NAME, MTP_TYPE_STR }, + { MTP_PROPERTY_DATE_ADDED, MTP_TYPE_STR }, + { MTP_PROPERTY_ARTIST, MTP_TYPE_STR }, + { MTP_PROPERTY_ALBUM_NAME, MTP_TYPE_STR }, + { MTP_PROPERTY_ALBUM_ARTIST, MTP_TYPE_STR }, + { MTP_PROPERTY_TRACK, MTP_TYPE_UINT16 }, + { MTP_PROPERTY_ORIGINAL_RELEASE_DATE, MTP_TYPE_STR }, + { MTP_PROPERTY_GENRE, MTP_TYPE_STR }, + { MTP_PROPERTY_COMPOSER, MTP_TYPE_STR }, + { MTP_PROPERTY_DURATION, MTP_TYPE_UINT32 }, + { MTP_PROPERTY_DESCRIPTION, MTP_TYPE_STR }, +}; + +static const PropertyTableEntry kDevicePropertyTable[] = { + { MTP_DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER, MTP_TYPE_STR }, + { MTP_DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME, MTP_TYPE_STR }, + { MTP_DEVICE_PROPERTY_IMAGE_SIZE, MTP_TYPE_STR }, +}; + +bool IMtpDatabase::getObjectPropertyInfo(MtpObjectProperty property, int& type) { + int count = sizeof(kObjectPropertyTable) / sizeof(kObjectPropertyTable[0]); + const PropertyTableEntry* entry = kObjectPropertyTable; + MTPD("IMtpDatabase::getObjectPropertyInfo size is: %i\n", count); + for (int i = 0; i < count; i++, entry++) { + if (entry->property == property) { + type = entry->type; + return true; + } + } + return false; +} + +bool IMtpDatabase::getDevicePropertyInfo(MtpDeviceProperty property, int& type) { + int count = sizeof(kDevicePropertyTable) / sizeof(kDevicePropertyTable[0]); + const PropertyTableEntry* entry = kDevicePropertyTable; + MTPD("IMtpDatabase::getDevicePropertyInfo count is: %i\n", count); + for (int i = 0; i < count; i++, entry++) { + if (entry->property == property) { + type = entry->type; + MTPD("type: %x\n", type); + return true; + } + } + return false; +} + +MtpObjectHandleList* IMtpDatabase::getObjectReferences(MtpObjectHandle handle) { + // call function and place files with associated handles into int array + MTPD( + "IMtpDatabase::getObjectReferences returning null, this seems to be what Android always " + "does.\n"); + MTPD("handle: %d\n", handle); + // Windows + Android seems to always return a NULL in this function, c == null path + // The way that this is handled in Android then is to do this: + return NULL; +} + +MtpResponseCode IMtpDatabase::setObjectReferences(__attribute__((unused)) MtpObjectHandle handle, + __attribute__((unused)) + MtpObjectHandleList* references) { + MTPE("IMtpDatabase::setObjectReferences not implemented, returning 0\n"); + return 0; +} + +MtpProperty* IMtpDatabase::getObjectPropertyDesc(MtpObjectProperty property, + MtpObjectFormat format) { + MTPD("IMtpDatabase::getObjectPropertyDesc start\n"); + MtpProperty* result = NULL; + switch (property) { + case MTP_PROPERTY_OBJECT_FORMAT: + // use format as default value + result = new MtpProperty(property, MTP_TYPE_UINT16, false, format); + break; + case MTP_PROPERTY_PROTECTION_STATUS: + case MTP_PROPERTY_TRACK: + result = new MtpProperty(property, MTP_TYPE_UINT16); + break; + case MTP_PROPERTY_STORAGE_ID: + case MTP_PROPERTY_PARENT_OBJECT: + case MTP_PROPERTY_DURATION: + result = new MtpProperty(property, MTP_TYPE_UINT32); + break; + case MTP_PROPERTY_OBJECT_SIZE: + result = new MtpProperty(property, MTP_TYPE_UINT64); + break; + case MTP_PROPERTY_PERSISTENT_UID: + result = new MtpProperty(property, MTP_TYPE_UINT128); + break; + case MTP_PROPERTY_NAME: + case MTP_PROPERTY_DISPLAY_NAME: + case MTP_PROPERTY_ARTIST: + case MTP_PROPERTY_ALBUM_NAME: + case MTP_PROPERTY_ALBUM_ARTIST: + case MTP_PROPERTY_GENRE: + case MTP_PROPERTY_COMPOSER: + case MTP_PROPERTY_DESCRIPTION: + result = new MtpProperty(property, MTP_TYPE_STR); + break; + case MTP_PROPERTY_DATE_MODIFIED: + case MTP_PROPERTY_DATE_ADDED: + case MTP_PROPERTY_ORIGINAL_RELEASE_DATE: + result = new MtpProperty(property, MTP_TYPE_STR); + result->setFormDateTime(); + break; + case MTP_PROPERTY_OBJECT_FILE_NAME: + // We allow renaming files and folders + result = new MtpProperty(property, MTP_TYPE_STR, true); + break; + } + return result; +} + +MtpProperty* IMtpDatabase::getDevicePropertyDesc(MtpDeviceProperty property) { + MtpProperty* result = NULL; + bool writable = false; + switch (property) { + case MTP_DEVICE_PROPERTY_SYNCHRONIZATION_PARTNER: + case MTP_DEVICE_PROPERTY_DEVICE_FRIENDLY_NAME: + writable = true; + // fall through + case MTP_DEVICE_PROPERTY_IMAGE_SIZE: + result = new MtpProperty(property, MTP_TYPE_STR, writable); + + // get current value + // TODO: add actual values + result->setCurrentValue(0); + result->setDefaultValue(0); + break; + } + + return result; +} + +void IMtpDatabase::sessionStarted() { + MTPD("IMtpDatabase::sessionStarted not implemented or does nothing, returning\n"); + return; +} + +void IMtpDatabase::sessionEnded() { + MTPD("IMtpDatabase::sessionEnded not implemented or does nothing, returning\n"); + return; +} + +// ---------------------------------------------------------------------------- + +void IMtpDatabase::lockMutex(void) { + std::map<int, MtpStorage*>::iterator storit; + for (storit = storagemap.begin(); storit != storagemap.end(); storit++) { + storit->second->lockMutex(0); + } +} + +void IMtpDatabase::unlockMutex(void) { + std::map<int, MtpStorage*>::iterator storit; + for (storit = storagemap.begin(); storit != storagemap.end(); storit++) { + storit->second->unlockMutex(0); + } +} + +MtpResponseCode IMtpDatabase::beginDeleteObject(MtpObjectHandle handle) { + MTPD("IMtoDatabase::beginDeleteObject handle: %u\n", handle); + std::map<int, MtpStorage*>::iterator storit; + for (storit = storagemap.begin(); storit != storagemap.end(); storit++) { + if (storit->second->deleteFile(handle) == 0) { + MTPD("IMtpDatabase::beginDeleteObject::MTP_RESPONSE_OK\n"); + return MTP_RESPONSE_OK; + } + } + return MTP_RESPONSE_INVALID_OBJECT_HANDLE; +} + +void IMtpDatabase::endDeleteObject(MtpObjectHandle handle __unused, bool succeeded __unused) { + MTPD("IMtpDatabase::endDeleteObject not implemented yet\n"); +} + +void IMtpDatabase::rescanFile(const char* path __unused, MtpObjectHandle handle __unused, + MtpObjectFormat format __unused) { + MTPD("IMtpDatabase::rescanFile not implemented yet\n"); +} + +MtpResponseCode IMtpDatabase::beginMoveObject(MtpObjectHandle handle __unused, + MtpObjectHandle newParent __unused, + MtpStorageID newStorage __unused) { + MTPD("IMtpDatabase::beginMoveObject not implemented yet\n"); + return MTP_RESPONSE_INVALID_OBJECT_HANDLE; +} + +void IMtpDatabase::endMoveObject(MtpObjectHandle oldParent __unused, + MtpObjectHandle newParent __unused, + MtpStorageID oldStorage __unused, MtpStorageID newStorage __unused, + MtpObjectHandle handle __unused, bool succeeded __unused) { + MTPD("IMtpDatabase::endMoveObject not implemented yet\n"); +} + +MtpResponseCode IMtpDatabase::beginCopyObject(MtpObjectHandle handle __unused, + MtpObjectHandle newParent __unused, + MtpStorageID newStorage __unused) { + MTPD("IMtpDatabase::beginCopyObject not implemented yet\n"); + return MTP_RESPONSE_INVALID_OBJECT_HANDLE; +} + +void IMtpDatabase::endCopyObject(MtpObjectHandle handle __unused, bool succeeded __unused) { + MTPD("IMtpDatabase::endCopyObject not implemented yet\n"); +} diff --git a/mtp/ffs/mtp_MtpDatabase.hpp b/mtp/ffs/mtp_MtpDatabase.hpp new file mode 100755 index 000000000..4b1889851 --- /dev/null +++ b/mtp/ffs/mtp_MtpDatabase.hpp @@ -0,0 +1,163 @@ +/* + * 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. + * + * Copyright (C) 2014 TeamWin - bigbiff and Dees_Troy mtp database conversion to C++ + */ + +#ifndef MTP_MTPDATABASE_HPP +#define MTP_MTPDATABASE_HPP + +#include <utils/Log.h> + +#include <stdio.h> +#include <assert.h> +#include <limits.h> +#include <unistd.h> +#include <fcntl.h> +#include <dirent.h> +#include <errno.h> +#include <sys/types.h> +#include <map> +#include <string> +#include <deque> + +#include "MtpDatabase.h" +#include "MtpDataPacket.h" +#include "MtpObjectInfo.h" +#include "MtpProperty.h" +#include "MtpStringBuffer.h" +#include "MtpUtils.h" +#include "mtp.h" + +class IMtpDatabase : public MtpDatabase { +private: + int* getSupportedObjectProperties(int format); + + static int FILE_PROPERTIES[10]; + static int DEVICE_PROPERTIES[3]; + static int AUDIO_PROPERTIES[19]; + static int VIDEO_PROPERTIES[15]; + static int IMAGE_PROPERTIES[12]; + static int ALL_PROPERTIES[25]; + static int SUPPORTED_PLAYBACK_FORMATS[26]; + int storagenum; + int count; + std::string lastfile; + std::map<int, MtpStorage*> storagemap; + void countDirs(std::string path); + int readParentDirs(std::string path, int storageID); + +public: + IMtpDatabase(); + virtual ~IMtpDatabase(); + + virtual void createDB(MtpStorage* storage, MtpStorageID storageID); + virtual void destroyDB(MtpStorageID storageID); + virtual MtpObjectHandle beginSendObject(const char* path, + MtpObjectFormat format, + MtpObjectHandle parent, + MtpStorageID storageID, + uint64_t size, + time_t modified); + + virtual void endSendObject(const char* path, + MtpObjectHandle handle, + MtpObjectFormat format, + bool succeeded); + + virtual MtpObjectHandleList* getObjectList(MtpStorageID storageID, + MtpObjectFormat format, + MtpObjectHandle parent); + + virtual int getNumObjects(MtpStorageID storageID, + MtpObjectFormat format, + MtpObjectHandle parent); + + // callee should delete[] the results from these + // results can be NULL + virtual MtpObjectFormatList* getSupportedPlaybackFormats(); + virtual MtpObjectFormatList* getSupportedCaptureFormats(); + virtual MtpObjectPropertyList* getSupportedObjectProperties(MtpObjectFormat format); + virtual MtpDevicePropertyList* getSupportedDeviceProperties(); + + virtual MtpResponseCode getObjectPropertyValue(MtpObjectHandle handle, + MtpObjectProperty property, + MtpDataPacket& packet); + + virtual MtpResponseCode setObjectPropertyValue(MtpObjectHandle handle, + MtpObjectProperty property, + MtpDataPacket& packet); + + virtual MtpResponseCode getDevicePropertyValue(MtpDeviceProperty property, + MtpDataPacket& packet); + + virtual MtpResponseCode setDevicePropertyValue(MtpDeviceProperty property, + MtpDataPacket& packet); + + virtual MtpResponseCode resetDeviceProperty(MtpDeviceProperty property); + + virtual MtpResponseCode getObjectPropertyList(MtpObjectHandle handle, + uint32_t format, uint32_t property, + int groupCode, int depth, + MtpDataPacket& packet); + + virtual MtpResponseCode getObjectInfo(MtpObjectHandle handle, + MtpObjectInfo& info); + + virtual void* getThumbnail(MtpObjectHandle handle, size_t& outThumbSize); + + virtual MtpResponseCode getObjectFilePath(MtpObjectHandle handle, + MtpStringBuffer& outFilePath, + int64_t& outFileLength, + MtpObjectFormat& outFormat); + // virtual MtpResponseCode deleteFile(MtpObjectHandle handle); + + bool getObjectPropertyInfo(MtpObjectProperty property, int& type); + bool getDevicePropertyInfo(MtpDeviceProperty property, int& type); + + virtual MtpObjectHandleList* getObjectReferences(MtpObjectHandle handle); + + virtual MtpResponseCode setObjectReferences(MtpObjectHandle handle, + MtpObjectHandleList* references); + + virtual MtpProperty* getObjectPropertyDesc(MtpObjectProperty property, + MtpObjectFormat format); + + virtual MtpProperty* getDevicePropertyDesc(MtpDeviceProperty property); + + virtual void sessionStarted(); + + virtual void sessionEnded(); + virtual void lockMutex(); + virtual void unlockMutex(); + + virtual MtpResponseCode beginDeleteObject(MtpObjectHandle handle); + virtual void endDeleteObject(MtpObjectHandle handle, bool succeeded); + // Called to rescan a file, such as after an edit. + virtual void rescanFile(const char* path, + MtpObjectHandle handle, + MtpObjectFormat format); + virtual MtpResponseCode beginMoveObject(MtpObjectHandle handle, MtpObjectHandle newParent, + MtpStorageID newStorage); + + virtual void endMoveObject(MtpObjectHandle oldParent, MtpObjectHandle newParent, + MtpStorageID oldStorage, MtpStorageID newStorage, + MtpObjectHandle handle, bool succeeded); + + virtual MtpResponseCode beginCopyObject(MtpObjectHandle handle, MtpObjectHandle newParent, + MtpStorageID newStorage); + virtual void endCopyObject(MtpObjectHandle handle, bool succeeded); +}; +#endif diff --git a/mtp/ffs/mtp_MtpServer.cpp b/mtp/ffs/mtp_MtpServer.cpp new file mode 100755 index 000000000..ce890d15d --- /dev/null +++ b/mtp/ffs/mtp_MtpServer.cpp @@ -0,0 +1,226 @@ +/* + * 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. + * + * Additional Copyright (C) 2018 TeamWin + */ + +#include <utils/Log.h> + +#include <stdio.h> +#include <assert.h> +#include <limits.h> +#include <unistd.h> +#include <fcntl.h> +#include <vector> +#include <utils/threads.h> +#include <pthread.h> +#include <cutils/properties.h> + +#include "mtp_MtpServer.hpp" +#include "MtpServer.h" +#include "MtpStorage.h" +#include "MtpDebug.h" +#include "MtpDescriptors.h" +#include "MtpMessage.hpp" +#include "mtp_MtpDatabase.hpp" + +#include <string> + +void twmtp_MtpServer::set_device_info() { + char property[512]; + property_get("ro.build.product", property, "unknown manufacturer"); + mtpinfo.deviceInfoManufacturer = MtpStringBuffer(property); + property_get("ro.product.model", property, "unknown model"); + mtpinfo.deviceInfoModel = MtpStringBuffer(property); + mtpinfo.deviceInfoDeviceVersion = MtpStringBuffer("None"); + property_get("ro.serialno", property, "unknown serial number"); + mtpinfo.deviceInfoSerialNumber = MtpStringBuffer(property); +} + +void twmtp_MtpServer::start() +{ + int controlFd = 0; + + usePtp = false; + IMtpDatabase* mtpdb = new IMtpDatabase(); + MTPD("launching server\n"); + /* Sleep for a bit before we open the MTP USB device because some + * devices are not ready due to the kernel not responding to our + * sysfs requests right away. + */ + usleep(800000); +#ifdef USB_MTP_DEVICE +#define STRINGIFY(x) #x +#define EXPAND(x) STRINGIFY(x) + const char* mtp_device = EXPAND(USB_MTP_DEVICE); + MTPI("Using '%s' for MTP device.\n", EXPAND(USB_MTP_DEVICE)); +#else + const char* mtp_device = "/dev/mtp_usb"; +#endif + bool ffs_ok = access(FFS_MTP_EP0, W_OK) == 0; + if (ffs_ok) { + MTPD("Opening FFS EPO\n"); + controlFd = open(FFS_MTP_EP0, O_RDWR); + } else { + controlFd = open(mtp_device, O_WRONLY); + } + if (controlFd < 0) { + MTPE("could not open MTP driver, errno: %d\n", errno); + return; + } + MTPD("MTP fd: %d\n", controlFd); + + server = new MtpServer(mtpdb,\ + controlFd,\ + usePtp,\ + mtpinfo.deviceInfoManufacturer, \ + mtpinfo.deviceInfoModel, \ + mtpinfo.deviceInfoDeviceVersion, \ + mtpinfo.deviceInfoSerialNumber); + refserver = server; + MTPI("created new mtpserver object\n"); + add_storage(); + MTPD("Starting add / remove mtppipe monitor thread\n"); + pthread_t thread; + ThreadPtr mtpptr = &twmtp_MtpServer::mtppipe_thread; + PThreadPtr p = *(PThreadPtr*)&mtpptr; + pthread_create(&thread, NULL, p, this); + // This loop restarts the MTP process if the device is unplugged and replugged in + while (true) { + server->run(); + usleep(800000); + } +} + +void twmtp_MtpServer::set_storages(storages* mtpstorages) { + stores = mtpstorages; +} + +void twmtp_MtpServer::cleanup() +{ + android::Mutex sMutex; + android::Mutex::Autolock autoLock(sMutex); + + if (server) { + delete server; + } else { + MTPD("server is null in cleanup"); + } +} + +void twmtp_MtpServer::send_object_added(int handle) +{ + android::Mutex sMutex; + android::Mutex::Autolock autoLock(sMutex); + + if (server) + server->sendObjectAdded(handle); + else + MTPD("server is null in send_object_added"); +} + +void twmtp_MtpServer::send_object_removed(int handle) +{ + android::Mutex sMutex; + android::Mutex::Autolock autoLock(sMutex); + + if (server) + server->sendObjectRemoved(handle); + else + MTPD("server is null in send_object_removed"); +} + +void twmtp_MtpServer::add_storage() +{ + android::Mutex sMutex; + android::Mutex::Autolock autoLock(sMutex); + + MTPD("twmtp_MtpServer::add_storage count of storage devices: %i\n", stores->size()); + for (unsigned int i = 0; i < stores->size(); ++i) { + std::string pathStr = stores->at(i)->mount; + + if (!pathStr.empty()) { + std::string descriptionStr = stores->at(i)->display; + int storageID = stores->at(i)->mtpid; + bool removable = false; + uint64_t maxFileSize = stores->at(i)->maxFileSize; + if (descriptionStr != "") { + MtpStorage* storage = new MtpStorage(storageID, &pathStr[0], &descriptionStr[0], removable, maxFileSize, refserver); + server->addStorage(storage); + } + } + } +} + +void twmtp_MtpServer::remove_storage(int storageId) +{ + android::Mutex sMutex; + android::Mutex::Autolock autoLock(sMutex); + + if (server) { + MtpStorage* storage = server->getStorage(storageId); + if (storage) { + MTPD("twmtp_MtpServer::remove_storage calling removeStorage\n"); + server->removeStorage(storage); + } + } else + MTPD("server is null in remove_storage"); + MTPD("twmtp_MtpServer::remove_storage DONE\n"); +} + +int twmtp_MtpServer::mtppipe_thread(void) +{ + if (mtp_read_pipe == -1) { + MTPD("mtppipe_thread exiting because mtp_read_pipe not set\n"); + return 0; + } + MTPD("Starting twmtp_MtpServer::mtppipe_thread\n"); + int read_count; + struct mtpmsg mtp_message; + while (1) { + read_count = ::read(mtp_read_pipe, &mtp_message, sizeof(mtp_message)); + MTPD("read %i from mtppipe\n", read_count); + if (read_count == sizeof(mtp_message)) { + if (mtp_message.message_type == MTP_MESSAGE_ADD_STORAGE) { + MTPI("mtppipe add storage %i '%s'\n", mtp_message.storage_id, mtp_message.path); + if (mtp_message.storage_id) { + bool removable = false; + MtpStorage* storage = new MtpStorage(mtp_message.storage_id, &mtp_message.path[0], &mtp_message.display[0], removable, mtp_message.maxFileSize, refserver); + server->addStorage(storage); + MTPD("mtppipe done adding storage\n"); + } else { + MTPE("Invalid storage ID %i specified\n", mtp_message.storage_id); + } + } else if (mtp_message.message_type == MTP_MESSAGE_REMOVE_STORAGE) { + MTPI("mtppipe remove storage %i\n", mtp_message.storage_id); + remove_storage(mtp_message.storage_id); + MTPD("mtppipe done removing storage\n"); + } else { + MTPE("Unknown mtppipe message value: %i\n", mtp_message.message_type); + } + } else { + MTPE("twmtp_MtpServer::mtppipe_thread unexpected read_count %i\n", read_count); + close(mtp_read_pipe); + break; + } + } + MTPD("twmtp_MtpServer::mtppipe_thread closing\n"); + return 0; +} + +void twmtp_MtpServer::set_read_pipe(int pipe) +{ + mtp_read_pipe = pipe; +} diff --git a/mtp/ffs/mtp_MtpServer.hpp b/mtp/ffs/mtp_MtpServer.hpp new file mode 100644 index 000000000..f2dc4cdbb --- /dev/null +++ b/mtp/ffs/mtp_MtpServer.hpp @@ -0,0 +1,78 @@ +/* + * 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. + * + * Copyright (C) 2014 TeamWin - bigbiff and Dees_Troy mtp database conversion to C++ + */ + +#ifndef MTP_MTPSERVER_HPP +#define MTP_MTPSERVER_HPP +#include <utils/Log.h> + +#include <string> +#include <stdio.h> +#include <assert.h> +#include <limits.h> +#include <unistd.h> +#include <fcntl.h> +#include <utils/threads.h> + +#include "MtpServer.h" +#include "MtpStorage.h" +#include "mtp_MtpDatabase.hpp" + +typedef struct Storage { + std::string display; + std::string mount; + int mtpid; + uint64_t maxFileSize; +} storage; + +typedef std::vector<storage*> storages; + +struct mtp_info { + MtpStringBuffer deviceInfoManufacturer; + MtpStringBuffer deviceInfoModel; + MtpStringBuffer deviceInfoDeviceVersion; + MtpStringBuffer deviceInfoSerialNumber; +}; + +class twmtp_MtpServer { + public: + void start(); + void cleanup(); + void send_object_added(int handle); + void send_object_removed(int handle); + void add_storage(); + void remove_storage(int storageId); + void set_storages(storages* mtpstorages); + void set_read_pipe(int pipe); + storages *stores; + struct mtp_info mtpinfo; + void set_device_info(); + + private: + typedef int (twmtp_MtpServer::*ThreadPtr)(void); + typedef void* (*PThreadPtr)(void *); + int mtppipe_thread(void); + bool usePtp; + MtpServer* server; + MtpServer* refserver; + int mtp_read_pipe; + MtpStringBuffer deviceInfoManufacturer; + MtpStringBuffer deviceInfoModel; + MtpStringBuffer deviceInfoDeviceVersion; + MtpStringBuffer deviceInfoSerialNumber; +}; +#endif diff --git a/mtp/ffs/node.cpp b/mtp/ffs/node.cpp new file mode 100644 index 000000000..7c57dc6bc --- /dev/null +++ b/mtp/ffs/node.cpp @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2014 TeamWin - bigbiff and Dees_Troy mtp database conversion to C++ + * + * 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 <vector> +#include <sys/stat.h> +#include <sys/statfs.h> +#include <sys/types.h> +#include <unistd.h> +#include <dirent.h> +#include <limits.h> +#include <errno.h> +#include <string.h> +#include <stdio.h> +#include <libgen.h> + +#include "btree.hpp" +#include "mtp.h" +#include "MtpDebug.h" + + +Node::Node() + : handle(-1), parent(0), name("") +{ +} + +Node::Node(MtpObjectHandle handle, MtpObjectHandle parent, const std::string& name) + : handle(handle), parent(parent), name(name) +{ + MTPD("handle: %d\n", handle); + MTPD("parent: %d\n", parent); + MTPD("name: %s\n", name.c_str()); +} + +void Node::rename(const std::string& newName) { + name = newName; + updateProperty(MTP_PROPERTY_OBJECT_FILE_NAME, 0, name.c_str(), MTP_TYPE_STR); + updateProperty(MTP_PROPERTY_NAME, 0, name.c_str(), MTP_TYPE_STR); + updateProperty(MTP_PROPERTY_DISPLAY_NAME, 0, name.c_str(), MTP_TYPE_STR); +} + +MtpObjectHandle Node::Mtpid() const { return handle; } +MtpObjectHandle Node::getMtpParentId() const { return parent; } +const std::string& Node::getName() const { return name; } + +uint64_t Node::getIntProperty(MtpPropertyCode property) { + for (unsigned index = 0; index < mtpProp.size(); ++index) { + if (mtpProp[index].property == property) + return mtpProp[index].valueInt; + } + MTPE("Node::getIntProperty failed to find property %x, returning -1\n", (unsigned)property); + return -1; +} + +const Node::mtpProperty& Node::getProperty(MtpPropertyCode property) { + static const mtpProperty dummyProp; + for (size_t i = 0; i < mtpProp.size(); ++i) { + if (mtpProp[i].property == property) + return mtpProp[i]; + } + MTPE("Node::getProperty failed to find property %x, returning dummy property\n", (unsigned)property); + return dummyProp; +} + +void Node::addProperty(MtpPropertyCode property, uint64_t valueInt, std::string valueStr, MtpDataType dataType) { +// MTPD("adding property: %lld, valueInt: %lld, valueStr: %s, dataType: %d\n", property, valueInt, valueStr.c_str(), dataType); + struct mtpProperty prop; + prop.property = property; + prop.valueInt = valueInt; + prop.valueStr = valueStr; + prop.dataType = dataType; + mtpProp.push_back(prop); +} + +void Node::updateProperty(MtpPropertyCode property, uint64_t valueInt, std::string valueStr, MtpDataType dataType) { + for (unsigned i = 0; i < mtpProp.size(); i++) { + if (mtpProp[i].property == property) { + mtpProp[i].valueInt = valueInt; + mtpProp[i].valueStr = valueStr; + mtpProp[i].dataType = dataType; + return; + } + } + addProperty(property, valueInt, valueStr, dataType); +} + +std::vector<Node::mtpProperty>& Node::getMtpProps() { + return mtpProp; +} + +void Node::addProperties(const std::string& path, int storageID) { + MTPD("addProperties: handle: %u, filename: '%s'\n", handle, getName().c_str()); + struct stat st; + int mFormat = 0; + uint64_t puid = ((uint64_t)storageID << 32) + handle; + off_t file_size = 0; + + mFormat = MTP_FORMAT_UNDEFINED; // file + if (lstat(path.c_str(), &st) == 0) { + file_size = st.st_size; + if (S_ISDIR(st.st_mode)) + mFormat = MTP_FORMAT_ASSOCIATION; // folder + } + + // TODO: don't store properties with constant values at all, add them at query time instead + addProperty(MTP_PROPERTY_STORAGE_ID, storageID, "", MTP_TYPE_UINT32); + addProperty(MTP_PROPERTY_OBJECT_FORMAT, mFormat, "", MTP_TYPE_UINT16); + addProperty(MTP_PROPERTY_PROTECTION_STATUS, 0, "", MTP_TYPE_UINT16); + addProperty(MTP_PROPERTY_OBJECT_SIZE, file_size, "", MTP_TYPE_UINT64); + addProperty(MTP_PROPERTY_OBJECT_FILE_NAME, 0, getName().c_str(), MTP_TYPE_STR); + addProperty(MTP_PROPERTY_DATE_MODIFIED, st.st_mtime, "", MTP_TYPE_UINT64); + addProperty(MTP_PROPERTY_PARENT_OBJECT, parent, "", MTP_TYPE_UINT32); + addProperty(MTP_PROPERTY_PERSISTENT_UID, puid, "", MTP_TYPE_UINT128); + // TODO: we can't really support persistent UIDs without a persistent DB. + // probably a combination of volume UUID + st_ino would come close. + // doesn't help for fs with no native inodes numbers like fat though... + // however, Microsoft's own impl (Zune, etc.) does not support persistent UIDs either + addProperty(MTP_PROPERTY_NAME, 0, getName().c_str(), MTP_TYPE_STR); + addProperty(MTP_PROPERTY_DISPLAY_NAME, 0, getName().c_str(), MTP_TYPE_STR); + addProperty(MTP_PROPERTY_DATE_ADDED, st.st_mtime, "", MTP_TYPE_UINT64); + addProperty(MTP_PROPERTY_DESCRIPTION, 0, "", MTP_TYPE_STR); + addProperty(MTP_PROPERTY_ARTIST, 0, "", MTP_TYPE_STR); + addProperty(MTP_PROPERTY_ALBUM_NAME, 0, "", MTP_TYPE_STR); + addProperty(MTP_PROPERTY_ALBUM_ARTIST, 0, "", MTP_TYPE_STR); + addProperty(MTP_PROPERTY_TRACK, 0, "", MTP_TYPE_UINT16); + addProperty(MTP_PROPERTY_ORIGINAL_RELEASE_DATE, 2014, "", MTP_TYPE_UINT64); // TODO: extract year from st.st_mtime? + addProperty(MTP_PROPERTY_DURATION, 0, "", MTP_TYPE_UINT32); + addProperty(MTP_PROPERTY_GENRE, 0, "", MTP_TYPE_STR); + addProperty(MTP_PROPERTY_COMPOSER, 0, "", MTP_TYPE_STR); + addProperty(MTP_PROPERTY_ARTIST, 0, "", MTP_TYPE_STR); + addProperty(MTP_PROPERTY_ALBUM_NAME, 0, "", MTP_TYPE_STR); + addProperty(MTP_PROPERTY_DURATION, 0, "", MTP_TYPE_UINT32); + addProperty(MTP_PROPERTY_DESCRIPTION, 0, "", MTP_TYPE_STR); +} diff --git a/mtp/ffs/twrpMtp.cpp b/mtp/ffs/twrpMtp.cpp new file mode 100644 index 000000000..d63658070 --- /dev/null +++ b/mtp/ffs/twrpMtp.cpp @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2014 TeamWin - bigbiff and Dees_Troy mtp database conversion to C++ + * + * 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 <string> +#include <fcntl.h> +#include <sys/wait.h> +#include <pthread.h> +#include <utils/Errors.h> +#include <utils/threads.h> +#include "MtpTypes.h" +#include "MtpPacket.h" +#include "MtpDataPacket.h" +#include "MtpDatabase.h" +#include "MtpRequestPacket.h" +#include "MtpResponsePacket.h" +#include "mtp_MtpDatabase.hpp" +#include "mtp_MtpServer.hpp" +#include "twrpMtp.hpp" +#include "MtpDebug.h" + +#ifdef TWRPMTP +static void usage(std::string prg) { + printf("Usage: %s <OPTIONS>\n", prg.c_str()); + printf("Options:\n"); + printf("\t-h, --help\t\tShow Usage\n"); + printf("\t-s1, --storage1 /path/to/dir\t\tDestination to first storage directory\n"); + printf("\t-s2, --storage2 /path/to/dir\t\tDestination to first storage directory\n"); + printf("\t-sN, --storageN /path/to/dir\t\tDestination to first storage directory\n"); +} + +int main(int argc, char* argv[]) { + printf("argc: %d\n", argc); + if (argc < 2) { + usage(argv[0]); + return 1; + } + + std::vector <std::string> storages; + + for (int i = 1; i < argc; ++i) { + std::string arg = argv[i]; + if ((arg == "-h") || (arg == "--help")) { + usage(argv[0]); + } + else { + storages.push_back(arg); + } + } + printf("starting\n"); + twmtp_MtpServer* mtp = new twmtp_MtpServer(); + mtp->set_storages(storages); + mtp->start(); + return 0; +} +#endif //def TWRPMTP + +twrpMtp::twrpMtp(int debug_enabled) { + if (debug_enabled) + MtpDebug::enableDebug(); + mtpstorages = new storages; + mtp_read_pipe = -1; +} + +int twrpMtp::start(void) { + MTPI("Starting MTP\n"); + twmtp_MtpServer *mtp = new twmtp_MtpServer(); + mtp->set_storages(mtpstorages); + mtp->set_device_info(); + mtp->set_read_pipe(mtp_read_pipe); + mtp->start(); + return 0; +} + +pthread_t twrpMtp::threadserver(void) { + pthread_t thread; + ThreadPtr mtpptr = &twrpMtp::start; + PThreadPtr p = *(PThreadPtr*)&mtpptr; + pthread_create(&thread, NULL, p, this); + return thread; +} + +pid_t twrpMtp::forkserver(int mtppipe[2]) { + pid_t pid; + if ((pid = fork()) == -1) { + MTPE("MTP fork failed.\n"); + return 0; + } + if (pid == 0) { + // Child process + close(mtppipe[1]); // Child closes write side + mtp_read_pipe = mtppipe[0]; + start(); + MTPD("MTP child process exited.\n"); + close(mtppipe[0]); + _exit(0); + } else { + MTPD("MTP child PID: %d\n", pid); + return pid; + } + return 0; +} + +void twrpMtp::addStorage(std::string display, std::string path, int mtpid, uint64_t maxFileSize) { + s = new storage; + s->display = display; + s->mount = path; + s->mtpid = mtpid; + s->maxFileSize = maxFileSize; + MTPD("twrpMtp mtpid: %d\n", s->mtpid); + mtpstorages->push_back(s); +} diff --git a/mtp/ffs/twrpMtp.hpp b/mtp/ffs/twrpMtp.hpp new file mode 100644 index 000000000..2e8c2b888 --- /dev/null +++ b/mtp/ffs/twrpMtp.hpp @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2018 TeamWin + * + * 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. + */ + +#ifndef TWRPMTP_HPP +#define TWRPMTP_HPP + +#include <fcntl.h> +#include <utils/Errors.h> +#include <utils/threads.h> +#include <string> +#include <vector> +#include <pthread.h> +#include "MtpTypes.h" +#include "MtpPacket.h" +#include "MtpDataPacket.h" +#include "MtpDatabase.h" +#include "MtpRequestPacket.h" +#include "MtpResponsePacket.h" +#include "mtp_MtpDatabase.hpp" +#include "mtp_MtpServer.hpp" + +class twrpMtp { + public: + twrpMtp(int debug_enabled = 0); + pthread_t threadserver(void); + pid_t forkserver(int mtppipe[2]); + void addStorage(std::string display, std::string path, int mtpid, uint64_t maxFileSize); + private: + int start(void); + typedef int (twrpMtp::*ThreadPtr)(void); + typedef void* (*PThreadPtr)(void *); + storages *mtpstorages; + storage *s; + int mtp_read_pipe; +}; +#endif |