Account for folders in copy and move.

Copy and move will use mkdir() if target
is a folder, and copyFile() if target is a file.

Move will recursively copy contents if moving
between different storages. Move will also
change the storageId.

Bug: 67028892
Test: Copy and move folders on win 10
Change-Id: If114ef74b9d8668cf66d45953d9ea8b17bc11ae8
diff --git a/media/mtp/MtpDatabase.h b/media/mtp/MtpDatabase.h
index c7021df..2395f4f 100644
--- a/media/mtp/MtpDatabase.h
+++ b/media/mtp/MtpDatabase.h
@@ -104,7 +104,7 @@
     virtual MtpProperty*            getDevicePropertyDesc(MtpDeviceProperty property) = 0;
 
     virtual MtpResponseCode         moveObject(MtpObjectHandle handle, MtpObjectHandle newParent,
-                                            MtpString& newPath) = 0;
+                                            MtpStorageID newStorage, MtpString& newPath) = 0;
 
     virtual void                    sessionStarted() = 0;
 
diff --git a/media/mtp/MtpServer.cpp b/media/mtp/MtpServer.cpp
index 236f3a9..a0944a9 100644
--- a/media/mtp/MtpServer.cpp
+++ b/media/mtp/MtpServer.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include <android-base/logging.h>
 #include <android-base/properties.h>
 #include <chrono>
 #include <dirent.h>
@@ -99,16 +100,12 @@
 };
 
 MtpServer::MtpServer(MtpDatabase* database, bool ptp,
-                    int fileGroup, int filePerm, int directoryPerm,
                     const MtpString& deviceInfoManufacturer,
                     const MtpString& deviceInfoModel,
                     const MtpString& deviceInfoDeviceVersion,
                     const MtpString& deviceInfoSerialNumber)
     :   mDatabase(database),
         mPtp(ptp),
-        mFileGroup(fileGroup),
-        mFilePermission(filePerm),
-        mDirectoryPermission(directoryPerm),
         mDeviceInfoManufacturer(deviceInfoManufacturer),
         mDeviceInfoModel(deviceInfoModel),
         mDeviceInfoDeviceVersion(deviceInfoDeviceVersion),
