/* * 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 <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> #define LOG_TAG "MtpServer" #include "MtpDebug.h" #include "MtpDatabase.h" #include "MtpObjectInfo.h" #include "MtpProperty.h" #include "MtpServer.h" #include "MtpStorage.h" #include "MtpStringBuffer.h" namespace android { 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(MtpDatabase* database, bool ptp, int fileGroup, int filePerm, int directoryPerm, const MtpString& deviceInfoManufacturer, const MtpString& deviceInfoModel, const MtpString& deviceInfoDeviceVersion, const MtpString& deviceInfoSerialNumber) : mDatabase(database), mPtp(ptp), mFileGroup(fileGroup), mFilePermission(filePerm), mDirectoryPermission(directoryPerm), mDeviceInfoManufacturer(deviceInfoManufacturer), mDeviceInfoModel(deviceInfoModel), mDeviceInfoDeviceVersion(deviceInfoDeviceVersion), mDeviceInfoSerialNumber(deviceInfoSerialNumber), mSessionID(0), mSessionOpen(false), mSendObjectHandle(kInvalidObjectHandle), mSendObjectFormat(0), mSendObjectFileSize(0) { } MtpServer::~MtpServer() { } IMtpHandle* MtpServer::sHandle = nullptr; int MtpServer::configure(bool usePtp) { if (sHandle == nullptr) { bool ffs_ok = access(FFS_MTP_EP0, W_OK) == 0; sHandle = ffs_ok ? get_ffs_handle() : get_mtp_handle(); } int ret = sHandle->configure(usePtp); if (ret) ALOGE("Failed to configure MTP driver!"); else android::base::SetProperty("sys.usb.ffs.mtp.ready", "1"); return ret; } void MtpServer::addStorage(MtpStorage* storage) { Mutex::Autolock autoLock(mMutex); mStorages.push(storage); sendStoreAdded(storage->getStorageID()); } void MtpServer::removeStorage(MtpStorage* storage) { Mutex::Autolock autoLock(mMutex); for (size_t i = 0; i < mStorages.size(); i++) { if (mStorages[i] == storage) { mStorages.removeAt(i); sendStoreRemoved(storage->getStorageID()); break; } } } MtpStorage* MtpServer::getStorage(MtpStorageID id) { if (id == 0) return mStorages[0]; for (size_t i = 0; i < mStorages.size(); i++) { MtpStorage* storage = mStorages[i]; 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 (!sHandle) { ALOGE("MtpServer was never configured!"); return; } if (sHandle->start()) { ALOGE("Failed to start usb driver!"); sHandle->close(); return; } while (1) { int ret = mRequest.read(sHandle); if (ret < 0) { ALOGE("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(); ALOGV("operation: %s", 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(sHandle); if (ret < 0) { ALOGE("data read returned %d, errno: %d", ret, errno); if (errno == ECANCELED) { // return to top of loop and wait for next command continue; } break; } ALOGV("received data:"); } else { mData.reset(); } if (handleRequest()) { if (!dataIn && mData.hasData()) { mData.setOperationCode(operation); mData.setTransactionID(transaction); ALOGV("sending data:"); ret = mData.write(sHandle); if (ret < 0) { ALOGE("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); ALOGV("sending response %04X", mResponse.getResponseCode()); ret = mResponse.write(sHandle); const int savedErrno = errno; if (ret < 0) { ALOGE("request write returned %d, errno: %d", ret, errno); if (savedErrno == ECANCELED) { // return to top of loop and wait for next command continue; } break; } } else { ALOGV("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(); sHandle->close(); } void MtpServer::sendObjectAdded(MtpObjectHandle handle) { ALOGV("sendObjectAdded %d\n", handle); sendEvent(MTP_EVENT_OBJECT_ADDED, handle); } void MtpServer::sendObjectRemoved(MtpObjectHandle handle) { ALOGV("sendObjectRemoved %d\n", handle); sendEvent(MTP_EVENT_OBJECT_REMOVED, handle); } void MtpServer::sendStoreAdded(MtpStorageID id) { ALOGV("sendStoreAdded %08X\n", id); sendEvent(MTP_EVENT_STORE_ADDED, id); } void MtpServer::sendStoreRemoved(MtpStorageID id) { ALOGV("sendStoreRemoved %08X\n", id); sendEvent(MTP_EVENT_STORE_REMOVED, id); } void MtpServer::sendDevicePropertyChanged(MtpDeviceProperty property) { ALOGV("sendDevicePropertyChanged %d\n", property); sendEvent(MTP_EVENT_DEVICE_PROP_CHANGED, property); } void MtpServer::sendEvent(MtpEventCode code, uint32_t param1) { if (mSessionOpen) { mEvent.setEventCode(code); mEvent.setTransactionID(mRequest.getTransactionID()); mEvent.setParameter(1, param1); if (mEvent.write(sHandle)) ALOGE("Mtp send event failed: %s", strerror(errno)); } } 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 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.removeAt(i); return; } } ALOGE("ObjectEdit not found in removeEditObject"); } void MtpServer::commitEdit(ObjectEdit* edit) { mDatabase->endSendObject((const char *)edit->mPath, edit->mHandle, edit->mFormat, true); } bool MtpServer::handleRequest() { 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 ALOGE("expected SendObject after SendObjectInfo"); mSendObjectHandle = kInvalidObjectHandle; } int containertype = mRequest.getContainerType(); if (containertype != MTP_CONTAINER_TYPE_COMMAND) { ALOGE("wrong container type %d", containertype); return false; } ALOGV("got command %s (%x)", 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_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: ALOGE("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; 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() { 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); 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); ALOGV("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); ALOGV("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); ALOGV("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); ALOGV("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); ALOGV("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); ALOGV("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); MtpString 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 = sHandle->sendFile(mfr); if (ret < 0) { ALOGE("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; ALOGV("Sent a file over MTP. Time: %f s, Size: %" PRIu64 ", Rate: %f bytes/s", diff.count(), finalsize, ((double) finalsize) / diff.count()); close(mfr.fd); 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(sHandle, 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); } MtpString 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; 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 = sHandle->sendFile(mfr); ALOGV("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; } close(mfr.fd); return result; } MtpResponseCode MtpServer::doSendObjectInfo() { MtpString 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 = 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.getCharCount() == 0) { ALOGE("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 ALOGV("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; } ALOGD("path: %s parent: %d storageID: %08X", (const char*)path, parent, storageID); MtpObjectHandle handle = mDatabase->beginSendObject((const char*)path, format, parent, storageID, mSendObjectFileSize, modifiedTime); if (handle == kInvalidObjectHandle) { return MTP_RESPONSE_GENERAL_ERROR; } if (format == MTP_FORMAT_ASSOCIATION) { mode_t mask = umask(0); int ret = mkdir((const char *)path, mDirectoryPermission); umask(mask); if (ret && ret != -EEXIST) return MTP_RESPONSE_GENERAL_ERROR; chown((const char *)path, getuid(), mFileGroup); // SendObject does not get sent for directories, so call endSendObject here instead mDatabase->endSendObject(path, handle, MTP_FORMAT_ASSOCIATION, MTP_RESPONSE_OK); } 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); return MTP_RESPONSE_OK; } MtpResponseCode MtpServer::doSendObject() { if (!hasStorage()) return MTP_RESPONSE_GENERAL_ERROR; MtpResponseCode result = MTP_RESPONSE_OK; mode_t mask; int ret, initialData; bool isCanceled = false; auto start = std::chrono::steady_clock::now(); if (mSendObjectHandle == kInvalidObjectHandle) { ALOGE("Expected SendObjectInfo before SendObject"); result = MTP_RESPONSE_NO_VALID_OBJECT_INFO; goto done; } // read the header, and possibly some data ret = mData.read(sHandle); if (ret < MTP_CONTAINER_HEADER_SIZE) { 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, S_IRUSR | S_IWUSR); if (mfr.fd < 0) { result = MTP_RESPONSE_GENERAL_ERROR; 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 (ret < 0) { ALOGE("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 = sHandle->receiveFile(mfr, mfr.length == 0 && initialData == MTP_BUFFER_SIZE - MTP_CONTAINER_HEADER_SIZE); if ((ret < 0) && (errno == ECANCELED)) { isCanceled = true; } } struct stat sstat; fstat(mfr.fd, &sstat); close(mfr.fd); if (ret < 0) { ALOGE("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; auto end = std::chrono::steady_clock::now(); std::chrono::duration<double> diff = end - start; uint64_t finalsize = sstat.st_size; ALOGV("Got a file over MTP. Time: %fs, Size: %" PRIu64 ", Rate: %f bytes/s", diff.count(), finalsize, ((double) finalsize) / diff.count()); return result; } static void deleteRecursive(const char* path) { char pathbuf[PATH_MAX]; size_t pathLength = strlen(path); if (pathLength >= sizeof(pathbuf) - 1) { ALOGE("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) { ALOGE("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) { ALOGE("path %s/%s too long\n", path, name); continue; } strcpy(fileSpot, name); if (entry->d_type == DT_DIR) { 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 { ALOGE("deletePath stat failed for %s: %s", path, strerror(errno)); } } MtpResponseCode MtpServer::doDeleteObject() { 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 MtpString filePath; int64_t fileLength; int result = mDatabase->getObjectFilePath(handle, filePath, fileLength, format); if (result == MTP_RESPONSE_OK) { ALOGV("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); } } return result; } MtpResponseCode MtpServer::doGetObjectPropDesc() { if (mRequest.getParameterCount() < 2) return MTP_RESPONSE_INVALID_PARAMETER; MtpObjectProperty propCode = mRequest.getParameter(1); MtpObjectFormat format = mRequest.getParameter(2); ALOGV("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); ALOGV("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) { ALOGE("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) { ALOGD("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; ALOGV("receiving partial %s %" PRIu64 " %" PRIu32, filePath, offset, length); // read the header, and possibly some data int ret = mData.read(sHandle); 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) { ALOGE("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 = sHandle->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) { ALOGE("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)) { ALOGE("object already open for edit in doBeginEditObject"); return MTP_RESPONSE_GENERAL_ERROR; } MtpString 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) { ALOGE("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) { ALOGE("object not open for edit in doEndEditObject"); return MTP_RESPONSE_GENERAL_ERROR; } commitEdit(edit); removeEditObject(handle); return MTP_RESPONSE_OK; } } // namespace android