source: trunk/EraserDll/NTFS.cpp @ 65

Revision 65, 23.6 KB checked in by lowjoel, 7 years ago (diff)

Disable file buffering on all levels when wiping data streams.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
Line 
1// NTFS.cpp
2//
3// Eraser. Secure data removal. For Windows.
4// Copyright © 1997-2001  Sami Tolvanen (sami@tolvanen.com).
5//
6// SDelete - Secure Delete
7// Copyright (C) 1999 Mark Russinovich
8// Systems Internals - http://www.sysinternals.com
9//
10// This program implements a secure delete function for
11// Windows NT/2K. It even works on WinNT compressed, encrypted
12// and sparse files.
13//
14// This program is copyrighted. You may not use the source, or
15// derived version of the source, in a secure delete application.
16// You may use the source or techniques herein in applications
17// with a purpose other than secure delete.
18
19#include "stdafx.h"
20#include "resource.h"
21#include "EraserDll.h"
22#include "Common.h"
23#include "File.h"
24#include "NTFS.h"
25#include "winioctl.h"
26// Invalid longlong number
27
28#define LLINVALID       ((E_UINT64) -1)
29
30// Size of the buffer we read file mapping information into.
31// The buffer is big enough to hold the 16 bytes that
32// come back at the head of the buffer (the number of entries
33// and the starting virtual cluster), as well as 512 pairs
34// of [virtual cluster, logical cluster] pairs.
35
36#define FILEMAPSIZE     (16384 + 2)
37
38
39static bool
40initEntryPoints(NTFSContext& ntc)
41{
42    // load the NTDLL entry point we need
43    ntc.m_hNTDLL = AfxLoadLibrary(ERASER_MODULENAME_NTDLL);
44
45    if (ntc.m_hNTDLL != NULL) {
46        ntc.NtFsControlFile =
47            (NTFSCONTROLFILE) GetProcAddress(ntc.m_hNTDLL, ERASER_FUNCTIONNAME_NTFSCONTROLFILE);
48        ntc.NtQueryInformationFile =
49            (NTQUERYINFORMATIONFILE) GetProcAddress(ntc.m_hNTDLL, ERASER_FUNCTIONNAME_NTQUERYINFORMATIONFILE);
50        ntc.RtlNtStatusToDosError =
51            (RTLNTSTATUSTODOSERROR) GetProcAddress(ntc.m_hNTDLL, ERASER_FUNCTIONNAME_RTLNTSTATUSTODOSERROR);
52
53        if (ntc.NtFsControlFile == NULL || ntc.RtlNtStatusToDosError == NULL) {
54            AfxFreeLibrary(ntc.m_hNTDLL);
55            ntc.m_hNTDLL = NULL;
56        }
57    }
58
59    return (ntc.m_hNTDLL != NULL);
60}
61
62static CString
63formatNTError(NTFSContext& ntc, NTSTATUS dwStatus)
64{
65    CString strMessage;
66    LPTSTR szMessage;
67
68    FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
69                  NULL,
70                  ntc.RtlNtStatusToDosError(dwStatus),
71                  MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
72                  (LPTSTR)&szMessage, 0, NULL);
73
74    strMessage = szMessage;
75    LocalFree(szMessage);
76
77    return strMessage;
78}
79
80static bool
81wipeClusters(NTFSContext& ntc, CEraserContext *context, bool& bCompressed)
82{
83    if (context->m_piCurrent.m_uCluster == 0) {
84        return false;
85    }
86
87    NTSTATUS                  status = STATUS_INVALID_PARAMETER;
88    E_INT32                   i;
89    IO_STATUS_BLOCK           ioStatus;
90    E_UINT64                  startVcn;
91    PGET_RETRIEVAL_DESCRIPTOR fileMappings;
92    E_UINT64                  fileMap[FILEMAPSIZE];
93    HANDLE                    hFile;
94
95    // set the handle passed to the wipe function
96    hFile = context->m_hFile;
97    context->m_hFile = ntc.m_hVolume;
98
99    // assume file is in an MFT record.
100    bCompressed = false;
101
102    startVcn = 0;
103    fileMappings = (PGET_RETRIEVAL_DESCRIPTOR) fileMap;
104
105    status = ntc.NtFsControlFile(hFile, NULL, NULL, 0, &ioStatus,
106                              FSCTL_GET_RETRIEVAL_POINTERS,
107                              &startVcn, sizeof(startVcn),
108                              fileMappings,
109                              FILEMAPSIZE * sizeof(ULONGLONG));
110
111    while (status == STATUS_SUCCESS || status == STATUS_BUFFER_OVERFLOW ||
112           status == STATUS_PENDING) {
113        // if the operation is pending, wait for it to finish
114        if (status == STATUS_PENDING) {
115            WaitForSingleObject(hFile, INFINITE);
116
117            // get the status from the status block
118            if (ioStatus.Status != STATUS_SUCCESS &&
119                ioStatus.Status != STATUS_BUFFER_OVERFLOW) {
120                context->m_hFile = hFile;
121                return false;
122            }
123        }
124
125        // progress information
126        context->m_uProgressSize = 0;
127
128        startVcn = fileMappings->StartVcn;
129
130        for (i = 0; i < (E_UINT64) fileMappings->NumberOfPairs; i++) {
131            if (fileMappings->Pair[i].Lcn != LLINVALID) {
132                context->m_uProgressSize += (fileMappings->Pair[i].Vcn - startVcn) *
133                                            (E_UINT64)context->m_piCurrent.m_uCluster;
134            }
135
136            startVcn = fileMappings->Pair[i].Vcn;
137        }
138
139        eraserProgressStartEstimate(context, context->m_uProgressSize);
140
141        // loop through the buffer of number/cluster pairs, printing them out.
142        startVcn = fileMappings->StartVcn;
143
144        for (i = 0; i < (E_UINT64)fileMappings->NumberOfPairs; i++) {
145            // On NT 4.0, a compressed virtual run (0-filled) is
146            // identified with a cluster offset of -1
147
148            if (fileMappings->Pair[i].Lcn != LLINVALID) {
149                // its compressed and outside the zone
150                bCompressed = true;
151
152                // Overwrite the clusters if we were able to open the volume
153                // for write access.
154                context->m_uiFileStart.QuadPart = fileMappings->Pair[i].Lcn * context->m_piCurrent.m_uCluster;
155                context->m_uiFileSize.QuadPart = (fileMappings->Pair[i].Vcn - startVcn) *
156                                                 (E_UINT64)context->m_piCurrent.m_uCluster;
157
158                if (!context->m_lpmMethod->m_pwfFunction(context)) {
159                    context->m_hFile = hFile;
160                    return false;
161                }
162
163            }
164
165            startVcn = fileMappings->Pair[i].Vcn;
166        }
167
168        // if the buffer wasn't overflowed, then we're done
169        if (NT_SUCCESS(status)) {
170            break;
171        }
172
173        status = ntc.NtFsControlFile(hFile, NULL, NULL, 0, &ioStatus,
174                                  FSCTL_GET_RETRIEVAL_POINTERS,
175                                  &startVcn, sizeof(startVcn),
176                                  fileMappings,
177                                  FILEMAPSIZE * sizeof(E_UINT64));
178    }
179
180    if (status != STATUS_SUCCESS && status != STATUS_INVALID_PARAMETER) {
181        context->m_saError.Add(formatNTError(ntc, status));
182    }
183
184    // restore the file handle
185    context->m_hFile = hFile;
186
187    // if we made through with no errors we've overwritten all the file's clusters.
188    return NT_SUCCESS(status);
189}
190
191static bool
192initAndOpenVolume(NTFSContext& ntc, TCHAR cDrive)
193{
194    TCHAR szVolumeName[] = "\\\\.\\ :";
195
196    if (initEntryPoints(ntc)) {
197        // open the volume for direct access
198        szVolumeName[4] = cDrive;
199        ntc.m_hVolume = CreateFile(szVolumeName, GENERIC_READ | GENERIC_WRITE,
200                                   FILE_SHARE_READ | FILE_SHARE_WRITE, NULL,
201                                   OPEN_EXISTING, FILE_FLAG_WRITE_THROUGH, 0);
202
203        return (ntc.m_hVolume != INVALID_HANDLE_VALUE);
204    }
205    return false;
206}
207
208// exported functions
209//
210
211E_UINT32
212wipeCompressedFile(CEraserContext *context)
213{
214    if (!isFileSystemNTFS(context->m_piCurrent)) {
215        return WCF_NOTCOMPRESSED;
216    }
217
218    NTFSContext ntc;
219    E_UINT32 uResult = WCF_FAILURE;
220    bool bCompressed = false;
221
222    if (initAndOpenVolume(ntc, context->m_strData[0])) {
223        // open the file exclusively
224        context->m_hFile = CreateFile((LPCTSTR)context->m_strData, GENERIC_READ,
225                                      (context->m_uTestMode) ?
226                                        FILE_SHARE_READ | FILE_SHARE_WRITE : 0,
227                                      NULL, OPEN_EXISTING,
228                                      FILE_FLAG_WRITE_THROUGH | FILE_FLAG_NO_BUFFERING, NULL);
229
230        if (context->m_hFile != INVALID_HANDLE_VALUE) {
231            try {
232                // scan the location of the file
233                if (wipeClusters(ntc, context, bCompressed)) {
234                    // done with the file handle
235                    CloseHandle(context->m_hFile);
236                    context->m_hFile = INVALID_HANDLE_VALUE;
237
238                    if (!bCompressed) {
239                        // if the file wasn't really compressed, erase normally
240                        uResult = WCF_NOTCOMPRESSED;
241                    } else {
242                        if (eraserOK(eraserRemoveFile((LPVOID)(LPCTSTR)context->m_strData,
243                                (E_UINT16)context->m_strData.GetLength()))) {
244                            uResult = WCF_SUCCESS;
245                        }
246                    }
247                } else {
248                    // close the handle
249                    CloseHandle(context->m_hFile);
250                    context->m_hFile = INVALID_HANDLE_VALUE;
251                }
252            } catch (CException *e) {
253                handleException(e, context);
254                uResult = WCF_FAILURE;
255
256                if (context->m_hFile != INVALID_HANDLE_VALUE) {
257                    CloseHandle(context->m_hFile);
258                    context->m_hFile = INVALID_HANDLE_VALUE;
259                }
260            }
261        }
262    } else {
263        // the user does not have privileges for low-level access
264        uResult = WCF_NOACCESS;
265    }
266
267    return uResult;
268}
269
270
271#define mftFastWriteTest(x) \
272    WriteFile((x)->m_hFile, uTestBuffer, (x)->m_uiFileSize.LowPart, &uTemp, NULL)
273
274bool
275wipeMFTRecords(CEraserContext *context)
276{
277    // On NTFS file system small files can be resident on the MFT record so we
278    // will need to overwrite empty records by creating as many of the largest
279    // sized files as possible (if there is space in the MFT, we'll be able to
280    // create non-zero sized files, where the data is resident in the MFT record)
281
282    if (!isFileSystemNTFS(context->m_piCurrent)) {
283        return false;
284    }
285
286    E_UINT64 uFreeSpace = 0;
287    eraserGetFreeDiskSpace((LPVOID)context->m_piCurrent.m_szDrive,
288                           (E_UINT16)lstrlen(context->m_piCurrent.m_szDrive),
289                           &uFreeSpace);
290
291    if (uFreeSpace == 0) {
292        const E_UINT16 maxMFTRecordSize = 4096;
293
294        TCHAR        szFileName[uShortFileNameLength + 1];
295        E_UINT16     uCounter = 1;
296        E_UINT32     uTestBuffer[maxMFTRecordSize];
297        E_UINT32     uTemp;
298        E_UINT64     ulPrevSize = maxMFTRecordSize;
299        CString      strTemp;
300        CStringArray saList;
301        bool         bCreatedFile;
302
303        // fill test buffer with random data
304        isaacFill((E_PUINT8)uTestBuffer, maxMFTRecordSize);
305
306        // do something with the progress bar to entertain the user
307        eraserDispMFT(context);
308        eraserBeginNotify(context);
309
310        context->m_uClusterSpace = 0;
311
312        do {
313            createRandomShortFileName(szFileName, uCounter++);
314            strTemp.Format("Eraser%s%s", context->m_piCurrent.m_szDrive, szFileName);
315
316            context->m_hFile = CreateFile((LPCTSTR)strTemp,
317                                         GENERIC_WRITE,
318                                         (context->m_uTestMode) ?
319                                            FILE_SHARE_READ | FILE_SHARE_WRITE : 0,
320                                         NULL,
321                                         CREATE_NEW,
322                                         FILE_ATTRIBUTE_HIDDEN | FILE_FLAG_WRITE_THROUGH,
323                                         NULL);
324
325            if (context->m_hFile == INVALID_HANDLE_VALUE) {
326                break;
327            }
328
329            saList.Add(strTemp);
330
331            try {
332                context->m_uiFileStart.QuadPart = 0;
333                context->m_uiFileSize.QuadPart = ulPrevSize;
334                bCreatedFile = false;
335
336                while (context->m_uiFileSize.QuadPart) {
337                    if (eraserInternalTerminated(context)) {
338                        bCreatedFile = false;
339                        break;
340                    }
341
342                    // trying simple write first is much faster than calling the wipe function
343                    if (!mftFastWriteTest(context) || !context->m_lpmMethod->m_pwfFunction(context)) {
344                        eraserProgressSetMessage(context, ERASER_MESSAGE_MFT);
345                        context->m_uiFileSize.QuadPart--;
346                    } else {
347                        strTemp.Format(ERASER_MESSAGE_MFT_WAIT, uCounter);
348                        eraserProgressSetMessage(context, strTemp);
349                        eraserUpdateNotify(context);
350
351                        ulPrevSize = context->m_uiFileSize.QuadPart;
352                        bCreatedFile = true;
353                        break;
354                    }
355
356                    eraserSafeAssign(context, context->m_uProgressPercent,
357                        (E_UINT8)(((maxMFTRecordSize - context->m_uiFileSize.QuadPart) * 100) / maxMFTRecordSize));
358                    setTotalProgress(context);
359                    eraserUpdateNotify(context);
360                }
361            } catch (CException *e) {
362                handleException(e, context);
363                bCreatedFile = false;
364            }
365
366            resetDate(context->m_hFile);
367            CloseHandle(context->m_hFile);
368
369        } while (bCreatedFile);
370
371        eraserSafeAssign(context, context->m_uProgressPercent, 100);
372        setTotalProgress(context);
373
374        eraserProgressSetMessage(context, ERASER_MESSAGE_REMOVING);
375        eraserUpdateNotify(context);
376
377        E_INT32 iSize = saList.GetSize();
378        for (E_INT32 i = 0; i < iSize; i++) {
379            eraserSafeAssign(context, context->m_uProgressPercent, (E_UINT8)((i * 100) / iSize));
380            eraserUpdateNotify(context);
381
382            // file names are already random, no need to use slower eraserRemoveFile
383            DeleteFile((LPCTSTR)saList[i]);
384        }
385
386        // add entropy to the pool, number of files created
387        randomAddEntropy((E_PUINT8)&iSize, sizeof(E_INT32));
388
389        eraserSafeAssign(context, context->m_uProgressPercent, 100);
390        eraserUpdateNotify(context);
391
392        // clean up
393        ZeroMemory(uTestBuffer, maxMFTRecordSize);
394
395        return true;
396    }
397
398    return false;
399}
400
401bool
402wipeNTFSFileEntries(CEraserContext *context)
403{
404    if (!isFileSystemNTFS(context->m_piCurrent)) {
405        return false;
406    }
407
408    NTFSContext ntc;
409    bool bResult = false;
410
411    if (initAndOpenVolume(ntc, context->m_strData[0])) {
412        IO_STATUS_BLOCK ioStatus;
413        NTSTATUS status;
414        NTFS_VOLUME_DATA_BUFFER nvd;
415
416        // find out MFT size
417        status = ntc.NtFsControlFile(ntc.m_hVolume, NULL, NULL, 0, &ioStatus,
418                                     FSCTL_GET_VOLUME_INFORMATION,
419                                     NULL, 0, &nvd,
420                                     sizeof(NTFS_VOLUME_DATA_BUFFER));
421
422        if (status == STATUS_SUCCESS) {
423            const E_UINT32  uMaxFilesPerFolder = 3000;
424            const E_UINT32  uMFTPollInterval = 20;
425            const E_UINT32  uFileNameLength = _MAX_FNAME - 14 /*strFolder.GetLength() + 1*/ - 8 - 1;
426
427            HANDLE          hFile, hFind;
428            WIN32_FIND_DATA wfdData;
429            E_UINT32        uSpeed, uTickCount;
430            E_UINT32        i, j;
431            E_UINT32        uFiles = 0, uFolders = 0;
432            E_UINT32        uEstimate;
433            LARGE_INTEGER   uOriginalMFTSize = nvd.MftValidDataLength;
434            CString         strPath, strFolder;
435            CStringArray    saFolders;
436            TCHAR           szPrefix[uFileNameLength + 1];
437
438            try {
439                // prefix each name with a couple of zeros
440                for (i = 0; i < uFileNameLength; i++) {
441                    szPrefix[i] = '0';
442                }
443                szPrefix[uFileNameLength] = 0;
444
445                // approximate the number of files we need to create (at least 1)
446                uEstimate = max(1, (E_UINT32)(nvd.MftValidDataLength.QuadPart / nvd.BytesPerFileRecordSegment));
447
448                if (uEstimate > context->m_uProgressFiles) {
449                    uEstimate -= context->m_uProgressFiles;
450                }
451
452                // this may take a while, so we'll try to estimate the time
453                context->m_uProgressFlags |= eraserDispTime;
454                context->m_uProgressStartTime = GetTickCount();
455
456                eraserBeginNotify(context);
457
458                do {
459                    if (uFiles % uMaxFilesPerFolder == 0) {
460                        strFolder.Format("%c:\\%s%04X", context->m_strData[0],
461                                    ERASER_TEMP_DIRECTORY_NTFS_ENTRIES, uFolders++);
462
463                        // remove possibly existing folder
464                        eraserRemoveFolder((LPVOID)(LPCTSTR)strFolder, (E_UINT16)strFolder.GetLength(),
465                                           ERASER_REMOVE_RECURSIVELY);
466
467                        // create new directory
468                        if (CreateDirectory((LPCTSTR)strFolder, NULL)) {
469                            saFolders.Add(strFolder + "\\");
470                        } else {
471                            eraserAddError(context, IDS_ERROR_TEMPFILE);
472                            break;
473                        }
474                    }
475
476                    strPath.Format("%s\\%s%08X", strFolder, szPrefix, uFiles);
477
478                    hFile = CreateFile((LPCTSTR)strPath, GENERIC_WRITE, 0, NULL, CREATE_NEW, 0, 0);
479
480                    if (hFile != INVALID_HANDLE_VALUE) {
481                        uFiles++;
482                        CloseHandle(hFile);
483                    } else {
484                        eraserAddError(context, IDS_ERROR_TEMPFILE);
485                        break;
486                    }
487
488                    if (uFiles % uMFTPollInterval == 0 || eraserInternalTerminated(context)) {
489                        status = ntc.NtFsControlFile(ntc.m_hVolume, NULL, NULL, 0, &ioStatus,
490                                                     FSCTL_GET_VOLUME_INFORMATION,
491                                                     NULL, 0, &nvd,
492                                                     sizeof(NTFS_VOLUME_DATA_BUFFER));
493
494                        if (eraserInternalTerminated(context)) {
495                            break;
496                        } else {
497                            uTickCount = GetTickCount();
498                            if (uTickCount > context->m_uProgressStartTime) {
499                                uSpeed = (uFiles * 1000) / (uTickCount - context->m_uProgressStartTime);
500
501                                if (uSpeed > 0) {
502                                    context->m_uProgressTimeLeft = ((uEstimate - uFiles) / uSpeed);
503                                } else {
504                                    context->m_uProgressTimeLeft = 0;
505                                }
506                            }
507
508                            eraserSafeAssign(context, context->m_uProgressPercent,
509                                (E_UINT8)min(100, (uFiles * 100) / uEstimate));
510                            setTotalProgress(context);
511                            eraserUpdateNotify(context);
512                        }
513                    }
514                } while (status == STATUS_SUCCESS && nvd.MftValidDataLength.QuadPart <= uOriginalMFTSize.QuadPart);
515                   
516
517                // if we managed to increase MFT size, slack space was filled
518                if (nvd.MftValidDataLength.QuadPart > uOriginalMFTSize.QuadPart) {
519                    bResult = true;
520                    eraserSafeAssign(context, context->m_uProgressPercent, 100);
521                    setTotalProgress(context);
522
523                    // add entropy to the pool, number of files created
524                    randomAddEntropy((E_PUINT8)&uFiles, sizeof(E_UINT32));
525                }
526
527                if (!eraserInternalTerminated(context)) {
528                    // show progress bar while removing files - may take a while
529                    eraserProgressSetMessage(context, ERASER_MESSAGE_REMOVING);
530                    eraserBeginNotify(context);
531                }
532
533                // remove temporary files
534                for (i = 0, j = 0; i < uFolders; i++) {
535                    strFolder = saFolders[i];
536                    hFind = FindFirstFile((LPCTSTR)(strFolder + "*"), &wfdData);
537
538                    if (hFind != INVALID_HANDLE_VALUE) {
539                        do {
540                            if (!bitSet(wfdData.dwFileAttributes, FILE_ATTRIBUTE_DIRECTORY)) {
541                                // eraserRemoveFile is way too slow
542                                DeleteFile((LPCTSTR)(strFolder + wfdData.cFileName));
543
544                                if (!eraserInternalTerminated(context)) {
545                                    eraserSafeAssign(context, context->m_uProgressPercent, (E_UINT8)((++j * 100) / uFiles));
546                                    eraserUpdateNotify(context);
547                                }
548                            }
549                        }
550                        while (FindNextFile(hFind, &wfdData));
551
552                        VERIFY(FindClose(hFind));
553                    }
554
555                    // remove the folder
556                    if (eraserError(eraserRemoveFolder((LPVOID)(LPCTSTR)strFolder,
557                            (E_UINT16)strFolder.GetLength(), ERASER_REMOVE_RECURSIVELY))) {
558                        context->m_saFailed.Add(strFolder);
559                    }
560                }
561
562                if (!eraserInternalTerminated(context)) {
563                    // and we're done
564                    eraserSafeAssign(context, context->m_uProgressPercent, 100);
565                    eraserUpdateNotify(context);
566                }
567            } catch (CException *e) {
568                handleException(e, context);
569                bResult = false;
570            }
571        }
572    }
573
574    if (!bResult) {
575        eraserAddError1(context, IDS_ERROR_DIRENTRIES, (LPCTSTR)context->m_strData);
576    }
577
578    return bResult;
579}
580
581bool
582findAlternateDataStreams(CEraserContext *context, LPCTSTR szFile, DataStreamArray& streams)
583{
584    if (!isFileSystemNTFS(context->m_piCurrent)) {
585        return false;
586    }
587
588    NTFSContext ntc;
589    if (initEntryPoints(ntc)) {
590        bool bResult = false;
591        HANDLE hFile;
592        PFILE_STREAM_INFORMATION psi = 0;
593        NTSTATUS status = STATUS_INVALID_PARAMETER;
594        WCHAR wszStreamName[MAX_PATH];
595        IO_STATUS_BLOCK ioStatus;
596        DataStream ads;
597
598        hFile = CreateFile(szFile,
599                           GENERIC_READ,
600                           FILE_SHARE_READ | FILE_SHARE_WRITE,
601                           NULL,
602                           OPEN_EXISTING,
603                           0, 0);
604
605        if (hFile != INVALID_HANDLE_VALUE) {
606            // use write buffer, should be large enough
607            status = ntc.NtQueryInformationFile(hFile, &ioStatus,
608                                                (PFILE_STREAM_INFORMATION)context->m_puBuffer,
609                                                ERASER_DISK_BUFFER_SIZE,
610                                                FileStreamInformation);
611
612            if (NT_SUCCESS(status)) {
613                try {
614                    psi = (PFILE_STREAM_INFORMATION)context->m_puBuffer;
615
616                    do {
617                        memcpy(wszStreamName, psi->Name, psi->NameLength);
618                        wszStreamName[psi->NameLength / sizeof(WCHAR)] = 0;
619
620                        if (_wcsicmp(wszStreamName, L"::$DATA")) {
621                            // name of the alternate data stream
622                            unicodeToCString(wszStreamName, ads.m_strName);
623                            ads.m_strName = szFile + ads.m_strName;
624
625                            ads.m_uSize = psi->Size.QuadPart;
626                            streams.Add(ads);
627                        }
628
629                        if (psi->NextEntry) {
630                            psi = (PFILE_STREAM_INFORMATION)((E_PUINT8)psi + psi->NextEntry);
631                        } else {
632                            psi = 0;
633                        }
634                    } while (psi);
635
636                    bResult = true;
637                } catch (...) {
638                    ASSERT(0);
639                    bResult = false;
640                }
641            }
642
643            CloseHandle(hFile);
644            return bResult;
645        }
646    }
647    return false;
648}
Note: See TracBrowser for help on using the repository browser.