Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

System.IO.Directory.Move fails between two mounts on Linux #31149

Open
hsorbo opened this issue Oct 11, 2019 · 25 comments
Open

System.IO.Directory.Move fails between two mounts on Linux #31149

hsorbo opened this issue Oct 11, 2019 · 25 comments
Milestone

Comments

@hsorbo
Copy link

hsorbo commented Oct 11, 2019

On Linux/netcore3 System.IO.Directory.Move throws System.IO.IOException: Invalid cross-device link when trying to move a folder between two mounts/disks in Linux.

System.IO.Directory.Move("/tmp/foo","/tmp/baz"); //works
System.IO.Directory.Move("/mnt/notsamedisk/foo","/tmp/baz"); //throws

Unhandled exception. System.IO.IOException: Invalid cross-device link
   at System.IO.FileSystem.MoveDirectory(String sourceFullPath, String destFullPath)
   at System.IO.Directory.Move(String sourceDirName, String destDirName)
   at tmp.Program.Main(String[] args) in /home/easysky/tmp/Program.cs:line 9
using System;

namespace tmp
{
    class Program
    {
        static void Main(string[] args)
        {
            System.IO.Directory.Move("/datadrive/tmp/foo","/tmp/foo");
        }
    }
}

uname -a Linux Test 5.0.0-1018-azure dotnet/corefx#19~18.04.1-Ubuntu SMP Wed Aug 21 05:13:05 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux

dotnet --version
3.0.100
@vcsjones
Copy link
Member

Based on the documentation, this might be expected, or at least known, behavior:

IOException
An attempt was made to move a directory to a different volume.

@vcsjones
Copy link
Member

/cc @JeremyKuhne

@danmoseley
Copy link
Member

danmoseley commented Oct 11, 2019

dotnet/corefx#40611 related?

@JeremyKuhne
Copy link
Member

It is something we didn't put effort in to support, primarily because the code was utilizing MoveFile. I'm not opposed to changing the behavior, it would just be significant effort (particularly from the test perspective).

@hsorbo
Copy link
Author

hsorbo commented Oct 12, 2019

@vcsjones is correct, this is documented and I didn't read the documentation thoroughly.
In that sense I think this issue can be closed.

On the other hand I think there are compelling arguments for a feature request for this.
Unix heavily uses these attached volumes example ramdisk /tmp, nfs (network) or encrypted home-folder, compressed filesystem for logs, etc.

Unix-programs are normally agnostic to which volume/mount a folder belongs to.
I think Users/admins expect to be able to set up that without breaking stuff.
In that sense, making a well-beaaving unix app, its hard to rely on Directory.Move

@svick
Copy link
Contributor

svick commented Oct 12, 2019

Would it be less effort if the fallback behavior was to run /bin/mv?

@joshudson
Copy link
Contributor

I'd personally have rathered Directory.Move and File.Move just failed when trying to span volumes even on Windows. Tracking down that bug left behind by another developer was not a fun day.

(The developer assumed it was atomic, and some other process managed to get in between and read half the file--hint: file locks don't always work on network volumes being accessed by multiple computers.)

@hsorbo
Copy link
Author

hsorbo commented Oct 20, 2019

@joshudson Aren’t you just back at writing your own Directory.Move then? (And in addition you’ll need mechanisms for checking volumes files belongs to, and in case of Unix where path/volumes are configurable, do that always)

@joshudson
Copy link
Contributor

@hsorbo: Better a misconfigured program failing than losing data. Detecting cross-volume is easy. Don't pass MOVEFILE_COPY_ALLOWED to MoveFileEx and check for the error code. Writing an algorithm that can handle it, now that's harder.

@hsorbo
Copy link
Author

hsorbo commented Oct 20, 2019

@joshudson getting rid of it on windows is probably not in the cards as its breaking change (maybe worth a new issue). MoveFileEx is windows API, not unix/posix (and i think file locking is uncommon thing on unixes). Making consumers implement Directory.Move because of the problem has no guarantees for data loss in concurrent programming is, in my book, not a very strong argument for not supporting this scenario.

@MichaelSimons
Copy link
Member

I came upon a scenario that encountered this today involving Docker. Anytime Directory.Move gets invoked on a directory created in a previous Docker layer, you will hit this issue. Docker utilizes overlay mounts to union the layers of a Docker image. Modifying files in a previous Docker layer is something you typically try to avoid because it causes image bloat. That being said there are times this has to be done.

The end user scenario that hit this is dotnet tool update. See dotnet/sdk#3838 for details.

@solrevdev
Copy link
Contributor

solrevdev commented Jan 7, 2020

FYI this also happens on macOS.

summary

Trying to move files on a folder shared to everyone on a Microsoft Windows Server 2012R2 to a local macos folder.

I should add this happens on System.IO.Directory.Move

from "/Volumes/WindowsShareName/foldername
to "/Users/solrevdev/TempFileLocalToMacOS"

dotnet info

dotnet --info
.NET Core SDK (reflecting any global.json):
 Version:   3.1.100
 Commit:    cd82f021f4

Runtime Environment:
 OS Name:     Mac OS X
 OS Version:  10.14
 OS Platform: Darwin
 RID:         osx.10.14-x64
 Base Path:   /usr/local/share/dotnet/sdk/3.1.100/

Host (useful for support):
  Version: 3.1.0
  Commit:  65f04fb6db

.NET Core SDKs installed:
  3.1.100 [/usr/local/share/dotnet/sdk]

.NET Core runtimes installed:
  Microsoft.AspNetCore.App 3.1.0 [/usr/local/share/dotnet/shared/Microsoft.AspNetCore.App]
  Microsoft.NETCore.App 2.1.13 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]
  Microsoft.NETCore.App 3.1.0 [/usr/local/share/dotnet/shared/Microsoft.NETCore.App]

