Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,110 @@ public async Task MountDisksMountsTheExpectedPathOnWindowsWhenAMountPrefixIsProv
}
}

[Test]
public async Task MountDisksSkipsReservedPartitionsThatCannotBeMountedOnWindows()
{
this.mockFixture.Setup(PlatformID.Win32NT);

// A real, mountable data volume (has a volume index + drive letter) with no access path yet.
DiskVolume dataVolume = this.mockFixture.CreateDiskVolume(1, @"D:\", PlatformID.Win32NT, os: false, lun: 0);
dataVolume.AccessPaths = new List<string>();

// A reserved/hidden partition with no mountable identity.
DiskVolume reservedVolume = new DiskVolume(
index: null,
devicePath: string.Empty,
accessPaths: new List<string>(),
properties: new Dictionary<string, IConvertible>
{
{ "Type", "e3c9e316-0b5c-4db8-817d-f92df00215ae" },
{ "PartitionIndex", "1" },
{ "Hidden", "Yes" },
{ "Required", "No" }
});

Disk dataDisk = new Disk(
1,
@"\\.\PHYSICALDISK1",
new List<DiskVolume> { reservedVolume, dataVolume },
properties: new Dictionary<string, IConvertible> { { "Index", 1 } });

Disk osDisk = this.mockFixture.CreateDisk(0, PlatformID.Win32NT, os: true, @"\\.\PHYSICALDISK0", @"C:\");

this.mockFixture.DiskManager.Setup(mgr => mgr.GetDisksAsync(It.IsAny<CancellationToken>()))
.ReturnsAsync(new List<Disk> { osDisk, dataDisk });
this.mockFixture.File.Setup(f => f.Exists(It.IsAny<string>())).Returns(true);
this.mockFixture.Directory.Setup(d => d.Exists(It.IsAny<string>())).Returns(true);

using (MountDisks diskMounter = new MountDisks(this.mockFixture.Dependencies, this.mockFixture.Parameters))
{
await diskMounter.ExecuteAsync(CancellationToken.None);

// The reserved partition must never be handed to the disk manager for mounting.
this.mockFixture.DiskManager.Verify(
mgr => mgr.CreateMountPointAsync(reservedVolume, It.IsAny<string>(), It.IsAny<CancellationToken>()),
Times.Never);

// The real data volume must still be mounted exactly once.
string expectedMountPoint = this.mockFixture.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
dataVolume.GetDefaultMountPointName());

this.mockFixture.DiskManager.Verify(
mgr => mgr.CreateMountPointAsync(dataVolume, expectedMountPoint, It.IsAny<CancellationToken>()),
Times.Once);
}
}

[Test]
public async Task MountDisksSkipsPartitionsWithoutADevicePathOnUnix()
{
// On Unix, a mount requires a device path (e.g. /dev/sdc1). A partition without one
// has no mountable identity and must be skipped rather than passed to the disk manager.
this.mockFixture.Setup(PlatformID.Unix);

// A real, mountable data volume (has a device path) with no access path yet.
DiskVolume dataVolume = this.mockFixture.CreateDiskVolume(1, "/dev/sdc1", PlatformID.Unix, os: false, lun: 0);
dataVolume.AccessPaths = new List<string>();

// A partition with no device path (nothing to mount).
DiskVolume reservedVolume = new DiskVolume(
index: null,
devicePath: string.Empty,
accessPaths: new List<string>(),
properties: new Dictionary<string, IConvertible> { { "name", "reserved" } });

Disk dataDisk = new Disk(
1,
"/dev/sdc",
new List<DiskVolume> { reservedVolume, dataVolume },
properties: new Dictionary<string, IConvertible> { { "logicalname", "/dev/sdc" } });

Disk osDisk = this.mockFixture.CreateDisk(0, PlatformID.Unix, os: true, "/dev/sda", "/dev/sda1");

this.mockFixture.DiskManager.Setup(mgr => mgr.GetDisksAsync(It.IsAny<CancellationToken>()))
.ReturnsAsync(new List<Disk> { osDisk, dataDisk });
this.mockFixture.File.Setup(f => f.Exists(It.IsAny<string>())).Returns(true);
this.mockFixture.Directory.Setup(d => d.Exists(It.IsAny<string>())).Returns(true);

using (MountDisks diskMounter = new MountDisks(this.mockFixture.Dependencies, this.mockFixture.Parameters))
{
await diskMounter.ExecuteAsync(CancellationToken.None);

// The partition without a device path must never be handed to the disk manager.
this.mockFixture.DiskManager.Verify(
mgr => mgr.CreateMountPointAsync(reservedVolume, It.IsAny<string>(), It.IsAny<CancellationToken>()),
Times.Never);

// The real data volume must still be mounted exactly once.
string expectedMountPoint = $"/home/{Environment.UserName}/{dataVolume.GetDefaultMountPointName()}";

this.mockFixture.DiskManager.Verify(
mgr => mgr.CreateMountPointAsync(dataVolume, expectedMountPoint, It.IsAny<CancellationToken>()),
Times.Once);
}
}

private void SetupTest(PlatformID platformID, bool withMultipleVolumes = false)
{
this.diskVolumes = new List<DiskVolume>();
Expand Down
25 changes: 23 additions & 2 deletions src/VirtualClient/VirtualClient.Dependencies/MountDisks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -134,12 +134,12 @@ protected override async Task ExecuteAsync(EventContext telemetryContext, Cancel
// Don't mount any partition in OS drive.
foreach (Disk disk in disks.Where(d => !d.IsOperatingSystem()))
{
IEnumerable<DiskVolume> diskVolumes = disk.Volumes.Where(v => v.AccessPaths?.Any() != true);
IEnumerable<DiskVolume> diskVolumes = disk.Volumes.Where(v => v.AccessPaths?.Any() != true && this.IsMountable(v));

if (diskVolumes?.Any() == true)
{
// mount every volume that doesn't have an accessPath.
foreach (DiskVolume volume in disk.Volumes.Where(v => v.AccessPaths?.Any() != true))
foreach (DiskVolume volume in diskVolumes)
{
string newMountPoint = null;
string mountPointName = volume.GetDefaultMountPointName(prefix: mountPrefix);
Expand Down Expand Up @@ -216,6 +216,27 @@ await this.systemManager.SetFullPermissionsAsync(
return mountPointsCreated;
}

private bool IsMountable(DiskVolume volume)
{
// A mount point can only be assigned to a volume that has the identity its platform's
// mount operation requires. Reserved/metadata partitions (e.g. the Windows Microsoft
// Reserved Partition) have none of these and cannot be mounted, so they must be skipped.
switch (this.Platform)
{
case PlatformID.Win32NT:
// Windows mounts a volume by its volume index or drive letter.
return volume.Index != null
|| volume.Properties?.ContainsKey(Disk.WindowsDiskProperties.Letter) == true;

case PlatformID.Unix:
// Unix mounts a volume by its device path (e.g. /dev/sdc1).
return !string.IsNullOrWhiteSpace(volume.DevicePath);

default:
return true;
}
}

private IEnumerable<Disk> GetTargetDisks(IEnumerable<Disk> disks, string diskFilter)
{
List<Disk> filteredDisks = new List<Disk>();
Expand Down
Loading