From 4d9da14065fea7bca759f83c419ce10bd1195e83 Mon Sep 17 00:00:00 2001 From: Mike Lockwood Date: Thu, 21 Apr 2011 17:05:55 -0700 Subject: DO NOT MERGE MTP: Add extended operations to support in-place editing of files MTP does not support partial writes of files (the entire file must be transferred at once). This makes it impossible to implement a FUSE file system for MTP with acceptable performance. To fix this problem, this change adds extended MTP operations to allow partial writes to files: SendPartialObject - allows writing a subset of a file, or appending to the end of a file TruncateObject - allows changing the size of a file BeginEditObject - must be called before using SendPartialObject and TruncateObject EndEditObject - commits changes to a file after it has been edited with SendPartialObject or TruncateObject We also add GetPartialObject64, which is the same as GetPartialObject but has a 64 bit offset rather than 32. Change-Id: Id5365e1c4dc55a2d819c16c9db0a3ac2260f9309 Signed-off-by: Mike Lockwood --- media/mtp/MtpDatabase.h | 3 +- media/mtp/MtpDebug.cpp | 6 ++ media/mtp/MtpServer.cpp | 231 ++++++++++++++++++++++++++++++++++++++++++++++-- media/mtp/MtpServer.h | 23 ++++- media/mtp/mtp.h | 13 +++ 5 files changed, 268 insertions(+), 8 deletions(-) (limited to 'media/mtp') diff --git a/media/mtp/MtpDatabase.h b/media/mtp/MtpDatabase.h index 4d9a1ae..d7bde00 100644 --- a/media/mtp/MtpDatabase.h +++ b/media/mtp/MtpDatabase.h @@ -23,6 +23,7 @@ namespace android { class MtpDataPacket; class MtpProperty; +class MtpObjectInfo; class MtpDatabase { public: @@ -81,7 +82,7 @@ public: MtpDataPacket& packet) = 0; virtual MtpResponseCode getObjectInfo(MtpObjectHandle handle, - MtpDataPacket& packet) = 0; + MtpObjectInfo& info) = 0; virtual MtpResponseCode getObjectFilePath(MtpObjectHandle handle, MtpString& outFilePath, diff --git a/media/mtp/MtpDebug.cpp b/media/mtp/MtpDebug.cpp index 1668ecf..9f3037d 100644 --- a/media/mtp/MtpDebug.cpp +++ b/media/mtp/MtpDebug.cpp @@ -63,6 +63,12 @@ static const CodeEntry sOperationCodes[] = { { "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 }, }; diff --git a/media/mtp/MtpServer.cpp b/media/mtp/MtpServer.cpp index 37e02a3..ff4009c 100644 --- a/media/mtp/MtpServer.cpp +++ b/media/mtp/MtpServer.cpp @@ -30,6 +30,7 @@ #include "MtpDebug.h" #include "MtpDatabase.h" +#include "MtpObjectInfo.h" #include "MtpProperty.h" #include "MtpServer.h" #include "MtpStorage.h" @@ -79,6 +80,12 @@ static const MtpOperationCode kSupportedOperationCodes[] = { 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[] = { @@ -218,6 +225,15 @@ void MtpServer::run() { } } + // 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(); + if (mSessionOpen) mDatabase->sessionEnded(); } @@ -252,6 +268,44 @@ void MtpServer::sendEvent(MtpEventCode code, uint32_t param1) { } } +void MtpServer::addEditObject(MtpObjectHandle handle, MtpString& path, + uint64_t size, MtpObjectFormat format, int fd) { + ObjectEdit* edit = new ObjectEdit; + edit->handle = handle; + edit->path = path; + edit->size = size; + edit->format = format; + edit->fd = fd; + mObjectEditList.add(edit); +} + +MtpServer::ObjectEdit* MtpServer::getEditObject(MtpObjectHandle handle) { + int count = mObjectEditList.size(); + for (int i = 0; i < count; i++) { + ObjectEdit* edit = mObjectEditList[i]; + if (edit->handle == handle) return edit; + } + return NULL; +} + +void MtpServer::removeEditObject(MtpObjectHandle handle) { + int count = mObjectEditList.size(); + for (int i = 0; i < count; i++) { + ObjectEdit* edit = mObjectEditList[i]; + if (edit->handle == handle) { + delete edit; + mObjectEditList.removeAt(i); + return; + } + } + LOGE("ObjectEdit not found in removeEditObject"); +} + +void MtpServer::commitEdit(ObjectEdit* edit) { + mDatabase->endSendObject((const char *)edit->path, edit->handle, edit->format, true); +} + + bool MtpServer::handleRequest() { Mutex::Autolock autoLock(mMutex); @@ -322,7 +376,8 @@ bool MtpServer::handleRequest() { response = doGetObject(); break; case MTP_OPERATION_GET_PARTIAL_OBJECT: - response = doGetPartialObject(); + case MTP_OPERATION_GET_PARTIAL_OBJECT_64: + response = doGetPartialObject(operation); break; case MTP_OPERATION_SEND_OBJECT_INFO: response = doSendObjectInfo(); @@ -339,6 +394,18 @@ bool MtpServer::handleRequest() { 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: LOGE("got unsupported command %s", MtpDebug::getOperationCodeName(operation)); response = MTP_RESPONSE_OPERATION_NOT_SUPPORTED; @@ -363,7 +430,7 @@ MtpResponseCode MtpServer::doGetDeviceInfo() { mData.putUInt16(MTP_STANDARD_VERSION); mData.putUInt32(6); // MTP Vendor Extension ID mData.putUInt16(MTP_STANDARD_VERSION); - string.set("microsoft.com: 1.0;"); + string.set("microsoft.com: 1.0; android.com: 1.0;"); mData.putString(string); // MTP Extensions mData.putUInt16(0); //Functional Mode mData.putAUInt16(kSupportedOperationCodes, @@ -601,7 +668,40 @@ MtpResponseCode MtpServer::doGetObjectInfo() { if (!hasStorage()) return MTP_RESPONSE_INVALID_OBJECT_HANDLE; MtpObjectHandle handle = mRequest.getParameter(1); - return mDatabase->getObjectInfo(handle, mData); + 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->size > 0xFFFFFFFFLL ? 0xFFFFFFFF : (uint32_t)edit->size); + 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); + mData.putEmptyString(); // date created + formatDateTime(info.mDateModified, date, sizeof(date)); + mData.putString(date); // date modified + mData.putEmptyString(); // keywords + } + return result; } MtpResponseCode MtpServer::doGetObject() { @@ -641,12 +741,22 @@ MtpResponseCode MtpServer::doGetObject() { return MTP_RESPONSE_OK; } -MtpResponseCode MtpServer::doGetPartialObject() { +MtpResponseCode MtpServer::doGetPartialObject(MtpOperationCode operation) { if (!hasStorage()) return MTP_RESPONSE_INVALID_OBJECT_HANDLE; MtpObjectHandle handle = mRequest.getParameter(1); - uint32_t offset = mRequest.getParameter(2); - uint32_t length = mRequest.getParameter(3); + uint64_t offset; + uint32_t length; + offset = mRequest.getParameter(2); + if (operation == MTP_OPERATION_GET_PARTIAL_OBJECT_64) { + // android extension with 64 bit offset + uint64_t offset2 = mRequest.getParameter(3); + offset = offset | (offset2 << 32); + length = mRequest.getParameter(4); + } else { + // standard GetPartialObject + length = mRequest.getParameter(3); + } MtpString pathBuf; int64_t fileLength; MtpObjectFormat format; @@ -933,4 +1043,113 @@ MtpResponseCode MtpServer::doGetDevicePropDesc() { return MTP_RESPONSE_OK; } +MtpResponseCode MtpServer::doSendPartialObject() { + if (!hasStorage()) + return MTP_RESPONSE_INVALID_OBJECT_HANDLE; + 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) { + LOGE("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->size) { + LOGD("writing past end of object, offset: %lld, edit->size: %lld", offset, edit->size); + return MTP_RESPONSE_GENERAL_ERROR; + } + + // read the header + int ret = mData.readDataHeader(mFD); + // FIXME - check for errors here. + + // reset so we don't attempt to send this back + mData.reset(); + + const char* filePath = (const char *)edit->path; + LOGV("receiving partial %s %lld %ld\n", filePath, offset, length); + mtp_file_range mfr; + mfr.fd = edit->fd; + mfr.offset = offset; + mfr.length = length; + + // transfer the file + ret = ioctl(mFD, MTP_RECEIVE_FILE, (unsigned long)&mfr); + LOGV("MTP_RECEIVE_FILE returned %d", ret); + if (ret < 0) { + mResponse.setParameter(1, 0); + if (errno == ECANCELED) + return MTP_RESPONSE_TRANSACTION_CANCELLED; + else + return MTP_RESPONSE_GENERAL_ERROR; + } + mResponse.setParameter(1, length); + uint64_t end = offset + length; + if (end > edit->size) { + edit->size = end; + } + return MTP_RESPONSE_OK; +} + +MtpResponseCode MtpServer::doTruncateObject() { + MtpObjectHandle handle = mRequest.getParameter(1); + ObjectEdit* edit = getEditObject(handle); + if (!edit) { + LOGE("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->fd, offset) != 0) { + return MTP_RESPONSE_GENERAL_ERROR; + } else { + edit->size = offset; + return MTP_RESPONSE_OK; + } +} + +MtpResponseCode MtpServer::doBeginEditObject() { + MtpObjectHandle handle = mRequest.getParameter(1); + if (getEditObject(handle)) { + LOGE("object already open for edit in doBeginEditObject"); + return MTP_RESPONSE_GENERAL_ERROR; + } + + MtpString 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) { + LOGE("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() { + MtpObjectHandle handle = mRequest.getParameter(1); + ObjectEdit* edit = getEditObject(handle); + if (!edit) { + LOGE("object not open for edit in doEndEditObject"); + return MTP_RESPONSE_GENERAL_ERROR; + } + + commitEdit(edit); + removeEditObject(handle); + return MTP_RESPONSE_OK; +} + } // namespace android diff --git a/media/mtp/MtpServer.h b/media/mtp/MtpServer.h index fa729a8..35a38e7 100644 --- a/media/mtp/MtpServer.h +++ b/media/mtp/MtpServer.h @@ -65,6 +65,17 @@ private: Mutex mMutex; + // represents an MTP object that is being edited using the android extensions + // for direct editing (BeginEditObject, SendPartialObject, TruncateObject and EndEditObject) + struct ObjectEdit { + MtpObjectHandle handle; + MtpString path; + uint64_t size; + MtpObjectFormat format; + int fd; + }; + Vector mObjectEditList; + public: MtpServer(int fd, MtpDatabase* database, int fileGroup, int filePerm, int directoryPerm); @@ -86,6 +97,12 @@ private: void sendStoreRemoved(MtpStorageID id); void sendEvent(MtpEventCode code, uint32_t param1); + void addEditObject(MtpObjectHandle handle, MtpString& path, + uint64_t size, MtpObjectFormat format, int fd); + ObjectEdit* getEditObject(MtpObjectHandle handle); + void removeEditObject(MtpObjectHandle handle); + void commitEdit(ObjectEdit* edit); + bool handleRequest(); MtpResponseCode doGetDeviceInfo(); @@ -106,12 +123,16 @@ private: MtpResponseCode doGetObjectPropList(); MtpResponseCode doGetObjectInfo(); MtpResponseCode doGetObject(); - MtpResponseCode doGetPartialObject(); + MtpResponseCode doGetPartialObject(MtpOperationCode operation); MtpResponseCode doSendObjectInfo(); MtpResponseCode doSendObject(); MtpResponseCode doDeleteObject(); MtpResponseCode doGetObjectPropDesc(); MtpResponseCode doGetDevicePropDesc(); + MtpResponseCode doSendPartialObject(); + MtpResponseCode doTruncateObject(); + MtpResponseCode doBeginEditObject(); + MtpResponseCode doEndEditObject(); }; }; // namespace android diff --git a/media/mtp/mtp.h b/media/mtp/mtp.h index 8bc2e22..d270df5 100644 --- a/media/mtp/mtp.h +++ b/media/mtp/mtp.h @@ -391,6 +391,19 @@ #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 -- cgit v1.1