Mike Lockwood 7f36b19b6b MTP: Add support for reserve storage setting to avoid low storage situations.
Set resource config_mtpReserveSpaceMegabytes to number of megabytes to reserve.
If MTP has dedicated storage this value should be zero, but if MTP is
sharing storage with the rest of the system, set this to a positive value
to ensure that MTP activity does not result in the storage being
too close to full.

BUG: 3250924

Change-Id: I881c87240da268bad1ea1b99ad03673ab85ffdbf
Signed-off-by: Mike Lockwood <lockwood@android.com>
2010-12-12 12:17:43 -08:00

888 lines
29 KiB
C++

/*
* 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 <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/stat.h>
#include <dirent.h>
#include <cutils/properties.h>
#define LOG_TAG "MtpServer"
#include "MtpDebug.h"
#include "MtpDatabase.h"
#include "MtpProperty.h"
#include "MtpServer.h"
#include "MtpStorage.h"
#include "MtpStringBuffer.h"
#include <linux/usb/f_mtp.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,
};
static const MtpEventCode kSupportedEventCodes[] = {
MTP_EVENT_OBJECT_ADDED,
MTP_EVENT_OBJECT_REMOVED,
};
MtpServer::MtpServer(int fd, MtpDatabase* database,
int fileGroup, int filePerm, int directoryPerm)
: mFD(fd),
mDatabase(database),
mFileGroup(fileGroup),
mFilePermission(filePerm),
mDirectoryPermission(directoryPerm),
mSessionID(0),
mSessionOpen(false),
mSendObjectHandle(kInvalidObjectHandle),
mSendObjectFormat(0),
mSendObjectFileSize(0)
{
}
MtpServer::~MtpServer() {
}
void MtpServer::addStorage(const char* filePath, uint64_t reserveSpace) {
int index = mStorages.size() + 1;
index |= index << 16; // set high and low part to our index
MtpStorage* storage = new MtpStorage(index, filePath, reserveSpace);
addStorage(storage);
}
MtpStorage* MtpServer::getStorage(MtpStorageID id) {
if (id == 0)
return mStorages[0];
for (int i = 0; i < mStorages.size(); i++) {
MtpStorage* storage = mStorages[i];
if (storage->getStorageID() == id)
return storage;
}
return NULL;
}
void MtpServer::run() {
int fd = mFD;
LOGV("MtpServer::run fd: %d\n", fd);
while (1) {
int ret = mRequest.read(fd);
if (ret < 0) {
LOGE("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();
LOGV("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) {
LOGE("data read returned %d, errno: %d", ret, errno);
if (errno == ECANCELED) {
// return to top of loop and wait for next command
continue;
}
break;
}
LOGV("received data:");
mData.dump();
} else {
mData.reset();
}
if (handleRequest()) {
if (!dataIn && mData.hasData()) {
mData.setOperationCode(operation);
mData.setTransactionID(transaction);
LOGV("sending data:");
mData.dump();
ret = mData.write(fd);
if (ret < 0) {
LOGE("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);
LOGV("sending response %04X", mResponse.getResponseCode());
ret = mResponse.write(fd);
mResponse.dump();
if (ret < 0) {
LOGE("request write returned %d, errno: %d", ret, errno);
if (errno == ECANCELED) {
// return to top of loop and wait for next command
continue;
}
break;
}
} else {
LOGV("skipping response\n");
}
}
if (mSessionOpen)
mDatabase->sessionEnded();
}
void MtpServer::sendObjectAdded(MtpObjectHandle handle) {
if (mSessionOpen) {
LOGD("sendObjectAdded %d\n", handle);
mEvent.setEventCode(MTP_EVENT_OBJECT_ADDED);
mEvent.setTransactionID(mRequest.getTransactionID());
mEvent.setParameter(1, handle);
int ret = mEvent.write(mFD);
LOGD("mEvent.write returned %d\n", ret);
}
}
void MtpServer::sendObjectRemoved(MtpObjectHandle handle) {
if (mSessionOpen) {
LOGD("sendObjectRemoved %d\n", handle);
mEvent.setEventCode(MTP_EVENT_OBJECT_REMOVED);
mEvent.setTransactionID(mRequest.getTransactionID());
mEvent.setParameter(1, handle);
int ret = mEvent.write(mFD);
LOGD("mEvent.write returned %d\n", ret);
}
}
bool MtpServer::handleRequest() {
MtpOperationCode operation = mRequest.getOperationCode();
MtpResponseCode response;
mResponse.reset();
if (mSendObjectHandle != kInvalidObjectHandle && operation != MTP_OPERATION_SEND_OBJECT) {
// FIXME - need to delete mSendObjectHandle from the database
LOGE("expected SendObject after SendObjectInfo");
mSendObjectHandle = kInvalidObjectHandle;
}
switch (operation) {
case MTP_OPERATION_GET_DEVICE_INFO:
response = doGetDeviceInfo();
break;
case MTP_OPERATION_OPEN_SESSION:
response = doOpenSession();
break;
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_PARTIAL_OBJECT:
response = doGetPartialObject();
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;
default:
LOGE("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);
mData.putUInt32(6); // MTP Vendor Extension ID
mData.putUInt16(MTP_STANDARD_VERSION);
string.set("microsoft.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
// FIXME
string.set("Google, Inc.");
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, "????????");
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() {
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;
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;
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;
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 (parent == 0xFFFFFFFF)
parent = 0;
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;
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 (parent == 0xFFFFFFFF)
parent = 0;
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;
MtpStorageID 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;
MtpStorageID handle = mRequest.getParameter(1);
MtpObjectHandleList* references = mData.getAUInt32();
MtpResponseCode result = mDatabase->setObjectReferences(handle, references);
delete references;
return result;
}
MtpResponseCode MtpServer::doGetObjectPropValue() {
MtpObjectHandle handle = mRequest.getParameter(1);
MtpObjectProperty property = mRequest.getParameter(2);
LOGD("GetObjectPropValue %d %s\n", handle,
MtpDebug::getObjectPropCodeName(property));
return mDatabase->getObjectPropertyValue(handle, property, mData);
}
MtpResponseCode MtpServer::doSetObjectPropValue() {
MtpObjectHandle handle = mRequest.getParameter(1);
MtpObjectProperty property = mRequest.getParameter(2);
LOGD("SetObjectPropValue %d %s\n", handle,
MtpDebug::getObjectPropCodeName(property));
return mDatabase->setObjectPropertyValue(handle, property, mData);
}
MtpResponseCode MtpServer::doGetDevicePropValue() {
MtpDeviceProperty property = mRequest.getParameter(1);
LOGD("GetDevicePropValue %s\n",
MtpDebug::getDevicePropCodeName(property));
return mDatabase->getDevicePropertyValue(property, mData);
}
MtpResponseCode MtpServer::doSetDevicePropValue() {
MtpDeviceProperty property = mRequest.getParameter(1);
LOGD("SetDevicePropValue %s\n",
MtpDebug::getDevicePropCodeName(property));
return mDatabase->setDevicePropertyValue(property, mData);
}
MtpResponseCode MtpServer::doResetDevicePropValue() {
MtpDeviceProperty property = mRequest.getParameter(1);
LOGD("ResetDevicePropValue %s\n",
MtpDebug::getDevicePropCodeName(property));
return mDatabase->resetDeviceProperty(property);
}
MtpResponseCode MtpServer::doGetObjectPropList() {
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);
LOGD("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() {
MtpObjectHandle handle = mRequest.getParameter(1);
return mDatabase->getObjectInfo(handle, mData);
}
MtpResponseCode MtpServer::doGetObject() {
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;
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;
// send data header
mData.setOperationCode(mRequest.getOperationCode());
mData.setTransactionID(mRequest.getTransactionID());
mData.writeDataHeader(mFD, fileLength + MTP_CONTAINER_HEADER_SIZE);
// then transfer the file
int ret = ioctl(mFD, MTP_SEND_FILE, (unsigned long)&mfr);
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::doGetPartialObject() {
MtpObjectHandle handle = mRequest.getParameter(1);
uint32_t offset = mRequest.getParameter(2);
uint32_t 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 > 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;
mResponse.setParameter(1, length);
// send data header
mData.setOperationCode(mRequest.getOperationCode());
mData.setTransactionID(mRequest.getTransactionID());
mData.writeDataHeader(mFD, length + MTP_CONTAINER_HEADER_SIZE);
// then transfer the file
int ret = ioctl(mFD, MTP_SEND_FILE, (unsigned long)&mfr);
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() {
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) {
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
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
LOGD("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;
// file should not already exist
if (access(path, R_OK) == 0)
return MTP_RESPONSE_GENERAL_ERROR;
// check space first
if (mSendObjectFileSize > storage->getFreeSpace())
return MTP_RESPONSE_STORAGE_FULL;
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);
} 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() {
MtpResponseCode result = MTP_RESPONSE_OK;
mode_t mask;
int ret;
uint64_t actualSize = -1;
if (mSendObjectHandle == kInvalidObjectHandle) {
LOGE("Expected SendObjectInfo before SendObject");
result = MTP_RESPONSE_NO_VALID_OBJECT_INFO;
goto done;
}
// read the header
ret = mData.readDataHeader(mFD);
// FIXME - check for errors here.
// reset so we don't attempt to send this back
mData.reset();
mtp_file_range mfr;
mfr.fd = open(mSendObjectFilePath, O_RDWR | O_CREAT | O_TRUNC);
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);
mfr.offset = 0;
mfr.length = mSendObjectFileSize;
LOGD("receiving %s\n", (const char *)mSendObjectFilePath);
// transfer the file
ret = ioctl(mFD, MTP_RECEIVE_FILE, (unsigned long)&mfr);
close(mfr.fd);
LOGV("MTP_RECEIVE_FILE returned %d", ret);
if (ret < 0) {
unlink(mSendObjectFilePath);
if (errno == ECANCELED)
result = MTP_RESPONSE_TRANSACTION_CANCELLED;
else
result = MTP_RESPONSE_GENERAL_ERROR;
} else if (mSendObjectFileSize == 0xFFFFFFFF) {
// actual size is likely > 4 gig so stat the file to compute actual length
struct stat s;
if (lstat(mSendObjectFilePath, &s) == 0) {
actualSize = s.st_size;
LOGD("actualSize: %lld\n", actualSize);
}
}
done:
mDatabase->endSendObject(mSendObjectFilePath, mSendObjectHandle, mSendObjectFormat,
actualSize, result == MTP_RESPONSE_OK);
mSendObjectHandle = kInvalidObjectHandle;
mSendObjectFormat = 0;
return result;
}
static void deleteRecursive(const char* path) {
char pathbuf[PATH_MAX];
int pathLength = strlen(path);
if (pathLength >= sizeof(pathbuf) - 1) {
LOGE("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) {
LOGE("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) {
LOGE("path %s/%s too long\n", path, name);
continue;
}
strcpy(fileSpot, name);
int type = entry->d_type;
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 {
LOGE("deletePath stat failed for %s: %s", path, strerror(errno));
}
}
MtpResponseCode MtpServer::doDeleteObject() {
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;
int result = mDatabase->getObjectFilePath(handle, filePath, fileLength, format);
if (result == MTP_RESPONSE_OK) {
LOGV("deleting %s", (const char *)filePath);
deletePath((const char *)filePath);
return mDatabase->deleteFile(handle);
} else {
return result;
}
}
MtpResponseCode MtpServer::doGetObjectPropDesc() {
MtpObjectProperty propCode = mRequest.getParameter(1);
MtpObjectFormat format = mRequest.getParameter(2);
LOGD("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() {
MtpDeviceProperty propCode = mRequest.getParameter(1);
LOGD("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;
}
} // namespace android