summaryrefslogtreecommitdiffstats
path: root/media/mtp
diff options
context:
space:
mode:
authorMike Lockwood <lockwood@android.com>2011-04-21 17:05:55 -0700
committerMike Lockwood <lockwood@android.com>2011-05-03 21:03:56 -0400
commit4d9da14065fea7bca759f83c419ce10bd1195e83 (patch)
tree803d17f1571306380ec986d87bdbc0dd03d041f4 /media/mtp
parent462accab9dbcf8d1597de999328fa74337b3b88c (diff)
downloadframeworks_av-4d9da14065fea7bca759f83c419ce10bd1195e83.zip
frameworks_av-4d9da14065fea7bca759f83c419ce10bd1195e83.tar.gz
frameworks_av-4d9da14065fea7bca759f83c419ce10bd1195e83.tar.bz2
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 <lockwood@android.com>
Diffstat (limited to 'media/mtp')
-rw-r--r--media/mtp/MtpDatabase.h3
-rw-r--r--media/mtp/MtpDebug.cpp6
-rw-r--r--media/mtp/MtpServer.cpp231
-rw-r--r--media/mtp/MtpServer.h23
-rw-r--r--media/mtp/mtp.h13
5 files changed, 268 insertions, 8 deletions
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<ObjectEdit*> 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