@@ -1002,12 +999,9 @@
     }
 
   if (format == MTP_FORMAT_ASSOCIATION) {
-        mode_t mask = umask(0);
-        int ret = mkdir((const char *)path, mDirectoryPermission);
-        umask(mask);
-        if (ret && ret != -EEXIST)
+        int ret = makeFolder((const char *)path);
+        if (ret)
             return MTP_RESPONSE_GENERAL_ERROR;
-        chown((const char *)path, getuid(), mFileGroup);
 
         // SendObject does not get sent for directories, so call endSendObject here instead
         mDatabase->endSendObject(path, handle, MTP_FORMAT_ASSOCIATION, MTP_RESPONSE_OK);
@@ -1068,28 +1062,39 @@
         path += "/";
     path += info.mName;
 
-    result = mDatabase->moveObject(objectHandle, parent, path);
+    result = mDatabase->moveObject(objectHandle, parent, storageID, 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);
+            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 (copyFile(fromPath, path)) {
-            result = MTP_RESPONSE_GENERAL_ERROR;
+        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 {
-            deletePath(fromPath);
+            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)
+        if (mDatabase->moveObject(objectHandle, info.mParent, info.mStorageID,
+                    fromPath) != MTP_RESPONSE_OK)
             ALOGE("Couldn't undo failed move");
 
     return result;
@@ -1148,8 +1153,15 @@
     }
 
     ALOGV("Copying file from %s to %s", (const char*)fromPath, (const char*)path);
-    if (copyFile(fromPath, path)) {
-        result = MTP_RESPONSE_GENERAL_ERROR;
+    if (format == MTP_FORMAT_ASSOCIATION) {
+        int ret = makeFolder((const char *)path);
+        if (ret) {
+            result = MTP_RESPONSE_GENERAL_ERROR;
+        }
+    } else {
+        if (copyFile(fromPath, path)) {
+            result = MTP_RESPONSE_GENERAL_ERROR;
+        }
     }
 
     mDatabase->endSendObject(path, handle, format, result);
@@ -1188,10 +1200,10 @@
         result = MTP_RESPONSE_GENERAL_ERROR;
         goto done;
     }
-    fchown(mfr.fd, getuid(), mFileGroup);
+    fchown(mfr.fd, getuid(), FILE_GROUP);
     // set permissions
     mask = umask(0);
-    fchmod(mfr.fd, mFilePermission);
+    fchmod(mfr.fd, FILE_PERM);
     umask(mask);
 
     if (initialData > 0) {
diff --git a/media/mtp/MtpServer.h b/media/mtp/MtpServer.h
index aafc753..0204b09 100644
--- a/media/mtp/MtpServer.h
+++ b/media/mtp/MtpServer.h
@@ -43,12 +43,6 @@
     // appear as a PTP device
     bool                mPtp;
 
-    // group to own new files and folders
-    int                 mFileGroup;
-    // permissions for new files and directories
-    int                 mFilePermission;
-    int                 mDirectoryPermission;
-
     // Manufacturer to report in DeviceInfo
     MtpString           mDeviceInfoManufacturer;
     // Model to report in DeviceInfo
@@ -105,7 +99,6 @@
 
 public:
                         MtpServer(MtpDatabase* database, bool ptp,
-                                    int fileGroup, int filePerm, int directoryPerm,
                                     const MtpString& deviceInfoManufacturer,
                                     const MtpString& deviceInfoModel,
                                     const MtpString& deviceInfoDeviceVersion,
diff --git a/media/mtp/MtpUtils.cpp b/media/mtp/MtpUtils.cpp
index 036ffe7..3f5648b 100644
--- a/media/mtp/MtpUtils.cpp
+++ b/media/mtp/MtpUtils.cpp
@@ -20,6 +20,7 @@
 #include <android-base/unique_fd.h>
 #include <dirent.h>
 #include <fcntl.h>
+#include <string>
 #include <sys/sendfile.h>
 #include <sys/stat.h>
 #include <sys/types.h>
@@ -29,6 +30,8 @@
 
 #include "MtpUtils.h"
 
+using namespace std;
+
 namespace android {
 
 constexpr unsigned long FILE_COPY_SIZE = 262144;
@@ -88,6 +91,60 @@
         tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
 }
 
+int makeFolder(const char *path) {
+    mode_t mask = umask(0);
+    int ret = mkdir((const char *)path, DIR_PERM);
+    umask(mask);
+    if (ret && ret != -EEXIST) {
+        PLOG(ERROR) << "Failed to create folder " << path;
+        ret = -1;
+    } else {
+        chown((const char *)path, getuid(), FILE_GROUP);
+    }
+    return ret;
+}
+
+/**
+ * Copies target path and all children to destination path.
+ *
+ * Returns 0 on success or a negative value indicating number of failures
+ */
+int copyRecursive(const char *fromPath, const char *toPath) {
+    int ret = 0;
+    string fromPathStr(fromPath);
+    string toPathStr(toPath);
+
+    DIR* dir = opendir(fromPath);
+    if (!dir) {
+        PLOG(ERROR) << "opendir " << fromPath << " failed";
+        return -1;
+    }
+    if (fromPathStr[fromPathStr.size()-1] != '/')
+        fromPathStr += '/';
+    if (toPathStr[toPathStr.size()-1] != '/')
+        toPathStr += '/';
+
+    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;
+        }
+        string oldFile = fromPathStr + name;
+        string newFile = toPathStr + name;
+
+        if (entry->d_type == DT_DIR) {
+            ret += makeFolder(newFile.c_str());
+            ret += copyRecursive(oldFile.c_str(), newFile.c_str());
+        } else {
+            ret += copyFile(oldFile.c_str(), newFile.c_str());
+        }
+    }
+    return ret;
+}
+
 int copyFile(const char *fromPath, const char *toPath) {
     auto start = std::chrono::steady_clock::now();
 
@@ -96,7 +153,7 @@
         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));
+    android::base::unique_fd toFd(open(toPath, O_CREAT | O_WRONLY, FILE_PERM));
     if (toFd == -1) {
         PLOG(ERROR) << "Failed to open copy to " << toPath;
         return -1;
@@ -121,23 +178,17 @@
     }
     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 <<
+    LOG(DEBUG) << "Copied a file with MTP. Time: " << diff.count() << " s, Size: " << length <<
         ", Rate: " << ((double) length) / diff.count() << " bytes/s";
+    chown(toPath, getuid(), FILE_GROUP);
     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;
+    string pathStr(path);
+    if (pathStr[pathStr.size()-1] != '/') {
+        pathStr += '/';
     }
-    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) {
@@ -153,19 +204,12 @@
         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);
-
+        pathStr.append(name);
         if (entry->d_type == DT_DIR) {
-            deleteRecursive(pathbuf);
-            rmdir(pathbuf);
+            deleteRecursive(pathStr.c_str());
+            rmdir(pathStr.c_str());
         } else {
-            unlink(pathbuf);
+            unlink(pathStr.c_str());
         }
     }
     closedir(dir);
diff --git a/media/mtp/MtpUtils.h b/media/mtp/MtpUtils.h
index a2bb7e1..b7c72f5 100644
--- a/media/mtp/MtpUtils.h
+++ b/media/mtp/MtpUtils.h
@@ -17,13 +17,21 @@
 #ifndef _MTP_UTILS_H
 #define _MTP_UTILS_H
 
+#include "private/android_filesystem_config.h"
+
 #include <stdint.h>
 
 namespace android {
 
+constexpr int FILE_GROUP = AID_MEDIA_RW;
+constexpr int FILE_PERM = 0664;
+constexpr int DIR_PERM = 0775;
+
 bool parseDateTime(const char* dateTime, time_t& outSeconds);
 void formatDateTime(time_t seconds, char* buffer, int bufferLength);
 
+int makeFolder(const char *path);
+int copyRecursive(const char *fromPath, const char *toPath);
 int copyFile(const char *fromPath, const char *toPath);
 void deleteRecursive(const char* path);
 void deletePath(const char* path);