summaryrefslogtreecommitdiffstats
path: root/mtp/ffs
diff options
context:
space:
mode:
authorbigbiff bigbiff <bigbiff@teamw.in>2018-12-19 00:39:53 +0100
committerEthan Yonker <dees_troy@teamw.in>2019-03-20 20:28:21 +0100
commitaf32bb9c4f4f06e92de3435ed2db3153c0701094 (patch)
tree622948fb3167dc17bb436c948d61df581d2e75f7 /mtp/ffs
parentAdding Edl button in reboot menu (diff)
downloadandroid_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')
-rw-r--r--mtp/ffs/Android.mk70
-rw-r--r--mtp/ffs/AsyncIO.cpp178
-rw-r--r--mtp/ffs/AsyncIO.h83
-rw-r--r--mtp/ffs/IMtpHandle.h43
-rw-r--r--mtp/ffs/MtpDataPacket.cpp650
-rw-r--r--mtp/ffs/MtpDataPacket.h127
-rwxr-xr-xmtp/ffs/MtpDatabase.h112
-rw-r--r--mtp/ffs/MtpDebug.cpp424
-rw-r--r--mtp/ffs/MtpDebug.h47
-rw-r--r--mtp/ffs/MtpDescriptors.cpp282
-rw-r--r--mtp/ffs/MtpDescriptors.h104
-rw-r--r--mtp/ffs/MtpDevHandle.cpp71
-rw-r--r--mtp/ffs/MtpDevHandle.h42
-rw-r--r--mtp/ffs/MtpDevice.cpp946
-rw-r--r--mtp/ffs/MtpDevice.h167
-rw-r--r--mtp/ffs/MtpDeviceInfo.cpp105
-rw-r--r--mtp/ffs/MtpDeviceInfo.h50
-rw-r--r--mtp/ffs/MtpEventPacket.cpp71
-rw-r--r--mtp/ffs/MtpEventPacket.h49
-rw-r--r--mtp/ffs/MtpFfsCompatHandle.cpp338
-rw-r--r--mtp/ffs/MtpFfsCompatHandle.h50
-rw-r--r--mtp/ffs/MtpFfsHandle.cpp674
-rw-r--r--mtp/ffs/MtpFfsHandle.h112
-rw-r--r--mtp/ffs/MtpMessage.hpp33
-rw-r--r--mtp/ffs/MtpObjectInfo.cpp108
-rw-r--r--mtp/ffs/MtpObjectInfo.h56
-rw-r--r--mtp/ffs/MtpPacket.cpp162
-rw-r--r--mtp/ffs/MtpPacket.h74
-rw-r--r--mtp/ffs/MtpProperty.cpp570
-rw-r--r--mtp/ffs/MtpProperty.h115
-rw-r--r--mtp/ffs/MtpRequestPacket.cpp71
-rw-r--r--mtp/ffs/MtpRequestPacket.h51
-rw-r--r--mtp/ffs/MtpResponsePacket.cpp57
-rw-r--r--mtp/ffs/MtpResponsePacket.h46
-rwxr-xr-xmtp/ffs/MtpServer.cpp1459
-rwxr-xr-xmtp/ffs/MtpServer.h167
-rwxr-xr-xmtp/ffs/MtpStorage.cpp810
-rwxr-xr-xmtp/ffs/MtpStorage.h103
-rw-r--r--mtp/ffs/MtpStorageInfo.cpp74
-rw-r--r--mtp/ffs/MtpStorageInfo.h45
-rw-r--r--mtp/ffs/MtpStringBuffer.cpp108
-rw-r--r--mtp/ffs/MtpStringBuffer.h66
-rw-r--r--mtp/ffs/MtpTypes.h78
-rw-r--r--mtp/ffs/MtpUtils.cpp263
-rw-r--r--mtp/ffs/MtpUtils.h38
-rw-r--r--mtp/ffs/NOTICE190
-rw-r--r--mtp/ffs/PosixAsyncIO.cpp76
-rw-r--r--mtp/ffs/PosixAsyncIO.h61
-rw-r--r--mtp/ffs/btree.cpp77
-rw-r--r--mtp/ffs/btree.hpp82
-rw-r--r--mtp/ffs/mtp.h616
-rwxr-xr-xmtp/ffs/mtp_MtpDatabase.cpp850
-rwxr-xr-xmtp/ffs/mtp_MtpDatabase.hpp163
-rwxr-xr-xmtp/ffs/mtp_MtpServer.cpp226
-rw-r--r--mtp/ffs/mtp_MtpServer.hpp78
-rw-r--r--mtp/ffs/node.cpp146
-rw-r--r--mtp/ffs/twrpMtp.cpp125
-rw-r--r--mtp/ffs/twrpMtp.hpp49
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