Enable File.Copy to EXFAT volumes when not running as root (#46027)
authorDan Moseley <danmose@microsoft.com>
Tue, 15 Dec 2020 03:20:09 +0000 (19:20 -0800)
committerGitHub <noreply@github.com>
Tue, 15 Dec 2020 03:20:09 +0000 (03:20 +0000)
* Allow File.Copy to succeed when futimens fails with EPERM

* Manual test

* Update pal_io.c

src/libraries/Native/Unix/System.Native/pal_io.c
src/libraries/System.IO.FileSystem/tests/ManualTests/ManualTests.cs

index 2b780b0..3d7420e 100644 (file)
@@ -1165,14 +1165,17 @@ int32_t SystemNative_CopyFile(intptr_t sourceFd, intptr_t destinationFd)
         while ((ret = futimes(outFd, origTimes)) < 0 && errno == EINTR);
 #endif
     }
-    if (ret != 0)
+    // If we copied to a filesystem (eg EXFAT) that does not preserve POSIX ownership, all files appear
+    // to be owned by root. If we aren't running as root, then we won't be an owner of our new file, and 
+    // attempting to copy metadata to it will fail with EPERM. We have copied successfully, we just can't
+    // copy metadata. The best thing we can do is skip copying the metadata.
+    if (ret != 0 && errno != EPERM)
     {
         return -1;
     }
-
     // Then copy permissions.
     while ((ret = fchmod(outFd, sourceStat.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO))) < 0 && errno == EINTR);
-    if (ret != 0)
+    if (ret != 0 && errno != EPERM) // See EPERM comment above
     {
         return -1;
     }
index c363e65..fe0395a 100644 (file)
@@ -76,6 +76,29 @@ namespace System.IO.ManualTests
             });
         }
 
+        [ConditionalFact(nameof(ManualTestsEnabled))]
+        [PlatformSpecific(TestPlatforms.Linux)]
+        public static void FileCopy_WorksToExFatVolume()
+        {
+            // We copy attributes after copying the file; when copying to EXFAT,
+            // where all files appeared to be owned by root, we can't copy attributes
+            // (unless we're root) and should skip silently
+
+            /* This test requires an EXFAT partition. That can be created in memory like this:
+
+            sudo mkdir /mnt/ramdisk
+            sudo mount -t ramfs ramfs /mnt/ramdisk
+            sudo dd if=/dev/zero of=/mnt/ramdisk/exfat.image bs=1M count=512
+            sudo mkfs.exfat /mnt/ramdisk/exfat.image
+            sudo mkdir /mnt/exfatrd
+            sudo mount -o loop /mnt/ramdisk/exfat.image /mnt/exfatrd
+
+            */
+
+            File.WriteAllText("/mnt/exfatrd/1", "content");
+            File.Copy("/mnt/exfatrd/1", "/mnt/exfatrd/2");
+            Assert.True(File.Exists("/mnt/exfatrd/2"));
+        }
 
         const long InitialFileSize = 1024;