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
diff --git a/media/mtp/MtpUtils.cpp b/media/mtp/MtpUtils.cpp
index ebf3601..036ffe7 100644
--- a/media/mtp/MtpUtils.cpp
+++ b/media/mtp/MtpUtils.cpp
@@ -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 @@
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