Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 708b3e0b authored by Jerry Zhang's avatar Jerry Zhang
Browse files

Add implementations for move and copy operations.

Previously, we didn't have these implemented so the
host OS would emulate them by pulling the object
and pushing it to a new location. This can result
in data being lost and it is slow.

With the operations implemented, moving objects
on the same storage is instantaneous since the data
doesn't need to move. Moving objects between storages
and copying objects are both much faster since no
data has to be moved through USB.

Bug: 66679910
Test: Move and copy objects, verify they are correct
Change-Id: I38c69eee41d883af46fa2f1b9e091193b2847e8c
parent 0d51135d
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -103,6 +103,9 @@ public:

    virtual MtpProperty*            getDevicePropertyDesc(MtpDeviceProperty property) = 0;

    virtual MtpResponseCode         moveObject(MtpObjectHandle handle, MtpObjectHandle newParent,
                                            MtpString& newPath) = 0;

    virtual void                    sessionStarted() = 0;

    virtual void                    sessionEnded() = 0;
+139 −61
Original line number Diff line number Diff line
@@ -67,8 +67,8 @@ static const MtpOperationCode kSupportedOperationCodes[] = {
    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_MOVE_OBJECT,
    MTP_OPERATION_COPY_OBJECT,
    MTP_OPERATION_GET_PARTIAL_OBJECT,
//    MTP_OPERATION_INITIATE_OPEN_CAPTURE,
    MTP_OPERATION_GET_OBJECT_PROPS_SUPPORTED,
@@ -437,6 +437,12 @@ bool MtpServer::handleRequest() {
        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;
@@ -1020,6 +1026,137 @@ MtpResponseCode MtpServer::doSendObjectInfo() {
    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;
    MtpString path;
    MtpResponseCode result;

    MtpString 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 = 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 += "/";
    path += info.mName;

    result = mDatabase->moveObject(objectHandle, parent, path);
    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 (rename(fromPath, path)) {
            ALOGE("rename() failed from %s to %s", (const char*)fromPath, (const char*)path);
            result = MTP_RESPONSE_GENERAL_ERROR;
        }
    } else {
        ALOGV("Moving across storages from %s to %s", (const char*)fromPath, (const char*)path);
        if (copyFile(fromPath, path)) {
            result = MTP_RESPONSE_GENERAL_ERROR;
        } else {
            deletePath(fromPath);
        }
    }

    // If the move failed, undo the database change
    if (result != MTP_RESPONSE_OK)
        if (mDatabase->moveObject(objectHandle, info.mParent, fromPath) != MTP_RESPONSE_OK)
            ALOGE("Couldn't undo failed move");

    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;
    MtpString path;

    MtpString 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 = 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 += "/";
    path += info.mName;

    MtpObjectHandle handle = mDatabase->beginSendObject((const char*)path,
            format, parent, storageID, fileLength, info.mDateModified);
    if (handle == kInvalidObjectHandle) {
        return MTP_RESPONSE_GENERAL_ERROR;
    }

    ALOGV("Copying file from %s to %s", (const char*)fromPath, (const char*)path);
    if (copyFile(fromPath, path)) {
        result = MTP_RESPONSE_GENERAL_ERROR;
    }

    mDatabase->endSendObject(path, handle, format, result);
    mResponse.setParameter(1, handle);
    return result;
}

MtpResponseCode MtpServer::doSendObject() {
    if (!hasStorage())
        return MTP_RESPONSE_GENERAL_ERROR;
@@ -1124,65 +1261,6 @@ done:
    return result;
}

static void deleteRecursive(const char* path) {
    char pathbuf[PATH_MAX];
    size_t pathLength = strlen(path);
    if (pathLength >= sizeof(pathbuf) - 1) {
        ALOGE("path too long: %s\n", path);
    }
    strcpy(pathbuf, path);
    if (pathbuf[pathLength - 1] != '/') {
        pathbuf[pathLength++] = '/';
    }
    char* fileSpot = pathbuf + pathLength;
    int pathRemaining = sizeof(pathbuf) - pathLength - 1;

    DIR* dir = opendir(path);
    if (!dir) {
        ALOGE("opendir %s failed: %s", path, strerror(errno));
        return;
    }

    struct dirent* entry;
    while ((entry = readdir(dir))) {
        const char* name = entry->d_name;

        // ignore "." and ".."
        if (name[0] == '.' && (name[1] == 0 || (name[1] == '.' && name[2] == 0))) {
            continue;
        }

        int nameLength = strlen(name);
        if (nameLength > pathRemaining) {
            ALOGE("path %s/%s too long\n", path, name);
            continue;
        }
        strcpy(fileSpot, name);

        if (entry->d_type == DT_DIR) {
            deleteRecursive(pathbuf);
            rmdir(pathbuf);
        } else {
            unlink(pathbuf);
        }
    }
    closedir(dir);
}

static void deletePath(const char* path) {
    struct stat statbuf;
    if (stat(path, &statbuf) == 0) {
        if (S_ISDIR(statbuf.st_mode)) {
            deleteRecursive(path);
            rmdir(path);
        } else {
            unlink(path);
        }
    } else {
        ALOGE("deletePath stat failed for %s: %s", path, strerror(errno));
    }
}

MtpResponseCode MtpServer::doDeleteObject() {
    if (!hasStorage())
        return MTP_RESPONSE_INVALID_OBJECT_HANDLE;
+2 −0
Original line number Diff line number Diff line
@@ -161,6 +161,8 @@ private:
    MtpResponseCode     doSendObjectInfo();
    MtpResponseCode     doSendObject();
    MtpResponseCode     doDeleteObject();
    MtpResponseCode     doMoveObject();
    MtpResponseCode     doCopyObject();
    MtpResponseCode     doGetObjectPropDesc();
    MtpResponseCode     doGetDevicePropDesc();
    MtpResponseCode     doSendPartialObject();
+107 −0
Original line number Diff line number Diff line
@@ -16,13 +16,23 @@

#define LOG_TAG "MtpUtils"

#include <android-base/logging.h>
#include <android-base/unique_fd.h>
#include <dirent.h>
#include <fcntl.h>
#include <sys/sendfile.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <stdio.h>
#include <time.h>
#include <unistd.h>

#include "MtpUtils.h"

namespace android {

constexpr unsigned long FILE_COPY_SIZE = 262144;

/*
DateTime strings follow a compatible subset of the definition found in ISO 8601, and
take the form of a Unicode string formatted as: "YYYYMMDDThhmmss.s". In this
@@ -78,4 +88,101 @@ void formatDateTime(time_t seconds, char* buffer, int bufferLength) {
        tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
}

int copyFile(const char *fromPath, const char *toPath) {
    auto start = std::chrono::steady_clock::now();

    android::base::unique_fd fromFd(open(fromPath, O_RDONLY));
    if (fromFd == -1) {
        PLOG(ERROR) << "Failed to open copy from " << fromPath;
        return -1;
    }
    android::base::unique_fd toFd(open(toPath, O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR));
    if (toFd == -1) {
        PLOG(ERROR) << "Failed to open copy to " << toPath;
        return -1;
    }
    off_t offset = 0;

    struct stat sstat = {};
    if (stat(fromPath, &sstat) == -1)
        return -1;

    off_t length = sstat.st_size;
    int ret = 0;

    while (offset < length) {
        ssize_t transfer_length = std::min(length - offset, (off_t) FILE_COPY_SIZE);
        ret = sendfile(toFd, fromFd, &offset, transfer_length);
        if (ret != transfer_length) {
            ret = -1;
            PLOG(ERROR) << "Copying failed!";
            break;
        }
    }
    auto end = std::chrono::steady_clock::now();
    std::chrono::duration<double> diff = end - start;
    LOG(INFO) << "Copied a file with MTP. Time: " << diff.count() << " s, Size: " << length <<
        ", Rate: " << ((double) length) / diff.count() << " bytes/s";
    return ret == -1 ? -1 : 0;
}

void deleteRecursive(const char* path) {
    char pathbuf[PATH_MAX];
    size_t pathLength = strlen(path);
    if (pathLength >= sizeof(pathbuf) - 1) {
        LOG(ERROR) << "path too long: " << 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) {
        PLOG(ERROR) << "opendir " << path << " failed";
        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) {
            LOG(ERROR) << "path " << path << "/" << name << " too long";
            continue;
        }
        strcpy(fileSpot, name);

        if (entry->d_type == DT_DIR) {
            deleteRecursive(pathbuf);
            rmdir(pathbuf);
        } else {
            unlink(pathbuf);
        }
    }
    closedir(dir);
}

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 {
        PLOG(ERROR) << "deletePath stat failed for " << path;;
    }
}

}  // namespace android
+4 −0
Original line number Diff line number Diff line
@@ -24,6 +24,10 @@ namespace android {
bool parseDateTime(const char* dateTime, time_t& outSeconds);
void formatDateTime(time_t seconds, char* buffer, int bufferLength);

int copyFile(const char *fromPath, const char *toPath);
void deleteRecursive(const char* path);
void deletePath(const char* path);

}; // namespace android

#endif // _MTP_UTILS_H