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;
6 using System.Collections.Generic;
7 using System.IO;
8 using System.Linq;
9 using System.Runtime.InteropServices;
10 using System.Text;
11 using System.Threading.Tasks;
12 using Xunit;
13 
14 internal class IOServices
15 {
GetReadyDrives()16     public static IEnumerable<string> GetReadyDrives()
17     {
18         foreach (string drive in GetLogicalDrives())
19         {
20             if (IsReady(drive))
21                 yield return drive;
22         }
23     }
24 
GetNotReadyDrive()25     public static string GetNotReadyDrive()
26     {
27         string[] drives = GetLogicalDrives();
28         foreach (string drive in drives)
29         {
30             if (!IsReady(drive))
31                 return drive;
32         }
33 
34         return null;
35     }
36 
GetNonExistentDrive()37     public static string GetNonExistentDrive()
38     {
39         string[] availableDrives = GetLogicalDrives();
40 
41         for (char drive = 'A'; drive <= 'Z'; drive++)
42         {
43             if (!availableDrives.Contains(drive + @":\"))
44                 return drive + @":\";
45         }
46 
47         return null;
48     }
49 
GetNtfsDriveOtherThanCurrent()50     public static string GetNtfsDriveOtherThanCurrent()
51     {
52         return GetNtfsDriveOtherThan(GetCurrentDrive());
53     }
54 
GetNtfsDriveOtherThan(string drive)55     public static string GetNtfsDriveOtherThan(string drive)
56     {
57         foreach (string otherDrive in GetLogicalDrives())
58         {
59             if (string.Equals(drive, otherDrive, StringComparison.OrdinalIgnoreCase))
60                 continue;
61 
62             if (!IsFixed(otherDrive))
63                 continue;
64 
65             if (!IsReady(otherDrive))
66                 continue;
67 
68             if (IsDriveNTFS(otherDrive))
69                 return otherDrive;
70         }
71 
72         return null;
73     }
74 
GetNonNtfsDriveOtherThanCurrent()75     public static string GetNonNtfsDriveOtherThanCurrent()
76     {
77         return GetNonNtfsDriveOtherThan(GetCurrentDrive());
78     }
79 
GetNonNtfsDriveOtherThan(string drive)80     public static string GetNonNtfsDriveOtherThan(string drive)
81     {
82         foreach (string otherDrive in GetLogicalDrives())
83         {
84             if (string.Equals(drive, otherDrive, StringComparison.OrdinalIgnoreCase))
85                 continue;
86 
87             if (!IsReady(otherDrive))
88                 continue;
89 
90             if (!IsDriveNTFS(otherDrive))
91                 return otherDrive;
92         }
93 
94         return null;
95     }
96 
GetPath(string rootPath, int characterCount, bool extended)97     public static string GetPath(string rootPath, int characterCount, bool extended)
98     {
99         if (extended)
100             rootPath = IOInputs.ExtendedPrefix + rootPath;
101         return GetPath(rootPath, characterCount);
102     }
103 
GetPath(string rootPath, int characterCount)104     public static string GetPath(string rootPath, int characterCount)
105     {
106         rootPath = rootPath.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
107 
108         StringBuilder path = new StringBuilder(characterCount);
109         path.Append(rootPath);
110 
111         while (path.Length < characterCount)
112         {
113             // Add directory seperator after each dir but not at the end of the path
114             path.Append(Path.DirectorySeparatorChar);
115 
116             // Continue adding unique path segments until the character count is hit
117             int remainingChars = characterCount - path.Length;
118             string guid = Guid.NewGuid().ToString("N"); // No dashes
119             if (remainingChars < guid.Length)
120             {
121                 path.Append(guid.Substring(0, remainingChars));
122             }
123             else
124             {
125                 // Long paths can be over 32K characters. Given that a guid is just 36 chars, this
126                 // can lead to crazy 800+ recursive call depths. We'll create large segments to
127                 // make tests more manageable.
128 
129                 path.Append(guid);
130                 remainingChars = characterCount - path.Length;
131                 path.Append('g', Math.Min(remainingChars, 200));
132             }
133 
134             if (path.Length + 1 == characterCount)
135             {
136                 // If only one character is missing add a k!
137                 path.Append('k');
138             }
139         }
140 
141         Assert.Equal(path.Length, characterCount);
142 
143         return path.ToString();
144     }
145 
CreateDirectories(string rootPath, params string[] names)146     public static IEnumerable<string> CreateDirectories(string rootPath, params string[] names)
147     {
148         List<string> paths = new List<string>();
149 
150         foreach (string name in names)
151         {
152             string path = Path.Combine(rootPath, name);
153 
154             Directory.CreateDirectory(path);
155 
156             paths.Add(path);
157         }
158 
159         return paths;
160     }
161 
CreateFiles(string rootPath, params string[] names)162     public static IEnumerable<string> CreateFiles(string rootPath, params string[] names)
163     {
164         List<string> paths = new List<string>();
165 
166         foreach (string name in names)
167         {
168             string path = Path.Combine(rootPath, name);
169 
170             FileStream stream = File.Create(path);
171             stream.Dispose();
172 
173             paths.Add(path);
174         }
175 
176         return paths;
177     }
178 
AddTrailingSlashIfNeeded(string path)179     public static string AddTrailingSlashIfNeeded(string path)
180     {
181         if (path.Length > 0 && path[path.Length - 1] != Path.DirectorySeparatorChar && path[path.Length - 1] != Path.AltDirectorySeparatorChar)
182         {
183             path = path + Path.DirectorySeparatorChar;
184         }
185 
186         return path;
187     }
188 
RemoveTrailingSlash(string path)189     public static string RemoveTrailingSlash(string path)
190     {
191         return path.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
192     }
193 
GetLogicalDrives()194     private static string[] GetLogicalDrives()
195     {   // From .NET Framework's Directory.GetLogicalDrives
196         int drives = DllImports.GetLogicalDrives();
197         if (drives == 0)
198             throw new InvalidOperationException();
199 
200         uint d = (uint)drives;
201         int count = 0;
202         while (d != 0)
203         {
204             if (((int)d & 1) != 0) count++;
205             d >>= 1;
206         }
207         string[] result = new string[count];
208         char[] root = new char[] { 'A', ':', '\\' };
209         d = (uint)drives;
210         count = 0;
211         while (d != 0)
212         {
213             if (((int)d & 1) != 0)
214             {
215                 result[count++] = new string(root);
216             }
217             d >>= 1;
218             root[0]++;
219         }
220 
221         return result;
222     }
223 
GetCurrentDrive()224     public static string GetCurrentDrive()
225     {
226         return Path.GetPathRoot(Directory.GetCurrentDirectory());
227     }
228 
IsDriveNTFS(string drive)229     public static bool IsDriveNTFS(string drive)
230     {
231         if (PlatformDetection.IsInAppContainer)
232         {
233             // we cannot determine filesystem so assume NTFS
234             return true;
235         }
236 
237         var di = new DriveInfo(drive);
238 
239         return string.Equals(di.DriveFormat, "NTFS", StringComparison.OrdinalIgnoreCase);
240     }
241 
GetAvailableFreeBytes(string drive)242     public static long GetAvailableFreeBytes(string drive)
243     {
244         long ignored;
245         long userBytes;
246         if (!DllImports.GetDiskFreeSpaceEx(drive, out userBytes, out ignored, out ignored))
247         {
248             throw new IOException("DriveName: " + drive + " ErrorCode:" + Marshal.GetLastWin32Error());
249         }
250 
251         return userBytes;
252     }
253 
IsReady(string drive)254     private static bool IsReady(string drive)
255     {
256         const int ERROR_NOT_READY = 0x00000015;
257 
258         long ignored;
259         if (!DllImports.GetDiskFreeSpaceEx(drive, out ignored, out ignored, out ignored))
260         {
261             return Marshal.GetLastWin32Error() != ERROR_NOT_READY;
262         }
263 
264         return true;
265     }
266 
IsFixed(string drive)267     private static bool IsFixed(string drive)
268     {
269         return DllImports.GetDriveType(drive) == 3;
270     }
271 }
272 
273