To install additional .NET Core runtimes or SDKs:
  https://aka.ms/dotnet-download

environment

screenfetch -n
 solrevdev@macmini
 OS: 64bit Mac OS X 10.14.6 18G95
 Kernel: x86_64 Darwin 18.7.0
 Uptime: 23d 19h 10m
 Packages: 415
 Shell: zsh 5.7.1
 Resolution: 2560x1440
 DE: Aqua
 WM: Quartz Compositor
 WM Theme: Blue (Dark)
 Disk: 404G / 525G (78%)
 CPU: Intel Core i5-4278U @ 2.60GHz
 GPU: Intel Iris
 RAM: 8856MiB / 16384MiB

@msftgits msftgits transferred this issue from dotnet/corefx Feb 1, 2020
@msftgits msftgits added this to the Future milestone Feb 1, 2020
@maryamariyan maryamariyan added the untriaged New issue has not been triaged by the area owner label Feb 23, 2020
@JeremyKuhne
Copy link
Member

Triage: As I mentioned above, I'm ok with trying to add cross volume moves, it would just require a lot of work and thorough tests. The underlying API on Windows that we use doesn't support it and we'd want to make sure we're matching in volume moves as closely as possible (that is, as the Win32 MoveFile would do).

@iSazonov
Copy link
Contributor

The underlying API on Windows that we use doesn't support it

@JeremyKuhne It seems typo - should be "on Unix".

One way to fix is in PowerShell/PowerShell#13044

@rjmholt
Copy link

rjmholt commented Jun 29, 2020

One way to fix is in PowerShell/PowerShell#13044

This behaviour is based on how mv implements the logic

@iSazonov
Copy link
Contributor

Perhaps we could enhance the API with a flag to disable default cross-volume coping for performance reasons.

@JeremyKuhne
Copy link
Member

@JeremyKuhne It seems typo - should be "on Unix".

No, I meant Windows. We'd want to also look at moving files from C: to D: on Windows, but that requires work.

@iSazonov
Copy link
Contributor

@JeremyKuhne This should already works if MOVEFILE_COPY_ALLOWED flag is used in MoveFile()

@iSazonov
Copy link
Contributor

iSazonov commented Jun 30, 2020

.Net Framework has another implementation https://referencesource.microsoft.com/#mscorlib/system/io/longpath.cs,410 - the MOVEFILE_COPY_ALLOWED flag is not used.
I believe .Net Core behavior was changed on Windows but not documented https://docs.microsoft.com/en-us/dotnet/api/system.io.directory.move?view=netcore-3.1#exceptions

Although the question is how correctly the Windows API works for mount reparse (OneDrive) points.

@iSazonov
Copy link
Contributor

It seems @rjmholt tried FileInfo.MoveTo() for cross-volume on current PowerShell Core and it throws - is it a bug in .Net or docs?

@JeremyKuhne
Copy link
Member

This should already works if MOVEFILE_COPY_ALLOWED flag is used in MoveFile()

Yes, my bad, flashbacks to MoveFile.

It seems @rjmholt tried FileInfo.MoveTo() for cross-volume on current PowerShell Core and it throws - is it a bug in .Net or docs?

