source: trunk/EraserDll/NTFS.cpp @ 4

Revision 4, 23.5 KB checked in by lowjoel, 7 years ago (diff)

Added the source files from EraserDll? (SVN migration commit 2)

  • 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, 0, NULL);
228
229        if (context->m_hFile != INVALID_HANDLE_VALUE) {
230            try {
231                // scan the location of the file
232                if (wipeClusters(ntc, context, bCompressed)) {
233                    // done with the file handle
234                    CloseHandle(context->m_hFile);
235                    context->m_hFile = INVALID_HANDLE_VALUE;
236
237                    if (!bCompressed) {
238                        // if the file wasn't really compressed, erase normally
239                        uResult = WCF_NOTCOMPRESSED;
240                    } else {
241                        if (eraserOK(eraserRemoveFile((LPVOID)(LPCTSTR)context->m_strData,
242                                (E_UINT16)context->m_strData.GetLength()))) {
243                            uResult = WCF_SUCCESS;
244                        }
245                    }
246                } else {
247                    // close the handle
248                    CloseHandle(context->m_hFile);
249                    context->m_hFile = INVALID_HANDLE_VALUE;
250                }
251            } catch (CException *e) {
252                handleException(e, context);
253                uResult = WCF_FAILURE;
254
255                if (context->m_hFile != INVALID_HANDLE_VALUE) {
256                    CloseHandle(context->m_hFile);
257                    context->m_hFile = INVALID_HANDLE_VALUE;
258                }
259            }
260        }
261    } else {
262        // the user does not have privileges for low-level access
263        uResult = WCF_NOACCESS;
264    }
265
266    return uResult;
267}
268
269
270#define mftFastWriteTest(x) \
271    WriteFile((x)->m_hFile, uTestBuffer, (x)->m_uiFileSize.LowPart, &uTemp, NULL)
272
273bool
274wipeMFTRecords(CEraserContext *context)
275{
276    // On NTFS file system small files can be resident on the MFT record so we
277    // will need to overwrite empty records by creating as many of the largest
278    // sized files as possible (if there is space in the MFT, we'll be able to
279    // create non-zero sized files, where the data is resident in the MFT record)
280
281    if (!isFileSystemNTFS(context->m_piCurrent)) {
282        return false;
283    }
284
285    E_UINT64 uFreeSpace = 0;
286    eraserGetFreeDiskSpace((LPVOID)context->m_piCurrent.m_szDrive,
287                           (E_UINT16)lstrlen(context->m_piCurrent.m_szDrive),
288                           &uFreeSpace);
289
290    if (uFreeSpace == 0) {
291        const E_UINT16 maxMFTRecordSize = 4096;
292
293        TCHAR        szFileName[uShortFileNameLength + 1];
294        E_UINT16     uCounter = 1;
295        E_UINT32     uTestBuffer[maxMFTRecordSize];
296        E_UINT32     uTemp;
297        E_UINT64     ulPrevSize = maxMFTRecordSize;
298        CString      strTemp;
299        CStringArray saList;
300        bool         bCreatedFile;
301
302        // fill test buffer with random data
303        isaacFill((E_PUINT8)uTestBuffer, maxMFTRecordSize);
304
305        // do something with the progress bar to entertain the user
306        eraserDispMFT(context);
307        eraserBeginNotify(context);
308
309        context->m_uClusterSpace = 0;
310
311        do {
312            createRandomShortFileName(szFileName, uCounter++);
313            strTemp.Format("Eraser%s%s", context->m_piCurrent.m_szDrive, szFileName);
314
315            context->m_hFile = CreateFile((LPCTSTR)strTemp,
316                                         GENERIC_WRITE,
317                                         (context->m_uTestMode) ?
318                                            FILE_SHARE_READ | FILE_SHARE_WRITE : 0,
319                                         NULL,
320                                         CREATE_NEW,
321                                         FILE_ATTRIBUTE_HIDDEN | FILE_FLAG_WRITE_THROUGH,
322                                         NULL);
323
324            if (context->m_hFile == INVALID_HANDLE_VALUE) {
325                break;
326            }
327
328            saList.Add(strTemp);
329
330            try {
331                context->m_uiFileStart.QuadPart = 0;
332                context->m_uiFileSize.QuadPart = ulPrevSize;
333                bCreatedFile = false;
334
335                while (context->m_uiFileSize.QuadPart) {
336                    if (eraserInternalTerminated(context)) {
337                        bCreatedFile = false;
338                        break;
339                    }
340
341                    // trying simple write first is much faster than calling the wipe function
342                    if (!mftFastWriteTest(context) || !context->m_lpmMethod->m_pwfFunction(context)) {
343                        eraserProgressSetMessage(context, ERASER_MESSAGE_MFT);
344                        context->m_uiFileSize.QuadPart--;
345                    } else {
346                        strTemp.Format(ERASER_MESSAGE_MFT_WAIT, uCounter);
347                        eraserProgressSetMessage(context, strTemp);
348                        eraserUpdateNotify(context);
349
350                        ulPrevSize = context->m_uiFileSize.QuadPart;
351                        bCreatedFile = true;
352                        break;
353                    }
354
355                    eraserSafeAssign(context, context->m_uProgressPercent,
356                        (E_UINT8)(((maxMFTRecordSize - context->m_uiFileSize.QuadPart) * 100) / maxMFTRecordSize));
357                    setTotalProgress(context);
358                    eraserUpdateNotify(context);
359                }
360            } catch (CException *e) {
361                handleException(e, context);
362                bCreatedFile = false;
363            }
364
365            resetDate(context->m_hFile);
366            CloseHandle(context->m_hFile);
367
368        } while (bCreatedFile);
369
370        eraserSafeAssign(context, context->m_uProgressPercent, 100);
371        setTotalProgress(context);
372
373        eraserProgressSetMessage(context, ERASER_MESSAGE_REMOVING);
374        eraserUpdateNotify(context);
375
376        E_INT32 iSize = saList.GetSize();
377        for (E_INT32 i = 0; i < iSize; i++) {
378            eraserSafeAssign(context, context->m_uProgressPercent, (E_UINT8)((i * 100) / iSize));
379            eraserUpdateNotify(context);
380
381            // file names are already random, no need to use slower eraserRemoveFile
382            DeleteFile((LPCTSTR)saList[i]);
383        }
384
385        // add entropy to the pool, number of files created
386        randomAddEntropy((E_PUINT8)&iSize, sizeof(E_INT32));
387
388        eraserSafeAssign(context, context->m_uProgressPercent, 100);
389        eraserUpdateNotify(context);
390
391        // clean up
392        ZeroMemory(uTestBuffer, maxMFTRecordSize);
393
394        return true;
395    }
396
397    return false;
398}
399
400bool
401wipeNTFSFileEntries(CEraserContext *context)
402{
403    if (!isFileSystemNTFS(context->m_piCurrent)) {
404        return false;
405    }
406
407    NTFSContext ntc;
408    bool bResult = false;
409
410    if (initAndOpenVolume(ntc, context->m_strData[0])) {
411        IO_STATUS_BLOCK ioStatus;
412        NTSTATUS status;
413        NTFS_VOLUME_DATA_BUFFER nvd;
414
415        // find out MFT size
416        status = ntc.NtFsControlFile(ntc.m_hVolume, NULL, NULL, 0, &ioStatus,
417                                     FSCTL_GET_VOLUME_INFORMATION,
418                                     NULL, 0, &nvd,
419                                     sizeof(NTFS_VOLUME_DATA_BUFFER));
420
421        if (status == STATUS_SUCCESS) {
422            const E_UINT32  uMaxFilesPerFolder = 3000;
423            const E_UINT32  uMFTPollInterval = 20;
424            const E_UINT32  uFileNameLength = _MAX_FNAME - 14 /*strFolder.GetLength() + 1*/ - 8 - 1;
425
426            HANDLE          hFile, hFind;
427            WIN32_FIND_DATA wfdData;
428            E_UINT32        uSpeed, uTickCount;
429            E_UINT32        i, j;
430            E_UINT32        uFiles = 0, uFolders = 0;
431            E_UINT32        uEstimate;
432            LARGE_INTEGER   uOriginalMFTSize = nvd.MftValidDataLength;
433            CString         strPath, strFolder;
434            CStringArray    saFolders;
435            TCHAR           szPrefix[uFileNameLength + 1];
436
437            try {
438                // prefix each name with a couple of zeros
439                for (i = 0; i < uFileNameLength; i++) {
440                    szPrefix[i] = '0';
441                }
442                szPrefix[uFileNameLength] = 0;
443
444                // approximate the number of files we need to create (at least 1)
445                uEstimate = max(1, (E_UINT32)(nvd.MftValidDataLength.QuadPart / nvd.BytesPerFileRecordSegment));
446
447                if (uEstimate > context->m_uProgressFiles) {
448                    uEstimate -= context->m_uProgressFiles;
449                }
450
451                // this may take a while, so we'll try to estimate the time
452                context->m_uProgressFlags |= eraserDispTime;
453                context->m_uProgressStartTime = GetTickCount();
454
455                eraserBeginNotify(context);
456
457                do {
458                    if (uFiles % uMaxFilesPerFolder == 0) {
459                        strFolder.Format("%c:\\%s%04X", context->m_strData[0],
460                                    ERASER_TEMP_DIRECTORY_NTFS_ENTRIES, uFolders++);
461
462                        // remove possibly existing folder
463                        eraserRemoveFolder((LPVOID)(LPCTSTR)strFolder, (E_UINT16)strFolder.GetLength(),
464                                           ERASER_REMOVE_RECURSIVELY);
465
466                        // create new directory
467                        if (CreateDirectory((LPCTSTR)strFolder, NULL)) {
468                            saFolders.Add(strFolder + "\\");
469                        } else {
470                            eraserAddError(context, IDS_ERROR_TEMPFILE);
471                            break;
472                        }
473                    }
474
475                    strPath.Format("%s\\%s%08X", strFolder, szPrefix, uFiles);
476
477                    hFile = CreateFile((LPCTSTR)strPath, GENERIC_WRITE, 0, NULL, CREATE_NEW, 0, 0);
478
479                    if (hFile != INVALID_HANDLE_VALUE) {
480                        uFiles++;
481                        CloseHandle(hFile);
482                    } else {
483                        eraserAddError(context, IDS_ERROR_TEMPFILE);
484                        break;
485                    }
486
487                    if (uFiles % uMFTPollInterval == 0 || eraserInternalTerminated(context)) {
488                        status = ntc.NtFsControlFile(ntc.m_hVolume, NULL, NULL, 0, &ioStatus,
489                                                     FSCTL_GET_VOLUME_INFORMATION,
490                                                     NULL, 0, &nvd,
491                                                     sizeof(NTFS_VOLUME_DATA_BUFFER));
492
493                        if (eraserInternalTerminated(context)) {
494                            break;
495                        } else {
496                            uTickCount = GetTickCount();
497                            if (uTickCount > context->m_uProgressStartTime) {
498                                uSpeed = (uFiles * 1000) / (uTickCount - context->m_uProgressStartTime);
499
500                                if (uSpeed > 0) {
501                                    context->m_uProgressTimeLeft = ((uEstimate - uFiles) / uSpeed);
502                                } else {
503                                    context->m_uProgressTimeLeft = 0;
504                                }
505                            }
506
507                            eraserSafeAssign(context, context->m_uProgressPercent,
508                                (E_UINT8)min(100, (uFiles * 100) / uEstimate));
509                            setTotalProgress(context);
510                            eraserUpdateNotify(context);
511                        }
512                    }
513                } while (status == STATUS_SUCCESS && nvd.MftValidDataLength.QuadPart <= uOriginalMFTSize.QuadPart);
514                   
515
516                // if we managed to increase MFT size, slack space was filled
517                if (nvd.MftValidDataLength.QuadPart > uOriginalMFTSize.QuadPart) {
518                    bResult = true;
519                    eraserSafeAssign(context, context->m_uProgressPercent, 100);
520                    setTotalProgress(context);
521
522                    // add entropy to the pool, number of files created
523                    randomAddEntropy((E_PUINT8)&uFiles, sizeof(E_UINT32));
524                }
525
526                if (!eraserInternalTerminated(context)) {
527                    // show progress bar while removing files - may take a while
528                    eraserProgressSetMessage(context, ERASER_MESSAGE_REMOVING);
529                    eraserBeginNotify(context);
530                }
531
532                // remove temporary files
533                for (i = 0, j = 0; i < uFolders; i++) {
534                    strFolder = saFolders[i];
535                    hFind = FindFirstFile((LPCTSTR)(strFolder + "*"), &wfdData);
536
537                    if (hFind != INVALID_HANDLE_VALUE) {
538                        do {
539                            if (!bitSet(wfdData.dwFileAttributes, FILE_ATTRIBUTE_DIRECTORY)) {
540                                // eraserRemoveFile is way too slow
541                                DeleteFile((LPCTSTR)(strFolder + wfdData.cFileName));
542
543                                if (!eraserInternalTerminated(context)) {
544                                    eraserSafeAssign(context, context->m_uProgressPercent, (E_UINT8)((++j * 100) / uFiles));
545                                    eraserUpdateNotify(context);
546                                }
547                            }
548                        }
549                        while (FindNextFile(hFind, &wfdData));
550
551                        VERIFY(FindClose(hFind));
552                    }
553
554                    // remove the folder
555                    if (eraserError(eraserRemoveFolder((LPVOID)(LPCTSTR)strFolder,
556                            (E_UINT16)strFolder.GetLength(), ERASER_REMOVE_RECURSIVELY))) {
557                        context->m_saFailed.Add(strFolder);
558                    }
559                }
560
561                if (!eraserInternalTerminated(context)) {
562                    // and we're done
563                    eraserSafeAssign(context, context->m_uProgressPercent, 100);
564                    eraserUpdateNotify(context);
565                }
566            } catch (CException *e) {
567                handleException(e, context);
568                bResult = false;
569            }
570        }
571    }
572
573    if (!bResult) {
574        eraserAddError1(context, IDS_ERROR_DIRENTRIES, (LPCTSTR)context->m_strData);
575    }
576
577    return bResult;
578}
579
580bool
581findAlternateDataStreams(CEraserContext *context, LPCTSTR szFile, DataStreamArray& streams)
582{
583    if (!isFileSystemNTFS(context->m_piCurrent)) {
584        return false;
585    }
586
587    NTFSContext ntc;
588    if (initEntryPoints(ntc)) {
589        bool bResult = false;
590        HANDLE hFile;
591        PFILE_STREAM_INFORMATION psi = 0;
592        NTSTATUS status = STATUS_INVALID_PARAMETER;
593        WCHAR wszStreamName[MAX_PATH];
594        IO_STATUS_BLOCK ioStatus;
595        DataStream ads;
596
597        hFile = CreateFile(szFile,
598                           GENERIC_READ,
599                           FILE_SHARE_READ | FILE_SHARE_WRITE,
600                           NULL,
601                           OPEN_EXISTING,
602                           0, 0);
603
604        if (hFile != INVALID_HANDLE_VALUE) {
605            // use write buffer, should be large enough
606            status = ntc.NtQueryInformationFile(hFile, &ioStatus,
607                                                (PFILE_STREAM_INFORMATION)context->m_puBuffer,
608                                                ERASER_DISK_BUFFER_SIZE,
609                                                FileStreamInformation);
610
611            if (NT_SUCCESS(status)) {
612                try {
613                    psi = (PFILE_STREAM_INFORMATION)context->m_puBuffer;
614
615                    do {
616                        memcpy(wszStreamName, psi->Name, psi->NameLength);
617                        wszStreamName[psi->NameLength / sizeof(WCHAR)] = 0;
618
619                        if (wcsicmp(wszStreamName, L"::$DATA")) {
620                            // name of the alternate data stream
621                            unicodeToCString(wszStreamName, ads.m_strName);
622                            ads.m_strName = szFile + ads.m_strName;
623
624                            ads.m_uSize = psi->Size.QuadPart;
625                            streams.Add(ads);
626                        }
627
628                        if (psi->NextEntry) {
629                            psi = (PFILE_STREAM_INFORMATION)((E_PUINT8)psi + psi->NextEntry);
630                        } else {
631                            psi = 0;
632                        }
633                    } while (psi);
634
635                    bResult = true;
636                } catch (...) {
637                    ASSERT(0);
638                    bResult = false;
639                }
640            }
641
642            CloseHandle(hFile);
643            return bResult;
644        }
645    }
646    return false;
647}
Note: See TracBrowser for help on using the repository browser.