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

Directory.Move behaves differently on Unix vs. Windows #27908

Closed
alexischr opened this issue Nov 14, 2018 · 15 comments · Fixed by dotnet/corefx#37121
Closed

Directory.Move behaves differently on Unix vs. Windows #27908

alexischr opened this issue Nov 14, 2018 · 15 comments · Fixed by dotnet/corefx#37121
Assignees
Milestone

Comments

@alexischr
Copy link
Contributor

The following program throws System.IO.IOException: Cannot create a file when that file already exists on Windows, but succeeds (and silently replaces the destination file) on i.e. macOS:

using System;
using System.IO;

namespace MoveFile_DestFile_Exists
{
    class Program
    {
        static void Main(string[] args)
        {
            string TempFolder = Path.GetTempPath ();
            string tempFile1 = Path.Combine (TempFolder, "temp1.txt");
            string tempFile2 = Path.Combine (TempFolder, "temp2.txt");

            using (StreamWriter sw = File.CreateText (tempFile1)) {
                sw.Write ("temp1");
            }
            using (StreamWriter sw = File.CreateText (tempFile2)) {
                sw.Write ("temp2");
            }

            Directory.Move (tempFile1, tempFile2);
        }
    }
}

For context, expecting this to fail is part of Mono's tests.

@marek-safar
Copy link
Contributor

@karelz @danmosemsft what is your take on this issue?

@jkotas
Copy link
Member

jkotas commented Nov 15, 2018

cc @JeremyKuhne

@JeremyKuhne
Copy link
Member

Seems like a bug/test hole to me. It should fail like Windows does if the destination exists when moving a file via Directory.

https://github.com/dotnet/corefx/blob/master/src/System.IO.FileSystem/src/System/IO/FileSystem.Unix.cs#L334

@stephentoub
Copy link
Member

Yeah, it's a bug that should be fixed.

@karelz
Copy link
Member

karelz commented Nov 15, 2018

@marek-safar is it blocking you? Or is 3.0 fix sufficient?

@danmoseley
Copy link
Member

@marek-safar
Copy link
Contributor

marek-safar commented Nov 17, 2018

@karelz yes, it's blocking us as it's a regression to .NET/Mono framework functionality

@danmoseley
Copy link
Member

@karelz Mono are absorbing more of corefx in 3.0 time frame

marek-safar referenced this issue in mono/mono Dec 7, 2018
Includes the following from System.IO:

* FileSystemInfo
* FileInfo
* DirectoryInfo
* Directory
* FileSystem
* Path `Join()` Span API

and all of System.IO.Enumeration namespace.

Adds about 1600 System.IO tests

Behavior changes:
* `File.Replace` now replaces files even if they are read-only (using the `rename` syscall on Unix)
* Directory enumeration is no longer stable in the way that many Mono tests expect
* Some changes in when/whether some representations of directories contain a trailing separator character
* Changes in which exceptions are thrown (sometimes these are platform-dependent on CoreFX)

Issues:
* https://github.com/dotnet/corefx/issues/33486
* https://github.com/dotnet/corefx/issues/33490

Part of #7246
@carlossanlop carlossanlop self-assigned this Apr 22, 2019
@carlossanlop
Copy link
Member

carlossanlop commented Apr 22, 2019

I have a fix for this and will submit a PR soon.

Mentioning the area owner @JeremyKuhne.

Explanation:

The behavior differs between Windows and Unix because the function that does the actual move (and unexpected file overwrite in Unix) is the C++ rename function , which has the following remark message in the description:

If newname names an existing file, the function may either fail or override the existing file, depending on the specific system and library implementation.

In Windows, when we attempt to move a file and the destination exists using an Interop function, Windows throws error ERROR_ALREADY_EXISTS = 0xB7, which then we convert into the appropriate exception:

/// File: corefx\src\System.IO.FileSystem\src\System\IO\FileSystem.Windows.cs, line 342:
public static void MoveDirectory(string sourceFullPath, string destFullPath)
{
    if (!Interop.Kernel32.MoveFile(sourceFullPath, destFullPath, overwrite: false))
    {
        int errorCode = Marshal.GetLastWin32Error();
        ...
        ...
        throw Win32Marshal.GetExceptionForWin32Error(errorCode);
    }
}

But in Unix, the Interop function does not fail when the destination file already exists, and we don't do any preemptive checks for the existence of the destFullPath:

/// File: corefx\src\System.IO.FileSystem\src\System\IO\FileSystem.Unix.cs
public static void MoveDirectory(string sourceFullPath, string destFullPath)
{
    if (FileExists(sourceFullPath))
    {
        ...
    }

    if (Interop.Sys.Rename(sourceFullPath, destFullPath) < 0)
    {
        ...
    }
}

Finally, the Interop.Sys.Rename function calls SystemNative_Rename:

// corefx\src\Common\src\Interop\Unix\System.Native\Interop.Rename.cs, line 20:
[DllImport(Libraries.SystemNative, EntryPoint = "SystemNative_Rename", SetLastError = true)]
internal static extern int Rename(string oldPath, string newPath);

And SystemNative_Rename in turn calls the C++ rename function:

// corefx\src\Native\Unix\System.Native\pal_io.c, line 1094:
int32_t SystemNative_Rename(const char* oldPath, const char* newPath)
{
    int32_t result;
    while ((result = rename(oldPath, newPath)) < 0 && errno == EINTR);
    return result;
}

@danmoseley
Copy link
Member

Linux man page https://linux.die.net/man/2/rename may be more helpful than the generic C++ std library page. Comes to the same thing, though.

@danmoseley
Copy link
Member

When you fix this, we should add it to the list of "potentially breaking changes" in case folks run into it.

@danmoseley
Copy link
Member

BTW the "list" is in the platform-compat analyzer
https://github.com/dotnet/platform-compat/tree/cbb1822f5d887e98ccdd903a705d625b2c9ddabc/etc

Unfortunately it has not had much attention for a while, and I am not sure how heavily used it is. If it is broadly useful, we should giev it more attention.

@carlossanlop
Copy link
Member

Thank you @danmosemsft. There are 3 files in the location you shared. I have some questions:

  1. I suspect the file I have to modify is "exceptions.csv" and I have to add a new exception for linux and osx. Is my assumption correct?

  2. What is the net461 file for?

  3. I think the change I have to make would look like the one below, but I'm not sure what the last 3 columns mean. I suspect they mean we will be throwing an exception on Linux and OSX from now on. Is my assumption correct?

This is the change I would make:

exceptions.csv

DocId Namespace Type Member linux osx win
M:System.IO.Directory.Move(System.String,System.String) System.IO Directory Move(System.String,System.String) X X X

As a FYI, the other two files have this structure but I suspect we don't care about them for this change:

deprecated.csv

DocId Namespace Type Member DiagnosticIds

net461.csv

DocId Namespace Type Member

@danmoseley
Copy link
Member

@carlossanlop the owner left. I think @joperezr may have worked on it (?) Otherwise do what you think is best - or open an issue there to do it later.

@carlossanlop
Copy link
Member

carlossanlop commented Apr 30, 2019

@terrajobst Immo, it seems you've modified the platform-compat files in the past. Could you please confirm if what I stated above is correct?

@msftgits msftgits transferred this issue from dotnet/corefx Jan 31, 2020
@msftgits msftgits added this to the 3.0 milestone Jan 31, 2020
@ghost ghost locked as resolved and limited conversation to collaborators Dec 15, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

9 participants