windows-development-sandbox 1.13.2 → 1.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app/features/storage/storage.run.js +1 -1
- package/dist/bin/agent-sandbox.js +2 -2
- package/dist/bin/{chunk-VENWSGX2.js → chunk-BQ23GBLC.js} +411 -58
- package/dist/bin/{chunk-K4SU3MHJ.js → chunk-F7IMME5G.js} +1 -1
- package/dist/bin/{chunk-IL7BXJF2.js → chunk-LQHEUN2A.js} +1 -1
- package/dist/bin/claude-host.js +2 -2
- package/dist/bin/claude-sbx.js +2 -2
- package/dist/bin/codex-sbx.js +2 -2
- package/dist/bin/gemini-sbx.js +2 -2
- package/dist/bin/sandbox.js +2 -2
- package/dist/bin/sbx-hide.js +2 -2
- package/dist/bin/sbx-show.js +2 -2
- package/dist/cli/commands/launch/launch.js +1 -1
- package/dist/cli/commands/repair/checks/contained.js +1 -1
- package/dist/guest/daemon/sessions.d.ts +7 -0
- package/dist/guest/daemon/sessions.js +23 -2
- package/dist/guest/entries/daemon.js +1 -0
- package/dist/guest/sessions/pty-host.d.ts +10 -0
- package/dist/guest/sessions/pty-host.js +48 -3
- package/dist/host/channel/channel.js +1 -1
- package/dist/host/install/install-record.d.ts +1 -0
- package/dist/host/install/install-record.js +2 -0
- package/dist/host/sandbox/contained-deps.d.ts +2 -1
- package/dist/host/sandbox/contained-deps.js +6 -4
- package/dist/host/sharing/lifecycle.js +9 -1
- package/dist/host/storage/contained/contained-storage.js +13 -0
- package/dist/host/storage/contained/disk-port.d.ts +5 -0
- package/dist/host/storage/contained/helper-port.d.ts +72 -0
- package/dist/host/storage/contained/helper-port.js +162 -0
- package/dist/host/storage/contained/index.d.ts +1 -0
- package/dist/host/storage/contained/index.js +1 -0
- package/dist/host/web/bridge.d.ts +5 -0
- package/dist/host/web/bridge.js +11 -1
- package/dist/host/web/server.d.ts +2 -0
- package/dist/host/web/server.js +11 -1
- package/dist/runtime/agent.js +9 -0
- package/dist/runtime/daemon.js +57 -3
- package/dist/runtime/db.js +9 -0
- package/dist/runtime/main.js +195 -12
- package/dist/runtime/netproxy.js +9 -0
- package/dist/runtime/view.js +195 -12
- package/dist/selftest/channel.test.js +72 -1
- package/dist/selftest/contained.test.js +1 -37
- package/dist/selftest/hcs.test.js +5 -2
- package/dist/selftest/index.js +2 -0
- package/dist/selftest/install.test.js +11 -0
- package/dist/selftest/live-daemon.test.js +51 -1
- package/dist/selftest/live-terminal.test.js +112 -4
- package/dist/selftest/storage-helper.test.d.ts +2 -0
- package/dist/selftest/storage-helper.test.js +493 -0
- package/dist/selftest/support/vhdx.d.ts +14 -0
- package/dist/selftest/support/vhdx.js +72 -0
- package/dist/selftest/web.test.js +14 -2
- package/dist/shared/channel/run-dir.d.ts +6 -0
- package/dist/shared/channel/run-dir.js +9 -0
- package/dist/shared/pty/index.d.ts +3 -1
- package/dist/shared/pty/index.js +3 -1
- package/dist/shared/pty/key-gate.d.ts +24 -0
- package/dist/shared/pty/key-gate.js +45 -0
- package/dist/shared/pty/mouse-modes.d.ts +27 -0
- package/dist/shared/pty/mouse-modes.js +155 -0
- package/dist/shared/pty/terminal.d.ts +0 -5
- package/dist/shared/pty/terminal.js +51 -20
- package/dist/sync/watcher.js +1 -1
- package/dist/web/index.html +1 -1
- package/dist/web/{main-NBQ3LF4E.js → main-6XNBTJLT.js} +33 -33
- package/native/csharp/HcsHost/AssemblyInfo.cs +3 -3
- package/native/csharp/HostTools/AssemblyInfo.cs +3 -3
- package/native/csharp/StorageHelper/AssemblyInfo.cs +15 -0
- package/native/csharp/StorageHelper/Client.cs +297 -0
- package/native/csharp/StorageHelper/Log.cs +177 -0
- package/native/csharp/StorageHelper/MountRecords.cs +160 -0
- package/native/csharp/StorageHelper/Operations.cs +596 -0
- package/native/csharp/StorageHelper/Pins.cs +270 -0
- package/native/csharp/StorageHelper/Privileges.cs +78 -0
- package/native/csharp/StorageHelper/Program.cs +349 -0
- package/native/csharp/StorageHelper/Protocol.cs +107 -0
- package/native/csharp/StorageHelper/RateLimiter.cs +97 -0
- package/native/csharp/StorageHelper/Rules.cs +156 -0
- package/native/csharp/StorageHelper/Server.cs +656 -0
- package/native/csharp/StorageHelper/Settings.cs +297 -0
- package/native/csharp/StorageHelper/StorageService.cs +53 -0
- package/native/csharp/StorageHelper/UserSettings.cs +206 -0
- package/native/csharp/StorageHelper/Vhdx.cs +230 -0
- package/native/csharp/StorageHelper/VirtualDisk.cs +241 -0
- package/native/csharp/StorageHelper/Volumes.cs +379 -0
- package/native/csharp/StorageHelper/Watchdog.cs +128 -0
- package/native/csharp/StorageHelper/Win32.cs +250 -0
- package/native/scripts/set-storage-helper.ps1 +203 -0
- package/package.json +1 -1
- package/sandbox/csharp/PtyHost.cs +236 -1
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
// Reads the VHDX header of an image through a stream the caller opened (the layout is in the VHDX specification), the
|
|
2
|
+
// same reading as Read-VhdxInfo in native\scripts\contained-common.ps1: is it a VHDX, how big may it grow, does it have
|
|
3
|
+
// a parent (a differencing disk would reach into another file)? It also reads the current header (the valid one with
|
|
4
|
+
// the higher sequence number, checksum CRC-32C) to see whether it names a log the disk driver would replay.
|
|
5
|
+
using System;
|
|
6
|
+
using System.IO;
|
|
7
|
+
using System.Text;
|
|
8
|
+
|
|
9
|
+
namespace AgentSandbox.StorageHelper
|
|
10
|
+
{
|
|
11
|
+
public sealed class VhdxInfo
|
|
12
|
+
{
|
|
13
|
+
public VhdxInfo(ulong virtualSize, bool hasParent, bool hasLog)
|
|
14
|
+
{
|
|
15
|
+
VirtualSize = virtualSize;
|
|
16
|
+
HasParent = hasParent;
|
|
17
|
+
HasLog = hasLog;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
public ulong VirtualSize { get; private set; }
|
|
21
|
+
public bool HasParent { get; private set; }
|
|
22
|
+
|
|
23
|
+
/// <summary>The current header names a log to replay (the image was not closed cleanly, or the log was written by hand).</summary>
|
|
24
|
+
public bool HasLog { get; private set; }
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
public static class Vhdx
|
|
28
|
+
{
|
|
29
|
+
private const long RegionTableOffset = 196608;
|
|
30
|
+
private const long Header1Offset = 65536;
|
|
31
|
+
private const long Header2Offset = 131072;
|
|
32
|
+
private const int HeaderBytes = 4096;
|
|
33
|
+
private const int TableBytes = 65536;
|
|
34
|
+
private const int MaxRegions = 64;
|
|
35
|
+
private const int MaxMetadataItems = 2047;
|
|
36
|
+
private static readonly Guid MetadataRegion = new Guid("8b7ca206-4790-4b9a-b8fe-575f050f886e");
|
|
37
|
+
private static readonly Guid VirtualSizeItem = new Guid("2fa54224-cd1b-4876-b211-5dbed83bf4b8");
|
|
38
|
+
private static readonly Guid ParentItem = new Guid("a8d35f2d-b30b-454d-abf7-d3d84834ab0c");
|
|
39
|
+
|
|
40
|
+
public static VhdxInfo Read(Stream stream)
|
|
41
|
+
{
|
|
42
|
+
if (Encoding.ASCII.GetString(ReadExact(stream, 0, 8)) != "vhdxfile")
|
|
43
|
+
{
|
|
44
|
+
throw new RefusedException("The file is not a VHDX image.");
|
|
45
|
+
}
|
|
46
|
+
byte[] table = ReadExact(stream, RegionTableOffset, TableBytes);
|
|
47
|
+
if (Encoding.ASCII.GetString(table, 0, 4) != "regi")
|
|
48
|
+
{
|
|
49
|
+
throw new RefusedException("The image has no region table.");
|
|
50
|
+
}
|
|
51
|
+
uint count = BitConverter.ToUInt32(table, 8);
|
|
52
|
+
if (count > MaxRegions)
|
|
53
|
+
{
|
|
54
|
+
throw new RefusedException("The image has too many regions.");
|
|
55
|
+
}
|
|
56
|
+
long metadataOffset = -1;
|
|
57
|
+
for (int i = 0; i < count; i++)
|
|
58
|
+
{
|
|
59
|
+
int at = 16 + 32 * i;
|
|
60
|
+
if (GuidAt(table, at) == MetadataRegion)
|
|
61
|
+
{
|
|
62
|
+
ulong offset = BitConverter.ToUInt64(table, at + 16);
|
|
63
|
+
if (offset > long.MaxValue)
|
|
64
|
+
{
|
|
65
|
+
throw new RefusedException("The image is not a valid VHDX file (a part is missing).");
|
|
66
|
+
}
|
|
67
|
+
metadataOffset = (long)offset;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (metadataOffset < 0)
|
|
71
|
+
{
|
|
72
|
+
throw new RefusedException("The image has no metadata region.");
|
|
73
|
+
}
|
|
74
|
+
byte[] meta = ReadExact(stream, metadataOffset, TableBytes);
|
|
75
|
+
if (Encoding.ASCII.GetString(meta, 0, 8) != "metadata")
|
|
76
|
+
{
|
|
77
|
+
throw new RefusedException("The image has no metadata table.");
|
|
78
|
+
}
|
|
79
|
+
int items = BitConverter.ToUInt16(meta, 10);
|
|
80
|
+
if (items > MaxMetadataItems)
|
|
81
|
+
{
|
|
82
|
+
throw new RefusedException("The image has too many metadata items.");
|
|
83
|
+
}
|
|
84
|
+
ulong size = 0;
|
|
85
|
+
bool hasParent = false;
|
|
86
|
+
for (int i = 0; i < items; i++)
|
|
87
|
+
{
|
|
88
|
+
int at = 32 + 32 * i;
|
|
89
|
+
Guid id = GuidAt(meta, at);
|
|
90
|
+
if (id == ParentItem)
|
|
91
|
+
{
|
|
92
|
+
hasParent = true;
|
|
93
|
+
}
|
|
94
|
+
if (id == VirtualSizeItem)
|
|
95
|
+
{
|
|
96
|
+
long itemOffset = BitConverter.ToUInt32(meta, at + 16);
|
|
97
|
+
size = BitConverter.ToUInt64(ReadExact(stream, metadataOffset + itemOffset, 8), 0);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return new VhdxInfo(size, hasParent, CurrentHeaderHasLog(stream));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private static bool CurrentHeaderHasLog(Stream stream)
|
|
104
|
+
{
|
|
105
|
+
byte[] best = null;
|
|
106
|
+
ulong bestSequence = 0;
|
|
107
|
+
foreach (long offset in new long[] { Header1Offset, Header2Offset })
|
|
108
|
+
{
|
|
109
|
+
byte[] header = ReadExact(stream, offset, HeaderBytes);
|
|
110
|
+
if (Encoding.ASCII.GetString(header, 0, 4) != "head" || !ChecksumOk(header))
|
|
111
|
+
{
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
ulong sequence = BitConverter.ToUInt64(header, 8);
|
|
115
|
+
if (best == null || sequence > bestSequence)
|
|
116
|
+
{
|
|
117
|
+
best = header;
|
|
118
|
+
bestSequence = sequence;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (best == null)
|
|
122
|
+
{
|
|
123
|
+
throw new RefusedException("The image has no valid header.");
|
|
124
|
+
}
|
|
125
|
+
for (int i = 48; i < 64; i++)
|
|
126
|
+
{
|
|
127
|
+
if (best[i] != 0)
|
|
128
|
+
{
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
private static bool ChecksumOk(byte[] header)
|
|
136
|
+
{
|
|
137
|
+
uint stored = BitConverter.ToUInt32(header, 4);
|
|
138
|
+
byte[] copy = (byte[])header.Clone();
|
|
139
|
+
copy[4] = 0;
|
|
140
|
+
copy[5] = 0;
|
|
141
|
+
copy[6] = 0;
|
|
142
|
+
copy[7] = 0;
|
|
143
|
+
return Crc32C(copy) == stored;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
private static uint[] crcTable;
|
|
147
|
+
|
|
148
|
+
public static uint Crc32C(byte[] data)
|
|
149
|
+
{
|
|
150
|
+
uint[] table = crcTable;
|
|
151
|
+
if (table == null)
|
|
152
|
+
{
|
|
153
|
+
table = new uint[256];
|
|
154
|
+
for (uint i = 0; i < 256; i++)
|
|
155
|
+
{
|
|
156
|
+
uint c = i;
|
|
157
|
+
for (int k = 0; k < 8; k++)
|
|
158
|
+
{
|
|
159
|
+
c = (c & 1) != 0 ? 0x82F63B78u ^ (c >> 1) : c >> 1;
|
|
160
|
+
}
|
|
161
|
+
table[i] = c;
|
|
162
|
+
}
|
|
163
|
+
crcTable = table;
|
|
164
|
+
}
|
|
165
|
+
uint crc = 0xFFFFFFFFu;
|
|
166
|
+
foreach (byte b in data)
|
|
167
|
+
{
|
|
168
|
+
crc = table[(crc ^ b) & 0xFF] ^ (crc >> 8);
|
|
169
|
+
}
|
|
170
|
+
return crc ^ 0xFFFFFFFFu;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/// <summary>Refuses a differencing disk, an unreadable size and one bigger than the limit.</summary>
|
|
174
|
+
public static void CheckPlain(VhdxInfo info, long maxGb)
|
|
175
|
+
{
|
|
176
|
+
if (info.HasParent)
|
|
177
|
+
{
|
|
178
|
+
throw new RefusedException("The image is a differencing disk (it has a parent file); refusing it.");
|
|
179
|
+
}
|
|
180
|
+
if (info.HasLog)
|
|
181
|
+
{
|
|
182
|
+
throw new LogPendingException();
|
|
183
|
+
}
|
|
184
|
+
if (info.VirtualSize == 0)
|
|
185
|
+
{
|
|
186
|
+
throw new RefusedException("The image size could not be read.");
|
|
187
|
+
}
|
|
188
|
+
if (info.VirtualSize > (ulong)maxGb * 1024UL * 1024UL * 1024UL)
|
|
189
|
+
{
|
|
190
|
+
throw new RefusedException("The image is bigger than the limit of " + maxGb + " GB.");
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
private static Guid GuidAt(byte[] bytes, int offset)
|
|
195
|
+
{
|
|
196
|
+
byte[] part = new byte[16];
|
|
197
|
+
Array.Copy(bytes, offset, part, 0, 16);
|
|
198
|
+
return new Guid(part);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
private static byte[] ReadExact(Stream stream, long offset, int count)
|
|
202
|
+
{
|
|
203
|
+
if (offset < 0 || offset > stream.Length - count)
|
|
204
|
+
{
|
|
205
|
+
throw new RefusedException("The image is not a valid VHDX file (a part is missing).");
|
|
206
|
+
}
|
|
207
|
+
stream.Seek(offset, SeekOrigin.Begin);
|
|
208
|
+
byte[] buffer = new byte[count];
|
|
209
|
+
int done = 0;
|
|
210
|
+
while (done < count)
|
|
211
|
+
{
|
|
212
|
+
int n = stream.Read(buffer, done, count - done);
|
|
213
|
+
if (n <= 0)
|
|
214
|
+
{
|
|
215
|
+
throw new RefusedException("The image could not be read.");
|
|
216
|
+
}
|
|
217
|
+
done += n;
|
|
218
|
+
}
|
|
219
|
+
return buffer;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/// <summary>The image names a log to replay: this service does not let the disk driver replay it (the UAC path may).</summary>
|
|
224
|
+
public sealed class LogPendingException : Exception
|
|
225
|
+
{
|
|
226
|
+
public LogPendingException() : base("The image was not closed cleanly (its log has changes to replay), so the helper leaves it to the Windows permission prompt.")
|
|
227
|
+
{
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
// One VHDX through virtdisk.dll, held by its handle from the open to the attach: open it (never its parents), ask the
|
|
2
|
+
// disk driver what it is (VHDX, not a differencing disk, its size), attach it without drive letters for good (until a
|
|
3
|
+
// detach or a restart), detach it. A new image is made with CreateVirtualDisk, which refuses a file that exists, with
|
|
4
|
+
// the user as its owner. Signatures from the Microsoft virtdisk.h reference.
|
|
5
|
+
using System;
|
|
6
|
+
using System.ComponentModel;
|
|
7
|
+
using System.Runtime.InteropServices;
|
|
8
|
+
using System.Text;
|
|
9
|
+
|
|
10
|
+
namespace AgentSandbox.StorageHelper
|
|
11
|
+
{
|
|
12
|
+
public sealed class VirtualDisk : IDisposable
|
|
13
|
+
{
|
|
14
|
+
private const uint DeviceVhdx = 3;
|
|
15
|
+
private static readonly Guid VendorMicrosoft = new Guid("EC984AEC-A0F9-47e9-901F-71415A66345B");
|
|
16
|
+
private const uint AccessAttachRw = 0x00020000;
|
|
17
|
+
private const uint AccessDetach = 0x00040000;
|
|
18
|
+
private const uint AccessGetInfo = 0x00080000;
|
|
19
|
+
private const uint AccessAll = 0x003f0000;
|
|
20
|
+
private const uint OpenFlagNoParents = 0x1;
|
|
21
|
+
private const uint OpenVersion1 = 1;
|
|
22
|
+
private const uint CreateVersion1 = 1;
|
|
23
|
+
private const uint AttachVersion1 = 1;
|
|
24
|
+
private const uint AttachNoDriveLetter = 0x2;
|
|
25
|
+
private const uint AttachPermanentLifetime = 0x4;
|
|
26
|
+
private const uint InfoSize = 1;
|
|
27
|
+
private const uint InfoStorageType = 6;
|
|
28
|
+
private const uint InfoProviderSubtype = 7;
|
|
29
|
+
private const uint SubtypeDifferencing = 4;
|
|
30
|
+
private const int ErrorFileExists = 80;
|
|
31
|
+
private const int ErrorAlreadyExists = 183;
|
|
32
|
+
private const int ErrorNotReady = 21;
|
|
33
|
+
private const int ErrorDevNotExist = 55;
|
|
34
|
+
private const int ErrorVirtualDiskNotAttached = unchecked((int)0xC03A0015);
|
|
35
|
+
|
|
36
|
+
private IntPtr handle;
|
|
37
|
+
|
|
38
|
+
private VirtualDisk(IntPtr handle)
|
|
39
|
+
{
|
|
40
|
+
this.handle = handle;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
44
|
+
private struct StorageType
|
|
45
|
+
{
|
|
46
|
+
public uint DeviceId;
|
|
47
|
+
public Guid VendorId;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
51
|
+
private struct OpenParametersV1
|
|
52
|
+
{
|
|
53
|
+
public uint Version;
|
|
54
|
+
public uint RwDepth;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
58
|
+
private struct CreateParametersV1
|
|
59
|
+
{
|
|
60
|
+
public uint Version;
|
|
61
|
+
public Guid UniqueId;
|
|
62
|
+
public ulong MaximumSize;
|
|
63
|
+
public uint BlockSizeInBytes;
|
|
64
|
+
public uint SectorSizeInBytes;
|
|
65
|
+
public IntPtr ParentPath;
|
|
66
|
+
public IntPtr SourcePath;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
[StructLayout(LayoutKind.Sequential)]
|
|
70
|
+
private struct AttachParametersV1
|
|
71
|
+
{
|
|
72
|
+
public uint Version;
|
|
73
|
+
public uint Reserved;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private static class NativeMethods
|
|
77
|
+
{
|
|
78
|
+
[DllImport("virtdisk.dll", CharSet = CharSet.Unicode)]
|
|
79
|
+
public static extern int OpenVirtualDisk(ref StorageType storageType, string path, uint accessMask, uint flags, ref OpenParametersV1 parameters, out IntPtr handle);
|
|
80
|
+
|
|
81
|
+
[DllImport("virtdisk.dll", CharSet = CharSet.Unicode)]
|
|
82
|
+
public static extern int CreateVirtualDisk(ref StorageType storageType, string path, uint accessMask, byte[] securityDescriptor, uint flags, uint providerSpecificFlags, ref CreateParametersV1 parameters, IntPtr overlapped, out IntPtr handle);
|
|
83
|
+
|
|
84
|
+
[DllImport("virtdisk.dll")]
|
|
85
|
+
public static extern int AttachVirtualDisk(IntPtr handle, IntPtr securityDescriptor, uint flags, uint providerSpecificFlags, ref AttachParametersV1 parameters, IntPtr overlapped);
|
|
86
|
+
|
|
87
|
+
[DllImport("virtdisk.dll")]
|
|
88
|
+
public static extern int DetachVirtualDisk(IntPtr handle, uint flags, uint providerSpecificFlags);
|
|
89
|
+
|
|
90
|
+
[DllImport("virtdisk.dll")]
|
|
91
|
+
public static extern int GetVirtualDiskInformation(IntPtr handle, ref uint size, byte[] info, IntPtr used);
|
|
92
|
+
|
|
93
|
+
[DllImport("virtdisk.dll", CharSet = CharSet.Unicode)]
|
|
94
|
+
public static extern int GetVirtualDiskPhysicalPath(IntPtr handle, ref uint size, StringBuilder path);
|
|
95
|
+
|
|
96
|
+
[DllImport("kernel32.dll", SetLastError = true)]
|
|
97
|
+
[return: MarshalAs(UnmanagedType.Bool)]
|
|
98
|
+
public static extern bool CloseHandle(IntPtr handle);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private static StorageType Vhdx()
|
|
102
|
+
{
|
|
103
|
+
StorageType type = new StorageType();
|
|
104
|
+
type.DeviceId = DeviceVhdx;
|
|
105
|
+
type.VendorId = VendorMicrosoft;
|
|
106
|
+
return type;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/// <summary>Opens an existing VHDX for attach, detach and questions; its parents (if any) are never opened.</summary>
|
|
110
|
+
public static VirtualDisk Open(string path)
|
|
111
|
+
{
|
|
112
|
+
StorageType type = Vhdx();
|
|
113
|
+
OpenParametersV1 parameters = new OpenParametersV1();
|
|
114
|
+
parameters.Version = OpenVersion1;
|
|
115
|
+
parameters.RwDepth = 1;
|
|
116
|
+
IntPtr h;
|
|
117
|
+
int error = NativeMethods.OpenVirtualDisk(ref type, path, AccessAttachRw | AccessDetach | AccessGetInfo, OpenFlagNoParents, ref parameters, out h);
|
|
118
|
+
if (error != 0)
|
|
119
|
+
{
|
|
120
|
+
throw new Win32Exception(error, "Could not open the image: " + new Win32Exception(error).Message);
|
|
121
|
+
}
|
|
122
|
+
return new VirtualDisk(h);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/// <summary>Makes a new dynamic VHDX (refused when any file is there) with this security descriptor, and keeps it open.</summary>
|
|
126
|
+
public static VirtualDisk Create(string path, long maxGb, byte[] securityDescriptor)
|
|
127
|
+
{
|
|
128
|
+
StorageType type = Vhdx();
|
|
129
|
+
CreateParametersV1 parameters = new CreateParametersV1();
|
|
130
|
+
parameters.Version = CreateVersion1;
|
|
131
|
+
parameters.MaximumSize = (ulong)maxGb * 1024UL * 1024UL * 1024UL;
|
|
132
|
+
IntPtr h;
|
|
133
|
+
int error = NativeMethods.CreateVirtualDisk(ref type, path, AccessAll, securityDescriptor, 0, 0, ref parameters, IntPtr.Zero, out h);
|
|
134
|
+
if (error == ErrorFileExists || error == ErrorAlreadyExists)
|
|
135
|
+
{
|
|
136
|
+
throw new RefusedException("An image exists already; refusing to create over it: " + path);
|
|
137
|
+
}
|
|
138
|
+
if (error != 0)
|
|
139
|
+
{
|
|
140
|
+
throw new Win32Exception(error, "Could not create the image: " + new Win32Exception(error).Message);
|
|
141
|
+
}
|
|
142
|
+
return new VirtualDisk(h);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/// <summary>What the disk driver says: a VHDX, not a differencing disk (provider subtype 4), its size.</summary>
|
|
146
|
+
public void CheckPlain(long maxGb)
|
|
147
|
+
{
|
|
148
|
+
byte[] type = Info(InfoStorageType);
|
|
149
|
+
if (BitConverter.ToUInt32(type, 8) != DeviceVhdx)
|
|
150
|
+
{
|
|
151
|
+
throw new RefusedException("The image is not a VHDX disk.");
|
|
152
|
+
}
|
|
153
|
+
if (BitConverter.ToUInt32(Info(InfoProviderSubtype), 8) == SubtypeDifferencing)
|
|
154
|
+
{
|
|
155
|
+
throw new RefusedException("The image is a differencing disk (it has a parent file); refusing it.");
|
|
156
|
+
}
|
|
157
|
+
ulong size = BitConverter.ToUInt64(Info(InfoSize), 8);
|
|
158
|
+
if (size == 0 || size > (ulong)maxGb * 1024UL * 1024UL * 1024UL)
|
|
159
|
+
{
|
|
160
|
+
throw new RefusedException("The image is bigger than the limit of " + maxGb + " GB.");
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
private byte[] Info(uint version)
|
|
165
|
+
{
|
|
166
|
+
byte[] buffer = new byte[1024];
|
|
167
|
+
BitConverter.GetBytes(version).CopyTo(buffer, 0);
|
|
168
|
+
uint size = (uint)buffer.Length;
|
|
169
|
+
int error = NativeMethods.GetVirtualDiskInformation(handle, ref size, buffer, IntPtr.Zero);
|
|
170
|
+
if (error != 0)
|
|
171
|
+
{
|
|
172
|
+
throw new Win32Exception(error, "Could not read the image's details: " + new Win32Exception(error).Message);
|
|
173
|
+
}
|
|
174
|
+
return buffer;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/// <summary>\\.\PhysicalDriveN while attached; null when it is not attached.</summary>
|
|
178
|
+
public string PhysicalPath()
|
|
179
|
+
{
|
|
180
|
+
StringBuilder sb = new StringBuilder(260);
|
|
181
|
+
uint size = (uint)(sb.Capacity * 2);
|
|
182
|
+
int error = NativeMethods.GetVirtualDiskPhysicalPath(handle, ref size, sb);
|
|
183
|
+
if (error == 0)
|
|
184
|
+
{
|
|
185
|
+
return sb.ToString();
|
|
186
|
+
}
|
|
187
|
+
if (error == ErrorVirtualDiskNotAttached || error == ErrorNotReady || error == ErrorDevNotExist || (error & 0xFFFF) == 0x15)
|
|
188
|
+
{
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
throw new Win32Exception(error, "Could not tell whether the image is attached: " + new Win32Exception(error).Message);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/// <summary>The disk number of an attached image (from \\.\PhysicalDriveN), or -1.</summary>
|
|
195
|
+
public int DiskNumber()
|
|
196
|
+
{
|
|
197
|
+
string path = PhysicalPath();
|
|
198
|
+
if (path == null)
|
|
199
|
+
{
|
|
200
|
+
return -1;
|
|
201
|
+
}
|
|
202
|
+
const string Prefix = "\\\\.\\PhysicalDrive";
|
|
203
|
+
int number;
|
|
204
|
+
if (!path.StartsWith(Prefix, StringComparison.OrdinalIgnoreCase) || !int.TryParse(path.Substring(Prefix.Length), out number))
|
|
205
|
+
{
|
|
206
|
+
throw new InvalidOperationException("Unexpected disk path: " + Safe.Text(path, 100));
|
|
207
|
+
}
|
|
208
|
+
return number;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/// <summary>Attaches until a detach or a restart (not only while this handle is open), with no drive letters.</summary>
|
|
212
|
+
public void Attach()
|
|
213
|
+
{
|
|
214
|
+
AttachParametersV1 parameters = new AttachParametersV1();
|
|
215
|
+
parameters.Version = AttachVersion1;
|
|
216
|
+
int error = NativeMethods.AttachVirtualDisk(handle, IntPtr.Zero, AttachNoDriveLetter | AttachPermanentLifetime, 0, ref parameters, IntPtr.Zero);
|
|
217
|
+
if (error != 0)
|
|
218
|
+
{
|
|
219
|
+
throw new Win32Exception(error, "Could not attach the image: " + new Win32Exception(error).Message);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
public void Detach()
|
|
224
|
+
{
|
|
225
|
+
int error = NativeMethods.DetachVirtualDisk(handle, 0, 0);
|
|
226
|
+
if (error != 0)
|
|
227
|
+
{
|
|
228
|
+
throw new Win32Exception(error, "Could not detach the image (something may still use files in it): " + new Win32Exception(error).Message);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
public void Dispose()
|
|
233
|
+
{
|
|
234
|
+
if (handle != IntPtr.Zero && handle != new IntPtr(-1))
|
|
235
|
+
{
|
|
236
|
+
NativeMethods.CloseHandle(handle);
|
|
237
|
+
}
|
|
238
|
+
handle = IntPtr.Zero;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|