1 /*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <sys/types.h>
20 #include <sys/ioctl.h>
21 #include <sys/stat.h>
22 #include <fcntl.h>
23 #include <errno.h>
24 #include <sys/stat.h>
25 #include <dirent.h>
26
27 #include <cutils/properties.h>
28
29 #define LOG_TAG "MtpServer"
30
31 #include "MtpDebug.h"
32 #include "MtpDatabase.h"
33 #include "MtpObjectInfo.h"
34 #include "MtpProperty.h"
35 #include "MtpServer.h"
36 #include "MtpStorage.h"
37 #include "MtpStringBuffer.h"
38
39 #include <linux/usb/f_mtp.h>
40
41 namespace android {
42
43 static const MtpOperationCode kSupportedOperationCodes[] = {
44 MTP_OPERATION_GET_DEVICE_INFO,
45 MTP_OPERATION_OPEN_SESSION,
46 MTP_OPERATION_CLOSE_SESSION,
47 MTP_OPERATION_GET_STORAGE_IDS,
48 MTP_OPERATION_GET_STORAGE_INFO,
49 MTP_OPERATION_GET_NUM_OBJECTS,
50 MTP_OPERATION_GET_OBJECT_HANDLES,
51 MTP_OPERATION_GET_OBJECT_INFO,
52 MTP_OPERATION_GET_OBJECT,
53 MTP_OPERATION_GET_THUMB,
54 MTP_OPERATION_DELETE_OBJECT,
55 MTP_OPERATION_SEND_OBJECT_INFO,
56 MTP_OPERATION_SEND_OBJECT,
57 // MTP_OPERATION_INITIATE_CAPTURE,
58 // MTP_OPERATION_FORMAT_STORE,
59 // MTP_OPERATION_RESET_DEVICE,
60 // MTP_OPERATION_SELF_TEST,
61 // MTP_OPERATION_SET_OBJECT_PROTECTION,
62 // MTP_OPERATION_POWER_DOWN,
63 MTP_OPERATION_GET_DEVICE_PROP_DESC,
64 MTP_OPERATION_GET_DEVICE_PROP_VALUE,
65 MTP_OPERATION_SET_DEVICE_PROP_VALUE,
66 MTP_OPERATION_RESET_DEVICE_PROP_VALUE,
67 // MTP_OPERATION_TERMINATE_OPEN_CAPTURE,
68 // MTP_OPERATION_MOVE_OBJECT,
69 // MTP_OPERATION_COPY_OBJECT,
70 MTP_OPERATION_GET_PARTIAL_OBJECT,
71 // MTP_OPERATION_INITIATE_OPEN_CAPTURE,
72 MTP_OPERATION_GET_OBJECT_PROPS_SUPPORTED,
73 MTP_OPERATION_GET_OBJECT_PROP_DESC,
74 MTP_OPERATION_GET_OBJECT_PROP_VALUE,
75 MTP_OPERATION_SET_OBJECT_PROP_VALUE,
76 MTP_OPERATION_GET_OBJECT_PROP_LIST,
77 // MTP_OPERATION_SET_OBJECT_PROP_LIST,
78 // MTP_OPERATION_GET_INTERDEPENDENT_PROP_DESC,
79 // MTP_OPERATION_SEND_OBJECT_PROP_LIST,
80 MTP_OPERATION_GET_OBJECT_REFERENCES,
81 MTP_OPERATION_SET_OBJECT_REFERENCES,
82 // MTP_OPERATION_SKIP,
83 // Android extension for direct file IO
84 MTP_OPERATION_GET_PARTIAL_OBJECT_64,
85 MTP_OPERATION_SEND_PARTIAL_OBJECT,
86 MTP_OPERATION_TRUNCATE_OBJECT,
87 MTP_OPERATION_BEGIN_EDIT_OBJECT,
88 MTP_OPERATION_END_EDIT_OBJECT,
89 };
90
91 static const MtpEventCode kSupportedEventCodes[] = {
92 MTP_EVENT_OBJECT_ADDED,
93 MTP_EVENT_OBJECT_REMOVED,
94 MTP_EVENT_STORE_ADDED,
95 MTP_EVENT_STORE_REMOVED,
96 };
97
MtpServer(int fd,MtpDatabase * database,bool ptp,int fileGroup,int filePerm,int directoryPerm)98 MtpServer::MtpServer(int fd, MtpDatabase* database, bool ptp,
99 int fileGroup, int filePerm, int directoryPerm)
100 : mFD(fd),
101 mDatabase(database),
102 mPtp(ptp),
103 mFileGroup(fileGroup),
104 mFilePermission(filePerm),
105 mDirectoryPermission(directoryPerm),
106 mSessionID(0),
107 mSessionOpen(false),
108 mSendObjectHandle(kInvalidObjectHandle),
109 mSendObjectFormat(0),
110 mSendObjectFileSize(0)
111 {
112 }
113
~MtpServer()114 MtpServer::~MtpServer() {
115 }
116
addStorage(MtpStorage * storage)117 void MtpServer::addStorage(MtpStorage* storage) {
118 Mutex::Autolock autoLock(mMutex);
119
120 mStorages.push(storage);
121 sendStoreAdded(storage->getStorageID());
122 }
123
removeStorage(MtpStorage * storage)124 void MtpServer::removeStorage(MtpStorage* storage) {
125 Mutex::Autolock autoLock(mMutex);
126
127 for (int i = 0; i < mStorages.size(); i++) {
128 if (mStorages[i] == storage) {
129 mStorages.removeAt(i);
130 sendStoreRemoved(storage->getStorageID());
131 break;
132 }
133 }
134 }
135
getStorage(MtpStorageID id)136 MtpStorage* MtpServer::getStorage(MtpStorageID id) {
137 if (id == 0)
138 return mStorages[0];
139 for (int i = 0; i < mStorages.size(); i++) {
140 MtpStorage* storage = mStorages[i];
141 if (storage->getStorageID() == id)
142 return storage;
143 }
144 return NULL;
145 }
146
hasStorage(MtpStorageID id)147 bool MtpServer::hasStorage(MtpStorageID id) {
148 if (id == 0 || id == 0xFFFFFFFF)
149 return mStorages.size() > 0;
150 return (getStorage(id) != NULL);
151 }
152
run()153 void MtpServer::run() {
154 int fd = mFD;
155
156 ALOGV("MtpServer::run fd: %d\n", fd);
157
158 while (1) {
159 int ret = mRequest.read(fd);
160 if (ret < 0) {
161 ALOGV("request read returned %d, errno: %d", ret, errno);
162 if (errno == ECANCELED) {
163 // return to top of loop and wait for next command
164 continue;
165 }
166 break;
167 }
168 MtpOperationCode operation = mRequest.getOperationCode();
169 MtpTransactionID transaction = mRequest.getTransactionID();
170
171 ALOGV("operation: %s", MtpDebug::getOperationCodeName(operation));
172 mRequest.dump();
173
174 // FIXME need to generalize this
175 bool dataIn = (operation == MTP_OPERATION_SEND_OBJECT_INFO
176 || operation == MTP_OPERATION_SET_OBJECT_REFERENCES
177 || operation == MTP_OPERATION_SET_OBJECT_PROP_VALUE
178 || operation == MTP_OPERATION_SET_DEVICE_PROP_VALUE);
179 if (dataIn) {
180 int ret = mData.read(fd);
181 if (ret < 0) {
182 ALOGE("data read returned %d, errno: %d", ret, errno);
183 if (errno == ECANCELED) {
184 // return to top of loop and wait for next command
185 continue;
186 }
187 break;
188 }
189 ALOGV("received data:");
190 mData.dump();
191 } else {
192 mData.reset();
193 }
194
195 if (handleRequest()) {
196 if (!dataIn && mData.hasData()) {
197 mData.setOperationCode(operation);
198 mData.setTransactionID(transaction);
199 ALOGV("sending data:");
200 mData.dump();
201 ret = mData.write(fd);
202 if (ret < 0) {
203 ALOGE("request write returned %d, errno: %d", ret, errno);
204 if (errno == ECANCELED) {
205 // return to top of loop and wait for next command
206 continue;
207 }
208 break;
209 }
210 }
211
212 mResponse.setTransactionID(transaction);
213 ALOGV("sending response %04X", mResponse.getResponseCode());
214 ret = mResponse.write(fd);
215 mResponse.dump();
216 if (ret < 0) {
217 ALOGE("request write returned %d, errno: %d", ret, errno);
218 if (errno == ECANCELED) {
219 // return to top of loop and wait for next command
220 continue;
221 }
222 break;
223 }
224 } else {
225 ALOGV("skipping response\n");
226 }
227 }
228
229 // commit any open edits
230 int count = mObjectEditList.size();
231 for (int i = 0; i < count; i++) {
232 ObjectEdit* edit = mObjectEditList[i];
233 commitEdit(edit);
234 delete edit;
235 }
236 mObjectEditList.clear();
237
238 if (mSessionOpen)
239 mDatabase->sessionEnded();
240 close(fd);
241 mFD = -1;
242 }
243
sendObjectAdded(MtpObjectHandle handle)244 void MtpServer::sendObjectAdded(MtpObjectHandle handle) {
245 ALOGV("sendObjectAdded %d\n", handle);
246 sendEvent(MTP_EVENT_OBJECT_ADDED, handle);
247 }
248
sendObjectRemoved(MtpObjectHandle handle)249 void MtpServer::sendObjectRemoved(MtpObjectHandle handle) {
250 ALOGV("sendObjectRemoved %d\n", handle);
251 sendEvent(MTP_EVENT_OBJECT_REMOVED, handle);
252 }
253
sendStoreAdded(MtpStorageID id)254 void MtpServer::sendStoreAdded(MtpStorageID id) {
255 ALOGV("sendStoreAdded %08X\n", id);
256 sendEvent(MTP_EVENT_STORE_ADDED, id);
257 }
258
sendStoreRemoved(MtpStorageID id)259 void MtpServer::sendStoreRemoved(MtpStorageID id) {
260 ALOGV("sendStoreRemoved %08X\n", id);
261 sendEvent(MTP_EVENT_STORE_REMOVED, id);
262 }
263
sendEvent(MtpEventCode code,uint32_t param1)264 void MtpServer::sendEvent(MtpEventCode code, uint32_t param1) {
265 if (mSessionOpen) {
266 mEvent.setEventCode(code);
267 mEvent.setTransactionID(mRequest.getTransactionID());
268 mEvent.setParameter(1, param1);
269 int ret = mEvent.write(mFD);
270 ALOGV("mEvent.write returned %d\n", ret);
271 }
272 }
273
addEditObject(MtpObjectHandle handle,MtpString & path,uint64_t size,MtpObjectFormat format,int fd)274 void MtpServer::addEditObject(MtpObjectHandle handle, MtpString& path,
275 uint64_t size, MtpObjectFormat format, int fd) {
276 ObjectEdit* edit = new ObjectEdit(handle, path, size, format, fd);
277 mObjectEditList.add(edit);
278 }
279
getEditObject(MtpObjectHandle handle)280 MtpServer::ObjectEdit* MtpServer::getEditObject(MtpObjectHandle handle) {
281 int count = mObjectEditList.size();
282 for (int i = 0; i < count; i++) {
283 ObjectEdit* edit = mObjectEditList[i];
284 if (edit->mHandle == handle) return edit;
285 }
286 return NULL;
287 }
288
removeEditObject(MtpObjectHandle handle)289 void MtpServer::removeEditObject(MtpObjectHandle handle) {
290 int count = mObjectEditList.size();
291 for (int i = 0; i < count; i++) {
292 ObjectEdit* edit = mObjectEditList[i];
293 if (edit->mHandle == handle) {
294 delete edit;
295 mObjectEditList.removeAt(i);
296 return;
297 }
298 }
299 ALOGE("ObjectEdit not found in removeEditObject");
300 }
301
commitEdit(ObjectEdit * edit)302 void MtpServer::commitEdit(ObjectEdit* edit) {
303 mDatabase->endSendObject((const char *)edit->mPath, edit->mHandle, edit->mFormat, true);
304 }
305
306
handleRequest()307 bool MtpServer::handleRequest() {
308 Mutex::Autolock autoLock(mMutex);
309
310 MtpOperationCode operation = mRequest.getOperationCode();
311 MtpResponseCode response;
312
313 mResponse.reset();
314
315 if (mSendObjectHandle != kInvalidObjectHandle && operation != MTP_OPERATION_SEND_OBJECT) {
316 // FIXME - need to delete mSendObjectHandle from the database
317 ALOGE("expected SendObject after SendObjectInfo");
318 mSendObjectHandle = kInvalidObjectHandle;
319 }
320
321 switch (operation) {
322 case MTP_OPERATION_GET_DEVICE_INFO:
323 response = doGetDeviceInfo();
324 break;
325 case MTP_OPERATION_OPEN_SESSION:
326 response = doOpenSession();
327 break;
328 case MTP_OPERATION_CLOSE_SESSION:
329 response = doCloseSession();
330 break;
331 case MTP_OPERATION_GET_STORAGE_IDS:
332 response = doGetStorageIDs();
333 break;
334 case MTP_OPERATION_GET_STORAGE_INFO:
335 response = doGetStorageInfo();
336 break;
337 case MTP_OPERATION_GET_OBJECT_PROPS_SUPPORTED:
338 response = doGetObjectPropsSupported();
339 break;
340 case MTP_OPERATION_GET_OBJECT_HANDLES:
341 response = doGetObjectHandles();
342 break;
343 case MTP_OPERATION_GET_NUM_OBJECTS:
344 response = doGetNumObjects();
345 break;
346 case MTP_OPERATION_GET_OBJECT_REFERENCES:
347 response = doGetObjectReferences();
348 break;
349 case MTP_OPERATION_SET_OBJECT_REFERENCES:
350 response = doSetObjectReferences();
351 break;
352 case MTP_OPERATION_GET_OBJECT_PROP_VALUE:
353 response = doGetObjectPropValue();
354 break;
355 case MTP_OPERATION_SET_OBJECT_PROP_VALUE:
356 response = doSetObjectPropValue();
357 break;
358 case MTP_OPERATION_GET_DEVICE_PROP_VALUE:
359 response = doGetDevicePropValue();
360 break;
361 case MTP_OPERATION_SET_DEVICE_PROP_VALUE:
362 response = doSetDevicePropValue();
363 break;
364 case MTP_OPERATION_RESET_DEVICE_PROP_VALUE:
365 response = doResetDevicePropValue();
366 break;
367 case MTP_OPERATION_GET_OBJECT_PROP_LIST:
368 response = doGetObjectPropList();
369 break;
370 case MTP_OPERATION_GET_OBJECT_INFO:
371 response = doGetObjectInfo();
372 break;
373 case MTP_OPERATION_GET_OBJECT:
374 response = doGetObject();
375 break;
376 case MTP_OPERATION_GET_THUMB:
377 response = doGetThumb();
378 break;
379 case MTP_OPERATION_GET_PARTIAL_OBJECT:
380 case MTP_OPERATION_GET_PARTIAL_OBJECT_64:
381 response = doGetPartialObject(operation);
382 break;
383 case MTP_OPERATION_SEND_OBJECT_INFO:
384 response = doSendObjectInfo();
385 break;
386 case MTP_OPERATION_SEND_OBJECT:
387 response = doSendObject();
388 break;
389 case MTP_OPERATION_DELETE_OBJECT:
390 response = doDeleteObject();
391 break;
392 case MTP_OPERATION_GET_OBJECT_PROP_DESC:
393 response = doGetObjectPropDesc();
394 break;
395 case MTP_OPERATION_GET_DEVICE_PROP_DESC:
396 response = doGetDevicePropDesc();
397 break;
398 case MTP_OPERATION_SEND_PARTIAL_OBJECT:
399 response = doSendPartialObject();
400 break;
401 case MTP_OPERATION_TRUNCATE_OBJECT:
402 response = doTruncateObject();
403 break;
404 case MTP_OPERATION_BEGIN_EDIT_OBJECT:
405 response = doBeginEditObject();
406 break;
407 case MTP_OPERATION_END_EDIT_OBJECT:
408 response = doEndEditObject();
409 break;
410 default:
411 ALOGE("got unsupported command %s", MtpDebug::getOperationCodeName(operation));
412 response = MTP_RESPONSE_OPERATION_NOT_SUPPORTED;
413 break;
414 }
415
416 if (response == MTP_RESPONSE_TRANSACTION_CANCELLED)
417 return false;
418 mResponse.setResponseCode(response);
419 return true;
420 }
421
doGetDeviceInfo()422 MtpResponseCode MtpServer::doGetDeviceInfo() {
423 MtpStringBuffer string;
424 char prop_value[PROPERTY_VALUE_MAX];
425
426 MtpObjectFormatList* playbackFormats = mDatabase->getSupportedPlaybackFormats();
427 MtpObjectFormatList* captureFormats = mDatabase->getSupportedCaptureFormats();
428 MtpDevicePropertyList* deviceProperties = mDatabase->getSupportedDeviceProperties();
429
430 // fill in device info
431 mData.putUInt16(MTP_STANDARD_VERSION);
432 if (mPtp) {
433 mData.putUInt32(0);
434 } else {
435 // MTP Vendor Extension ID
436 mData.putUInt32(6);
437 }
438 mData.putUInt16(MTP_STANDARD_VERSION);
439 if (mPtp) {
440 // no extensions
441 string.set("");
442 } else {
443 // MTP extensions
444 string.set("microsoft.com: 1.0; android.com: 1.0;");
445 }
446 mData.putString(string); // MTP Extensions
447 mData.putUInt16(0); //Functional Mode
448 mData.putAUInt16(kSupportedOperationCodes,
449 sizeof(kSupportedOperationCodes) / sizeof(uint16_t)); // Operations Supported
450 mData.putAUInt16(kSupportedEventCodes,
451 sizeof(kSupportedEventCodes) / sizeof(uint16_t)); // Events Supported
452 mData.putAUInt16(deviceProperties); // Device Properties Supported
453 mData.putAUInt16(captureFormats); // Capture Formats
454 mData.putAUInt16(playbackFormats); // Playback Formats
455
456 property_get("ro.product.manufacturer", prop_value, "unknown manufacturer");
457 string.set(prop_value);
458 mData.putString(string); // Manufacturer
459
460 property_get("ro.product.model", prop_value, "MTP Device");
461 string.set(prop_value);
462 mData.putString(string); // Model
463 string.set("1.0");
464 mData.putString(string); // Device Version
465
466 property_get("ro.serialno", prop_value, "????????");
467 string.set(prop_value);
468 mData.putString(string); // Serial Number
469
470 delete playbackFormats;
471 delete captureFormats;
472 delete deviceProperties;
473
474 return MTP_RESPONSE_OK;
475 }
476
doOpenSession()477 MtpResponseCode MtpServer::doOpenSession() {
478 if (mSessionOpen) {
479 mResponse.setParameter(1, mSessionID);
480 return MTP_RESPONSE_SESSION_ALREADY_OPEN;
481 }
482 mSessionID = mRequest.getParameter(1);
483 mSessionOpen = true;
484
485 mDatabase->sessionStarted();
486
487 return MTP_RESPONSE_OK;
488 }
489
doCloseSession()490 MtpResponseCode MtpServer::doCloseSession() {
491 if (!mSessionOpen)
492 return MTP_RESPONSE_SESSION_NOT_OPEN;
493 mSessionID = 0;
494 mSessionOpen = false;
495 mDatabase->sessionEnded();
496 return MTP_RESPONSE_OK;
497 }
498
doGetStorageIDs()499 MtpResponseCode MtpServer::doGetStorageIDs() {
500 if (!mSessionOpen)
501 return MTP_RESPONSE_SESSION_NOT_OPEN;
502
503 int count = mStorages.size();
504 mData.putUInt32(count);
505 for (int i = 0; i < count; i++)
506 mData.putUInt32(mStorages[i]->getStorageID());
507
508 return MTP_RESPONSE_OK;
509 }
510
doGetStorageInfo()511 MtpResponseCode MtpServer::doGetStorageInfo() {
512 MtpStringBuffer string;
513
514 if (!mSessionOpen)
515 return MTP_RESPONSE_SESSION_NOT_OPEN;
516 MtpStorageID id = mRequest.getParameter(1);
517 MtpStorage* storage = getStorage(id);
518 if (!storage)
519 return MTP_RESPONSE_INVALID_STORAGE_ID;
520
521 mData.putUInt16(storage->getType());
522 mData.putUInt16(storage->getFileSystemType());
523 mData.putUInt16(storage->getAccessCapability());
524 mData.putUInt64(storage->getMaxCapacity());
525 mData.putUInt64(storage->getFreeSpace());
526 mData.putUInt32(1024*1024*1024); // Free Space in Objects
527 string.set(storage->getDescription());
528 mData.putString(string);
529 mData.putEmptyString(); // Volume Identifier
530
531 return MTP_RESPONSE_OK;
532 }
533
doGetObjectPropsSupported()534 MtpResponseCode MtpServer::doGetObjectPropsSupported() {
535 if (!mSessionOpen)
536 return MTP_RESPONSE_SESSION_NOT_OPEN;
537 MtpObjectFormat format = mRequest.getParameter(1);
538 MtpObjectPropertyList* properties = mDatabase->getSupportedObjectProperties(format);
539 mData.putAUInt16(properties);
540 delete properties;
541 return MTP_RESPONSE_OK;
542 }
543
doGetObjectHandles()544 MtpResponseCode MtpServer::doGetObjectHandles() {
545 if (!mSessionOpen)
546 return MTP_RESPONSE_SESSION_NOT_OPEN;
547 MtpStorageID storageID = mRequest.getParameter(1); // 0xFFFFFFFF for all storage
548 MtpObjectFormat format = mRequest.getParameter(2); // 0 for all formats
549 MtpObjectHandle parent = mRequest.getParameter(3); // 0xFFFFFFFF for objects with no parent
550 // 0x00000000 for all objects
551
552 if (!hasStorage(storageID))
553 return MTP_RESPONSE_INVALID_STORAGE_ID;
554
555 MtpObjectHandleList* handles = mDatabase->getObjectList(storageID, format, parent);
556 mData.putAUInt32(handles);
557 delete handles;
558 return MTP_RESPONSE_OK;
559 }
560
doGetNumObjects()561 MtpResponseCode MtpServer::doGetNumObjects() {
562 if (!mSessionOpen)
563 return MTP_RESPONSE_SESSION_NOT_OPEN;
564 MtpStorageID storageID = mRequest.getParameter(1); // 0xFFFFFFFF for all storage
565 MtpObjectFormat format = mRequest.getParameter(2); // 0 for all formats
566 MtpObjectHandle parent = mRequest.getParameter(3); // 0xFFFFFFFF for objects with no parent
567 // 0x00000000 for all objects
568 if (!hasStorage(storageID))
569 return MTP_RESPONSE_INVALID_STORAGE_ID;
570
571 int count = mDatabase->getNumObjects(storageID, format, parent);
572 if (count >= 0) {
573 mResponse.setParameter(1, count);
574 return MTP_RESPONSE_OK;
575 } else {
576 mResponse.setParameter(1, 0);
577 return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
578 }
579 }
580
doGetObjectReferences()581 MtpResponseCode MtpServer::doGetObjectReferences() {
582 if (!mSessionOpen)
583 return MTP_RESPONSE_SESSION_NOT_OPEN;
584 if (!hasStorage())
585 return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
586 MtpObjectHandle handle = mRequest.getParameter(1);
587
588 // FIXME - check for invalid object handle
589 MtpObjectHandleList* handles = mDatabase->getObjectReferences(handle);
590 if (handles) {
591 mData.putAUInt32(handles);
592 delete handles;
593 } else {
594 mData.putEmptyArray();
595 }
596 return MTP_RESPONSE_OK;
597 }
598
doSetObjectReferences()599 MtpResponseCode MtpServer::doSetObjectReferences() {
600 if (!mSessionOpen)
601 return MTP_RESPONSE_SESSION_NOT_OPEN;
602 if (!hasStorage())
603 return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
604 MtpStorageID handle = mRequest.getParameter(1);
605
606 MtpObjectHandleList* references = mData.getAUInt32();
607 MtpResponseCode result = mDatabase->setObjectReferences(handle, references);
608 delete references;
609 return result;
610 }
611
doGetObjectPropValue()612 MtpResponseCode MtpServer::doGetObjectPropValue() {
613 if (!hasStorage())
614 return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
615 MtpObjectHandle handle = mRequest.getParameter(1);
616 MtpObjectProperty property = mRequest.getParameter(2);
617 ALOGV("GetObjectPropValue %d %s\n", handle,
618 MtpDebug::getObjectPropCodeName(property));
619
620 return mDatabase->getObjectPropertyValue(handle, property, mData);
621 }
622
doSetObjectPropValue()623 MtpResponseCode MtpServer::doSetObjectPropValue() {
624 if (!hasStorage())
625 return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
626 MtpObjectHandle handle = mRequest.getParameter(1);
627 MtpObjectProperty property = mRequest.getParameter(2);
628 ALOGV("SetObjectPropValue %d %s\n", handle,
629 MtpDebug::getObjectPropCodeName(property));
630
631 return mDatabase->setObjectPropertyValue(handle, property, mData);
632 }
633
doGetDevicePropValue()634 MtpResponseCode MtpServer::doGetDevicePropValue() {
635 MtpDeviceProperty property = mRequest.getParameter(1);
636 ALOGV("GetDevicePropValue %s\n",
637 MtpDebug::getDevicePropCodeName(property));
638
639 return mDatabase->getDevicePropertyValue(property, mData);
640 }
641
doSetDevicePropValue()642 MtpResponseCode MtpServer::doSetDevicePropValue() {
643 MtpDeviceProperty property = mRequest.getParameter(1);
644 ALOGV("SetDevicePropValue %s\n",
645 MtpDebug::getDevicePropCodeName(property));
646
647 return mDatabase->setDevicePropertyValue(property, mData);
648 }
649
doResetDevicePropValue()650 MtpResponseCode MtpServer::doResetDevicePropValue() {
651 MtpDeviceProperty property = mRequest.getParameter(1);
652 ALOGV("ResetDevicePropValue %s\n",
653 MtpDebug::getDevicePropCodeName(property));
654
655 return mDatabase->resetDeviceProperty(property);
656 }
657
doGetObjectPropList()658 MtpResponseCode MtpServer::doGetObjectPropList() {
659 if (!hasStorage())
660 return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
661
662 MtpObjectHandle handle = mRequest.getParameter(1);
663 // use uint32_t so we can support 0xFFFFFFFF
664 uint32_t format = mRequest.getParameter(2);
665 uint32_t property = mRequest.getParameter(3);
666 int groupCode = mRequest.getParameter(4);
667 int depth = mRequest.getParameter(5);
668 ALOGV("GetObjectPropList %d format: %s property: %s group: %d depth: %d\n",
669 handle, MtpDebug::getFormatCodeName(format),
670 MtpDebug::getObjectPropCodeName(property), groupCode, depth);
671
672 return mDatabase->getObjectPropertyList(handle, format, property, groupCode, depth, mData);
673 }
674
doGetObjectInfo()675 MtpResponseCode MtpServer::doGetObjectInfo() {
676 if (!hasStorage())
677 return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
678 MtpObjectHandle handle = mRequest.getParameter(1);
679 MtpObjectInfo info(handle);
680 MtpResponseCode result = mDatabase->getObjectInfo(handle, info);
681 if (result == MTP_RESPONSE_OK) {
682 char date[20];
683
684 mData.putUInt32(info.mStorageID);
685 mData.putUInt16(info.mFormat);
686 mData.putUInt16(info.mProtectionStatus);
687
688 // if object is being edited the database size may be out of date
689 uint32_t size = info.mCompressedSize;
690 ObjectEdit* edit = getEditObject(handle);
691 if (edit)
692 size = (edit->mSize > 0xFFFFFFFFLL ? 0xFFFFFFFF : (uint32_t)edit->mSize);
693 mData.putUInt32(size);
694
695 mData.putUInt16(info.mThumbFormat);
696 mData.putUInt32(info.mThumbCompressedSize);
697 mData.putUInt32(info.mThumbPixWidth);
698 mData.putUInt32(info.mThumbPixHeight);
699 mData.putUInt32(info.mImagePixWidth);
700 mData.putUInt32(info.mImagePixHeight);
701 mData.putUInt32(info.mImagePixDepth);
702 mData.putUInt32(info.mParent);
703 mData.putUInt16(info.mAssociationType);
704 mData.putUInt32(info.mAssociationDesc);
705 mData.putUInt32(info.mSequenceNumber);
706 mData.putString(info.mName);
707 mData.putEmptyString(); // date created
708 formatDateTime(info.mDateModified, date, sizeof(date));
709 mData.putString(date); // date modified
710 mData.putEmptyString(); // keywords
711 }
712 return result;
713 }
714
doGetObject()715 MtpResponseCode MtpServer::doGetObject() {
716 if (!hasStorage())
717 return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
718 MtpObjectHandle handle = mRequest.getParameter(1);
719 MtpString pathBuf;
720 int64_t fileLength;
721 MtpObjectFormat format;
722 int result = mDatabase->getObjectFilePath(handle, pathBuf, fileLength, format);
723 if (result != MTP_RESPONSE_OK)
724 return result;
725
726 const char* filePath = (const char *)pathBuf;
727 mtp_file_range mfr;
728 mfr.fd = open(filePath, O_RDONLY);
729 if (mfr.fd < 0) {
730 return MTP_RESPONSE_GENERAL_ERROR;
731 }
732 mfr.offset = 0;
733 mfr.length = fileLength;
734 mfr.command = mRequest.getOperationCode();
735 mfr.transaction_id = mRequest.getTransactionID();
736
737 // then transfer the file
738 int ret = ioctl(mFD, MTP_SEND_FILE_WITH_HEADER, (unsigned long)&mfr);
739 ALOGV("MTP_SEND_FILE_WITH_HEADER returned %d\n", ret);
740 close(mfr.fd);
741 if (ret < 0) {
742 if (errno == ECANCELED)
743 return MTP_RESPONSE_TRANSACTION_CANCELLED;
744 else
745 return MTP_RESPONSE_GENERAL_ERROR;
746 }
747 return MTP_RESPONSE_OK;
748 }
749
doGetThumb()750 MtpResponseCode MtpServer::doGetThumb() {
751 MtpObjectHandle handle = mRequest.getParameter(1);
752 size_t thumbSize;
753 void* thumb = mDatabase->getThumbnail(handle, thumbSize);
754 if (thumb) {
755 // send data
756 mData.setOperationCode(mRequest.getOperationCode());
757 mData.setTransactionID(mRequest.getTransactionID());
758 mData.writeData(mFD, thumb, thumbSize);
759 free(thumb);
760 return MTP_RESPONSE_OK;
761 } else {
762 return MTP_RESPONSE_GENERAL_ERROR;
763 }
764 }
765
doGetPartialObject(MtpOperationCode operation)766 MtpResponseCode MtpServer::doGetPartialObject(MtpOperationCode operation) {
767 if (!hasStorage())
768 return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
769 MtpObjectHandle handle = mRequest.getParameter(1);
770 uint64_t offset;
771 uint32_t length;
772 offset = mRequest.getParameter(2);
773 if (operation == MTP_OPERATION_GET_PARTIAL_OBJECT_64) {
774 // android extension with 64 bit offset
775 uint64_t offset2 = mRequest.getParameter(3);
776 offset = offset | (offset2 << 32);
777 length = mRequest.getParameter(4);
778 } else {
779 // standard GetPartialObject
780 length = mRequest.getParameter(3);
781 }
782 MtpString pathBuf;
783 int64_t fileLength;
784 MtpObjectFormat format;
785 int result = mDatabase->getObjectFilePath(handle, pathBuf, fileLength, format);
786 if (result != MTP_RESPONSE_OK)
787 return result;
788 if (offset + length > fileLength)
789 length = fileLength - offset;
790
791 const char* filePath = (const char *)pathBuf;
792 mtp_file_range mfr;
793 mfr.fd = open(filePath, O_RDONLY);
794 if (mfr.fd < 0) {
795 return MTP_RESPONSE_GENERAL_ERROR;
796 }
797 mfr.offset = offset;
798 mfr.length = length;
799 mfr.command = mRequest.getOperationCode();
800 mfr.transaction_id = mRequest.getTransactionID();
801 mResponse.setParameter(1, length);
802
803 // transfer the file
804 int ret = ioctl(mFD, MTP_SEND_FILE_WITH_HEADER, (unsigned long)&mfr);
805 ALOGV("MTP_SEND_FILE_WITH_HEADER returned %d\n", ret);
806 close(mfr.fd);
807 if (ret < 0) {
808 if (errno == ECANCELED)
809 return MTP_RESPONSE_TRANSACTION_CANCELLED;
810 else
811 return MTP_RESPONSE_GENERAL_ERROR;
812 }
813 return MTP_RESPONSE_OK;
814 }
815
doSendObjectInfo()816 MtpResponseCode MtpServer::doSendObjectInfo() {
817 MtpString path;
818 MtpStorageID storageID = mRequest.getParameter(1);
819 MtpStorage* storage = getStorage(storageID);
820 MtpObjectHandle parent = mRequest.getParameter(2);
821 if (!storage)
822 return MTP_RESPONSE_INVALID_STORAGE_ID;
823
824 // special case the root
825 if (parent == MTP_PARENT_ROOT) {
826 path = storage->getPath();
827 parent = 0;
828 } else {
829 int64_t length;
830 MtpObjectFormat format;
831 int result = mDatabase->getObjectFilePath(parent, path, length, format);
832 if (result != MTP_RESPONSE_OK)
833 return result;
834 if (format != MTP_FORMAT_ASSOCIATION)
835 return MTP_RESPONSE_INVALID_PARENT_OBJECT;
836 }
837
838 // read only the fields we need
839 mData.getUInt32(); // storage ID
840 MtpObjectFormat format = mData.getUInt16();
841 mData.getUInt16(); // protection status
842 mSendObjectFileSize = mData.getUInt32();
843 mData.getUInt16(); // thumb format
844 mData.getUInt32(); // thumb compressed size
845 mData.getUInt32(); // thumb pix width
846 mData.getUInt32(); // thumb pix height
847 mData.getUInt32(); // image pix width
848 mData.getUInt32(); // image pix height
849 mData.getUInt32(); // image bit depth
850 mData.getUInt32(); // parent
851 uint16_t associationType = mData.getUInt16();
852 uint32_t associationDesc = mData.getUInt32(); // association desc
853 mData.getUInt32(); // sequence number
854 MtpStringBuffer name, created, modified;
855 mData.getString(name); // file name
856 mData.getString(created); // date created
857 mData.getString(modified); // date modified
858 // keywords follow
859
860 ALOGV("name: %s format: %04X\n", (const char *)name, format);
861 time_t modifiedTime;
862 if (!parseDateTime(modified, modifiedTime))
863 modifiedTime = 0;
864
865 if (path[path.size() - 1] != '/')
866 path += "/";
867 path += (const char *)name;
868
869 // check space first
870 if (mSendObjectFileSize > storage->getFreeSpace())
871 return MTP_RESPONSE_STORAGE_FULL;
872 uint64_t maxFileSize = storage->getMaxFileSize();
873 // check storage max file size
874 if (maxFileSize != 0) {
875 // if mSendObjectFileSize is 0xFFFFFFFF, then all we know is the file size
876 // is >= 0xFFFFFFFF
877 if (mSendObjectFileSize > maxFileSize || mSendObjectFileSize == 0xFFFFFFFF)
878 return MTP_RESPONSE_OBJECT_TOO_LARGE;
879 }
880
881 ALOGD("path: %s parent: %d storageID: %08X", (const char*)path, parent, storageID);
882 MtpObjectHandle handle = mDatabase->beginSendObject((const char*)path,
883 format, parent, storageID, mSendObjectFileSize, modifiedTime);
884 if (handle == kInvalidObjectHandle) {
885 return MTP_RESPONSE_GENERAL_ERROR;
886 }
887
888 if (format == MTP_FORMAT_ASSOCIATION) {
889 mode_t mask = umask(0);
890 int ret = mkdir((const char *)path, mDirectoryPermission);
891 umask(mask);
892 if (ret && ret != -EEXIST)
893 return MTP_RESPONSE_GENERAL_ERROR;
894 chown((const char *)path, getuid(), mFileGroup);
895
896 // SendObject does not get sent for directories, so call endSendObject here instead
897 mDatabase->endSendObject(path, handle, MTP_FORMAT_ASSOCIATION, MTP_RESPONSE_OK);
898 } else {
899 mSendObjectFilePath = path;
900 // save the handle for the SendObject call, which should follow
901 mSendObjectHandle = handle;
902 mSendObjectFormat = format;
903 }
904
905 mResponse.setParameter(1, storageID);
906 mResponse.setParameter(2, parent);
907 mResponse.setParameter(3, handle);
908
909 return MTP_RESPONSE_OK;
910 }
911
doSendObject()912 MtpResponseCode MtpServer::doSendObject() {
913 if (!hasStorage())
914 return MTP_RESPONSE_GENERAL_ERROR;
915 MtpResponseCode result = MTP_RESPONSE_OK;
916 mode_t mask;
917 int ret, initialData;
918
919 if (mSendObjectHandle == kInvalidObjectHandle) {
920 ALOGE("Expected SendObjectInfo before SendObject");
921 result = MTP_RESPONSE_NO_VALID_OBJECT_INFO;
922 goto done;
923 }
924
925 // read the header, and possibly some data
926 ret = mData.read(mFD);
927 if (ret < MTP_CONTAINER_HEADER_SIZE) {
928 result = MTP_RESPONSE_GENERAL_ERROR;
929 goto done;
930 }
931 initialData = ret - MTP_CONTAINER_HEADER_SIZE;
932
933 mtp_file_range mfr;
934 mfr.fd = open(mSendObjectFilePath, O_RDWR | O_CREAT | O_TRUNC);
935 if (mfr.fd < 0) {
936 result = MTP_RESPONSE_GENERAL_ERROR;
937 goto done;
938 }
939 fchown(mfr.fd, getuid(), mFileGroup);
940 // set permissions
941 mask = umask(0);
942 fchmod(mfr.fd, mFilePermission);
943 umask(mask);
944
945 if (initialData > 0)
946 ret = write(mfr.fd, mData.getData(), initialData);
947
948 if (mSendObjectFileSize - initialData > 0) {
949 mfr.offset = initialData;
950 if (mSendObjectFileSize == 0xFFFFFFFF) {
951 // tell driver to read until it receives a short packet
952 mfr.length = 0xFFFFFFFF;
953 } else {
954 mfr.length = mSendObjectFileSize - initialData;
955 }
956
957 ALOGV("receiving %s\n", (const char *)mSendObjectFilePath);
958 // transfer the file
959 ret = ioctl(mFD, MTP_RECEIVE_FILE, (unsigned long)&mfr);
960 ALOGV("MTP_RECEIVE_FILE returned %d\n", ret);
961 }
962 close(mfr.fd);
963
964 if (ret < 0) {
965 unlink(mSendObjectFilePath);
966 if (errno == ECANCELED)
967 result = MTP_RESPONSE_TRANSACTION_CANCELLED;
968 else
969 result = MTP_RESPONSE_GENERAL_ERROR;
970 }
971
972 done:
973 // reset so we don't attempt to send the data back
974 mData.reset();
975
976 mDatabase->endSendObject(mSendObjectFilePath, mSendObjectHandle, mSendObjectFormat,
977 result == MTP_RESPONSE_OK);
978 mSendObjectHandle = kInvalidObjectHandle;
979 mSendObjectFormat = 0;
980 return result;
981 }
982
deleteRecursive(const char * path)983 static void deleteRecursive(const char* path) {
984 char pathbuf[PATH_MAX];
985 int pathLength = strlen(path);
986 if (pathLength >= sizeof(pathbuf) - 1) {
987 ALOGE("path too long: %s\n", path);
988 }
989 strcpy(pathbuf, path);
990 if (pathbuf[pathLength - 1] != '/') {
991 pathbuf[pathLength++] = '/';
992 }
993 char* fileSpot = pathbuf + pathLength;
994 int pathRemaining = sizeof(pathbuf) - pathLength - 1;
995
996 DIR* dir = opendir(path);
997 if (!dir) {
998 ALOGE("opendir %s failed: %s", path, strerror(errno));
999 return;
1000 }
1001
1002 struct dirent* entry;
1003 while ((entry = readdir(dir))) {
1004 const char* name = entry->d_name;
1005
1006 // ignore "." and ".."
1007 if (name[0] == '.' && (name[1] == 0 || (name[1] == '.' && name[2] == 0))) {
1008 continue;
1009 }
1010
1011 int nameLength = strlen(name);
1012 if (nameLength > pathRemaining) {
1013 ALOGE("path %s/%s too long\n", path, name);
1014 continue;
1015 }
1016 strcpy(fileSpot, name);
1017
1018 int type = entry->d_type;
1019 if (entry->d_type == DT_DIR) {
1020 deleteRecursive(pathbuf);
1021 rmdir(pathbuf);
1022 } else {
1023 unlink(pathbuf);
1024 }
1025 }
1026 closedir(dir);
1027 }
1028
deletePath(const char * path)1029 static void deletePath(const char* path) {
1030 struct stat statbuf;
1031 if (stat(path, &statbuf) == 0) {
1032 if (S_ISDIR(statbuf.st_mode)) {
1033 deleteRecursive(path);
1034 rmdir(path);
1035 } else {
1036 unlink(path);
1037 }
1038 } else {
1039 ALOGE("deletePath stat failed for %s: %s", path, strerror(errno));
1040 }
1041 }
1042
doDeleteObject()1043 MtpResponseCode MtpServer::doDeleteObject() {
1044 if (!hasStorage())
1045 return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
1046 MtpObjectHandle handle = mRequest.getParameter(1);
1047 MtpObjectFormat format = mRequest.getParameter(2);
1048 // FIXME - support deleting all objects if handle is 0xFFFFFFFF
1049 // FIXME - implement deleting objects by format
1050
1051 MtpString filePath;
1052 int64_t fileLength;
1053 int result = mDatabase->getObjectFilePath(handle, filePath, fileLength, format);
1054 if (result == MTP_RESPONSE_OK) {
1055 ALOGV("deleting %s", (const char *)filePath);
1056 result = mDatabase->deleteFile(handle);
1057 // Don't delete the actual files unless the database deletion is allowed
1058 if (result == MTP_RESPONSE_OK) {
1059 deletePath((const char *)filePath);
1060 }
1061 }
1062
1063 return result;
1064 }
1065
doGetObjectPropDesc()1066 MtpResponseCode MtpServer::doGetObjectPropDesc() {
1067 MtpObjectProperty propCode = mRequest.getParameter(1);
1068 MtpObjectFormat format = mRequest.getParameter(2);
1069 ALOGV("GetObjectPropDesc %s %s\n", MtpDebug::getObjectPropCodeName(propCode),
1070 MtpDebug::getFormatCodeName(format));
1071 MtpProperty* property = mDatabase->getObjectPropertyDesc(propCode, format);
1072 if (!property)
1073 return MTP_RESPONSE_OBJECT_PROP_NOT_SUPPORTED;
1074 property->write(mData);
1075 delete property;
1076 return MTP_RESPONSE_OK;
1077 }
1078
doGetDevicePropDesc()1079 MtpResponseCode MtpServer::doGetDevicePropDesc() {
1080 MtpDeviceProperty propCode = mRequest.getParameter(1);
1081 ALOGV("GetDevicePropDesc %s\n", MtpDebug::getDevicePropCodeName(propCode));
1082 MtpProperty* property = mDatabase->getDevicePropertyDesc(propCode);
1083 if (!property)
1084 return MTP_RESPONSE_DEVICE_PROP_NOT_SUPPORTED;
1085 property->write(mData);
1086 delete property;
1087 return MTP_RESPONSE_OK;
1088 }
1089
doSendPartialObject()1090 MtpResponseCode MtpServer::doSendPartialObject() {
1091 if (!hasStorage())
1092 return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
1093 MtpObjectHandle handle = mRequest.getParameter(1);
1094 uint64_t offset = mRequest.getParameter(2);
1095 uint64_t offset2 = mRequest.getParameter(3);
1096 offset = offset | (offset2 << 32);
1097 uint32_t length = mRequest.getParameter(4);
1098
1099 ObjectEdit* edit = getEditObject(handle);
1100 if (!edit) {
1101 ALOGE("object not open for edit in doSendPartialObject");
1102 return MTP_RESPONSE_GENERAL_ERROR;
1103 }
1104
1105 // can't start writing past the end of the file
1106 if (offset > edit->mSize) {
1107 ALOGD("writing past end of object, offset: %lld, edit->mSize: %lld", offset, edit->mSize);
1108 return MTP_RESPONSE_GENERAL_ERROR;
1109 }
1110
1111 const char* filePath = (const char *)edit->mPath;
1112 ALOGV("receiving partial %s %lld %lld\n", filePath, offset, length);
1113
1114 // read the header, and possibly some data
1115 int ret = mData.read(mFD);
1116 if (ret < MTP_CONTAINER_HEADER_SIZE)
1117 return MTP_RESPONSE_GENERAL_ERROR;
1118 int initialData = ret - MTP_CONTAINER_HEADER_SIZE;
1119
1120 if (initialData > 0) {
1121 ret = write(edit->mFD, mData.getData(), initialData);
1122 offset += initialData;
1123 length -= initialData;
1124 }
1125
1126 if (length > 0) {
1127 mtp_file_range mfr;
1128 mfr.fd = edit->mFD;
1129 mfr.offset = offset;
1130 mfr.length = length;
1131
1132 // transfer the file
1133 ret = ioctl(mFD, MTP_RECEIVE_FILE, (unsigned long)&mfr);
1134 ALOGV("MTP_RECEIVE_FILE returned %d", ret);
1135 }
1136 if (ret < 0) {
1137 mResponse.setParameter(1, 0);
1138 if (errno == ECANCELED)
1139 return MTP_RESPONSE_TRANSACTION_CANCELLED;
1140 else
1141 return MTP_RESPONSE_GENERAL_ERROR;
1142 }
1143
1144 // reset so we don't attempt to send this back
1145 mData.reset();
1146 mResponse.setParameter(1, length);
1147 uint64_t end = offset + length;
1148 if (end > edit->mSize) {
1149 edit->mSize = end;
1150 }
1151 return MTP_RESPONSE_OK;
1152 }
1153
doTruncateObject()1154 MtpResponseCode MtpServer::doTruncateObject() {
1155 MtpObjectHandle handle = mRequest.getParameter(1);
1156 ObjectEdit* edit = getEditObject(handle);
1157 if (!edit) {
1158 ALOGE("object not open for edit in doTruncateObject");
1159 return MTP_RESPONSE_GENERAL_ERROR;
1160 }
1161
1162 uint64_t offset = mRequest.getParameter(2);
1163 uint64_t offset2 = mRequest.getParameter(3);
1164 offset |= (offset2 << 32);
1165 if (ftruncate(edit->mFD, offset) != 0) {
1166 return MTP_RESPONSE_GENERAL_ERROR;
1167 } else {
1168 edit->mSize = offset;
1169 return MTP_RESPONSE_OK;
1170 }
1171 }
1172
doBeginEditObject()1173 MtpResponseCode MtpServer::doBeginEditObject() {
1174 MtpObjectHandle handle = mRequest.getParameter(1);
1175 if (getEditObject(handle)) {
1176 ALOGE("object already open for edit in doBeginEditObject");
1177 return MTP_RESPONSE_GENERAL_ERROR;
1178 }
1179
1180 MtpString path;
1181 int64_t fileLength;
1182 MtpObjectFormat format;
1183 int result = mDatabase->getObjectFilePath(handle, path, fileLength, format);
1184 if (result != MTP_RESPONSE_OK)
1185 return result;
1186
1187 int fd = open((const char *)path, O_RDWR | O_EXCL);
1188 if (fd < 0) {
1189 ALOGE("open failed for %s in doBeginEditObject (%d)", (const char *)path, errno);
1190 return MTP_RESPONSE_GENERAL_ERROR;
1191 }
1192
1193 addEditObject(handle, path, fileLength, format, fd);
1194 return MTP_RESPONSE_OK;
1195 }
1196
doEndEditObject()1197 MtpResponseCode MtpServer::doEndEditObject() {
1198 MtpObjectHandle handle = mRequest.getParameter(1);
1199 ObjectEdit* edit = getEditObject(handle);
1200 if (!edit) {
1201 ALOGE("object not open for edit in doEndEditObject");
1202 return MTP_RESPONSE_GENERAL_ERROR;
1203 }
1204
1205 commitEdit(edit);
1206 removeEditObject(handle);
1207 return MTP_RESPONSE_OK;
1208 }
1209
1210 } // namespace android
1211