Files
android_bootable_recovery/mtp/ffs/MtpServer.cpp
bigbiff bigbiff af32bb9c4f MTP FFS updates:
This update splits old MTP code and new MTP code from Google
into two trees, legacy and ffs. Depending on the SDK level,
the build system will select the correct version. The reason
for separating the versions out are due to older android trees
not supporting the updated MTP code from Google.

Most MTP code is from Google, with additions needed from
implementing the Java functions in C++ for TWRP and FFS.

We assume if you are in android-9.0 or above, your kernel
has support for FFS over MTP. Verify that your init.rc
is mounting the MTP FFS driver to the proper location.

Change-Id: I4b107b239bd9bc5699527f9c8c77d9079f264a7e
2019-03-20 14:28:21 -05:00

1460 lines
44 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.
*/
#include <algorithm>
#include <android-base/logging.h>
#include <android-base/properties.h>
#include <chrono>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/stat.h>
#include <sys/time.h>
#define LOG_TAG "MtpServer"
#include "MtpDebug.h"
#include "mtp_MtpDatabase.hpp"
#include "MtpDescriptors.h"
#include "MtpDevHandle.h"
#include "MtpFfsCompatHandle.h"
#include "MtpFfsHandle.h"
#include "MtpObjectInfo.h"
#include "MtpProperty.h"
#include "MtpServer.h"
#include "MtpStorage.h"
#include "MtpStringBuffer.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_DEVICE_PROP_CHANGED,
};
MtpServer::MtpServer(IMtpDatabase* database, int controlFd, bool ptp,
const char *deviceInfoManufacturer,
const char *deviceInfoModel,
const char *deviceInfoDeviceVersion,
const char *deviceInfoSerialNumber)
: mDatabase(database),
mPtp(ptp),
mDeviceInfoManufacturer(deviceInfoManufacturer),
mDeviceInfoModel(deviceInfoModel),
mDeviceInfoDeviceVersion(deviceInfoDeviceVersion),
mDeviceInfoSerialNumber(deviceInfoSerialNumber),
mSessionID(0),
mSessionOpen(false),
mSendObjectHandle(kInvalidObjectHandle),
mSendObjectFormat(0),
mSendObjectFileSize(0),
mSendObjectModifiedTime(0)
{
bool ffs_ok = access(FFS_MTP_EP0, W_OK) == 0;
if (ffs_ok) {
bool aio_compat = android::base::GetBoolProperty("sys.usb.ffs.aio_compat", false);
mHandle = aio_compat ? new MtpFfsCompatHandle(controlFd) : new MtpFfsHandle(controlFd);
mHandle->writeDescriptors(mPtp);
} else {
mHandle = new MtpDevHandle();
}
}
MtpServer::~MtpServer() {
}
void MtpServer::addStorage(MtpStorage* storage) {
std::lock_guard<std::mutex> lg(mMutex);
mDatabase->createDB(storage, storage->getStorageID());
mStorages.push_back(storage);
sendStoreAdded(storage->getStorageID());
}
void MtpServer::removeStorage(MtpStorage* storage) {
std::lock_guard<std::mutex> lg(mMutex);
auto iter = std::find(mStorages.begin(), mStorages.end(), storage);
if (iter != mStorages.end()) {
sendStoreRemoved(storage->getStorageID());
mStorages.erase(iter);
}
}
MtpStorage* MtpServer::getStorage(MtpStorageID id) {
if (id == 0)
return mStorages[0];
for (MtpStorage *storage : mStorages) {
if (storage->getStorageID() == id)
return storage;
}
return nullptr;
}
bool MtpServer::hasStorage(MtpStorageID id) {
if (id == 0 || id == 0xFFFFFFFF)
return mStorages.size() > 0;
return (getStorage(id) != nullptr);
}
void MtpServer::run() {
if (mHandle->start(mPtp)) {
MTPE("Failed to start usb driver!");
mHandle->close();
return;
}
while (1) {
int ret = mRequest.read(mHandle);
if (ret < 0) {
MTPE("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\n", MtpDebug::getOperationCodeName(operation));
// 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(mHandle);
if (ret < 0) {
MTPE("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:");
} else {
mData.reset();
}
if (handleRequest()) {
if (!dataIn && mData.hasData()) {
mData.setOperationCode(operation);
mData.setTransactionID(transaction);
MTPD("sending data:");
ret = mData.write(mHandle);
if (ret < 0) {
MTPE("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", mResponse.getResponseCode());
ret = mResponse.write(mHandle);
const int savedErrno = errno;
if (ret < 0) {
MTPE("request write returned %d, errno: %d", ret, errno);
if (savedErrno == 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();
mHandle->close();
}
void MtpServer::sendObjectAdded(MtpObjectHandle handle) {
MTPD("MtpServer::sendObjectAdded %d\n", handle);
sendEvent(MTP_EVENT_OBJECT_ADDED, handle);
}
void MtpServer::sendObjectRemoved(MtpObjectHandle handle) {
MTPD("MtpServer::sendObjectRemoved %d\n", handle);
sendEvent(MTP_EVENT_OBJECT_REMOVED, handle);
}
void MtpServer::sendStoreRemoved(MtpStorageID id) {
MTPD("MtpServer::sendStoreRemoved %08X\n", id);
sendEvent(MTP_EVENT_STORE_REMOVED, id);
}
void MtpServer::sendStoreAdded(MtpStorageID id) {
MTPD("MtpServer::sendStoreAdded %08X\n", id);
sendEvent(MTP_EVENT_STORE_ADDED, id);
}
void MtpServer::sendObjectUpdated(MtpObjectHandle handle) {
MTPD("MtpServer::sendObjectUpdated %d\n", handle);
sendEvent(MTP_EVENT_OBJECT_PROP_CHANGED, handle);
}
void MtpServer::sendDevicePropertyChanged(MtpDeviceProperty property) {
MTPD("MtpServer::sendDevicePropertyChanged %d\n", property);
sendEvent(MTP_EVENT_DEVICE_PROP_CHANGED, property);
}
void MtpServer::sendObjectInfoChanged(MtpObjectHandle handle) {
MTPD("MtpServer::sendObjectInfoChanged %d\n", handle);
sendEvent(MTP_EVENT_OBJECT_INFO_CHANGED, handle);
}
void MtpServer::sendEvent(MtpEventCode code, uint32_t param1) {
if (mSessionOpen) {
mEvent.setEventCode(code);
mEvent.setTransactionID(mRequest.getTransactionID());
mEvent.setParameter(1, param1);
if (mEvent.write(mHandle))
MTPE("Mtp send event failed: %s\n", strerror(errno));
}
}
void MtpServer::addEditObject(MtpObjectHandle handle, MtpStringBuffer& path,
uint64_t size, MtpObjectFormat format, int fd) {
ObjectEdit* edit = new ObjectEdit(handle, path, size, format, fd);
mObjectEditList.push_back(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 nullptr;
}
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.erase(mObjectEditList.begin() + i);
return;
}
}
MTPE("ObjectEdit not found in removeEditObject");
}
void MtpServer::commitEdit(ObjectEdit* edit) {
mDatabase->rescanFile((const char *)edit->mPath, edit->mHandle, edit->mFormat);
}
bool MtpServer::handleRequest() {
std::lock_guard<std::mutex> lg(mMutex);
MtpOperationCode operation = mRequest.getOperationCode();
MtpResponseCode response;
mResponse.reset();
if (mSendObjectHandle != kInvalidObjectHandle && operation != MTP_OPERATION_SEND_OBJECT) {
mSendObjectHandle = kInvalidObjectHandle;
mSendObjectFormat = 0;
mSendObjectModifiedTime = 0;
}
int containertype = mRequest.getContainerType();
if (containertype != MTP_CONTAINER_TYPE_COMMAND) {
MTPE("wrong container type %d", containertype);
return false;
}
MTPD("got command %s (%x)\n", MtpDebug::getOperationCodeName(operation), operation);
switch (operation) {
case MTP_OPERATION_GET_DEVICE_INFO:
response = doGetDeviceInfo();
break;
case MTP_OPERATION_OPEN_SESSION:
response = doOpenSession();
break;
case MTP_OPERATION_RESET_DEVICE:
case MTP_OPERATION_CLOSE_SESSION:
response = doCloseSession();
break;
case MTP_OPERATION_GET_STORAGE_IDS:
response = doGetStorageIDs();
break;
case MTP_OPERATION_GET_STORAGE_INFO:
response = doGetStorageInfo();
break;
case MTP_OPERATION_GET_OBJECT_PROPS_SUPPORTED:
response = doGetObjectPropsSupported();
break;
case MTP_OPERATION_GET_OBJECT_HANDLES:
response = doGetObjectHandles();
break;
case MTP_OPERATION_GET_NUM_OBJECTS:
response = doGetNumObjects();
break;
case MTP_OPERATION_GET_OBJECT_REFERENCES:
response = doGetObjectReferences();
break;
case MTP_OPERATION_SET_OBJECT_REFERENCES:
response = doSetObjectReferences();
break;
case MTP_OPERATION_GET_OBJECT_PROP_VALUE:
response = doGetObjectPropValue();
break;
case MTP_OPERATION_SET_OBJECT_PROP_VALUE:
response = doSetObjectPropValue();
break;
case MTP_OPERATION_GET_DEVICE_PROP_VALUE:
response = doGetDevicePropValue();
break;
case MTP_OPERATION_SET_DEVICE_PROP_VALUE:
response = doSetDevicePropValue();
break;
case MTP_OPERATION_RESET_DEVICE_PROP_VALUE:
response = doResetDevicePropValue();
break;
case MTP_OPERATION_GET_OBJECT_PROP_LIST:
response = doGetObjectPropList();
break;
case MTP_OPERATION_GET_OBJECT_INFO:
response = doGetObjectInfo();
break;
case MTP_OPERATION_GET_OBJECT:
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:
response = doSendObjectInfo();
break;
case MTP_OPERATION_SEND_OBJECT:
response = doSendObject();
break;
case MTP_OPERATION_DELETE_OBJECT:
response = doDeleteObject();
break;
case MTP_OPERATION_COPY_OBJECT:
response = doCopyObject();
break;
case MTP_OPERATION_MOVE_OBJECT:
response = doMoveObject();
break;
case MTP_OPERATION_GET_OBJECT_PROP_DESC:
response = doGetObjectPropDesc();
break;
case MTP_OPERATION_GET_DEVICE_PROP_DESC:
response = doGetDevicePropDesc();
break;
case MTP_OPERATION_SEND_PARTIAL_OBJECT:
response = doSendPartialObject();
break;
case MTP_OPERATION_TRUNCATE_OBJECT:
response = doTruncateObject();
break;
case MTP_OPERATION_BEGIN_EDIT_OBJECT:
response = doBeginEditObject();
break;
case MTP_OPERATION_END_EDIT_OBJECT:
response = doEndEditObject();
break;
default:
MTPE("got unsupported command %s (%x)",
MtpDebug::getOperationCodeName(operation), 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;
MtpObjectFormatList* playbackFormats = mDatabase->getSupportedPlaybackFormats();
MtpObjectFormatList* captureFormats = mDatabase->getSupportedCaptureFormats();
MtpDevicePropertyList* deviceProperties = mDatabase->getSupportedDeviceProperties();
// fill in device info
mData.putUInt16(MTP_STANDARD_VERSION);
if (mPtp) {
mData.putUInt32(0);
} else {
// MTP Vendor Extension ID
mData.putUInt32(6);
}
mData.putUInt16(MTP_STANDARD_VERSION);
if (mPtp) {
// no extensions
string.set("");
} else {
// MTP extensions
string.set("microsoft.com: 1.0; android.com: 1.0;");
}
mData.putString(string); // MTP Extensions
mData.putUInt16(0); //Functional Mode
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
mData.putString(mDeviceInfoManufacturer); // Manufacturer
mData.putString(mDeviceInfoModel); // Model
mData.putString(mDeviceInfoDeviceVersion); // Device Version
mData.putString(mDeviceInfoSerialNumber); // 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;
}
if (mRequest.getParameterCount() < 1)
return MTP_RESPONSE_INVALID_PARAMETER;
mSessionID = mRequest.getParameter(1);
mSessionOpen = true;
return MTP_RESPONSE_OK;
}
MtpResponseCode MtpServer::doCloseSession() {
if (!mSessionOpen)
return MTP_RESPONSE_SESSION_NOT_OPEN;
mSessionID = 0;
mSessionOpen = false;
return MTP_RESPONSE_OK;
}
MtpResponseCode MtpServer::doGetStorageIDs() {
if (!mSessionOpen)
return MTP_RESPONSE_SESSION_NOT_OPEN;
int count = mStorages.size();
mData.putUInt32(count);
for (int i = 0; i < count; i++)
mData.putUInt32(mStorages[i]->getStorageID());
return MTP_RESPONSE_OK;
}
MtpResponseCode MtpServer::doGetStorageInfo() {
MtpStringBuffer string;
if (!mSessionOpen)
return MTP_RESPONSE_SESSION_NOT_OPEN;
if (mRequest.getParameterCount() < 1)
return MTP_RESPONSE_INVALID_PARAMETER;
MtpStorageID id = mRequest.getParameter(1);
MtpStorage* storage = getStorage(id);
if (!storage)
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() {
if (!mSessionOpen)
return MTP_RESPONSE_SESSION_NOT_OPEN;
if (mRequest.getParameterCount() < 1)
return MTP_RESPONSE_INVALID_PARAMETER;
MtpObjectFormat format = mRequest.getParameter(1);
MtpObjectPropertyList* properties = mDatabase->getSupportedObjectProperties(format);
mData.putAUInt16(properties);
delete properties;
return MTP_RESPONSE_OK;
}
MtpResponseCode MtpServer::doGetObjectHandles() {
if (!mSessionOpen)
return MTP_RESPONSE_SESSION_NOT_OPEN;
if (mRequest.getParameterCount() < 3)
return MTP_RESPONSE_INVALID_PARAMETER;
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;
MtpObjectHandleList* handles = mDatabase->getObjectList(storageID, format, parent);
if (handles == NULL)
return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
mData.putAUInt32(handles);
delete handles;
return MTP_RESPONSE_OK;
}
MtpResponseCode MtpServer::doGetNumObjects() {
if (!mSessionOpen)
return MTP_RESPONSE_SESSION_NOT_OPEN;
if (mRequest.getParameterCount() < 3)
return MTP_RESPONSE_INVALID_PARAMETER;
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;
int count = mDatabase->getNumObjects(storageID, format, parent);
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;
if (mRequest.getParameterCount() < 1)
return MTP_RESPONSE_INVALID_PARAMETER;
MtpObjectHandle handle = mRequest.getParameter(1);
// FIXME - check for invalid object handle
MtpObjectHandleList* handles = mDatabase->getObjectReferences(handle);
if (handles) {
mData.putAUInt32(handles);
delete handles;
} else {
mData.putEmptyArray();
}
return MTP_RESPONSE_OK;
}
MtpResponseCode MtpServer::doSetObjectReferences() {
if (!mSessionOpen)
return MTP_RESPONSE_SESSION_NOT_OPEN;
if (!hasStorage())
return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
if (mRequest.getParameterCount() < 1)
return MTP_RESPONSE_INVALID_PARAMETER;
MtpStorageID handle = mRequest.getParameter(1);
MtpObjectHandleList* references = mData.getAUInt32();
if (!references)
return MTP_RESPONSE_INVALID_PARAMETER;
MtpResponseCode result = mDatabase->setObjectReferences(handle, references);
delete references;
return result;
}
MtpResponseCode MtpServer::doGetObjectPropValue() {
if (!hasStorage())
return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
if (mRequest.getParameterCount() < 2)
return MTP_RESPONSE_INVALID_PARAMETER;
MtpObjectHandle handle = mRequest.getParameter(1);
MtpObjectProperty property = mRequest.getParameter(2);
MTPD("GetObjectPropValue %d %s\n", handle,
MtpDebug::getObjectPropCodeName(property));
return mDatabase->getObjectPropertyValue(handle, property, mData);
}
MtpResponseCode MtpServer::doSetObjectPropValue() {
if (!hasStorage())
return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
if (mRequest.getParameterCount() < 2)
return MTP_RESPONSE_INVALID_PARAMETER;
MtpObjectHandle handle = mRequest.getParameter(1);
MtpObjectProperty property = mRequest.getParameter(2);
MTPD("SetObjectPropValue %d %s\n", handle,
MtpDebug::getObjectPropCodeName(property));
return mDatabase->setObjectPropertyValue(handle, property, mData);
}
MtpResponseCode MtpServer::doGetDevicePropValue() {
if (mRequest.getParameterCount() < 1)
return MTP_RESPONSE_INVALID_PARAMETER;
MtpDeviceProperty property = mRequest.getParameter(1);
MTPD("GetDevicePropValue %s\n",
MtpDebug::getDevicePropCodeName(property));
return mDatabase->getDevicePropertyValue(property, mData);
}
MtpResponseCode MtpServer::doSetDevicePropValue() {
if (mRequest.getParameterCount() < 1)
return MTP_RESPONSE_INVALID_PARAMETER;
MtpDeviceProperty property = mRequest.getParameter(1);
MTPD("SetDevicePropValue %s\n",
MtpDebug::getDevicePropCodeName(property));
return mDatabase->setDevicePropertyValue(property, mData);
}
MtpResponseCode MtpServer::doResetDevicePropValue() {
if (mRequest.getParameterCount() < 1)
return MTP_RESPONSE_INVALID_PARAMETER;
MtpDeviceProperty property = mRequest.getParameter(1);
MTPD("ResetDevicePropValue %s\n",
MtpDebug::getDevicePropCodeName(property));
return mDatabase->resetDeviceProperty(property);
}
MtpResponseCode MtpServer::doGetObjectPropList() {
if (!hasStorage())
return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
if (mRequest.getParameterCount() < 5)
return MTP_RESPONSE_INVALID_PARAMETER;
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: %s group: %d depth: %d\n",
handle, MtpDebug::getFormatCodeName(format),
MtpDebug::getObjectPropCodeName(property), groupCode, depth);
return mDatabase->getObjectPropertyList(handle, format, property, groupCode, depth, mData);
}
MtpResponseCode MtpServer::doGetObjectInfo() {
if (!hasStorage())
return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
if (mRequest.getParameterCount() < 1)
return MTP_RESPONSE_INVALID_PARAMETER;
MtpObjectHandle handle = mRequest.getParameter(1);
MtpObjectInfo info(handle);
MtpResponseCode result = mDatabase->getObjectInfo(handle, info);
if (result == MTP_RESPONSE_OK) {
char date[20];
mData.putUInt32(info.mStorageID);
mData.putUInt16(info.mFormat);
mData.putUInt16(info.mProtectionStatus);
// if object is being edited the database size may be out of date
uint32_t size = info.mCompressedSize;
ObjectEdit* edit = getEditObject(handle);
if (edit)
size = (edit->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);
mData.putString(info.mName);
formatDateTime(info.mDateCreated, date, sizeof(date));
mData.putString(date); // 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;
if (mRequest.getParameterCount() < 1)
return MTP_RESPONSE_INVALID_PARAMETER;
MtpObjectHandle handle = mRequest.getParameter(1);
MtpStringBuffer pathBuf;
int64_t fileLength;
MtpObjectFormat format;
int result = mDatabase->getObjectFilePath(handle, pathBuf, fileLength, format);
if (result != MTP_RESPONSE_OK)
return result;
auto start = std::chrono::steady_clock::now();
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 = 0;
mfr.length = fileLength;
mfr.command = mRequest.getOperationCode();
mfr.transaction_id = mRequest.getTransactionID();
// then transfer the file
int ret = mHandle->sendFile(mfr);
if (ret < 0) {
MTPE("Mtp send file got error %s", strerror(errno));
if (errno == ECANCELED) {
result = MTP_RESPONSE_TRANSACTION_CANCELLED;
} else {
result = MTP_RESPONSE_GENERAL_ERROR;
}
} else {
result = MTP_RESPONSE_OK;
}
auto end = std::chrono::steady_clock::now();
std::chrono::duration<double> diff = end - start;
struct stat sstat;
fstat(mfr.fd, &sstat);
uint64_t finalsize = sstat.st_size;
MTPD("Sent a file over MTP. Time: %f s, Size: %" PRIu64 ", Rate: %f bytes/s",
diff.count(), finalsize, ((double) finalsize) / diff.count());
closeObjFd(mfr.fd, filePath);
return result;
}
MtpResponseCode MtpServer::doGetThumb() {
if (mRequest.getParameterCount() < 1)
return MTP_RESPONSE_INVALID_PARAMETER;
MtpObjectHandle handle = mRequest.getParameter(1);
size_t thumbSize;
void* thumb = mDatabase->getThumbnail(handle, thumbSize);
if (thumb) {
// send data
mData.setOperationCode(mRequest.getOperationCode());
mData.setTransactionID(mRequest.getTransactionID());
mData.writeData(mHandle, 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) {
// MTP_OPERATION_GET_PARTIAL_OBJECT_64 takes 4 arguments
if (mRequest.getParameterCount() < 4)
return MTP_RESPONSE_INVALID_PARAMETER;
// android extension with 64 bit offset
uint64_t offset2 = mRequest.getParameter(3);
offset = offset | (offset2 << 32);
length = mRequest.getParameter(4);
} else {
// MTP_OPERATION_GET_PARTIAL_OBJECT takes 3 arguments
if (mRequest.getParameterCount() < 3)
return MTP_RESPONSE_INVALID_PARAMETER;
// standard GetPartialObject
length = mRequest.getParameter(3);
}
MtpStringBuffer pathBuf;
int64_t fileLength;
MtpObjectFormat format;
int result = mDatabase->getObjectFilePath(handle, pathBuf, fileLength, format);
if (result != MTP_RESPONSE_OK)
return result;
if (offset + length > (uint64_t)fileLength)
length = fileLength - offset;
const char* filePath = (const char *)pathBuf;
MTPD("sending partial %s\n %" PRIu64 " %" PRIu32, filePath, offset, length);
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 = mHandle->sendFile(mfr);
MTPD("MTP_SEND_FILE_WITH_HEADER returned %d\n", ret);
result = MTP_RESPONSE_OK;
if (ret < 0) {
if (errno == ECANCELED)
result = MTP_RESPONSE_TRANSACTION_CANCELLED;
else
result = MTP_RESPONSE_GENERAL_ERROR;
}
closeObjFd(mfr.fd, filePath);
return result;
}
MtpResponseCode MtpServer::doSendObjectInfo() {
MtpStringBuffer path;
uint16_t temp16;
uint32_t temp32;
if (mRequest.getParameterCount() < 2)
return MTP_RESPONSE_INVALID_PARAMETER;
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) {
path.set(storage->getPath());
parent = 0;
} else {
int64_t length;
MtpObjectFormat format;
int result = mDatabase->getObjectFilePath(parent, path, length, format);
if (result != MTP_RESPONSE_OK)
return result;
if (format != MTP_FORMAT_ASSOCIATION)
return MTP_RESPONSE_INVALID_PARENT_OBJECT;
}
// read only the fields we need
if (!mData.getUInt32(temp32)) return MTP_RESPONSE_INVALID_PARAMETER; // storage ID
if (!mData.getUInt16(temp16)) return MTP_RESPONSE_INVALID_PARAMETER;
MtpObjectFormat format = temp16;
if (!mData.getUInt16(temp16)) return MTP_RESPONSE_INVALID_PARAMETER; // protection status
if (!mData.getUInt32(temp32)) return MTP_RESPONSE_INVALID_PARAMETER;
mSendObjectFileSize = temp32;
if (!mData.getUInt16(temp16)) return MTP_RESPONSE_INVALID_PARAMETER; // thumb format
if (!mData.getUInt32(temp32)) return MTP_RESPONSE_INVALID_PARAMETER; // thumb compressed size
if (!mData.getUInt32(temp32)) return MTP_RESPONSE_INVALID_PARAMETER; // thumb pix width
if (!mData.getUInt32(temp32)) return MTP_RESPONSE_INVALID_PARAMETER; // thumb pix height
if (!mData.getUInt32(temp32)) return MTP_RESPONSE_INVALID_PARAMETER; // image pix width
if (!mData.getUInt32(temp32)) return MTP_RESPONSE_INVALID_PARAMETER; // image pix height
if (!mData.getUInt32(temp32)) return MTP_RESPONSE_INVALID_PARAMETER; // image bit depth
if (!mData.getUInt32(temp32)) return MTP_RESPONSE_INVALID_PARAMETER; // parent
if (!mData.getUInt16(temp16)) return MTP_RESPONSE_INVALID_PARAMETER;
if (!mData.getUInt32(temp32)) return MTP_RESPONSE_INVALID_PARAMETER;
if (!mData.getUInt32(temp32)) return MTP_RESPONSE_INVALID_PARAMETER; // sequence number
MtpStringBuffer name, created, modified;
if (!mData.getString(name)) return MTP_RESPONSE_INVALID_PARAMETER; // file name
if (name.isEmpty()) {
MTPE("empty name");
return MTP_RESPONSE_INVALID_PARAMETER;
}
if (!mData.getString(created)) return MTP_RESPONSE_INVALID_PARAMETER; // date created
if (!mData.getString(modified)) return MTP_RESPONSE_INVALID_PARAMETER; // 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.append("/");
path.append(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("path: %s parent: %d storageID: %08X", (const char*)path, parent, storageID);
uint64_t size = 0; // TODO: this needs to be implemented
time_t modified_time = 0; // TODO: this needs to be implemented
MtpObjectHandle handle = mDatabase->beginSendObject((const char*)path, format,
parent, storageID, size, modified_time);
if (handle == kInvalidObjectHandle) {
return MTP_RESPONSE_GENERAL_ERROR;
}
if (format == MTP_FORMAT_ASSOCIATION) {
int ret = makeFolder((const char *)path);
if (ret)
return MTP_RESPONSE_GENERAL_ERROR;
// SendObject does not get sent for directories, so call endSendObject here instead
mDatabase->endSendObject((const char*)path, handle, format, MTP_RESPONSE_OK);
}
mSendObjectFilePath = path;
// save the handle for the SendObject call, which should follow
mSendObjectHandle = handle;
mSendObjectFormat = format;
mSendObjectModifiedTime = modifiedTime;
mResponse.setParameter(1, storageID);
mResponse.setParameter(2, parent);
mResponse.setParameter(3, handle);
return MTP_RESPONSE_OK;
}
MtpResponseCode MtpServer::doMoveObject() {
if (!hasStorage())
return MTP_RESPONSE_GENERAL_ERROR;
if (mRequest.getParameterCount() < 3)
return MTP_RESPONSE_INVALID_PARAMETER;
MtpObjectHandle objectHandle = mRequest.getParameter(1);
MtpStorageID storageID = mRequest.getParameter(2);
MtpStorage* storage = getStorage(storageID);
MtpObjectHandle parent = mRequest.getParameter(3);
if (!storage)
return MTP_RESPONSE_INVALID_STORAGE_ID;
MtpStringBuffer path;
MtpResponseCode result;
MtpStringBuffer fromPath;
int64_t fileLength;
MtpObjectFormat format;
MtpObjectInfo info(objectHandle);
result = mDatabase->getObjectInfo(objectHandle, info);
if (result != MTP_RESPONSE_OK)
return result;
result = mDatabase->getObjectFilePath(objectHandle, fromPath, fileLength, format);
if (result != MTP_RESPONSE_OK)
return result;
// special case the root
if (parent == 0) {
path.set(storage->getPath());
} else {
int64_t parentLength;
MtpObjectFormat parentFormat;
result = mDatabase->getObjectFilePath(parent, path, parentLength, parentFormat);
if (result != MTP_RESPONSE_OK)
return result;
if (parentFormat != MTP_FORMAT_ASSOCIATION)
return MTP_RESPONSE_INVALID_PARENT_OBJECT;
}
if (path[path.size() - 1] != '/')
path.append("/");
path.append(info.mName);
result = mDatabase->beginMoveObject(objectHandle, parent, storageID);
if (result != MTP_RESPONSE_OK)
return result;
if (info.mStorageID == storageID) {
MTPD("Moving file from %s to %s", (const char*)fromPath, (const char*)path);
if (renameTo(fromPath, path)) {
PLOG(ERROR) << "rename() failed from " << fromPath << " to " << path;
result = MTP_RESPONSE_GENERAL_ERROR;
}
} else {
MTPD("Moving across storages from %s to %s", (const char*)fromPath, (const char*)path);
if (format == MTP_FORMAT_ASSOCIATION) {
int ret = makeFolder((const char *)path);
ret += copyRecursive(fromPath, path);
if (ret) {
result = MTP_RESPONSE_GENERAL_ERROR;
} else {
deletePath(fromPath);
}
} else {
if (copyFile(fromPath, path)) {
result = MTP_RESPONSE_GENERAL_ERROR;
} else {
deletePath(fromPath);
}
}
}
// If the move failed, undo the database change
mDatabase->endMoveObject(info.mParent, parent, info.mStorageID, storageID, objectHandle,
result == MTP_RESPONSE_OK);
return result;
}
MtpResponseCode MtpServer::doCopyObject() {
if (!hasStorage())
return MTP_RESPONSE_GENERAL_ERROR;
MtpResponseCode result = MTP_RESPONSE_OK;
if (mRequest.getParameterCount() < 3)
return MTP_RESPONSE_INVALID_PARAMETER;
MtpObjectHandle objectHandle = mRequest.getParameter(1);
MtpStorageID storageID = mRequest.getParameter(2);
MtpStorage* storage = getStorage(storageID);
MtpObjectHandle parent = mRequest.getParameter(3);
if (!storage)
return MTP_RESPONSE_INVALID_STORAGE_ID;
MtpStringBuffer path;
MtpStringBuffer fromPath;
int64_t fileLength;
MtpObjectFormat format;
MtpObjectInfo info(objectHandle);
result = mDatabase->getObjectInfo(objectHandle, info);
if (result != MTP_RESPONSE_OK)
return result;
result = mDatabase->getObjectFilePath(objectHandle, fromPath, fileLength, format);
if (result != MTP_RESPONSE_OK)
return result;
// special case the root
if (parent == 0) {
path.set(storage->getPath());
} else {
int64_t parentLength;
MtpObjectFormat parentFormat;
result = mDatabase->getObjectFilePath(parent, path, parentLength, parentFormat);
if (result != MTP_RESPONSE_OK)
return result;
if (parentFormat != MTP_FORMAT_ASSOCIATION)
return MTP_RESPONSE_INVALID_PARENT_OBJECT;
}
// check space first
if ((uint64_t) fileLength > storage->getFreeSpace())
return MTP_RESPONSE_STORAGE_FULL;
if (path[path.size() - 1] != '/')
path.append("/");
path.append(info.mName);
MtpObjectHandle handle = mDatabase->beginCopyObject(objectHandle, parent, storageID);
if (handle == kInvalidObjectHandle) {
return MTP_RESPONSE_GENERAL_ERROR;
}
MTPD("Copying file from %s to %s", (const char*)fromPath, (const char*)path);
if (format == MTP_FORMAT_ASSOCIATION) {
int ret = makeFolder((const char *)path);
ret += copyRecursive(fromPath, path);
if (ret) {
result = MTP_RESPONSE_GENERAL_ERROR;
}
} else {
if (copyFile(fromPath, path)) {
result = MTP_RESPONSE_GENERAL_ERROR;
}
}
mDatabase->endCopyObject(handle, result);
mResponse.setParameter(1, handle);
return result;
}
MtpResponseCode MtpServer::doSendObject() {
if (!hasStorage())
return MTP_RESPONSE_GENERAL_ERROR;
MtpResponseCode result = MTP_RESPONSE_OK;
mode_t mask;
int ret, initialData;
bool isCanceled = false;
struct stat sstat = {};
auto start = std::chrono::steady_clock::now();
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(mHandle);
if (ret < MTP_CONTAINER_HEADER_SIZE) {
result = MTP_RESPONSE_GENERAL_ERROR;
goto done;
}
initialData = ret - MTP_CONTAINER_HEADER_SIZE;
if (mSendObjectFormat == MTP_FORMAT_ASSOCIATION) {
if (initialData != 0)
MTPE("Expected folder size to be 0!");
mSendObjectHandle = kInvalidObjectHandle;
mSendObjectFormat = 0;
mSendObjectModifiedTime = 0;
return result;
}
mtp_file_range mfr;
mfr.fd = open(mSendObjectFilePath, O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
if (mfr.fd < 0) {
result = MTP_RESPONSE_GENERAL_ERROR;
goto done;
}
fchown(mfr.fd, getuid(), FILE_GROUP);
// set permissions
mask = umask(0);
fchmod(mfr.fd, FILE_PERM);
umask(mask);
if (initialData > 0) {
ret = write(mfr.fd, mData.getData(), initialData);
}
if (ret < 0) {
MTPE("failed to write initial data");
result = MTP_RESPONSE_GENERAL_ERROR;
} else {
mfr.offset = initialData;
if (mSendObjectFileSize == 0xFFFFFFFF) {
// tell driver to read until it receives a short packet
mfr.length = 0xFFFFFFFF;
} else {
mfr.length = mSendObjectFileSize - initialData;
}
mfr.command = 0;
mfr.transaction_id = 0;
// transfer the file
ret = mHandle->receiveFile(mfr, mfr.length == 0 &&
initialData == MTP_BUFFER_SIZE - MTP_CONTAINER_HEADER_SIZE);
if ((ret < 0) && (errno == ECANCELED)) {
isCanceled = true;
}
}
if (mSendObjectModifiedTime) {
struct timespec newTime[2];
newTime[0].tv_nsec = UTIME_NOW;
newTime[1].tv_sec = mSendObjectModifiedTime;
newTime[1].tv_nsec = 0;
if (futimens(mfr.fd, newTime) < 0) {
MTPE("changing modified time failed, %s", strerror(errno));
}
}
fstat(mfr.fd, &sstat);
closeObjFd(mfr.fd, mSendObjectFilePath);
if (ret < 0) {
MTPE("Mtp receive file got error %s", strerror(errno));
unlink(mSendObjectFilePath);
if (isCanceled)
result = MTP_RESPONSE_TRANSACTION_CANCELLED;
else
result = MTP_RESPONSE_GENERAL_ERROR;
}
done:
// reset so we don't attempt to send the data back
mData.reset();
mDatabase->endSendObject(mSendObjectFilePath, mSendObjectHandle, mSendObjectFormat, result == MTP_RESPONSE_OK);
mSendObjectHandle = kInvalidObjectHandle;
mSendObjectFormat = 0;
mSendObjectModifiedTime = 0;
auto end = std::chrono::steady_clock::now();
std::chrono::duration<double> diff = end - start;
uint64_t finalsize = sstat.st_size;
MTPD("Got a file over MTP. Time: %fs, Size: %" PRIu64 ", Rate: %f bytes/s",
diff.count(), finalsize, ((double) finalsize) / diff.count());
return result;
}
MtpResponseCode MtpServer::doDeleteObject() {
MTPD("In MtpServer::doDeleteObject\n");
if (!hasStorage())
return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
if (mRequest.getParameterCount() < 1)
return MTP_RESPONSE_INVALID_PARAMETER;
MtpObjectHandle handle = mRequest.getParameter(1);
MtpObjectFormat format;
// FIXME - support deleting all objects if handle is 0xFFFFFFFF
// FIXME - implement deleting objects by format
MtpStringBuffer filePath;
int64_t fileLength;
int result = mDatabase->getObjectFilePath(handle, filePath, fileLength, format);
if (result != MTP_RESPONSE_OK)
return result;
// Don't delete the actual files unless the database deletion is allowed
result = mDatabase->beginDeleteObject(handle);
if (result != MTP_RESPONSE_OK)
return result;
bool success = deletePath((const char *)filePath);
mDatabase->endDeleteObject(handle, success);
return success ? result : MTP_RESPONSE_PARTIAL_DELETION;
}
MtpResponseCode MtpServer::doGetObjectPropDesc() {
if (mRequest.getParameterCount() < 2)
return MTP_RESPONSE_INVALID_PARAMETER;
MtpObjectProperty propCode = mRequest.getParameter(1);
MtpObjectFormat format = mRequest.getParameter(2);
MTPD("GetObjectPropDesc %s %s\n", MtpDebug::getObjectPropCodeName(propCode),
MtpDebug::getFormatCodeName(format));
MtpProperty* property = mDatabase->getObjectPropertyDesc(propCode, format);
if (!property)
return MTP_RESPONSE_OBJECT_PROP_NOT_SUPPORTED;
property->write(mData);
delete property;
return MTP_RESPONSE_OK;
}
MtpResponseCode MtpServer::doGetDevicePropDesc() {
if (mRequest.getParameterCount() < 1)
return MTP_RESPONSE_INVALID_PARAMETER;
MtpDeviceProperty propCode = mRequest.getParameter(1);
MTPD("GetDevicePropDesc %s\n", MtpDebug::getDevicePropCodeName(propCode));
MtpProperty* property = mDatabase->getDevicePropertyDesc(propCode);
if (!property)
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;
if (mRequest.getParameterCount() < 4)
return MTP_RESPONSE_INVALID_PARAMETER;
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) {
MTPD("writing past end of object, offset: %" PRIu64 ", edit->mSize: %" PRIu64,
offset, edit->mSize);
return MTP_RESPONSE_GENERAL_ERROR;
}
const char* filePath = (const char *)edit->mPath;
MTPD("receiving partial %s %" PRIu64 " %" PRIu32, filePath, offset, length);
// read the header, and possibly some data
int ret = mData.read(mHandle);
if (ret < MTP_CONTAINER_HEADER_SIZE)
return MTP_RESPONSE_GENERAL_ERROR;
int initialData = ret - MTP_CONTAINER_HEADER_SIZE;
if (initialData > 0) {
ret = pwrite(edit->mFD, mData.getData(), initialData, offset);
offset += initialData;
length -= initialData;
}
bool isCanceled = false;
if (ret < 0) {
MTPE("failed to write initial data");
} else {
mtp_file_range mfr;
mfr.fd = edit->mFD;
mfr.offset = offset;
mfr.length = length;
mfr.command = 0;
mfr.transaction_id = 0;
// transfer the file
ret = mHandle->receiveFile(mfr, mfr.length == 0 &&
initialData == MTP_BUFFER_SIZE - MTP_CONTAINER_HEADER_SIZE);
if ((ret < 0) && (errno == ECANCELED)) {
isCanceled = true;
}
}
if (ret < 0) {
mResponse.setParameter(1, 0);
if (isCanceled)
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() {
if (mRequest.getParameterCount() < 3)
return MTP_RESPONSE_INVALID_PARAMETER;
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() {
if (mRequest.getParameterCount() < 1)
return MTP_RESPONSE_INVALID_PARAMETER;
MtpObjectHandle handle = mRequest.getParameter(1);
if (getEditObject(handle)) {
MTPE("object already open for edit in doBeginEditObject");
return MTP_RESPONSE_GENERAL_ERROR;
}
MtpStringBuffer path;
int64_t fileLength;
MtpObjectFormat format;
int result = mDatabase->getObjectFilePath(handle, path, fileLength, format);
if (result != MTP_RESPONSE_OK)
return result;
int fd = open((const char *)path, O_RDWR | O_EXCL);
if (fd < 0) {
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() {
if (mRequest.getParameterCount() < 1)
return MTP_RESPONSE_INVALID_PARAMETER;
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;
}