@carlossanlop can you follow up?

@rjmholt
Copy link

rjmholt commented Jun 30, 2020

Full error:


[C:\Users\Robert Holt\Documents\Dev\Microsoft\PSScriptAnalyzer]> gi .\tools_copy\ | Sv t

[C:\Users\Robert Holt\Documents\Dev\Microsoft\PSScriptAnalyzer]> $t.MoveTo('D:\tools')
MethodInvocationException: Exception calling "MoveTo" with "1" argument(s): "Source and destination path must have identical roots. Move will not work across volumes."

[C:\Users\Robert Holt\Documents\Dev\Microsoft\PSScriptAnalyzer]> get-error

Exception             :
    Type           : System.Management.Automation.MethodInvocationException
    ErrorRecord    :
        Exception             :
            Type    : System.Management.Automation.ParentContainsErrorRecordException
            Message : Exception calling "MoveTo" with "1" argument(s): "Source and destination path
must have identical roots. Move will not work across volumes."
            HResult : -2146233087
        CategoryInfo          : NotSpecified: (:) [], ParentContainsErrorRecordException
        FullyQualifiedErrorId : IOException
        InvocationInfo        :
            ScriptLineNumber : 1
            OffsetInLine     : 1
            HistoryId        : -1
            Line             : $t.MoveTo('D:\tools')
            PositionMessage  : At line:1 char:1
                               + $t.MoveTo('D:\tools')
                               + ~~~~~~~~~~~~~~~~~~~~~
            CommandOrigin    : Internal
        ScriptStackTrace      : at <ScriptBlock>, <No file>: line 1
    TargetSite     :
        Name          : ConvertToMethodInvocationException
        DeclaringType : System.Management.Automation.ExceptionHandlingOps,
System.Management.Automation, Version=7.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
        MemberType    : Method
        Module        : System.Management.Automation.dll
    StackTrace     :
   at System.Management.Automation.ExceptionHandlingOps.ConvertToMethodInvocationException(Exception
exception, Type typeToThrow, String methodName, Int32 numArgs, MemberInfo memberInfo)
   at CallSite.Target(Closure , CallSite , Object , String )
   at System.Dynamic.UpdateDelegates.UpdateAndExecute2[T0,T1,TRet](CallSite site, T0 arg0, T1 arg1)
   at System.Management.Automation.Interpreter.DynamicInstruction`3.Run(InterpretedFrame frame)
   at System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction.Run(InterpretedFrame
frame)
    Message        : Exception calling "MoveTo" with "1" argument(s): "Source and destination path
must have identical roots. Move will not work across volumes."
    Data           : System.Collections.ListDictionaryInternal
    InnerException :
        Type       : System.IO.IOException
        TargetSite :
            Name          : MoveTo
            DeclaringType : System.IO.DirectoryInfo
            MemberType    : Method
            Module        : System.IO.FileSystem.dll
        StackTrace :
   at System.IO.DirectoryInfo.MoveTo(String destDirName)
   at CallSite.Target(Closure , CallSite , Object , String )
        Message    : Source and destination path must have identical roots. Move will not work
across volumes.
        Source     : System.IO.FileSystem
        HResult    : -2146232800
    Source         : System.Management.Automation
    HResult        : -2146233087
CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
FullyQualifiedErrorId : IOException
InvocationInfo        :
    ScriptLineNumber : 1
    OffsetInLine     : 1
    HistoryId        : -1
    Line             : $t.MoveTo('D:\tools')
    PositionMessage  : At line:1 char:1
                       + $t.MoveTo('D:\tools')
                       + ~~~~~~~~~~~~~~~~~~~~~
    CommandOrigin    : Internal
ScriptStackTrace      : at <ScriptBlock>, <No file>: line 1

Context

@DaniilShmelev
Copy link

@JeremyKuhne
Are there any updates regarding this issue? We've faced a regression on the azure-pipelines-agent due to this.
Reproducible on .NET 3.1 and 6.0.2.

@danmoseley
Copy link
Member

@DaniilShmelev can you share more about the regression? What regressed?

@DaniilShmelev
Copy link

@danmoseley just to be clear - the regression happened due to us adding Directory.Move call in one of our updates.
You can take a look at the relevant code here: https://github.com/microsoft/azure-pipelines-agent/pull/3684/files#diff-9b306a433872dce88aa973c97bfa0851fba2b88a18e556938fc768a0c640beb1R88
On some of the customer's setups source and destination directories are located on different drives, which caused the Directory.Move call to fail.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests