Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

652 lines
18 KiB

  1. /*++
  2. Copyright (c) 1998-2000 Microsoft Corporation
  3. Module Name:
  4. main.c
  5. Abstract:
  6. TRACELIB dll main file
  7. Author:
  8. 08-Apr-1998 mraghu
  9. Revision History:
  10. --*/
  11. #include <stdio.h>
  12. #include "cpdata.h"
  13. #include "tracectr.h"
  14. // Globals
  15. extern ULONGLONG StartTime;
  16. extern ULONGLONG EndTime;
  17. SYSTEM_RECORD CurrentSystem;
  18. static ULONG lCachedFlushTimer = 1;
  19. PTRACE_CONTEXT_BLOCK TraceContext = NULL;
  20. RTL_CRITICAL_SECTION TLCritSect;
  21. BOOLEAN fDSOnly = FALSE;
  22. ULONGLONG DSStartTime = 0;
  23. ULONGLONG DSEndTime = 0;
  24. ULONG TotalBuffersRead = 0;
  25. WCHAR TempFile[MAXSTR] = L"";
  26. ULONG
  27. WINAPI
  28. TerminateOnBufferCallback(
  29. PEVENT_TRACE_LOGFILE pLog
  30. );
  31. extern WriteProc(
  32. LPWSTR filename,
  33. ULONG flags,
  34. PVOID pUserContext
  35. );
  36. HRESULT
  37. OnProcess(
  38. PTRACE_CONTEXT_BLOCK TraceContext
  39. );
  40. ULONG GetMoreBuffers(
  41. PEVENT_TRACE_LOGFILE logfile
  42. );
  43. void
  44. ReorderThreadList()
  45. {
  46. PLIST_ENTRY Head, Next;
  47. PTHREAD_RECORD Thread;
  48. int i;
  49. PPROCESS_RECORD Process;
  50. for (i=0; i < THREAD_HASH_TABLESIZE; i++) {
  51. Head = &CurrentSystem.ThreadHashList[i];
  52. Next = Head->Flink;
  53. while (Next != Head) {
  54. Thread = CONTAINING_RECORD( Next, THREAD_RECORD, Entry );
  55. Next = Next->Flink;
  56. RemoveEntryList( &Thread->Entry );
  57. Process = Thread->pProcess;
  58. if(Process != NULL){
  59. InsertTailList( &Process->ThreadListHead, &Thread->Entry );
  60. }
  61. }
  62. }
  63. }
  64. ULONG
  65. CPDAPI
  66. GetMaxLoggers()
  67. {
  68. return MAXLOGGERS;
  69. }
  70. ULONG
  71. CPDAPI
  72. InitTraceContextW(
  73. PTRACE_BASIC_INFOW pUserInfo
  74. )
  75. {
  76. UINT i, j;
  77. PFILE_OBJECT *fileTable;
  78. ULONG SizeNeeded, SizeIncrement;
  79. char * pStorage;
  80. HRESULT hr;
  81. BOOL bProcessing = FALSE;
  82. if (pUserInfo == NULL) {
  83. return ERROR_INVALID_DATA;
  84. }
  85. //
  86. // Must provide at least one logfile or a trace seassion to process
  87. //
  88. if ( (pUserInfo->LoggerCount == 0) && (pUserInfo->LogFileCount == 0) ) {
  89. return ERROR_INVALID_DATA;
  90. }
  91. //
  92. // Can not process both RealTime stream and a logfile at the same time
  93. //
  94. if ( (pUserInfo->LoggerCount > 0) && (pUserInfo->LogFileCount > 0) ) {
  95. return ERROR_INVALID_DATA;
  96. }
  97. //
  98. // Compute the Size Needed for allocation.
  99. //
  100. SizeNeeded = sizeof(TRACE_CONTEXT_BLOCK);
  101. // Add LogFileName Strings
  102. for (i = 0; i < pUserInfo->LogFileCount; i++) {
  103. SizeNeeded += sizeof(WCHAR) * ( wcslen( pUserInfo->LogFileName[i] ) + 1);
  104. SizeNeeded = (SizeNeeded + 7) & ~7;
  105. }
  106. // Add LoggerName Strings
  107. for (i = 0; i < pUserInfo->LoggerCount; i++) {
  108. SizeNeeded += sizeof(WCHAR) * ( wcslen(pUserInfo->LoggerName[i]) + 1);
  109. SizeNeeded = (SizeNeeded + 7) & ~7;
  110. }
  111. //
  112. // Add ProcFile, MofFile, DumpFile, SummaryFile, TempFile name strings
  113. if (pUserInfo->ProcFileName != NULL) {
  114. SizeNeeded += sizeof(WCHAR) * (wcslen(pUserInfo->ProcFileName) + 1);
  115. SizeNeeded = (SizeNeeded + 7) & ~7;
  116. }
  117. if (pUserInfo->MofFileName != NULL) {
  118. SizeNeeded += sizeof(WCHAR) * (wcslen(pUserInfo->MofFileName) + 1);
  119. SizeNeeded = (SizeNeeded + 7) & ~7;
  120. }
  121. if (pUserInfo->DumpFileName != NULL) {
  122. SizeNeeded += sizeof(WCHAR) * (wcslen(pUserInfo->DumpFileName) + 1);
  123. SizeNeeded = (SizeNeeded + 7) & ~7;
  124. }
  125. if (pUserInfo->MergeFileName != NULL) {
  126. SizeNeeded += sizeof(WCHAR) * (wcslen(pUserInfo->MergeFileName) + 1);
  127. SizeNeeded = (SizeNeeded + 7) & ~7;
  128. }
  129. if (pUserInfo->CompFileName != NULL) {
  130. SizeNeeded += sizeof(WCHAR) * (wcslen(pUserInfo->CompFileName) + 1);
  131. SizeNeeded = (SizeNeeded + 7) & ~7;
  132. }
  133. if (pUserInfo->SummaryFileName != NULL) {
  134. SizeNeeded += sizeof(WCHAR) * (wcslen(pUserInfo->SummaryFileName) + 1);
  135. SizeNeeded = (SizeNeeded + 7) & ~7;
  136. }
  137. //
  138. // Add Room for the FileTable Caching
  139. //
  140. SizeNeeded += sizeof(PFILE_OBJECT) * MAX_FILE_TABLE_SIZE;
  141. //
  142. // Add Room for Thread Hash List
  143. //
  144. SizeNeeded += sizeof(LIST_ENTRY) * THREAD_HASH_TABLESIZE;
  145. //
  146. // Allocate Memory for TraceContext
  147. //
  148. pStorage = malloc(SizeNeeded);
  149. if (pStorage == NULL) {
  150. return ERROR_OUTOFMEMORY;
  151. }
  152. RtlZeroMemory(pStorage, SizeNeeded);
  153. TraceContext = (PTRACE_CONTEXT_BLOCK)pStorage;
  154. pStorage += sizeof(TRACE_CONTEXT_BLOCK);
  155. //
  156. // Initialize HandleArray
  157. //
  158. for (i=0; i < MAXLOGGERS; i++) {
  159. TraceContext->HandleArray[i] = (TRACEHANDLE)INVALID_HANDLE_VALUE;
  160. }
  161. //
  162. // Copy LogFileNames
  163. //
  164. for (i = 0; i < pUserInfo->LogFileCount; i++) {
  165. TraceContext->LogFileName[i] = (LPWSTR)pStorage;
  166. wcscpy(TraceContext->LogFileName[i], pUserInfo->LogFileName[i]);
  167. SizeIncrement = (wcslen(TraceContext->LogFileName[i]) + 1) * sizeof(WCHAR);
  168. SizeIncrement = (SizeIncrement + 7) & ~7;
  169. pStorage += SizeIncrement;
  170. }
  171. //
  172. // Copy LoggerNames
  173. //
  174. for (i = 0; i < pUserInfo->LoggerCount; i++) {
  175. j = i + pUserInfo->LogFileCount;
  176. TraceContext->LoggerName[j] =(LPWSTR) pStorage;
  177. wcscpy(TraceContext->LoggerName[j], pUserInfo->LoggerName[i]);
  178. SizeIncrement = (wcslen(TraceContext->LoggerName[j]) + 1) * sizeof(WCHAR);
  179. SizeIncrement = (SizeIncrement + 7) & ~7;
  180. pStorage += SizeIncrement;
  181. }
  182. //
  183. // Copy Other File Names
  184. //
  185. if (pUserInfo->ProcFileName != NULL) {
  186. TraceContext->ProcFileName = (LPWSTR)pStorage;
  187. wcscpy( TraceContext->ProcFileName, pUserInfo->ProcFileName);
  188. SizeIncrement = (wcslen(TraceContext->ProcFileName) + 1) * sizeof(WCHAR);
  189. SizeIncrement = (SizeIncrement + 7) & ~7;
  190. pStorage += SizeIncrement;
  191. }
  192. if (pUserInfo->DumpFileName != NULL) {
  193. TraceContext->DumpFileName = (LPWSTR)pStorage;
  194. wcscpy( TraceContext->DumpFileName, pUserInfo->DumpFileName);
  195. SizeIncrement = (wcslen(TraceContext->DumpFileName) + 1) * sizeof(WCHAR);
  196. SizeIncrement = (SizeIncrement + 7) & ~7;
  197. pStorage += SizeIncrement;
  198. }
  199. if (pUserInfo->MofFileName != NULL) {
  200. TraceContext->MofFileName = (LPWSTR)pStorage;
  201. wcscpy( TraceContext->MofFileName, pUserInfo->MofFileName);
  202. SizeIncrement = (wcslen(TraceContext->MofFileName) + 1) * sizeof(WCHAR);
  203. SizeIncrement = (SizeIncrement + 7) & ~7;
  204. pStorage += SizeIncrement;
  205. }
  206. if (pUserInfo->MergeFileName != NULL) {
  207. TraceContext->MergeFileName = (LPWSTR)pStorage;
  208. wcscpy( TraceContext->MergeFileName, pUserInfo->MergeFileName);
  209. SizeIncrement = (wcslen(TraceContext->MergeFileName) + 1) * sizeof(WCHAR);
  210. SizeIncrement = (SizeIncrement + 7) & ~7;
  211. pStorage += SizeIncrement;
  212. }
  213. if (pUserInfo->CompFileName != NULL) {
  214. TraceContext->CompFileName = (LPWSTR)pStorage;
  215. wcscpy( TraceContext->CompFileName, pUserInfo->CompFileName);
  216. SizeIncrement = (wcslen(TraceContext->CompFileName) + 1) * sizeof(WCHAR);
  217. SizeIncrement = (SizeIncrement + 7) & ~7;
  218. pStorage += SizeIncrement;
  219. }
  220. if (pUserInfo->SummaryFileName != NULL) {
  221. TraceContext->SummaryFileName = (LPWSTR)pStorage;
  222. wcscpy( TraceContext->SummaryFileName, pUserInfo->SummaryFileName);
  223. SizeIncrement = (wcslen(TraceContext->SummaryFileName) + 1) * sizeof(WCHAR);
  224. SizeIncrement = (SizeIncrement + 7) & ~7;
  225. pStorage += SizeIncrement;
  226. }
  227. TraceContext->LogFileCount = pUserInfo->LogFileCount;
  228. TraceContext->LoggerCount = pUserInfo->LoggerCount;
  229. TraceContext->StartTime = pUserInfo->StartTime;
  230. TraceContext->EndTime = pUserInfo->EndTime;
  231. TraceContext->Flags = pUserInfo->Flags;
  232. TraceContext->hEvent = pUserInfo->hEvent;
  233. TraceContext->pUserContext = pUserInfo->pUserContext;
  234. RtlZeroMemory(&CurrentSystem, sizeof(SYSTEM_RECORD));
  235. InitializeListHead ( &CurrentSystem.ProcessListHead );
  236. InitializeListHead ( &CurrentSystem.GlobalThreadListHead );
  237. InitializeListHead ( &CurrentSystem.GlobalDiskListHead );
  238. InitializeListHead ( &CurrentSystem.HotFileListHead );
  239. InitializeListHead ( &CurrentSystem.WorkloadListHead );
  240. InitializeListHead ( &CurrentSystem.InstanceListHead );
  241. InitializeListHead ( &CurrentSystem.EventListHead );
  242. InitializeListHead ( &CurrentSystem.GlobalModuleListHead );
  243. InitializeListHead ( &CurrentSystem.ProcessFileListHead );
  244. InitializeListHead ( &CurrentSystem.JobListHead);
  245. CurrentSystem.FileTable = (PFILE_OBJECT *) pStorage;
  246. pStorage += ( sizeof(PFILE_OBJECT) * MAX_FILE_TABLE_SIZE);
  247. CurrentSystem.ThreadHashList = (PLIST_ENTRY)pStorage;
  248. pStorage += (sizeof(LIST_ENTRY) * THREAD_HASH_TABLESIZE);
  249. RtlZeroMemory(CurrentSystem.ThreadHashList, sizeof(LIST_ENTRY) * THREAD_HASH_TABLESIZE);
  250. for (i=0; i < THREAD_HASH_TABLESIZE; i++) {
  251. InitializeListHead (&CurrentSystem.ThreadHashList[i]);
  252. }
  253. if( (pUserInfo->Flags & TRACE_DUMP) && NULL != pUserInfo->DumpFileName ){
  254. TraceContext->Flags |= TRACE_DUMP;
  255. }
  256. if( (pUserInfo->Flags & TRACE_SUMMARY) && NULL != pUserInfo->SummaryFileName ){
  257. TraceContext->Flags |= TRACE_SUMMARY;
  258. }
  259. if( (pUserInfo->Flags & TRACE_INTERPRET) && NULL != pUserInfo->CompFileName ){
  260. TraceContext->Flags |= TRACE_INTERPRET;
  261. }
  262. hr = GetTempName( TempFile, MAXSTR );
  263. CHECK_HR(hr);
  264. CurrentSystem.TempFile = _wfopen( TempFile, L"w+");
  265. if( CurrentSystem.TempFile == NULL ){
  266. hr = GetLastError();
  267. }
  268. CHECK_HR(hr);
  269. CurrentSystem.fNoEndTime = FALSE;
  270. fileTable = CurrentSystem.FileTable;
  271. for ( i= 0; i<MAX_FILE_TABLE_SIZE; i++){ fileTable[i] = NULL; }
  272. //
  273. // Set the default Processing Flags to Dump
  274. //
  275. if( pUserInfo->Flags & TRACE_EXTENDED_FMT ){
  276. TraceContext->Flags |= TRACE_EXTENDED_FMT;
  277. }
  278. if( pUserInfo->Flags & TRACE_REDUCE ) {
  279. TraceContext->Flags |= TRACE_REDUCE;
  280. TraceContext->Flags |= TRACE_LOG_REPORT_BASIC;
  281. }
  282. if (TraceContext->Flags & TRACE_DS_ONLY) {
  283. fDSOnly = TRUE;
  284. DSStartTime = pUserInfo->DSStartTime;
  285. DSEndTime = pUserInfo->DSEndTime;
  286. }
  287. if( TraceContext->Flags & TRACE_MERGE_ETL ){
  288. hr = EtwRelogEtl( TraceContext );
  289. goto cleanup;
  290. }
  291. bProcessing = TRUE;
  292. RtlInitializeCriticalSection(&TLCritSect);
  293. //
  294. // Startup a Thread to update the counters.
  295. // For Logfile replay we burn a thread and throttle it at the
  296. // BufferCallbacks.
  297. //
  298. hr = OnProcess(TraceContext);// Then process Trace Event Data.
  299. ShutdownThreads();
  300. ShutdownProcesses();
  301. ReorderThreadList();
  302. cleanup:
  303. if( ERROR_SUCCESS != hr ){
  304. __try{
  305. if( TraceContext->hDumpFile ){
  306. fclose( TraceContext->hDumpFile );
  307. }
  308. if( bProcessing ){
  309. Cleanup();
  310. RtlDeleteCriticalSection(&TLCritSect);
  311. }
  312. if( CurrentSystem.TempFile != NULL ){
  313. fclose( CurrentSystem.TempFile );
  314. DeleteFile( TempFile );
  315. }
  316. if( NULL != TraceContext ){
  317. free(TraceContext);
  318. TraceContext = NULL;
  319. }
  320. } __except (EXCEPTION_EXECUTE_HANDLER) {
  321. }
  322. }
  323. return hr;
  324. }
  325. // Buffer Callback. Used to send a flag to the logstream processing thread.
  326. //
  327. ULONG
  328. GetMoreBuffers(
  329. PEVENT_TRACE_LOGFILE logfile
  330. )
  331. {
  332. TotalBuffersRead++;
  333. if (TraceContext->hEvent) {
  334. SetEvent(TraceContext->hEvent);
  335. }
  336. //
  337. // While processing logfile playback, we can throttle the processing
  338. // of buffers by the FlushTimer value (in Seconds)
  339. //
  340. if (TraceContext->Flags & TRACE_LOG_REPLAY) {
  341. _sleep(TraceContext->LoggerInfo->FlushTimer * 1000);
  342. }
  343. if(logfile->EventsLost) {
  344. #if DBG
  345. DbgPrint("(TRACECTR) GetMorBuffers(Lost: %9d Filled: %9d\n",
  346. logfile->EventsLost, logfile->Filled );
  347. #endif
  348. }
  349. return (TRUE);
  350. }
  351. ULONG
  352. CPDAPI
  353. DeinitTraceContext(
  354. PTRACE_BASIC_INFOW pUserInfo
  355. )
  356. {
  357. ULONG Status = ERROR_SUCCESS;
  358. ULONG LogFileCount, i;
  359. if (TraceContext == NULL) {
  360. return ERROR_INVALID_HANDLE;
  361. }
  362. LogFileCount = TraceContext->LogFileCount + TraceContext->LoggerCount;
  363. for (i=0; i < LogFileCount; i++) {
  364. if (TraceContext->HandleArray[i] != (TRACEHANDLE)INVALID_HANDLE_VALUE) {
  365. CloseTrace(TraceContext->HandleArray[i]);
  366. TraceContext->HandleArray[i] = (TRACEHANDLE)INVALID_HANDLE_VALUE;
  367. }
  368. }
  369. //
  370. // Write the Summary File
  371. //
  372. if (TraceContext->Flags & TRACE_SUMMARY) {
  373. WriteSummary();
  374. }
  375. if (TraceContext->Flags & TRACE_REDUCE) {
  376. if ((TraceContext->ProcFileName != NULL) &&
  377. (lstrlenW(TraceContext->ProcFileName) ) ){
  378. WriteProc(TraceContext->ProcFileName,
  379. TraceContext->Flags,
  380. TraceContext->pUserContext
  381. );
  382. }
  383. }
  384. if( CurrentSystem.TempFile != NULL ){
  385. fclose(CurrentSystem.TempFile);
  386. DeleteFile( TempFile );
  387. }
  388. if (TraceContext->Flags & TRACE_DUMP) {
  389. if (TraceContext->hDumpFile != NULL) {
  390. fclose(TraceContext->hDumpFile);
  391. }
  392. }
  393. Cleanup();
  394. RtlDeleteCriticalSection(&TLCritSect);
  395. free (TraceContext);
  396. TraceContext = NULL;
  397. return (Status);
  398. }
  399. HRESULT
  400. OnProcess(
  401. PTRACE_CONTEXT_BLOCK TraceContext
  402. )
  403. {
  404. ULONG LogFileCount;
  405. ULONG i;
  406. ULONG Status;
  407. PEVENT_TRACE_LOGFILE LogFile[MAXLOGGERS];
  408. BOOL bRealTime;
  409. SYSTEMTIME stLocalTime;
  410. FILETIME ftLocalTime;
  411. RtlZeroMemory( &LogFile[0], sizeof(PVOID) * MAXLOGGERS );
  412. if( TraceContext->LogFileCount > 0 ){
  413. LogFileCount = TraceContext->LogFileCount;
  414. bRealTime = FALSE;
  415. }else{
  416. LogFileCount = TraceContext->LoggerCount;
  417. bRealTime = TRUE;
  418. }
  419. for (i = 0; i < LogFileCount; i++) {
  420. LogFile[i] = malloc(sizeof(EVENT_TRACE_LOGFILE));
  421. if (LogFile[i] == NULL) {
  422. Status = ERROR_OUTOFMEMORY;
  423. goto cleanup;
  424. }
  425. RtlZeroMemory(LogFile[i], sizeof(EVENT_TRACE_LOGFILE));
  426. LogFile[i]->BufferCallback = (PEVENT_TRACE_BUFFER_CALLBACK)&TerminateOnBufferCallback;
  427. if( bRealTime ){
  428. LogFile[i]->LoggerName = TraceContext->LoggerName[i];
  429. LogFile[i]->LogFileMode = EVENT_TRACE_REAL_TIME_MODE;
  430. }else{
  431. LogFile[i]->LogFileName = TraceContext->LogFileName[i];
  432. }
  433. }
  434. for (i = 0; i < LogFileCount; i++) {
  435. TraceContext->HandleArray[i] = OpenTrace(LogFile[i]);;
  436. if ((TRACEHANDLE)INVALID_HANDLE_VALUE == TraceContext->HandleArray[i] ) {
  437. Status = GetLastError();
  438. goto cleanup;
  439. }
  440. Status = ProcessTrace( &(TraceContext->HandleArray[i]), 1, NULL, NULL);
  441. if( ERROR_CANCELLED != Status && ERROR_SUCCESS != Status ){
  442. goto cleanup;
  443. }
  444. }
  445. for (i = 0; i < LogFileCount; i++){
  446. Status = CloseTrace(TraceContext->HandleArray[i]);
  447. }
  448. for (i=0; i<LogFileCount; i++) {
  449. LogFile[i]->BufferCallback = (PEVENT_TRACE_BUFFER_CALLBACK)&GetMoreBuffers;
  450. LogFile[i]->EventCallback = (PEVENT_CALLBACK)GeneralEventCallback;
  451. TraceContext->HandleArray[i] = OpenTrace( (PEVENT_TRACE_LOGFILE)LogFile[i]);
  452. if ( TraceContext->HandleArray[i] == (TRACEHANDLE)INVALID_HANDLE_VALUE) {
  453. Status = GetLastError();
  454. goto cleanup;
  455. }
  456. }
  457. if( TraceContext->Flags & TRACE_DUMP ){
  458. FILE* f = _wfopen ( TraceContext->DumpFileName, L"w" );
  459. if( f == NULL) {
  460. Status = GetLastError();
  461. goto cleanup;
  462. }
  463. if( TraceContext->Flags & TRACE_EXTENDED_FMT ){
  464. _ftprintf( f,
  465. _T("%12s, %10s, %8s,%8s,%8s,%7s,%21s,%11s,%11s, User Data\n"),
  466. _T("Event Name"), _T("Type"),
  467. _T("Type"), _T("Level"), _T("Version"),
  468. _T("TID"), _T("Clock-Time"),
  469. _T("Kernel(ms)"), _T("User(ms)")
  470. );
  471. }else{
  472. _ftprintf( f,
  473. _T("%12s, %10s,%7s,%21s,%11s,%11s, User Data\n"),
  474. _T("Event Name"), _T("Type"), _T("TID"), _T("Clock-Time"),
  475. _T("Kernel(ms)"), _T("User(ms)")
  476. );
  477. }
  478. TraceContext->hDumpFile = f;
  479. }
  480. DeclareKernelEvents();
  481. GetLocalTime (&stLocalTime);
  482. SystemTimeToFileTime (&stLocalTime, &ftLocalTime);
  483. StartTime =
  484. (((ULONGLONG) ftLocalTime.dwHighDateTime) << 32) +
  485. ftLocalTime.dwLowDateTime;
  486. Status = ProcessTrace(TraceContext->HandleArray,
  487. LogFileCount,
  488. NULL,
  489. NULL);
  490. if( 0 == EndTime ){
  491. GetLocalTime (&stLocalTime);
  492. SystemTimeToFileTime (&stLocalTime, &ftLocalTime);
  493. EndTime =
  494. (((ULONGLONG) ftLocalTime.dwHighDateTime) << 32) +
  495. ftLocalTime.dwLowDateTime;
  496. }
  497. if( bRealTime && (ERROR_WMI_INSTANCE_NOT_FOUND == Status) ){
  498. Status = ERROR_SUCCESS;
  499. }
  500. CurrentSystem.ElapseTime = (ULONG) ( CurrentSystem.EndTime
  501. - CurrentSystem.StartTime);
  502. cleanup:
  503. for (i=0; i < LogFileCount; i++){
  504. if( (TRACEHANDLE)INVALID_HANDLE_VALUE != TraceContext->HandleArray[i] ){
  505. CloseTrace(TraceContext->HandleArray[i]);
  506. TraceContext->HandleArray[i] = (TRACEHANDLE)INVALID_HANDLE_VALUE;
  507. }
  508. if( NULL != LogFile[i] ){
  509. free(LogFile[i]);
  510. }
  511. }
  512. return Status;
  513. }