Files
android_bootable_recovery/mtp/MtpServer.cpp
Ethan Yonker 726a020632 MTP add/remove storage instead of disabling MTP
Implement a pipe between TWRP and MTP to allow TWRP to tell MTP
to remove storage partitions as they become unavailable (e.g.
during a wipe, unmount, etc) instead of disabling MTP completely.
This includes some fixes and improvements in destructors to
properly remove / delete various items. This also means that we
will not be toggling adb off and on quite as often.

I do not like that we had to add another thread, but we were
unable to use select() on the mtp_usb character device because
this device does not support polling. Select always returned
indicating that the mtp file descriptor was ready to be read and
the resulting read would block. The read block prevented us from
being able to include reading of the pipe between TWRP and MTP in
the main MTP thread.

We might want to add a return pipe letting TWRP know if the
removal of the storage device was successful, but I am not sure
how we want to implement this. It would invovle timeouts in both
TWRP and MTP to ensure that we returned a failure indicator in a
timely manner to TWRP and prevent deleting the storage device in
the case of a failure. Right now we make no attempt to ensure that
an MTP operation is underway like a large file transfer, but we
were not doing anything like this in the past. In some respects we
have limited control over what happens. If the user installs a
zip that unmounts a storage partition, we will not know about the
change in storage status anyway. Regular Android does not have
these troubles because partitions rarely get unmounted like in
recovery. At some point, we have to hold the user accountable for
performing actions that may remove a storage partition while they
are using MTP anyway.

Ideally we do not want to toggle the USB IDs and thus toggle adb
off and on during early boot, but I am not sure what the best way
to handle that at this time.

Change-Id: I9343e5396bf6023d3b994de1bf01ed91d129bc14
2014-12-19 16:27:34 -06:00

1373 lines
41 KiB
C++
Executable File

/*
* 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) {
MTPD("request read returned %d, errno: %d", ret, errno);
if (errno == ECANCELED) {
// return to top of loop and wait for next command
continue;
}
break;
}
MtpOperationCode operation = mRequest.getOperationCode();
MtpTransactionID transaction = mRequest.getTransactionID();
MTPD("operation: %s", 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) {
MTPD("data read returned %d, errno: %d", ret, errno);
if (errno == ECANCELED) {
// return to top of loop and wait for next command
continue;
}
break;
}
MTPD("received data:");
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) {
MTPD("request write returned %d, errno: %d", ret, errno);
if (errno == ECANCELED) {
// return to top of loop and wait for next command
continue;
}
break;
}
}
mResponse.setTransactionID(transaction);
MTPD("sending response %04X\n", mResponse.getResponseCode());
ret = mResponse.write(fd);
MTPD("ret: %d\n", ret);
mResponse.dump();
if (ret < 0) {
MTPD("request write returned %d, errno: %d", ret, errno);
if (errno == ECANCELED) {
// return to top of loop and wait for next command
continue;
}
break;
}
} else {
MTPD("skipping response\n");
}
}
// commit any open edits
int count = mObjectEditList.size();
for (int i = 0; i < count; i++) {
ObjectEdit* edit = mObjectEditList[i];
commitEdit(edit);
delete edit;
}
mObjectEditList.clear();
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
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;
}