You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1505 lines
51 KiB
1505 lines
51 KiB
/*
|
|
* 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 "IMtpDatabase.h"
|
|
#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"
|
|
#include "android-base/strings.h"
|
|
|
|
namespace android {
|
|
static const int SN_EVENT_LOG_ID = 0x534e4554;
|
|
|
|
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,
|
|
MTP_EVENT_OBJECT_INFO_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);
|
|
} else {
|
|
mHandle = new MtpDevHandle();
|
|
}
|
|
}
|
|
|
|
MtpServer::~MtpServer() {
|
|
}
|
|
|
|
void MtpServer::addStorage(MtpStorage* storage) {
|
|
std::lock_guard<std::mutex> lg(mMutex);
|
|
|
|
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)) {
|
|
ALOGE("Failed to start usb driver!");
|
|
mHandle->close();
|
|
return;
|
|
}
|
|
|
|
while (1) {
|
|
int ret = mRequest.read(mHandle);
|
|
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(mHandle);
|
|
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(mHandle);
|
|
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(mHandle);
|
|
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();
|
|
|
|
mHandle->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::sendObjectInfoChanged(MtpObjectHandle handle) {
|
|
ALOGV("sendObjectInfoChanged %d\n", handle);
|
|
sendEvent(MTP_EVENT_OBJECT_INFO_CHANGED, 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(mHandle))
|
|
ALOGE("Mtp send event failed: %s", 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;
|
|
}
|
|
}
|
|
ALOGE("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) {
|
|
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_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:
|
|
ALOGE("got unsupported command %s (%x)",
|
|
MtpDebug::getOperationCodeName(operation), operation);
|
|
response = MTP_RESPONSE_OPERATION_NOT_SUPPORTED;
|
|
break;
|
|
}
|
|
|
|
if (response != MTP_RESPONSE_OK)
|
|
ALOGW("[MTP] got response 0x%X in command %s (%x)", response,
|
|
MtpDebug::getOperationCodeName(operation), operation);
|
|
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);
|
|
ALOGV("GetObjectPropValue %d %s (0x%04X)\n", handle,
|
|
MtpDebug::getObjectPropCodeName(property), 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);
|
|
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;
|
|
struct stat sstat;
|
|
uint64_t finalsize;
|
|
bool transcode = android::base::GetBoolProperty("sys.fuse.transcode_mtp", false);
|
|
bool filePathAccess = true;
|
|
ALOGD("Mtp transcode = %d", transcode);
|
|
|
|
// For performance reasons, only attempt a ContentResolver open when transcode is required.
|
|
// This is fine as long as we don't transcode by default on the device. If we suddenly
|
|
// transcode by default, we'll need to ensure that MTP doesn't transcode by default and we
|
|
// might need to make a binder call to avoid transcoding or come up with a better strategy.
|
|
if (transcode) {
|
|
mfr.fd = mDatabase->openFilePath(filePath, true);
|
|
fstat(mfr.fd, &sstat);
|
|
finalsize = sstat.st_size;
|
|
fileLength = finalsize;
|
|
if (mfr.fd < 0) {
|
|
ALOGW("Mtp open via IMtpDatabase failed for %s. Falling back to the original",
|
|
filePath);
|
|
filePathAccess = true;
|
|
} else {
|
|
filePathAccess = false;
|
|
}
|
|
}
|
|
|
|
if (filePathAccess) {
|
|
mfr.fd = open(filePath, O_RDONLY);
|
|
if (mfr.fd < 0) {
|
|
return MTP_RESPONSE_GENERAL_ERROR;
|
|
}
|
|
fstat(mfr.fd, &sstat);
|
|
finalsize = sstat.st_size;
|
|
}
|
|
|
|
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) {
|
|
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;
|
|
ALOGV("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;
|
|
ALOGV("sending partial %s %" 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);
|
|
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;
|
|
}
|
|
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()) {
|
|
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
|
|
|
|
int type = storage->getType();
|
|
if (type == MTP_STORAGE_REMOVABLE_RAM) {
|
|
std::string str = android::base::Trim((const char*)name);
|
|
name.set(str.c_str());
|
|
}
|
|
ALOGV("name: %s format: 0x%04X (%s)\n", (const char*)name, format,
|
|
MtpDebug::getFormatCodeName(format));
|
|
time_t modifiedTime;
|
|
if (!parseDateTime(modified, modifiedTime))
|
|
modifiedTime = 0;
|
|
|
|
if ((strcmp(name, ".") == 0) || (strcmp(name, "..") == 0) ||
|
|
(strchr(name, '/') != NULL)) {
|
|
char errMsg[80];
|
|
|
|
snprintf(errMsg, sizeof(errMsg), "Invalid name: %s", (const char *) name);
|
|
ALOGE("%s (b/130656917)", errMsg);
|
|
android_errorWriteWithInfoLog(SN_EVENT_LOG_ID, "130656917", -1, errMsg,
|
|
strlen(errMsg));
|
|
|
|
return MTP_RESPONSE_INVALID_PARAMETER;
|
|
}
|
|
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;
|
|
}
|
|
|
|
ALOGV("path: %s parent: %d storageID: %08X", (const char*)path, parent, storageID);
|
|
MtpObjectHandle handle = mDatabase->beginSendObject((const char*)path, format,
|
|
parent, storageID);
|
|
ALOGD("handle: %d, parent: %d, storageID: %08X", handle, parent, storageID);
|
|
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(handle, 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) {
|
|
ALOGV("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 {
|
|
ALOGV("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;
|
|
}
|
|
|
|
ALOGV("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) {
|
|
ALOGE("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)
|
|
ALOGE("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) {
|
|
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 = 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) {
|
|
ALOGW("changing modified time failed, %s", strerror(errno));
|
|
}
|
|
}
|
|
|
|
fstat(mfr.fd, &sstat);
|
|
closeObjFd(mfr.fd, mSendObjectFilePath);
|
|
|
|
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(mSendObjectHandle, 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;
|
|
ALOGV("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() {
|
|
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);
|
|
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(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) {
|
|
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 = 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) {
|
|
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;
|
|
}
|
|
|
|
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) {
|
|
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
|