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