/*
* 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 <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/stat.h>
#include <dirent.h>
#include "../twcommon.h"
#include "../set_metadata.h"
#include <cutils/properties.h>
#include "MtpTypes.h"
#include "MtpDebug.h"
#include "MtpDatabase.h"
#include "MtpObjectInfo.h"
#include "MtpProperty.h"
#include "MtpServer.h"
#include "MtpStorage.h"
#include "MtpStringBuffer.h"
#include <linux/usb/f_mtp.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_OBJECT_PROP_CHANGED,
};
MtpServer::MtpServer(int fd, MtpDatabase* database, bool ptp,
int fileGroup, int filePerm, int directoryPerm)
: mFD(fd),
mDatabase(database),
mPtp(ptp),
mFileGroup(fileGroup),
mFilePermission(filePerm),
mDirectoryPermission(directoryPerm),
mSessionID(0),
mSessionOpen(false),
mSendObjectHandle(kInvalidObjectHandle),
mSendObjectFormat(0),
mSendObjectFileSize(0)
{
}
MtpServer::~MtpServer() {
}
void MtpServer::addStorage(MtpStorage* storage) {
android::Mutex::Autolock autoLock(mMutex);
MTPD("addStorage(): storage: %x\n", storage);
if (getStorage(storage->getStorageID()) != NULL) {
MTPE("MtpServer::addStorage Storage for storage ID %i already exists.\n", storage->getStorageID());
return;
}
mDatabase->createDB(storage, storage->getStorageID());
mStorages.push(storage);
sendStoreAdded(storage->getStorageID());
}
void MtpServer::removeStorage(MtpStorage* storage) {
android::Mutex::Autolock autoLock(mMutex);
for (size_t i = 0; i < mStorages.size(); i++) {
if (mStorages[i] == storage) {
MTPD("MtpServer::removeStorage calling sendStoreRemoved\n");
// First lock the mutex so that the inotify thread and main
// thread do not do anything while we remove the storage
// item, and to make sure we don't remove the item while an
// operation is in progress
mDatabase->lockMutex();
// Grab the storage ID before we delete the item from the
// database
MtpStorageID storageID = storage->getStorageID();
// Remove the item from the mStorages from the vector. At
// this point the main thread will no longer be able to find
// this storage item anymore.
mStorages.removeAt(i);
// Destroy the storage item, free up all the memory, kill
// the inotify thread.
mDatabase->destroyDB(storageID);
// Tell the host OS that the storage item is gone.
sendStoreRemoved(storageID);
// Unlock any remaining mutexes on other storage devices.
// If no storage devices exist anymore this will do nothing.
mDatabase->unlockMutex();
break;
}
}
MTPD("MtpServer::removeStorage DONE\n");
}
MtpStorage* MtpServer::getStorage(MtpStorageID id) {
MTPD("getStorage\n");
if (id == 0) {
MTPD("mStorages\n");
return mStorages[0];
}
for (size_t i = 0; i < mStorages.size(); i++) {
MtpStorage* storage = mStorages[i];
MTPD("id: %d\n", id);
MTPD("storage: %d\n", storage->getStorageID());
if (storage->getStorageID() == id) {
return storage;
}
}
return NULL;
}
bool MtpServer::hasStorage(MtpStorageID id) {
MTPD("in hasStorage\n");
if (id == 0 || id == 0xFFFFFFFF)
return mStorages.size() > 0;
return (getStorage(id) != NULL);
}
void MtpServer::run() {
int fd = mFD;
MTPI("MtpServer::run fd: %d\n", fd);
while (1) {
MTPD("About to read device...\n");
int ret = mRequest.read(fd);
if (ret < 0) {
if (errno == ECANCELED) {
// return to top of loop and wait for next command
MTPD("request read returned %d ECANCELED, starting over\n", ret);
continue;
}
MTPE("request read returned %d, errno: %d, exiting MtpServer::run loop\n", ret, errno);
break;
}
MtpOperationCode operation = mRequest.getOperationCode();
MtpTransactionID transaction = mRequest.getTransactionID();
MTPD("operation: %s", MtpDebug::getOperationCodeName(operation));
mRequest.dump();
// 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(fd);
if (ret < 0) {
if (errno == ECANCELED) {
// return to top of loop and wait for next command
MTPD("data read returned %d ECANCELED, starting over\n", ret);
continue;
}
MTPD("data read returned %d, errno: %d, exiting MtpServer::run loop\n", ret, errno);
break;
}
MTPD("received data:");
mData.dump();
} else {
mData.reset();
}
if (handleRequest()) {
if (!dataIn && mData.hasData()) {
mData.setOperationCode(operation);
mData.setTransactionID(transaction);
MTPD("sending data:");
mData.dump();
ret = mData.write(fd);
if (ret < 0) {
if (errno == ECANCELED) {
// return to top of loop and wait for next command
MTPD("data write returned %d ECANCELED, starting over\n", ret);
continue;
}
MTPE("data write returned %d, errno: %d, exiting MtpServer::run loop\n", ret, errno);
break;
}
}
mResponse.setTransactionID(transaction);
MTPD("sending response %04X\n", mResponse.getResponseCode());
ret = mResponse.write(fd);
MTPD("ret: %d\n", ret);
mResponse.dump();
if (ret < 0) {
if (errno == ECANCELED) {
// return to top of loop and wait for next command
MTPD("response write returned %d ECANCELED, starting over\n", ret);
continue;
}
MTPE("response write returned %d, errno: %d, exiting MtpServer::run loop\n", ret, errno);
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();
if (mSessionOpen)
mDatabase->sessionEnded();
close(fd);
mFD = -1;
}
void MtpServer::sendObjectAdded(MtpObjectHandle handle) {
MTPD("sendObjectAdded %d\n", handle);
sendEvent(MTP_EVENT_OBJECT_ADDED, handle);
}
void MtpServer::sendObjectRemoved(MtpObjectHandle handle) {
MTPD("sendObjectRemoved %d\n", handle);
sendEvent(MTP_EVENT_OBJECT_REMOVED, handle);
}
void MtpServer::sendObjectUpdated(MtpObjectHandle handle) {
MTPD("sendObjectUpdated %d\n", handle);
sendEvent(MTP_EVENT_OBJECT_PROP_CHANGED, handle);
}
void MtpServer::sendStoreAdded(MtpStorageID id) {
MTPD("sendStoreAdded %08X\n", id);
sendEvent(MTP_EVENT_STORE_ADDED, id);
}
void MtpServer::sendStoreRemoved(MtpStorageID id) {
MTPD("sendStoreRemoved %08X\n", id);
sendEvent(MTP_EVENT_STORE_REMOVED, id);
MTPD("MtpServer::sendStoreRemoved done\n");
}
void MtpServer::sendEvent(MtpEventCode code, uint32_t param1) {
MTPD("MtpServer::sendEvent sending event code: %x\n", code);
if (mSessionOpen) {
mEvent.setEventCode(code);
mEvent.setTransactionID(mRequest.getTransactionID());
mEvent.setParameter(1, param1);
int ret = mEvent.write(mFD);
MTPD("mEvent.write returned %d\n", ret);
}
}
void MtpServer::addEditObject(MtpObjectHandle handle, MtpString& path,
uint64_t size, MtpObjectFormat format, int fd) {
ObjectEdit* edit = new ObjectEdit(handle, path, size, format, 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->mHandle == 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->mHandle == handle) {
delete edit;
mObjectEditList.removeAt(i);
return;
}
}
MTPE("ObjectEdit not found in removeEditObject");
}
void MtpServer::commitEdit(ObjectEdit* edit) {
mDatabase->endSendObject((const char *)edit->mPath, edit->mHandle, edit->mFormat, true);
}
bool MtpServer::handleRequest() {
android::Mutex::Autolock autoLock(mMutex);
MtpOperationCode operation = mRequest.getOperationCode();
MtpResponseCode response;
mResponse.reset();
if (mSendObjectHandle != kInvalidObjectHandle && operation != MTP_OPERATION_SEND_OBJECT) {
// FIXME - need to delete mSendObjectHandle from the database
MTPE("expected SendObject after SendObjectInfo");
mSendObjectHandle = kInvalidObjectHandle;
}
switch (operation) {
case MTP_OPERATION_GET_DEVICE_INFO:
MTPD("doGetDeviceInfo()\n");
response = doGetDeviceInfo();
break;
case MTP_OPERATION_OPEN_SESSION:
MTPD("doOpenSesion()\n");
response = doOpenSession();
break;
case MTP_OPERATION_CLOSE_SESSION:
MTPD("doCloseSession()\n");
response = doCloseSession();
break;
case MTP_OPERATION_GET_STORAGE_IDS:
MTPD("doGetStorageIDs()\n");
response = doGetStorageIDs();
break;
case MTP_OPERATION_GET_STORAGE_INFO:
MTPD("about to call doGetStorageInfo()\n");
response = doGetStorageInfo();
break;
case MTP_OPERATION_GET_OBJECT_PROPS_SUPPORTED:
MTPD("about to call doGetObjectPropsSupported()\n");
response = doGetObjectPropsSupported();
break;
case MTP_OPERATION_GET_OBJECT_HANDLES:
MTPD("about to call doGetObjectHandles()\n");
response = doGetObjectHandles();
break;
case MTP_OPERATION_GET_NUM_OBJECTS:
MTPD("about to call doGetNumbObjects()\n");
response = doGetNumObjects();
break;
case MTP_OPERATION_GET_OBJECT_REFERENCES:
MTPD("about to call doGetObjectReferences()\n");
response = doGetObjectReferences();
break;
case MTP_OPERATION_SET_OBJECT_REFERENCES:
MTPD("about to call doSetObjectReferences()\n");
response = doSetObjectReferences();
break;
case MTP_OPERATION_GET_OBJECT_PROP_VALUE:
MTPD("about to call doGetObjectPropValue()\n");
response = doGetObjectPropValue();
break;
case MTP_OPERATION_SET_OBJECT_PROP_VALUE:
MTPD("about to call doSetObjectPropValue()\n");
response = doSetObjectPropValue();
break;
case MTP_OPERATION_GET_DEVICE_PROP_VALUE:
MTPD("about to call doGetDevicPropValue()\n");
response = doGetDevicePropValue();
break;
case MTP_OPERATION_SET_DEVICE_PROP_VALUE:
MTPD("about to call doSetDevicePropVaue()\n");
response = doSetDevicePropValue();
break;
case MTP_OPERATION_RESET_DEVICE_PROP_VALUE:
MTPD("about to call doResetDevicePropValue()\n");
response = doResetDevicePropValue();
break;
case MTP_OPERATION_GET_OBJECT_PROP_LIST:
MTPD("calling doGetObjectPropList()\n");
response = doGetObjectPropList();
break;
case MTP_OPERATION_GET_OBJECT_INFO:
MTPD("calling doGetObjectInfo()\n");
response = doGetObjectInfo();
break;
case MTP_OPERATION_GET_OBJECT:
MTPD("about to call doGetObject()\n");
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:
MTPD("about to call doSendObjectInfo()\n");
response = doSendObjectInfo();
break;
case MTP_OPERATION_SEND_OBJECT:
MTPD("about to call doSendObject()\n");
response = doSendObject();
break;
case MTP_OPERATION_DELETE_OBJECT:
response = doDeleteObject();
break;
case MTP_OPERATION_GET_OBJECT_PROP_DESC:
MTPD("about to call doGetObjectPropDesc()\n");
response = doGetObjectPropDesc();
break;
case MTP_OPERATION_GET_DEVICE_PROP_DESC:
MTPD("about to call doGetDevicePropDesc()\n");
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", MtpDebug::getOperationCodeName(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;
char prop_value[PROPERTY_VALUE_MAX];
MtpObjectFormatList* playbackFormats = mDatabase->getSupportedPlaybackFormats();
MtpObjectFormatList* captureFormats = mDatabase->getSupportedCaptureFormats();
MtpDevicePropertyList* deviceProperties = mDatabase->getSupportedDeviceProperties();
// fill in device info
mData.putUInt16(MTP_STANDARD_VERSION);
if (mPtp) {
MTPD("doGetDeviceInfo putting 0\n");
mData.putUInt32(0);
} else {
// MTP Vendor Extension ID
MTPD("doGetDeviceInfo putting 6\n");
mData.putUInt32(6);
}
mData.putUInt16(MTP_STANDARD_VERSION);
if (mPtp) {
// no extensions
MTPD("doGetDeviceInfo no extensions\n");
string.set("");
} else {
// MTP extensions
MTPD("doGetDeviceInfo microsoft.com: 1.0; android.com: 1.0;\n");
string.set("microsoft.com: 1.0; android.com: 1.0;");
}
mData.putString(string); // MTP Extensions
mData.putUInt16(0); //Functional Mode
MTPD("doGetDeviceInfo opcodes, %i\n", sizeof(kSupportedOperationCodes) / sizeof(uint16_t));
MTPD("doGetDeviceInfo eventcodes, %i\n", sizeof(kSupportedEventCodes) / sizeof(uint16_t));
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
property_get("ro.product.manufacturer", prop_value, "unknown manufacturer");
MTPD("prop: %s\n", prop_value);
string.set(prop_value);
mData.putString(string); // Manufacturer
property_get("ro.product.model", prop_value, "MTP Device");
string.set(prop_value);
mData.putString(string); // Model
string.set("1.0");
mData.putString(string); // Device Version
property_get("ro.serialno", prop_value, "????????");
MTPD("sn: %s\n", prop_value);
string.set(prop_value);
mData.putString(string); // 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;
}
mSessionID = mRequest.getParameter(1);
mSessionOpen = true;
mDatabase->sessionStarted();
return MTP_RESPONSE_OK;
}
MtpResponseCode MtpServer::doCloseSession() {
if (!mSessionOpen)
return MTP_RESPONSE_SESSION_NOT_OPEN;
mSessionID = 0;
mSessionOpen = false;
mDatabase->sessionEnded();
return MTP_RESPONSE_OK;
}
MtpResponseCode MtpServer::doGetStorageIDs() {
MTPD("doGetStorageIDs()\n");
if (!mSessionOpen)
return MTP_RESPONSE_SESSION_NOT_OPEN;
int count = mStorages.size();
mData.putUInt32(count);
for (int i = 0; i < count; i++) {
MTPD("getting storageid %d\n", mStorages[i]->getStorageID());
mData.putUInt32(mStorages[i]->getStorageID());
}
return MTP_RESPONSE_OK;
}
MtpResponseCode MtpServer::doGetStorageInfo() {
MtpStringBuffer string;
MTPD("doGetStorageInfo()\n");
if (!mSessionOpen)
return MTP_RESPONSE_SESSION_NOT_OPEN;
MtpStorageID id = mRequest.getParameter(1);
MtpStorage* storage = getStorage(id);
if (!storage) {
MTPE("invalid storage id\n");
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() {
MTPD("doGetObjectPropsSupported()\n");
if (!mSessionOpen)
return MTP_RESPONSE_SESSION_NOT_OPEN;
MtpObjectFormat format = mRequest.getParameter(1);
mDatabase->lockMutex();
MtpObjectPropertyList* properties = mDatabase->getSupportedObjectProperties(format);
mData.putAUInt16(properties);
delete properties;
mDatabase->unlockMutex();
return MTP_RESPONSE_OK;
}
MtpResponseCode MtpServer::doGetObjectHandles() {
if (!mSessionOpen)
return MTP_RESPONSE_SESSION_NOT_OPEN;
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;
MTPD("calling MtpDatabase->getObjectList()\n");
mDatabase->lockMutex();
MtpObjectHandleList* handles = mDatabase->getObjectList(storageID, format, parent);
mData.putAUInt32(handles);
delete handles;
mDatabase->unlockMutex();
return MTP_RESPONSE_OK;
}
MtpResponseCode MtpServer::doGetNumObjects() {
if (!mSessionOpen)
return MTP_RESPONSE_SESSION_NOT_OPEN;
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;
mDatabase->lockMutex();
int count = mDatabase->getNumObjects(storageID, format, parent);
mDatabase->unlockMutex();
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;
MtpObjectHandle handle = mRequest.getParameter(1);
// FIXME - check for invalid object handle
mDatabase->lockMutex();
MtpObjectHandleList* handles = mDatabase->getObjectReferences(handle);
if (handles) {
mData.putAUInt32(handles);
delete handles;
} else {
MTPD("MtpServer::doGetObjectReferences putEmptyArray\n");
mData.putEmptyArray();
}
mDatabase->unlockMutex();
return MTP_RESPONSE_OK;
}
MtpResponseCode MtpServer::doSetObjectReferences() {
if (!mSessionOpen)
return MTP_RESPONSE_SESSION_NOT_OPEN;
if (!hasStorage())
return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
MtpStorageID handle = mRequest.getParameter(1);
MtpObjectHandleList* references = mData.getAUInt32();
mDatabase->lockMutex();
MtpResponseCode result = mDatabase->setObjectReferences(handle, references);
mDatabase->unlockMutex();
delete references;
return result;
}
MtpResponseCode MtpServer::doGetObjectPropValue() {
if (!hasStorage())
return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
MtpObjectHandle handle = mRequest.getParameter(1);
MtpObjectProperty property = mRequest.getParameter(2);
MTPD("GetObjectPropValue %d %s\n", handle,
MtpDebug::getObjectPropCodeName(property));
mDatabase->lockMutex();
MtpResponseCode res = mDatabase->getObjectPropertyValue(handle, property, mData);
mDatabase->unlockMutex();
return res;
}
MtpResponseCode MtpServer::doSetObjectPropValue() {
if (!hasStorage())
return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
MtpObjectHandle handle = mRequest.getParameter(1);
MtpObjectProperty property = mRequest.getParameter(2);
MTPD("SetObjectPropValue %d %s\n", handle,
MtpDebug::getObjectPropCodeName(property));
mDatabase->lockMutex();
MtpResponseCode res = mDatabase->setObjectPropertyValue(handle, property, mData);
mDatabase->unlockMutex();
return res;
}
MtpResponseCode MtpServer::doGetDevicePropValue() {
MtpDeviceProperty property = mRequest.getParameter(1);
MTPD("GetDevicePropValue %s\n",
MtpDebug::getDevicePropCodeName(property));
mDatabase->lockMutex();
MtpResponseCode res = mDatabase->getDevicePropertyValue(property, mData);
mDatabase->unlockMutex();
return res;
}
MtpResponseCode MtpServer::doSetDevicePropValue() {
MtpDeviceProperty property = mRequest.getParameter(1);
MTPD("SetDevicePropValue %s\n",
MtpDebug::getDevicePropCodeName(property));
mDatabase->lockMutex();
MtpResponseCode res = mDatabase->setDevicePropertyValue(property, mData);
mDatabase->unlockMutex();
return res;
}
MtpResponseCode MtpServer::doResetDevicePropValue() {
MtpDeviceProperty property = mRequest.getParameter(1);
MTPD("ResetDevicePropValue %s\n",
MtpDebug::getDevicePropCodeName(property));
mDatabase->lockMutex();
MtpResponseCode res = mDatabase->resetDeviceProperty(property);
mDatabase->unlockMutex();
return res;
}
MtpResponseCode MtpServer::doGetObjectPropList() {
if (!hasStorage())
return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
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: %x group: %d depth: %d\n",
handle, MtpDebug::getFormatCodeName(format),
property, groupCode, depth);
mDatabase->lockMutex();
MtpResponseCode res = mDatabase->getObjectPropertyList(handle, format, property, groupCode, depth, mData);
mDatabase->unlockMutex();
return res;
}
MtpResponseCode MtpServer::doGetObjectInfo() {
MTPD("inside doGetObjectInfo()\n");
if (!hasStorage())
return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
MtpObjectHandle handle = mRequest.getParameter(1);
MtpObjectInfo info(handle);
MTPD("calling mtpdatabase getObjectInfo()\n");
mDatabase->lockMutex();
MtpResponseCode result = mDatabase->getObjectInfo(handle, info);
mDatabase->unlockMutex();
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);
MTPD("info.mName: %s\n", info.mName);
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() {
if (!hasStorage())
return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
MtpObjectHandle handle = mRequest.getParameter(1);
MtpString pathBuf;
int64_t fileLength;
MtpObjectFormat format;
MTPD("MtpServer::doGetObject calling getObjectFilePath\n");
mDatabase->lockMutex();
int result = mDatabase->getObjectFilePath(handle, pathBuf, fileLength, format);
mDatabase->unlockMutex();
if (result != MTP_RESPONSE_OK)
return result;
const char* filePath = (const char *)pathBuf;
MTPD("filePath: %s\n", filePath);
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;
MTPD("mfr.length: %lld\n", mfr.length);
mfr.command = mRequest.getOperationCode();
mfr.transaction_id = mRequest.getTransactionID();
// then transfer the file
int ret = ioctl(mFD, MTP_SEND_FILE_WITH_HEADER, (unsigned long)&mfr);
MTPD("MTP_SEND_FILE_WITH_HEADER returned %d\n", ret);
close(mfr.fd);
if (ret < 0) {
if (errno == ECANCELED)
return MTP_RESPONSE_TRANSACTION_CANCELLED;
else
return MTP_RESPONSE_GENERAL_ERROR;
}
return MTP_RESPONSE_OK;
}
MtpResponseCode MtpServer::doGetThumb() {
MtpObjectHandle handle = mRequest.getParameter(1);
size_t thumbSize;
mDatabase->lockMutex();
void* thumb = mDatabase->getThumbnail(handle, thumbSize);
mDatabase->unlockMutex();
if (thumb) {
// send data
mData.setOperationCode(mRequest.getOperationCode());
mData.setTransactionID(mRequest.getTransactionID());
mData.writeData(mFD, 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) {
// 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;
MTPD("MtpServer::doGetPartialObject calling getObjectFilePath\n");
mDatabase->lockMutex();
int result = mDatabase->getObjectFilePath(handle, pathBuf, fileLength, format);
mDatabase->unlockMutex();
if (result != MTP_RESPONSE_OK) {
return result;
}
if (offset + length > (uint64_t)fileLength)
length = fileLength - offset;
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 = offset;
mfr.length = length;
mfr.command = mRequest.getOperationCode();
mfr.transaction_id = mRequest.getTransactionID();
mResponse.setParameter(1, length);
// transfer the file
int ret = ioctl(mFD, MTP_SEND_FILE_WITH_HEADER, (unsigned long)&mfr);
MTPD("MTP_SEND_FILE_WITH_HEADER returned %d\n", ret);
close(mfr.fd);
if (ret < 0) {
if (errno == ECANCELED)
return MTP_RESPONSE_TRANSACTION_CANCELLED;
else
return MTP_RESPONSE_GENERAL_ERROR;
}
return MTP_RESPONSE_OK;
}
MtpResponseCode MtpServer::doSendObjectInfo() {
MTPD("MtpServer::doSendObjectInfo starting\n");
MtpString path;
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) {
MTPD("MtpServer::doSendObjectInfo special case root\n");
path = storage->getPath();
parent = 0;
} else {
int64_t length;
MtpObjectFormat format;
MTPD("MtpServer::doSendObjectInfo calling getObjectFilePath\n");
mDatabase->lockMutex();
int result = mDatabase->getObjectFilePath(parent, path, length, format);
mDatabase->unlockMutex();
if (result != MTP_RESPONSE_OK) {
return result;
}
if (format != MTP_FORMAT_ASSOCIATION)
return MTP_RESPONSE_INVALID_PARENT_OBJECT;
}
// read only the fields we need
mData.getUInt32(); // storage ID
MtpObjectFormat format = mData.getUInt16();
mData.getUInt16(); // protection status
mSendObjectFileSize = mData.getUInt32();
mData.getUInt16(); // thumb format
mData.getUInt32(); // thumb compressed size
mData.getUInt32(); // thumb pix width
mData.getUInt32(); // thumb pix height
mData.getUInt32(); // image pix width
mData.getUInt32(); // image pix height
mData.getUInt32(); // image bit depth
mData.getUInt32(); // parent
uint16_t associationType = mData.getUInt16();
uint32_t associationDesc = mData.getUInt32(); // association desc
mData.getUInt32(); // sequence number
MtpStringBuffer name, created, modified;
mData.getString(name); // file name
mData.getString(created); // date created
mData.getString(modified); // 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 += "/";
}
path += (const char *)name;
// check space first
if (mSendObjectFileSize > storage->getFreeSpace())
return MTP_RESPONSE_STORAGE_FULL;
uint64_t maxFileSize = storage->getMaxFileSize();
// check storage max file size
MTPD("maxFileSize: %ld\n", maxFileSize);
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("MtpServer::doSendObjectInfo path: %s parent: %d storageID: %08X\n", (const char*)path, parent, storageID);
mDatabase->lockMutex();
MtpObjectHandle handle = mDatabase->beginSendObject((const char*)path,
format, parent, storageID, mSendObjectFileSize, modifiedTime);
mDatabase->unlockMutex();
if (handle == kInvalidObjectHandle) {
MTPE("MtpServer::doSendObjectInfo returning MTP_RESPONSE_GENERAL_ERROR, handle == kInvalidObjectHandle\n");
return MTP_RESPONSE_GENERAL_ERROR;
}
if (format == MTP_FORMAT_ASSOCIATION) {
mode_t mask = umask(0);
MTPD("MtpServer::doSendObjectInfo mkdir '%s'\n", (const char *)path);
int ret = mkdir((const char *)path, mDirectoryPermission);
umask(mask);
if (ret && ret != -EEXIST) {
MTPE("MtpServer::doSendObjectInfo returning MTP_RESPONSE_GENERAL_ERROR, ret && ret != -EEXIST\n");
return MTP_RESPONSE_GENERAL_ERROR;
}
chown((const char *)path, getuid(), mFileGroup);
tw_set_default_metadata((const char *)path);
// SendObject does not get sent for directories, so call endSendObject here instead
mDatabase->lockMutex();
mDatabase->endSendObject(path, handle, MTP_FORMAT_ASSOCIATION, MTP_RESPONSE_OK);
mDatabase->unlockMutex();
} else {
mSendObjectFilePath = path;
// save the handle for the SendObject call, which should follow
mSendObjectHandle = handle;
mSendObjectFormat = format;
}
mResponse.setParameter(1, storageID);
mResponse.setParameter(2, parent);
mResponse.setParameter(3, handle);
MTPD("MtpServer::doSendObjectInfo returning MTP_RESPONSE_OK\n");
return MTP_RESPONSE_OK;
}
MtpResponseCode MtpServer::doSendObject() {
if (!hasStorage())
return MTP_RESPONSE_GENERAL_ERROR;
MtpResponseCode result = MTP_RESPONSE_OK;
mode_t mask;
int ret = 0, initialData;
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(mFD);
if (ret < MTP_CONTAINER_HEADER_SIZE) {
MTPE("MTP_RESPONSE_GENERAL_ERROR\n");
result = MTP_RESPONSE_GENERAL_ERROR;
goto done;
}
initialData = ret - MTP_CONTAINER_HEADER_SIZE;
mtp_file_range mfr;
mfr.fd = open(mSendObjectFilePath, O_RDWR | O_CREAT | O_TRUNC, 0640);
if (mfr.fd < 0) {
result = MTP_RESPONSE_GENERAL_ERROR;
MTPE("fd error\n");
goto done;
}
fchown(mfr.fd, getuid(), mFileGroup);
// set permissions
mask = umask(0);
fchmod(mfr.fd, mFilePermission);
umask(mask);
if (initialData > 0)
ret = write(mfr.fd, mData.getData(), initialData);
if (mSendObjectFileSize - initialData > 0) {
mfr.offset = initialData;
if (mSendObjectFileSize == 0xFFFFFFFF) {
// tell driver to read until it receives a short packet
mfr.length = 0xFFFFFFFF;
} else {
mfr.length = mSendObjectFileSize - initialData;
}
MTPD("receiving %s\n", (const char *)mSendObjectFilePath);
// transfer the file
ret = ioctl(mFD, MTP_RECEIVE_FILE, (unsigned long)&mfr);
}
close(mfr.fd);
tw_set_default_metadata((const char *)mSendObjectFilePath);
if (ret < 0) {
unlink(mSendObjectFilePath);
if (errno == ECANCELED)
result = MTP_RESPONSE_TRANSACTION_CANCELLED;
else {
MTPD("errno: %d\n", errno);
result = MTP_RESPONSE_GENERAL_ERROR;
}
}
done:
// reset so we don't attempt to send the data back
MTPD("MTP_RECEIVE_FILE returned %d\n", ret);
mData.reset();
mDatabase->lockMutex();
mDatabase->endSendObject(mSendObjectFilePath, mSendObjectHandle, mSendObjectFormat,
result == MTP_RESPONSE_OK);
mDatabase->unlockMutex();
mSendObjectHandle = kInvalidObjectHandle;
MTPD("result: %d\n", result);
mSendObjectFormat = 0;
return result;
}
static void deleteRecursive(const char* path) {
char pathbuf[PATH_MAX];
size_t pathLength = strlen(path);
if (pathLength >= sizeof(pathbuf) - 1) {
MTPE("path too long: %s\n", path);
}
strcpy(pathbuf, path);
if (pathbuf[pathLength - 1] != '/') {
pathbuf[pathLength++] = '/';
}
char* fileSpot = pathbuf + pathLength;
int pathRemaining = sizeof(pathbuf) - pathLength - 1;
DIR* dir = opendir(path);
if (!dir) {
MTPE("opendir %s failed: %s", path, strerror(errno));
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;
}
int nameLength = strlen(name);
if (nameLength > pathRemaining) {
MTPE("path %s/%s too long\n", path, name);
continue;
}
strcpy(fileSpot, name);
int type = entry->d_type;
struct stat st;
if (lstat(pathbuf, &st)) {
MTPE("Failed to lstat '%s'\n", pathbuf);
continue;
}
if (st.st_mode & S_IFDIR) {
deleteRecursive(pathbuf);
rmdir(pathbuf);
} else {
unlink(pathbuf);
}
}
closedir(dir);
}
static void deletePath(const char* path) {
struct stat statbuf;
if (stat(path, &statbuf) == 0) {
if (S_ISDIR(statbuf.st_mode)) {
deleteRecursive(path);
rmdir(path);
} else {
unlink(path);
}
} else {
MTPE("deletePath stat failed for %s: %s", path, strerror(errno));
}
}
MtpResponseCode MtpServer::doDeleteObject() {
if (!hasStorage())
return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
MtpObjectHandle handle = mRequest.getParameter(1);
MtpObjectFormat format = mRequest.getParameter(2);
// FIXME - support deleting all objects if handle is 0xFFFFFFFF
// FIXME - implement deleting objects by format
MtpString filePath;
int64_t fileLength;
MTPD("MtpServer::doDeleteObject calling getObjectFilePath\n");
mDatabase->lockMutex();
int result = mDatabase->getObjectFilePath(handle, filePath, fileLength, format);
if (result == MTP_RESPONSE_OK) {
MTPD("deleting %s", (const char *)filePath);
result = mDatabase->deleteFile(handle);
// Don't delete the actual files unless the database deletion is allowed
if (result == MTP_RESPONSE_OK) {
deletePath((const char *)filePath);
}
}
mDatabase->unlockMutex();
return result;
}
MtpResponseCode MtpServer::doGetObjectPropDesc() {
MtpObjectProperty propCode = mRequest.getParameter(1);
MtpObjectFormat format = mRequest.getParameter(2);
MTPD("MtpServer::doGetObjectPropDesc %s %s\n", MtpDebug::getObjectPropCodeName(propCode),
MtpDebug::getFormatCodeName(format));
mDatabase->lockMutex();
MtpProperty* property = mDatabase->getObjectPropertyDesc(propCode, format);
mDatabase->unlockMutex();
if (!property) {
MTPE("MtpServer::doGetObjectPropDesc propery not supported\n");
return MTP_RESPONSE_OBJECT_PROP_NOT_SUPPORTED;
}
property->write(mData);
delete property;
return MTP_RESPONSE_OK;
}
MtpResponseCode MtpServer::doGetDevicePropDesc() {
MtpDeviceProperty propCode = mRequest.getParameter(1);
MTPD("GetDevicePropDesc %s\n", MtpDebug::getDevicePropCodeName(propCode));
mDatabase->lockMutex();
MtpProperty* property = mDatabase->getDevicePropertyDesc(propCode);
mDatabase->unlockMutex();
if (!property) {
MTPE("MtpServer::doGetDevicePropDesc property not supported\n");
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;
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) {
MTPE("writing past end of object, offset: %lld, edit->mSize: %lld", offset, edit->mSize);
return MTP_RESPONSE_GENERAL_ERROR;
}
const char* filePath = (const char *)edit->mPath;
MTPD("receiving partial %s %lld %lld\n", filePath, offset, length);
// read the header, and possibly some data
int ret = mData.read(mFD);
if (ret < MTP_CONTAINER_HEADER_SIZE)
return MTP_RESPONSE_GENERAL_ERROR;
int initialData = ret - MTP_CONTAINER_HEADER_SIZE;
if (initialData > 0) {
ret = write(edit->mFD, mData.getData(), initialData);
offset += initialData;
length -= initialData;
}
if (length > 0) {
mtp_file_range mfr;
mfr.fd = edit->mFD;
mfr.offset = offset;
mfr.length = length;
// transfer the file
ret = ioctl(mFD, MTP_RECEIVE_FILE, (unsigned long)&mfr);
MTPD("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;
}
// 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() {
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() {
MtpObjectHandle handle = mRequest.getParameter(1);
if (getEditObject(handle)) {
MTPE("object already open for edit in doBeginEditObject");
return MTP_RESPONSE_GENERAL_ERROR;
}
MtpString path;
int64_t fileLength;
MtpObjectFormat format;
MTPD("MtpServer::doBeginEditObject calling getObjectFilePath\n");
mDatabase->lockMutex();
int result = mDatabase->getObjectFilePath(handle, path, fileLength, format);
mDatabase->unlockMutex();
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() {
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;
}