-
Notifications
You must be signed in to change notification settings - Fork 4.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
1d34c1d
commit c5676d7
Showing
5 changed files
with
136 additions
and
145 deletions.
There are no files selected for viewing
218 changes: 103 additions & 115 deletions
218
src/libraries/Common/tests/System/IO/VirtualDriveHelper.Windows.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,160 +1,148 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Diagnostics; | ||
using System.IO; | ||
using System.Linq; | ||
using System.Runtime.Versioning; | ||
|
||
public class VirtualDriveHelper : IDisposable | ||
namespace System.IO.FileSystem | ||
{ | ||
// Temporary Windows directory that can be mounted to a drive letter using the subst command | ||
private string? _virtualDriveTargetDir = null; | ||
// Windows drive letter that points to a mounted directory using the subst command | ||
private char _virtualDriveLetter = default; | ||
|
||
/// <summary> | ||
/// If there is a SUBST'ed drive, Dispose unmounts it to free the drive letter. | ||
/// </summary> | ||
public void Dispose() | ||
// Adds test helper APIs to manipulate Windows virtual drives via SUBST. | ||
[SupportedOSPlatform("windows")] | ||
public class VirtualDriveHelper : IDisposable | ||
{ | ||
try | ||
// Temporary Windows directory that can be mounted to a drive letter using the subst command | ||
private string? _virtualDriveTargetDir = null; | ||
// Windows drive letter that points to a mounted directory using the subst command | ||
private char _virtualDriveLetter = default; | ||
|
||
/// <summary> | ||
/// If there is a SUBST'ed drive, Dispose unmounts it to free the drive letter. | ||
/// </summary> | ||
public void Dispose() | ||
{ | ||
if (VirtualDriveLetter != default) | ||
try | ||
{ | ||
DeleteVirtualDrive(VirtualDriveLetter); | ||
Directory.Delete(VirtualDriveTargetDir, recursive: true); | ||
if (VirtualDriveLetter != default) | ||
{ | ||
DeleteVirtualDrive(VirtualDriveLetter); | ||
Directory.Delete(VirtualDriveTargetDir, recursive: true); | ||
} | ||
} | ||
catch { } // avoid exceptions on dispose | ||
} | ||
catch { } // avoid exceptions on dispose | ||
} | ||
|
||
/// <summary> | ||
/// Returns the path of a folder that is to be mounted using SUBST. | ||
/// </summary> | ||
public string VirtualDriveTargetDir | ||
{ | ||
get | ||
/// <summary> | ||
/// Returns the path of a folder that is to be mounted using SUBST. | ||
/// </summary> | ||
public string VirtualDriveTargetDir | ||
{ | ||
if (_virtualDriveTargetDir == null) | ||
get | ||
{ | ||
// Create a folder inside the temp directory so that it can be mounted to a drive letter with subst | ||
_virtualDriveTargetDir = Path.Join(Path.GetTempPath(), Path.GetRandomFileName()); | ||
Directory.CreateDirectory(_virtualDriveTargetDir); | ||
if (_virtualDriveTargetDir == null) | ||
{ | ||
// Create a folder inside the temp directory so that it can be mounted to a drive letter with subst | ||
_virtualDriveTargetDir = Path.Join(Path.GetTempPath(), Path.GetRandomFileName()); | ||
Directory.CreateDirectory(_virtualDriveTargetDir); | ||
} | ||
|
||
return _virtualDriveTargetDir; | ||
} | ||
|
||
return _virtualDriveTargetDir; | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Returns the drive letter of a drive letter that represents a mounted folder using SUBST. | ||
/// </summary> | ||
public char VirtualDriveLetter | ||
{ | ||
get | ||
/// <summary> | ||
/// Returns the drive letter of a drive letter that represents a mounted folder using SUBST. | ||
/// </summary> | ||
public char VirtualDriveLetter | ||
{ | ||
if (_virtualDriveLetter == default) | ||
get | ||
{ | ||
// Mount the folder to a drive letter | ||
_virtualDriveLetter = CreateVirtualDrive(VirtualDriveTargetDir); | ||
if (_virtualDriveLetter == default) | ||
{ | ||
// Mount the folder to a drive letter | ||
_virtualDriveLetter = CreateVirtualDrive(VirtualDriveTargetDir); | ||
} | ||
return _virtualDriveLetter; | ||
} | ||
return _virtualDriveLetter; | ||
} | ||
} | ||
|
||
///<summary> | ||
/// On Windows, mounts a folder to an assigned virtual drive letter using the subst command. | ||
/// subst is not available in Windows Nano. | ||
/// </summary> | ||
public char CreateVirtualDrive(string targetDir) | ||
{ | ||
if (!OperatingSystem.IsWindows()) | ||
///<summary> | ||
/// On Windows, mounts a folder to an assigned virtual drive letter using the subst command. | ||
/// subst is not available in Windows Nano. | ||
/// </summary> | ||
private static char CreateVirtualDrive(string targetDir) | ||
{ | ||
throw new PlatformNotSupportedException(); | ||
} | ||
char driveLetter = GetNextAvailableDriveLetter(); | ||
bool success = RunProcess(CreateProcessStartInfo("cmd", "/c", SubstPath, $"{driveLetter}:", targetDir)); | ||
if (!success || !DriveInfo.GetDrives().Any(x => x.Name[0] == driveLetter)) | ||
{ | ||
throw new InvalidOperationException($"Could not create virtual drive {driveLetter}: with subst"); | ||
} | ||
return driveLetter; | ||
|
||
char driveLetter = GetNextAvailableDriveLetter(); | ||
bool success = RunProcess(CreateProcessStartInfo("cmd", "/c", SubstPath, $"{driveLetter}:", targetDir)); | ||
if (!success || !DriveInfo.GetDrives().Any(x => x.Name[0] == driveLetter)) | ||
{ | ||
throw new InvalidOperationException($"Could not create virtual drive {driveLetter}: with subst"); | ||
} | ||
return driveLetter; | ||
// Finds the next unused drive letter and returns it. | ||
char GetNextAvailableDriveLetter() | ||
{ | ||
List<char> existingDrives = DriveInfo.GetDrives().Select(x => x.Name[0]).ToList(); | ||
|
||
// Finds the next unused drive letter and returns it. | ||
char GetNextAvailableDriveLetter() | ||
{ | ||
List<char> existingDrives = DriveInfo.GetDrives().Select(x => x.Name[0]).ToList(); | ||
// A,B are reserved, C is usually reserved | ||
IEnumerable<int> range = Enumerable.Range('D', 'Z' - 'D'); | ||
IEnumerable<char> castRange = range.Select(x => Convert.ToChar(x)); | ||
IEnumerable<char> allDrivesLetters = castRange.Except(existingDrives); | ||
|
||
// A,B are reserved, C is usually reserved | ||
IEnumerable<int> range = Enumerable.Range('D', 'Z' - 'D'); | ||
IEnumerable<char> castRange = range.Select(x => Convert.ToChar(x)); | ||
IEnumerable<char> allDrivesLetters = castRange.Except(existingDrives); | ||
if (!allDrivesLetters.Any()) | ||
{ | ||
throw new ArgumentOutOfRangeException("No drive letters available"); | ||
} | ||
|
||
if (!allDrivesLetters.Any()) | ||
{ | ||
throw new ArgumentOutOfRangeException("No drive letters available"); | ||
return allDrivesLetters.First(); | ||
} | ||
|
||
return allDrivesLetters.First(); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// On Windows, unassigns the specified virtual drive letter from its mounted folder. | ||
/// </summary> | ||
public void DeleteVirtualDrive(char driveLetter) | ||
{ | ||
if (!OperatingSystem.IsWindows()) | ||
/// <summary> | ||
/// On Windows, unassigns the specified virtual drive letter from its mounted folder. | ||
/// </summary> | ||
private static void DeleteVirtualDrive(char driveLetter) | ||
{ | ||
throw new PlatformNotSupportedException(); | ||
bool success = RunProcess(CreateProcessStartInfo("cmd", "/c", SubstPath, "/d", $"{driveLetter}:")); | ||
if (!success || DriveInfo.GetDrives().Any(x => x.Name[0] == driveLetter)) | ||
{ | ||
throw new InvalidOperationException($"Could not delete virtual drive {driveLetter}: with subst"); | ||
} | ||
} | ||
|
||
bool success = RunProcess(CreateProcessStartInfo("cmd", "/c", SubstPath, "/d", $"{driveLetter}:")); | ||
if (!success || DriveInfo.GetDrives().Any(x => x.Name[0] == driveLetter)) | ||
private static ProcessStartInfo CreateProcessStartInfo(string fileName, params string[] arguments) | ||
{ | ||
throw new InvalidOperationException($"Could not delete virtual drive {driveLetter}: with subst"); | ||
} | ||
} | ||
var info = new ProcessStartInfo | ||
{ | ||
FileName = fileName, | ||
UseShellExecute = false, | ||
RedirectStandardOutput = true | ||
}; | ||
|
||
private static ProcessStartInfo CreateProcessStartInfo(string fileName, params string[] arguments) | ||
{ | ||
var info = new ProcessStartInfo | ||
{ | ||
FileName = fileName, | ||
UseShellExecute = false, | ||
RedirectStandardOutput = true | ||
}; | ||
foreach (var argument in arguments) | ||
{ | ||
info.ArgumentList.Add(argument); | ||
} | ||
|
||
foreach (var argument in arguments) | ||
{ | ||
info.ArgumentList.Add(argument); | ||
return info; | ||
} | ||
|
||
return info; | ||
} | ||
|
||
private static bool RunProcess(ProcessStartInfo startInfo) | ||
{ | ||
var process = Process.Start(startInfo); | ||
process.WaitForExit(); | ||
return process.ExitCode == 0; | ||
} | ||
private static bool RunProcess(ProcessStartInfo startInfo) | ||
{ | ||
using var process = Process.Start(startInfo); | ||
process.WaitForExit(); | ||
return process.ExitCode == 0; | ||
} | ||
|
||
private string SubstPath | ||
{ | ||
get | ||
private static string SubstPath | ||
{ | ||
if (!OperatingSystem.IsWindows()) | ||
get | ||
{ | ||
throw new PlatformNotSupportedException(); | ||
string systemRoot = Environment.GetEnvironmentVariable("SystemRoot") ?? @"C:\Windows"; | ||
return Path.Join(systemRoot, "System32", "subst.exe"); | ||
} | ||
|
||
string systemRoot = Environment.GetEnvironmentVariable("SystemRoot") ?? @"C:\Windows"; | ||
string system32 = Path.Join(systemRoot, "System32"); | ||
return Path.Join(system32, "subst.exe"); | ||
} | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.