anshdadhich commited on
Commit
1459e6c
·
verified ·
1 Parent(s): c878efa

CRITICAL FIX: Use Win32Api.ERROR_HANDLE_EOF (raw 38) instead of literal HRESULT 0x80070026. GetLastWin32Error() returns raw Win32 codes, not HRESULTs. This was causing EOF to never be detected, making the scan loop run forever on garbage data and return 0 records.

Browse files
Files changed (1) hide show
  1. FastSeekWpf/Core/MftReader.cs +17 -38
FastSeekWpf/Core/MftReader.cs CHANGED
@@ -9,7 +9,7 @@ namespace FastSeekWpf.Core;
9
 
10
  public class MftReader : IDisposable
11
  {
12
- private readonly IntPtr _volumeHandle;
13
  private readonly NtfsDrive _drive;
14
  private bool _disposed;
15
 
@@ -21,7 +21,7 @@ public class MftReader : IDisposable
21
  public MftReader(NtfsDrive drive)
22
  {
23
  _drive = drive;
24
- _volumeHandle = Win32Api.CreateFileW(
25
  drive.DevicePath,
26
  Win32Api.GENERIC_READ,
27
  Win32Api.FILE_SHARE_READ | Win32Api.FILE_SHARE_WRITE | Win32Api.FILE_SHARE_DELETE,
@@ -30,17 +30,14 @@ public class MftReader : IDisposable
30
  Win32Api.FILE_FLAG_BACKUP_SEMANTICS,
31
  IntPtr.Zero);
32
 
33
- if (_volumeHandle == new IntPtr(-1))
34
  {
35
  int err = Marshal.GetLastWin32Error();
36
  throw new IOException($"Failed to open volume {drive.DevicePath}: {Win32Error(err)} (code={err})");
37
  }
38
  }
39
 
40
- // ------------------------------------------------------------------
41
- // Try direct first, fall back to FSCTL (matches Rust main.rs exactly)
42
- // ------------------------------------------------------------------
43
-
44
  public (ScanResult scan, string method) ScanAny()
45
  {
46
  var direct = ScanDirect();
@@ -49,10 +46,7 @@ public class MftReader : IDisposable
49
  return (Scan(), "ioctl");
50
  }
51
 
52
- // ------------------------------------------------------------------
53
- // Primary: direct $MFT file read (matches Rust scan_direct())
54
- // ------------------------------------------------------------------
55
-
56
  public ScanResult? ScanDirect()
57
  {
58
  int? recordSize = ReadMftRecordSize();
@@ -113,13 +107,13 @@ public class MftReader : IDisposable
113
  offset += recordSize.Value;
114
  }
115
 
116
- // Align down to record boundary (matches Rust: offset = total - (total % record_size))
117
  offset = total - (total % recordSize.Value);
118
 
119
  leftover = total - offset;
120
  if (leftover > 0)
121
  {
122
- // Copy tail to front of buffer (matches Rust std::ptr::copy)
123
  Buffer.BlockCopy(buffer, offset, buffer, 0, leftover);
124
  }
125
  }
@@ -133,11 +127,7 @@ public class MftReader : IDisposable
133
  }
134
  }
135
 
136
- // ------------------------------------------------------------------
137
- // Fallback: FSCTL_ENUM_USN_DATA (4 MB buffer)
138
- // Matches Rust scan() exactly.
139
- // ------------------------------------------------------------------
140
-
141
  public ScanResult Scan()
142
  {
143
  var records = new List<CompactRecord>();
@@ -157,16 +147,14 @@ public class MftReader : IDisposable
157
 
158
  try
159
  {
160
- int iterations = 0;
161
  while (true)
162
  {
163
- iterations++;
164
  IntPtr bufferPtr = Marshal.AllocHGlobal(buffer.Length);
165
  try
166
  {
167
  uint bytesReturned = 0;
168
  bool ok = Win32Api.DeviceIoControl(
169
- _volumeHandle,
170
  Win32Api.FSCTL_ENUM_USN_DATA,
171
  enumDataPtr,
172
  (uint)enumDataSize,
@@ -178,13 +166,13 @@ public class MftReader : IDisposable
178
  if (!ok)
179
  {
180
  int error = Marshal.GetLastWin32Error();
181
- uint code = (uint)error;
182
- if (code == 0x80070026) // ERROR_HANDLE_EOF
183
  {
184
- Logger.Log($"[{Drive.Letter}] FSCTL_ENUM_USN_DATA: EOF after {iterations} iterations");
185
  break;
186
  }
187
- if (error == 122) // ERROR_INSUFFICIENT_BUFFER
188
  {
189
  Logger.Log($"[{Drive.Letter}] FSCTL buffer too small ({buffer.Length}), retrying");
190
  buffer = new byte[buffer.Length * 2];
@@ -249,13 +237,9 @@ public class MftReader : IDisposable
249
  return new ScanResult(records, nameData);
250
  }
251
 
252
- // ------------------------------------------------------------------
253
- // NTFS helpers (match Rust exactly)
254
- // ------------------------------------------------------------------
255
-
256
  private int? ReadMftRecordSize()
257
  {
258
- // Seek to beginning of volume
259
  Win32Api.SetFilePointerEx(_volumeHandle, 0, out _, 0);
260
 
261
  byte[] boot = new byte[512];
@@ -278,10 +262,7 @@ public class MftReader : IDisposable
278
  return recordSize;
279
  }
280
 
281
- /// <summary>
282
- /// Apply NTFS multi-sector fixup. Returns false if record is invalid.
283
- /// CRITICAL: restores bytes from fixup array back to sector ends (matches Rust exactly).
284
- /// </summary>
285
  private static bool ApplyFixup(byte[] record, int offset, int recordSize)
286
  {
287
  if (recordSize < 48 || record[offset] != 'F' || record[offset + 1] != 'I'
@@ -305,7 +286,7 @@ public class MftReader : IDisposable
305
  if (record[offset + end] != check0 || record[offset + end + 1] != check1)
306
  return false;
307
 
308
- // RESTORE real bytes from fixup array (this is what Rust does)
309
  record[offset + end] = record[offset + fixupOff + i * 2];
310
  record[offset + end + 1] = record[offset + fixupOff + i * 2 + 1];
311
  }
@@ -313,9 +294,7 @@ public class MftReader : IDisposable
313
  return true;
314
  }
315
 
316
- /// <summary>
317
- /// Parse one MFT FILE record (matches Rust parse_file_record exactly).
318
- /// </summary>
319
  private static void ParseFileRecord(
320
  ReadOnlySpan<byte> record,
321
  ulong mftIndex,
 
9
 
10
  public class MftReader : IDisposable
11
  {
12
+ private readonly IntPtr _handle;
13
  private readonly NtfsDrive _drive;
14
  private bool _disposed;
15
 
 
21
  public MftReader(NtfsDrive drive)
22
  {
23
  _drive = drive;
24
+ _handle = Win32Api.CreateFileW(
25
  drive.DevicePath,
26
  Win32Api.GENERIC_READ,
27
  Win32Api.FILE_SHARE_READ | Win32Api.FILE_SHARE_WRITE | Win32Api.FILE_SHARE_DELETE,
 
30
  Win32Api.FILE_FLAG_BACKUP_SEMANTICS,
31
  IntPtr.Zero);
32
 
33
+ if (_handle == new IntPtr(-1))
34
  {
35
  int err = Marshal.GetLastWin32Error();
36
  throw new IOException($"Failed to open volume {drive.DevicePath}: {Win32Error(err)} (code={err})");
37
  }
38
  }
39
 
40
+ // Try direct first, fall back to FSCTL — matches Rust main.rs exactly
 
 
 
41
  public (ScanResult scan, string method) ScanAny()
42
  {
43
  var direct = ScanDirect();
 
46
  return (Scan(), "ioctl");
47
  }
48
 
49
+ // Primary: direct $MFT file read — matches Rust scan_direct()
 
 
 
50
  public ScanResult? ScanDirect()
51
  {
52
  int? recordSize = ReadMftRecordSize();
 
107
  offset += recordSize.Value;
108
  }
109
 
110
+ // Align down to record boundary — matches Rust offset = total - (total % record_size)
111
  offset = total - (total % recordSize.Value);
112
 
113
  leftover = total - offset;
114
  if (leftover > 0)
115
  {
116
+ // Copy tail to front — matches Rust std::ptr::copy
117
  Buffer.BlockCopy(buffer, offset, buffer, 0, leftover);
118
  }
119
  }
 
127
  }
128
  }
129
 
130
+ // Fallback: FSCTL_ENUM_USN_DATA (4 MB buffer) — matches Rust scan() exactly
 
 
 
 
131
  public ScanResult Scan()
132
  {
133
  var records = new List<CompactRecord>();
 
147
 
148
  try
149
  {
 
150
  while (true)
151
  {
 
152
  IntPtr bufferPtr = Marshal.AllocHGlobal(buffer.Length);
153
  try
154
  {
155
  uint bytesReturned = 0;
156
  bool ok = Win32Api.DeviceIoControl(
157
+ _handle,
158
  Win32Api.FSCTL_ENUM_USN_DATA,
159
  enumDataPtr,
160
  (uint)enumDataSize,
 
166
  if (!ok)
167
  {
168
  int error = Marshal.GetLastWin32Error();
169
+ // CRITICAL: GetLastWin32Error returns RAW Win32 error (38), NOT HRESULT (0x80070026)
170
+ if (error == Win32Api.ERROR_HANDLE_EOF)
171
  {
172
+ Logger.Log($"[{Drive.Letter}] FSCTL_ENUM_USN_DATA: EOF");
173
  break;
174
  }
175
+ if (error == Win32Api.ERROR_INSUFFICIENT_BUFFER)
176
  {
177
  Logger.Log($"[{Drive.Letter}] FSCTL buffer too small ({buffer.Length}), retrying");
178
  buffer = new byte[buffer.Length * 2];
 
237
  return new ScanResult(records, nameData);
238
  }
239
 
240
+ // Read MFT record size from NTFS boot sector — matches Rust read_mft_record_size()
 
 
 
241
  private int? ReadMftRecordSize()
242
  {
 
243
  Win32Api.SetFilePointerEx(_volumeHandle, 0, out _, 0);
244
 
245
  byte[] boot = new byte[512];
 
262
  return recordSize;
263
  }
264
 
265
+ // Apply NTFS multi-sector fixup — matches Rust apply_fixup() exactly
 
 
 
266
  private static bool ApplyFixup(byte[] record, int offset, int recordSize)
267
  {
268
  if (recordSize < 48 || record[offset] != 'F' || record[offset + 1] != 'I'
 
286
  if (record[offset + end] != check0 || record[offset + end + 1] != check1)
287
  return false;
288
 
289
+ // RESTORE real bytes from fixup array — matches Rust exactly
290
  record[offset + end] = record[offset + fixupOff + i * 2];
291
  record[offset + end + 1] = record[offset + fixupOff + i * 2 + 1];
292
  }
 
294
  return true;
295
  }
296
 
297
+ // Parse one MFT FILE record — matches Rust parse_file_record() exactly
 
 
298
  private static void ParseFileRecord(
299
  ReadOnlySpan<byte> record,
300
  ulong mftIndex,