dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
15.38k stars 4.75k forks source link

Path functions behaving differently on UNC device paths than implied from documentation #95619

Open s5bug opened 11 months ago

s5bug commented 11 months ago

Description

File path formats on Windows systems states:

For device UNCs, the server/share portion forms the volume. For example, in \\?\server1\utilities\\filecomparer\, the server/share portion is server1\utilities. This is significant when calling a method such as Path.GetFullPath(String, String) with relative directory segments; it is never possible to navigate past the volume.

The way this reads to me is that \\?\server1\utilities\\ should be considered the "root" of \\?\server1\utilities\\filecomparer\.

Specifically, the documentation states

This seems to not be respected:

Reproduction Steps

> dotnet fsi

Microsoft (R) F# Interactive version 12.4.0.0 for F# 7.0
Copyright (c) Microsoft Corporation. All Rights Reserved.

For help type #help;;

> open System.IO;;
> Path.GetFullPath("""..\..""", """\\?\server1\utilities\\filecomparer\""");;
val it: string = "\\?\server1\"

Expected behavior

> Path.GetFullPath("""..\..""", """\\?\server1\utilities\\filecomparer\""");;
val it: string = "\\?\server1\utilities\\"

> Path.GetPathRoot("""\\?\server1\utilities\\filecomparer\""");;
val it: string = "\\?\server1\utilities\\"

> Path.GetDirectoryName("""\\?\server1\utilities\\""");;
val it: string = <null>

Actual behavior

> Path.GetFullPath("""..\..""", """\\?\server1\utilities\\filecomparer\""");;
val it: string = "\\?\server1\"

> Path.GetPathRoot("""\\?\server1\utilities\\filecomparer\""");;
val it: string = "\\?\server1\"

> Path.GetDirectoryName("""\\?\server1\utilities\\""");;
val it: string = "\\?\server1\utilities"

Regression?

I don't know how to test old .NET versions, I'm not a long-time .NET user.

Known Workarounds

No response

Configuration

> dotnet --version
7.0.100
> Get-ComputerInfo -Property "Os*"

OsName                                     : Microsoft Windows 10 Home
OsType                                     : WINNT
OsOperatingSystemSKU                       : WindowsHome
OsVersion                                  : 10.0.19045
OsArchitecture                                          : 64-bit
OsLanguage                                              : en-US

I would be pretty sure it's Windows (Path)-specific.

Other information

No response

danmoseley commented 11 months ago

FYI a good way to do a test on .NET Framework (ie to check old behavior) is http://sharplab.io (pick it from the drop-down)

s5bug commented 11 months ago

It seems sharplab is not a fan of System.IO.Path:

Unbreakable.AssemblyGuardException: Type System.IO.Path is not allowed.

Example Code

using System;
using System.IO;

Console.WriteLine(
  Path.Combine("\\\\?\\server1\\utilities\\\\filecomparer\\", "..\\..")
);
ghost commented 11 months ago

Tagging subscribers to this area: @dotnet/area-system-io See info in area-owners.md if you want to be subscribed.

Issue Details
### Description [File path formats on Windows systems](https://learn.microsoft.com/en-us/dotnet/standard/io/file-path-formats) states: > For device UNCs, the server/share portion forms the volume. For example, in `\\?\server1\utilities\\filecomparer\`, the server/share portion is `server1\utilities`. This is significant when calling a method such as [Path.GetFullPath(String, String)](https://learn.microsoft.com/en-us/dotnet/api/system.io.path.getfullpath#system-io-path-getfullpath(system-string-system-string)) with relative directory segments; it is never possible to navigate past the volume. The way this reads to me is that `\\?\server1\utilities\\` should be considered the "root" of `\\?\server1\utilities\\filecomparer\`. Specifically, the documentation states - "the server/share portion forms the volume" - "in [...] the server/share portion is `server1\utilities`" - "...it is never possible to navigate past the volume" This seems to not be respected: ### Reproduction Steps ```fs > dotnet fsi Microsoft (R) F# Interactive version 12.4.0.0 for F# 7.0 Copyright (c) Microsoft Corporation. All Rights Reserved. For help type #help;; > open System.IO;; > Path.GetFullPath("""..\..""", """\\?\server1\utilities\\filecomparer\""");; val it: string = "\\?\server1\" ``` ### Expected behavior ```fs > Path.GetFullPath("""..\..""", """\\?\server1\utilities\\filecomparer\""");; val it: string = "\\?\server1\utilities\\" > Path.GetPathRoot("""\\?\server1\utilities\\filecomparer\""");; val it: string = "\\?\server1\utilities\\" > Path.GetDirectoryName("""\\?\server1\utilities\\""");; val it: string = ``` ### Actual behavior ```fs > Path.GetFullPath("""..\..""", """\\?\server1\utilities\\filecomparer\""");; val it: string = "\\?\server1\" > Path.GetPathRoot("""\\?\server1\utilities\\filecomparer\""");; val it: string = "\\?\server1\" > Path.GetDirectoryName("""\\?\server1\utilities\\""");; val it: string = "\\?\server1\utilities" ``` ### Regression? I don't know how to test old .NET versions, I'm not a long-time .NET user. ### Known Workarounds _No response_ ### Configuration ``` > dotnet --version 7.0.100 ``` ``` > Get-ComputerInfo -Property "Os*" OsName : Microsoft Windows 10 Home OsType : WINNT OsOperatingSystemSKU : WindowsHome OsVersion : 10.0.19045 OsArchitecture : 64-bit OsLanguage : en-US ``` I would be pretty sure it's Windows (Path)-specific. ### Other information _No response_
Author: s5bug
Assignees: -
Labels: `area-System.IO`, `untriaged`
Milestone: -
huoyaoyuan commented 11 months ago

Path.GetPathRoot(@"\\?\server1\utilities\\filecomparer\") returns empty string on .NET Framework. Path.GetFullPath(path, basePath) isn't supported on .NET Framework. So this is likely not a regression.

s5bug commented 11 months ago

What about Path.Combine for .NET Framework, as shown in the example code?

jozkee commented 11 months ago

What about Path.Combine

Path.Combine doesn't resolve relative segments, what you are referring to is being discussed in https://github.com/dotnet/runtime/issues/2162.

ghost commented 11 months ago

Added needs-breaking-change-doc-created label because this issue has the breaking-change label.

  1. [ ] Create and link to this issue a matching issue in the dotnet/docs repo using the breaking change documentation template, then remove this needs-breaking-change-doc-created label.

Tagging @dotnet/compat for awareness of the breaking change.