diff options
author | bigbiff bigbiff <bigbiff@teamw.in> | 2018-12-19 00:39:53 +0100 |
---|---|---|
committer | Ethan Yonker <dees_troy@teamw.in> | 2019-03-20 20:28:21 +0100 |
commit | af32bb9c4f4f06e92de3435ed2db3153c0701094 (patch) | |
tree | 622948fb3167dc17bb436c948d61df581d2e75f7 /mtp/ffs/MtpServer.cpp | |
parent | Adding Edl button in reboot menu (diff) | |
download | android_bootable_recovery-af32bb9c4f4f06e92de3435ed2db3153c0701094.tar android_bootable_recovery-af32bb9c4f4f06e92de3435ed2db3153c0701094.tar.gz android_bootable_recovery-af32bb9c4f4f06e92de3435ed2db3153c0701094.tar.bz2 android_bootable_recovery-af32bb9c4f4f06e92de3435ed2db3153c0701094.tar.lz android_bootable_recovery-af32bb9c4f4f06e92de3435ed2db3153c0701094.tar.xz android_bootable_recovery-af32bb9c4f4f06e92de3435ed2db3153c0701094.tar.zst android_bootable_recovery-af32bb9c4f4f06e92de3435ed2db3153c0701094.zip |
Diffstat (limited to 'mtp/ffs/MtpServer.cpp')
-rwxr-xr-x | mtp/ffs/MtpServer.cpp | 1459 |
1 files changed, 1459 insertions, 0 deletions
diff --git a/mtp/ffs/MtpServer.cpp b/mtp/ffs/MtpServer.cpp new file mode 100755 index 000000000..5f17ff2ff --- /dev/null +++ b/mtp/ffs/MtpServer.cpp @@ -0,0 +1,1459 @@ +/* + * 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; +} |