1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 
5 using System.Reflection;
6 using System.Runtime.InteropServices;
7 
8 namespace System.IO
9 {
10     public static class PathFeatures
11     {
12         private enum State
13         {
14             Uninitialized,
15             True,
16             False
17         }
18 
19         // Note that this class is using APIs that allow it to run on all platforms (including Core 5.0)
20         // That is why we have .GetTypeInfo(), don't use the Registry, etc...
21 
22         private static State s_osEnabled;
23         private static State s_onCore;
24 
25         /// <summary>
26         /// Returns true if you can use long paths, including long DOS style paths (e.g. over 260 without \\?\).
27         /// </summary>
AreAllLongPathsAvailable()28         public static bool AreAllLongPathsAvailable()
29         {
30             // We have support built-in for all platforms in Core
31             if (RunningOnCoreLib)
32                 return true;
33 
34             // Otherwise we're running on Windows, see if we've got the capability in .NET, and that the feature is enabled in the OS
35             return !AreLongPathsBlocked() && AreOsLongPathsEnabled();
36         }
37 
IsUsingLegacyPathNormalization()38         public static bool IsUsingLegacyPathNormalization()
39         {
40             return HasLegacyIoBehavior("UseLegacyPathHandling");
41         }
42 
43         /// <summary>
44         /// Returns true if > MAX_PATH (260) character paths are blocked.
45         /// Note that this doesn't reflect that you can actually use long paths without device syntax when on Windows.
46         /// Use AreAllLongPathsAvailable() to see that you can use long DOS style paths if on Windows.
47         /// </summary>
AreLongPathsBlocked()48         public static bool AreLongPathsBlocked()
49         {
50             return HasLegacyIoBehavior("BlockLongPaths");
51         }
52 
HasLegacyIoBehavior(string propertyName)53         private static bool HasLegacyIoBehavior(string propertyName)
54         {
55             // Core doesn't have legacy behaviors
56             if (RunningOnCoreLib)
57                 return false;
58 
59             Type t = typeof(object).GetTypeInfo().Assembly.GetType("System.AppContextSwitches");
60             var p = t.GetProperty(propertyName, BindingFlags.Static | BindingFlags.Public);
61 
62             // If the switch actually exists use it, otherwise we predate the switch and are effectively on
63             return (bool)(p?.GetValue(null) ?? true);
64         }
65 
66         private static bool RunningOnCoreLib
67         {
68             get
69             {
70                 // Not particularly elegant
71                 if (s_onCore == State.Uninitialized)
72                     s_onCore = typeof(object).GetTypeInfo().Assembly.GetName().Name == "System.Private.CoreLib" ? State.True : State.False;
73 
74                 return s_onCore == State.True;
75             }
76         }
77 
AreOsLongPathsEnabled()78         private static bool AreOsLongPathsEnabled()
79         {
80             if (s_osEnabled == State.Uninitialized)
81             {
82                 // No official way to check yet this is good enough for tests
83                 try
84                 {
85                     s_osEnabled = RtlAreLongPathsEnabled() ? State.True : State.False;
86                 }
87                 catch
88                 {
89                     s_osEnabled = State.False;
90                 }
91             }
92 
93             return s_osEnabled == State.True;
94         }
95 
96         [DllImport("ntdll", ExactSpelling = true)]
RtlAreLongPathsEnabled()97         private static extern bool RtlAreLongPathsEnabled();
98     }
99 }
100