-
Notifications
You must be signed in to change notification settings - Fork 1
/
ReferenceImplementation.cs
368 lines (333 loc) · 16.7 KB
/
ReferenceImplementation.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
namespace EnumerateVolume
{
class PInvokeWin32
{
#region DllImports and Constants
public const UInt32 GENERIC_READ = 0x80000000;
public const UInt32 GENERIC_WRITE = 0x40000000;
public const UInt32 FILE_SHARE_READ = 0x00000001;
public const UInt32 FILE_SHARE_WRITE = 0x00000002;
public const UInt32 FILE_ATTRIBUTE_DIRECTORY = 0x00000010;
public const UInt32 OPEN_EXISTING = 3;
public const UInt32 FILE_FLAG_BACKUP_SEMANTICS = 0x02000000;
public const Int32 INVALID_HANDLE_VALUE = -1;
public const UInt32 FSCTL_QUERY_USN_JOURNAL = 0x000900f4;
public const UInt32 FSCTL_ENUM_USN_DATA = 0x000900b3;
public const UInt32 FSCTL_CREATE_USN_JOURNAL = 0x000900e7;
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr CreateFile(string lpFileName, uint dwDesiredAccess,
uint dwShareMode, IntPtr lpSecurityAttributes,
uint dwCreationDisposition, uint dwFlagsAndAttributes,
IntPtr hTemplateFile);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetFileInformationByHandle(IntPtr hFile,
out BY_HANDLE_FILE_INFORMATION lpFileInformation);
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CloseHandle(IntPtr hObject);
[DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true, CharSetCharSet = CharSet.Auto)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool DeviceIoControl(IntPtr hDevice,
UInt32 dwIoControlCode,
IntPtr lpInBuffer, Int32 nInBufferSize,
out USN_JOURNAL_DATA lpOutBuffer, Int32 nOutBufferSize,
out uint lpBytesReturned, IntPtr lpOverlapped);
[DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true, CharSetCharSet = CharSet.Auto)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool DeviceIoControl(IntPtr hDevice,
UInt32 dwIoControlCode,
IntPtr lpInBuffer, Int32 nInBufferSize,
IntPtr lpOutBuffer, Int32 nOutBufferSize,
out uint lpBytesReturned, IntPtr lpOverlapped);
[DllImport("kernel32.dll")]
public static extern void ZeroMemory(IntPtr ptr, Int32 size);
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct BY_HANDLE_FILE_INFORMATION
{
public uint FileAttributes;
public FILETIME CreationTime;
public FILETIME LastAccessTime;
public FILETIME LastWriteTime;
public uint VolumeSerialNumber;
public uint FileSizeHigh;
public uint FileSizeLow;
public uint NumberOfLinks;
public uint FileIndexHigh;
public uint FileIndexLow;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct FILETIME
{
public uint DateTimeLow;
public uint DateTimeHigh;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct USN_JOURNAL_DATA
{
public UInt64 UsnJournalID;
public Int64 FirstUsn;
public Int64 NextUsn;
public Int64 LowestValidUsn;
public Int64 MaxUsn;
public UInt64 MaximumSize;
public UInt64 AllocationDelta;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct MFT_ENUM_DATA
{
public UInt64 StartFileReferenceNumber;
public Int64 LowUsn;
public Int64 HighUsn;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct CREATE_USN_JOURNAL_DATA
{
public UInt64 MaximumSize;
public UInt64 AllocationDelta;
}
public class USN_RECORD
{
public UInt32 RecordLength;
public UInt64 FileReferenceNumber;
public UInt64 ParentFileReferenceNumber;
public UInt32 FileAttributes;
public Int32 FileNameLength;
public Int32 FileNameOffset;
public string FileName = string.Empty;
private const int FR_OFFSET = 8;
private const int PFR_OFFSET = 16;
private const int FA_OFFSET = 52;
private const int FNL_OFFSET = 56;
private const int FN_OFFSET = 58;
public USN_RECORD(IntPtr p)
{
this.RecordLength = (UInt32)Marshal.ReadInt32(p);
this.FileReferenceNumber = (UInt64)Marshal.ReadInt64(p, FR_OFFSET);
this.ParentFileReferenceNumber = (UInt64)Marshal.ReadInt64(p, PFR_OFFSET);
this.FileAttributes = (UInt32)Marshal.ReadInt32(p, FA_OFFSET);
this.FileNameLength = Marshal.ReadInt16(p, FNL_OFFSET);
this.FileNameOffset = Marshal.ReadInt16(p, FN_OFFSET);
FileName = Marshal.PtrToStringUni(new IntPtr(p.ToInt32() + this.FileNameOffset), this.FileNameLength / sizeof(char));
}
}
#endregion
}
}
public void EnumerateVolume(out Dictionary<UInt64, FileNameAndFrn> files, string[] fileExtensions)
{
files = new Dictionary<ulong, FileNameAndFrn>();
IntPtr medBuffer = IntPtr.Zero;
try
{
GetRootFrnEntry();
GetRootHandle();
CreateChangeJournal();
SetupMFT_Enum_DataBuffer(ref medBuffer);
EnumerateFiles(medBuffer, ref files, fileExtensions);
}
catch (Exception e)
{
Log.Info(e.Message, e);
Exception innerException = e.InnerException;
while (innerException != null)
{
Log.Info(innerException.Message, inner Exception);
innerException = innerException.InnerException;
}
throw new ApplicationException("Error in EnumerateVolume()", e);
}
finally
{
if (_changeJournalRootHandle.ToInt32() != PInvokeWin32.INVALID_HANDLE_VALUE)
{
PInvokeWin32.CloseHandle(_changeJournalRootHandle);
}
if (medBuffer != IntPtr.Zero)
{
Marshal.FreeHGlobal(medBuffer);
}
}
}
private void GetRootFrnEntry()
{
string driveRoot = string.Concat("\\\\.\\", _drive);
driveRoot = string.Concat(driveRoot, Path.DirectorySeparatorChar);
IntPtr hRoot = PInvokeWin32.CreateFile(driveRoot,
0,
PInvokeWin32.FILE_SHARE_READ | PInvokeWin32.FILE_SHARE_WRITE,
IntPtr.Zero,
PInvokeWin32.OPEN_EXISTING,
PInvokeWin32.FILE_FLAG_BACKUP_SEMANTICS,
IntPtr.Zero);
if (hRoot.ToInt32() != PInvokeWin32.INVALID_HANDLE_VALUE)
{
PInvokeWin32.BY_HANDLE_FILE_INFORMATION fi = new PInvokeWin32.BY_HANDLE_FILE_INFORMATION();
bool bRtn = PInvokeWin32.GetFileInformationByHandle(hRoot, out fi);
if (bRtn)
{
UInt64 fileIndexHigh = (UInt64)fi.FileIndexHigh;
UInt64 indexRoot = (fileIndexHigh << 32) | fi.FileIndexLow;
FileNameAndFrn f = new FileNameAndFrn(driveRoot, 0);
_directories.Add(indexRoot, f);
}
else
{
throw new IOException("GetFileInformationbyHandle() returned invalid handle",
new Win32Exception(Marshal.GetLastWin32Error()));
}
PInvokeWin32.CloseHandle(hRoot);
}
else
{
throw new IOException("Unable to get root frn entry", new Win32Exception(Marshal.GetLastWin32Error()));
}
}
private void GetRootHandle()
{
string vol = string.Concat("\\\\.\\", _drive);
_changeJournalRootHandle = PInvokeWin32.CreateFile(vol,
PInvokeWin32.GENERIC_READ | PInvokeWin32.GENERIC_WRITE,
PInvokeWin32.FILE_SHARE_READ | PInvokeWin32.FILE_SHARE_WRITE,
IntPtr.Zero,
PInvokeWin32.OPEN_EXISTING,
0,
IntPtr.Zero);
if (_changeJournalRootHandle.ToInt32() == PInvokeWin32.INVALID_HANDLE_VALUE)
{
throw new IOException("CreateFile() returned invalid handle",
new Win32Exception(Marshal.GetLastWin32Error()));
}
}
unsafe private void EnumerateFiles(IntPtr medBuffer, ref Dictionary<ulong, FileNameAndFrn> files, string[] fileExtensions)
{
IntPtr pData = Marshal.AllocHGlobal(sizeof(UInt64) + 0x10000);
PInvokeWin32.ZeroMemory(pData, sizeof(UInt64) + 0x10000);
uint outBytesReturned = 0;
while (false != PInvokeWin32.DeviceIoControl(_changeJournalRootHandle, PInvokeWin32.FSCTL_ENUM_USN_DATA, medBuffer,
sizeof(PInvokeWin32.MFT_ENUM_DATA), pData, sizeof(UInt64) + 0x10000, out outBytesReturned,
IntPtr.Zero))
{
IntPtr pUsnRecord = new IntPtr(pData.ToInt32() + sizeof(Int64));
while (outBytesReturned > 60)
{
PInvokeWin32.USN_RECORD usn = new PInvokeWin32.USN_RECORD(pUsnRecord);
if (0 != (usn.FileAttributes & PInvokeWin32.FILE_ATTRIBUTE_DIRECTORY))
{
//
// handle directories
//
if (!_directories.ContainsKey(usn.FileReferenceNumber))
{
_directories.Add(usn.FileReferenceNumber,
new FileNameAndFrn(usn.FileName, usn.ParentFileReferenceNumber));
}
else
{ // this is debug code and should be removed when we are certain that
// duplicate frn's don't exist on a given drive. To date, this exception has
// never been thrown. Removing this code improves performance....
throw new Exception(string.Format("Duplicate FRN: {0} for {1}",
usn.FileReferenceNumber, usn.FileName));
}
}
else
{
//
// handle files
//
bool add = true;
if (fileExtensions != null && fileExtensions.Length != 0)
{
add = false;
string s = Path.GetExtension(usn.FileName);
foreach (string extension in fileExtensions)
{
if (0 == string.Compare(s, extension, true))
{
add = true;
break;
}
}
}
if (add)
{
if (!files.ContainsKey(usn.FileReferenceNumber))
{
files.Add(usn.FileReferenceNumber,
new FileNameAndFrn(usn.FileName, usn.ParentFileReferenceNumber));
}
else
{
FileNameAndFrn frn = files[usn.FileReferenceNumber];
if (0 != string.Compare(usn.FileName, frn.Name, true))
{
Log.InfoFormat(
"Attempt to add duplicate file reference number: {0} for file {1}, file from index {2}",
usn.FileReferenceNumber, usn.FileName, frn.Name);
throw new Exception(string.Format("Duplicate FRN: {0} for {1}",
usn.FileReferenceNumber, usn.FileName));
}
}
}
}
pUsnRecord = new IntPtr(pUsnRecord.ToInt32() + usn.RecordLength);
outBytesReturned -= usn.RecordLength;
}
Marshal.WriteInt64(medBuffer, Marshal.ReadInt64(pData, 0));
}
Marshal.FreeHGlobal(pData);
}
unsafe private void CreateChangeJournal()
{
// This function creates a journal on the volume. If a journal already
// exists this function will adjust the MaximumSize and AllocationDelta
// parameters of the journal
UInt64 MaximumSize = 0x800000;
UInt64 AllocationDelta = 0x100000;
UInt32 cb;
PInvokeWin32.CREATE_USN_JOURNAL_DATA cujd;
cujd.MaximumSize = MaximumSize;
cujd.AllocationDelta = AllocationDelta;
int sizeCujd = Marshal.SizeOf(cujd);
IntPtr cujdBuffer = Marshal.AllocHGlobal(sizeCujd);
PInvokeWin32.ZeroMemory(cujdBuffer, sizeCujd);
Marshal.StructureToPtr(cujd, cujdBuffer, true);
bool fOk = PInvokeWin32.DeviceIoControl(_changeJournalRootHandle, PInvokeWin32.FSCTL_CREATE_USN_JOURNAL,
cujdBuffer, sizeCujd, IntPtr.Zero, 0, out cb, IntPtr.Zero);
if (!fOk)
{
throw new IOException("DeviceIoControl() returned false", new Win32Exception(Marshal.GetLastWin32Error()));
}
}
unsafe private void SetupMFT_Enum_DataBuffer(ref IntPtr medBuffer)
{
uint bytesReturned = 0;
PInvokeWin32.USN_JOURNAL_DATA ujd = new PInvokeWin32.USN_JOURNAL_DATA();
bool bOk = PInvokeWin32.DeviceIoControl(_changeJournalRootHandle, // Handle to drive
PInvokeWin32.FSCTL_QUERY_USN_JOURNAL, // IO Control Code
IntPtr.Zero, // In Buffer
0, // In Buffer Size
out ujd, // Out Buffer
sizeof(PInvokeWin32.USN_JOURNAL_DATA), // Size Of Out Buffer
out bytesReturned, // Bytes Returned
IntPtr.Zero); // lpOverlapped
if (bOk)
{
PInvokeWin32.MFT_ENUM_DATA med;
med.StartFileReferenceNumber = 0;
med.LowUsn = 0;
med.HighUsn = ujd.NextUsn;
int sizeMftEnumData = Marshal.SizeOf(med);
medBuffer = Marshal.AllocHGlobal(sizeMftEnumData);
PInvokeWin32.ZeroMemory(medBuffer, sizeMftEnumData);
Marshal.StructureToPtr(med, medBuffer, true);
}
else
{
throw new IOException("DeviceIoControl() returned false", new Win32Exception(Marshal.GetLastWin32Error()));
}
}