xref: /reactos/dll/win32/kernel32/client/toolhelp.c (revision c2c66aff)
1 /*
2  * COPYRIGHT:       See COPYING in the top level directory
3  * PROJECT:         ReactOS system libraries
4  * FILE:            dll/win32/kernel32/client/toolhelp.c
5  * PURPOSE:         Toolhelp functions
6  * PROGRAMMER:      Thomas Weidenmueller <w3seek@reactos.com>
7  *                  Robert Dickenson (robd@mok.lvcm.com)
8  *
9  * NOTES:           Do NOT use the heap functions in here because they
10  *                  adulterate the heap statistics!
11  *
12  * UPDATE HISTORY:
13  *                  10/30/2004 Implemented some parts (w3)
14  *                             Inspired by the book "Windows NT Native API"
15  *                  Created 05 January 2003 (robd)
16  */
17 
18 #include <k32.h>
19 
20 #define NDEBUG
21 #include <debug.h>
22 
23 /* INTERNAL DEFINITIONS *******************************************************/
24 
25 typedef struct _RTLP_HEAP_ENTRY
26 {
27     ULONG Size;
28     USHORT Flags;
29     USHORT Unknown1; /* FIXME */
30     ULONG Unknown2; /* FIXME */
31     PVOID Address;
32 } RTLP_HEAP_ENTRY, *PRTLP_HEAP_ENTRY;
33 
34 #define CHECK_PARAM_SIZE(ptr, siz)                                             \
35   if((ptr) == NULL || (ptr)->dwSize != (siz))                                  \
36   {                                                                            \
37     SetLastError(ERROR_INVALID_PARAMETER);                                     \
38     return FALSE;                                                              \
39   }
40 
41 /*
42  * Tests in win showed that the dwSize field can be greater than the actual size
43  * of the structure for the ansi functions. I found this out by accidently
44  * forgetting to set the dwSize field in a test application and it just didn't
45  * work in ros but in win.
46  */
47 
48 #define CHECK_PARAM_SIZEA(ptr, siz)                                            \
49   if((ptr) == NULL || (ptr)->dwSize < (siz))                                   \
50   {                                                                            \
51     SetLastError(ERROR_INVALID_PARAMETER);                                     \
52     return FALSE;                                                              \
53   }
54 
55 #define OffsetToPtr(Snapshot, Offset)                                          \
56   ((ULONG_PTR)((Snapshot) + 1) + (ULONG_PTR)(Offset))
57 
58 typedef struct _TH32SNAPSHOT
59 {
60   /* Heap list */
61   ULONG HeapListCount;
62   ULONG HeapListIndex;
63   ULONG_PTR HeapListOffset;
64   /* Module list */
65   ULONG ModuleListCount;
66   ULONG ModuleListIndex;
67   ULONG_PTR ModuleListOffset;
68   /* Process list */
69   ULONG ProcessListCount;
70   ULONG ProcessListIndex;
71   ULONG_PTR ProcessListOffset;
72   /* Thread list */
73   ULONG ThreadListCount;
74   ULONG ThreadListIndex;
75   ULONG_PTR ThreadListOffset;
76 } TH32SNAPSHOT, *PTH32SNAPSHOT;
77 
78 /* INTERNAL FUNCTIONS *********************************************************/
79 
80 static VOID
TH32FreeAllocatedResources(PRTL_DEBUG_INFORMATION HeapDebug,PRTL_DEBUG_INFORMATION ModuleDebug,PVOID ProcThrdInfo,SIZE_T ProcThrdInfoSize)81 TH32FreeAllocatedResources(PRTL_DEBUG_INFORMATION HeapDebug,
82                            PRTL_DEBUG_INFORMATION ModuleDebug,
83                            PVOID ProcThrdInfo,
84                            SIZE_T ProcThrdInfoSize)
85 {
86   if(HeapDebug != NULL)
87   {
88     RtlDestroyQueryDebugBuffer(HeapDebug);
89   }
90   if(ModuleDebug != NULL)
91   {
92     RtlDestroyQueryDebugBuffer(ModuleDebug);
93   }
94 
95   if(ProcThrdInfo != NULL)
96   {
97     NtFreeVirtualMemory(NtCurrentProcess(),
98                         &ProcThrdInfo,
99                         &ProcThrdInfoSize,
100                         MEM_RELEASE);
101   }
102 }
103 
104 static NTSTATUS
TH32CreateSnapshot(DWORD dwFlags,DWORD th32ProcessID,PRTL_DEBUG_INFORMATION * HeapDebug,PRTL_DEBUG_INFORMATION * ModuleDebug,PVOID * ProcThrdInfo,SIZE_T * ProcThrdInfoSize)105 TH32CreateSnapshot(DWORD dwFlags,
106                    DWORD th32ProcessID,
107                    PRTL_DEBUG_INFORMATION *HeapDebug,
108                    PRTL_DEBUG_INFORMATION *ModuleDebug,
109                    PVOID *ProcThrdInfo,
110                    SIZE_T *ProcThrdInfoSize)
111 {
112   NTSTATUS Status = STATUS_SUCCESS;
113 
114   *HeapDebug = NULL;
115   *ModuleDebug = NULL;
116   *ProcThrdInfo = NULL;
117   *ProcThrdInfoSize = 0;
118 
119   /*
120    * Allocate the debug information for a heap snapshot
121    */
122   if(dwFlags & TH32CS_SNAPHEAPLIST)
123   {
124     *HeapDebug = RtlCreateQueryDebugBuffer(0, FALSE);
125     if(*HeapDebug != NULL)
126     {
127       Status = RtlQueryProcessDebugInformation(th32ProcessID,
128                                                RTL_DEBUG_QUERY_HEAPS,
129                                                *HeapDebug);
130     }
131     else
132       Status = STATUS_UNSUCCESSFUL;
133   }
134 
135   /*
136    * Allocate the debug information for a module snapshot
137    */
138   if(dwFlags & TH32CS_SNAPMODULE &&
139      NT_SUCCESS(Status))
140   {
141     *ModuleDebug = RtlCreateQueryDebugBuffer(0, FALSE);
142     if(*ModuleDebug != NULL)
143     {
144       Status = RtlQueryProcessDebugInformation(th32ProcessID,
145                                                RTL_DEBUG_QUERY_MODULES,
146                                                *ModuleDebug);
147     }
148     else
149       Status = STATUS_UNSUCCESSFUL;
150   }
151 
152   /*
153    * Allocate enough memory for the system's process list
154    */
155 
156   if(dwFlags & (TH32CS_SNAPPROCESS | TH32CS_SNAPTHREAD) &&
157      NT_SUCCESS(Status))
158   {
159     for(;;)
160     {
161       (*ProcThrdInfoSize) += 0x10000;
162       Status = NtAllocateVirtualMemory(NtCurrentProcess(),
163                                        ProcThrdInfo,
164                                        0,
165                                        ProcThrdInfoSize,
166                                        MEM_COMMIT,
167                                        PAGE_READWRITE);
168       if(!NT_SUCCESS(Status))
169       {
170         break;
171       }
172 
173       Status = NtQuerySystemInformation(SystemProcessInformation,
174                                         *ProcThrdInfo,
175                                         *ProcThrdInfoSize,
176                                         NULL);
177       if(Status == STATUS_INFO_LENGTH_MISMATCH)
178       {
179         NtFreeVirtualMemory(NtCurrentProcess(),
180                             ProcThrdInfo,
181                             ProcThrdInfoSize,
182                             MEM_RELEASE);
183         *ProcThrdInfo = NULL;
184       }
185       else
186       {
187         break;
188       }
189     }
190   }
191 
192   /*
193    * Free resources in case of failure!
194    */
195 
196   if(!NT_SUCCESS(Status))
197   {
198     TH32FreeAllocatedResources(*HeapDebug,
199                                *ModuleDebug,
200                                *ProcThrdInfo,
201                                *ProcThrdInfoSize);
202   }
203 
204   return Status;
205 }
206 
207 static NTSTATUS
TH32CreateSnapshotSectionInitialize(DWORD dwFlags,DWORD th32ProcessID,PRTL_DEBUG_INFORMATION HeapDebug,PRTL_DEBUG_INFORMATION ModuleDebug,PVOID ProcThrdInfo,HANDLE * SectionHandle)208 TH32CreateSnapshotSectionInitialize(DWORD dwFlags,
209                                     DWORD th32ProcessID,
210                                     PRTL_DEBUG_INFORMATION HeapDebug,
211                                     PRTL_DEBUG_INFORMATION ModuleDebug,
212                                     PVOID ProcThrdInfo,
213                                     HANDLE *SectionHandle)
214 {
215   PSYSTEM_PROCESS_INFORMATION ProcessInfo;
216   LPHEAPLIST32 HeapListEntry;
217   LPMODULEENTRY32W ModuleListEntry;
218   LPPROCESSENTRY32W ProcessListEntry;
219   LPTHREADENTRY32 ThreadListEntry;
220   OBJECT_ATTRIBUTES ObjectAttributes;
221   LARGE_INTEGER SSize, SOffset;
222   HANDLE hSection;
223   PTH32SNAPSHOT Snapshot;
224   ULONG_PTR DataOffset;
225   SIZE_T ViewSize;
226   ULONG i, nProcesses = 0, nThreads = 0, nHeaps = 0, nModules = 0;
227   ULONG RequiredSnapshotSize = sizeof(TH32SNAPSHOT);
228   PRTL_PROCESS_HEAPS hi = NULL;
229   PRTL_PROCESS_MODULES mi = NULL;
230   NTSTATUS Status = STATUS_SUCCESS;
231 
232   /*
233    * Determine the required size for the heap snapshot
234    */
235   if(dwFlags & TH32CS_SNAPHEAPLIST)
236   {
237     hi = (PRTL_PROCESS_HEAPS)HeapDebug->Heaps;
238     nHeaps = hi->NumberOfHeaps;
239     RequiredSnapshotSize += nHeaps * sizeof(HEAPLIST32);
240   }
241 
242   /*
243    * Determine the required size for the module snapshot
244    */
245   if(dwFlags & TH32CS_SNAPMODULE)
246   {
247     mi = (PRTL_PROCESS_MODULES)ModuleDebug->Modules;
248     nModules = mi->NumberOfModules;
249     RequiredSnapshotSize += nModules * sizeof(MODULEENTRY32W);
250   }
251 
252   /*
253    * Determine the required size for the processes and threads snapshot
254    */
255   if(dwFlags & (TH32CS_SNAPPROCESS | TH32CS_SNAPTHREAD))
256   {
257     ULONG ProcOffset = 0;
258 
259     ProcessInfo = (PSYSTEM_PROCESS_INFORMATION)ProcThrdInfo;
260     do
261     {
262       ProcessInfo = (PSYSTEM_PROCESS_INFORMATION)((ULONG_PTR)ProcessInfo + ProcOffset);
263       nProcesses++;
264       nThreads += ProcessInfo->NumberOfThreads;
265       ProcOffset = ProcessInfo->NextEntryOffset;
266     } while(ProcOffset != 0);
267 
268     if(dwFlags & TH32CS_SNAPPROCESS)
269     {
270       RequiredSnapshotSize += nProcesses * sizeof(PROCESSENTRY32W);
271     }
272     if(dwFlags & TH32CS_SNAPTHREAD)
273     {
274       RequiredSnapshotSize += nThreads * sizeof(THREADENTRY32);
275     }
276   }
277 
278   /*
279    * Create and map the section
280    */
281 
282   SSize.QuadPart = RequiredSnapshotSize;
283 
284   InitializeObjectAttributes(&ObjectAttributes,
285                              NULL,
286                              ((dwFlags & TH32CS_INHERIT) ? OBJ_INHERIT : 0),
287 			     NULL,
288 			     NULL);
289 
290   Status = NtCreateSection(&hSection,
291                            SECTION_ALL_ACCESS,
292                            &ObjectAttributes,
293                            &SSize,
294                            PAGE_READWRITE,
295                            SEC_COMMIT,
296                            NULL);
297   if(!NT_SUCCESS(Status))
298   {
299     return Status;
300   }
301 
302   SOffset.QuadPart = 0;
303   ViewSize = 0;
304   Snapshot = NULL;
305 
306   Status = NtMapViewOfSection(hSection,
307                               NtCurrentProcess(),
308                               (PVOID*)&Snapshot,
309                               0,
310                               0,
311                               &SOffset,
312                               &ViewSize,
313                               ViewShare,
314                               0,
315                               PAGE_READWRITE);
316   if(!NT_SUCCESS(Status))
317   {
318     NtClose(hSection);
319     return Status;
320   }
321 
322   RtlZeroMemory(Snapshot, sizeof(TH32SNAPSHOT));
323   DataOffset = 0;
324 
325   /*
326    * Initialize the section data and fill it with all the data we collected
327    */
328 
329   /* initialize the heap list */
330   if(dwFlags & TH32CS_SNAPHEAPLIST)
331   {
332     Snapshot->HeapListCount = nHeaps;
333     Snapshot->HeapListOffset = DataOffset;
334     HeapListEntry = (LPHEAPLIST32)OffsetToPtr(Snapshot, DataOffset);
335     for(i = 0; i < nHeaps; i++)
336     {
337       HeapListEntry->dwSize = sizeof(HEAPLIST32);
338       HeapListEntry->th32ProcessID = th32ProcessID;
339       HeapListEntry->th32HeapID = (ULONG_PTR)hi->Heaps[i].BaseAddress;
340       HeapListEntry->dwFlags = hi->Heaps[i].Flags;
341 
342       HeapListEntry++;
343     }
344 
345     DataOffset += hi->NumberOfHeaps * sizeof(HEAPLIST32);
346   }
347 
348   /* initialize the module list */
349   if(dwFlags & TH32CS_SNAPMODULE)
350   {
351     Snapshot->ModuleListCount = nModules;
352     Snapshot->ModuleListOffset = DataOffset;
353     ModuleListEntry = (LPMODULEENTRY32W)OffsetToPtr(Snapshot, DataOffset);
354     for(i = 0; i < nModules; i++)
355     {
356       ModuleListEntry->dwSize = sizeof(MODULEENTRY32W);
357       ModuleListEntry->th32ModuleID = 1; /* no longer used, always set to one! */
358       ModuleListEntry->th32ProcessID = th32ProcessID;
359       ModuleListEntry->GlblcntUsage = mi->Modules[i].LoadCount;
360       ModuleListEntry->ProccntUsage = mi->Modules[i].LoadCount;
361       ModuleListEntry->modBaseAddr = (BYTE*)mi->Modules[i].ImageBase;
362       ModuleListEntry->modBaseSize = mi->Modules[i].ImageSize;
363       ModuleListEntry->hModule = (HMODULE)mi->Modules[i].ImageBase;
364 
365       MultiByteToWideChar(CP_ACP,
366                           0,
367                           &mi->Modules[i].FullPathName[mi->Modules[i].OffsetToFileName],
368                           -1,
369                           ModuleListEntry->szModule,
370                           sizeof(ModuleListEntry->szModule) / sizeof(ModuleListEntry->szModule[0]));
371 
372       MultiByteToWideChar(CP_ACP,
373                           0,
374                           mi->Modules[i].FullPathName,
375                           -1,
376                           ModuleListEntry->szExePath,
377                           sizeof(ModuleListEntry->szExePath) / sizeof(ModuleListEntry->szExePath[0]));
378 
379       ModuleListEntry++;
380     }
381 
382     DataOffset += mi->NumberOfModules * sizeof(MODULEENTRY32W);
383   }
384 
385   /* initialize the process list */
386   if(dwFlags & TH32CS_SNAPPROCESS)
387   {
388     ULONG ProcOffset = 0;
389 
390     Snapshot->ProcessListCount = nProcesses;
391     Snapshot->ProcessListOffset = DataOffset;
392     ProcessListEntry = (LPPROCESSENTRY32W)OffsetToPtr(Snapshot, DataOffset);
393     ProcessInfo = (PSYSTEM_PROCESS_INFORMATION)ProcThrdInfo;
394     do
395     {
396       ProcessInfo = (PSYSTEM_PROCESS_INFORMATION)((ULONG_PTR)ProcessInfo + ProcOffset);
397 
398       ProcessListEntry->dwSize = sizeof(PROCESSENTRY32W);
399       ProcessListEntry->cntUsage = 0; /* no longer used */
400       ProcessListEntry->th32ProcessID = (ULONG_PTR)ProcessInfo->UniqueProcessId;
401       ProcessListEntry->th32DefaultHeapID = 0; /* no longer used */
402       ProcessListEntry->th32ModuleID = 0; /* no longer used */
403       ProcessListEntry->cntThreads = ProcessInfo->NumberOfThreads;
404       ProcessListEntry->th32ParentProcessID = (ULONG_PTR)ProcessInfo->InheritedFromUniqueProcessId;
405       ProcessListEntry->pcPriClassBase = ProcessInfo->BasePriority;
406       ProcessListEntry->dwFlags = 0; /* no longer used */
407       if(ProcessInfo->ImageName.Buffer != NULL)
408       {
409         ULONG ExeFileLength = min(ProcessInfo->ImageName.Length,
410                                   sizeof(ProcessListEntry->szExeFile) - sizeof(WCHAR));
411         RtlCopyMemory(ProcessListEntry->szExeFile,
412                       ProcessInfo->ImageName.Buffer,
413                       ExeFileLength);
414         ProcessListEntry->szExeFile[ExeFileLength / sizeof(WCHAR)] = UNICODE_NULL;
415       }
416       else
417       {
418         lstrcpyW(ProcessListEntry->szExeFile, L"[System Process]");
419       }
420 
421       ProcessListEntry++;
422 
423       ProcOffset = ProcessInfo->NextEntryOffset;
424     } while(ProcOffset != 0);
425 
426     DataOffset += nProcesses * sizeof(PROCESSENTRY32W);
427   }
428 
429   /* initialize the thread list */
430   if(dwFlags & TH32CS_SNAPTHREAD)
431   {
432     ULONG ProcOffset = 0;
433 
434     Snapshot->ThreadListCount = nThreads;
435     Snapshot->ThreadListOffset = DataOffset;
436     ThreadListEntry = (LPTHREADENTRY32)OffsetToPtr(Snapshot, DataOffset);
437     ProcessInfo = (PSYSTEM_PROCESS_INFORMATION)ProcThrdInfo;
438     do
439     {
440       PSYSTEM_THREAD_INFORMATION ThreadInfo;
441       ULONG n;
442 
443       ProcessInfo = (PSYSTEM_PROCESS_INFORMATION)((ULONG_PTR)ProcessInfo + ProcOffset);
444       ThreadInfo = (PSYSTEM_THREAD_INFORMATION)(ProcessInfo + 1);
445 
446       for(n = 0; n < ProcessInfo->NumberOfThreads; n++)
447       {
448         ThreadListEntry->dwSize = sizeof(THREADENTRY32);
449         ThreadListEntry->cntUsage = 0; /* no longer used */
450         ThreadListEntry->th32ThreadID = (ULONG_PTR)ThreadInfo->ClientId.UniqueThread;
451         ThreadListEntry->th32OwnerProcessID = (ULONG_PTR)ThreadInfo->ClientId.UniqueProcess;
452         ThreadListEntry->tpBasePri = ThreadInfo->BasePriority;
453         ThreadListEntry->tpDeltaPri = 0; /* no longer used */
454         ThreadListEntry->dwFlags = 0; /* no longer used */
455 
456         ThreadInfo++;
457         ThreadListEntry++;
458       }
459 
460       ProcOffset = ProcessInfo->NextEntryOffset;
461     } while(ProcOffset != 0);
462   }
463 
464   /*
465    * We're done, unmap the view and return the section handle
466    */
467 
468   Status = NtUnmapViewOfSection(NtCurrentProcess(), (PVOID)Snapshot);
469 
470   if(NT_SUCCESS(Status))
471   {
472     *SectionHandle = hSection;
473   }
474   else
475   {
476     NtClose(hSection);
477   }
478 
479   return Status;
480 }
481 
482 /* PUBLIC FUNCTIONS ***********************************************************/
483 
484 /*
485  * @implemented
486  */
487 BOOL
488 WINAPI
Heap32First(LPHEAPENTRY32 lphe,DWORD th32ProcessID,DWORD th32HeapID)489 Heap32First(LPHEAPENTRY32 lphe, DWORD th32ProcessID, DWORD th32HeapID)
490 {
491   PRTL_DEBUG_INFORMATION DebugInfo;
492   PRTL_HEAP_INFORMATION Heap;
493   PRTLP_HEAP_ENTRY Block, LastBlock;
494   ULONG i;
495   NTSTATUS Status;
496 
497   CHECK_PARAM_SIZE(lphe, sizeof(HEAPENTRY32));
498 
499   DebugInfo = RtlCreateQueryDebugBuffer(0,
500                                         FALSE);
501   if (DebugInfo == NULL)
502   {
503     SetLastError(ERROR_NOT_ENOUGH_MEMORY);
504     return FALSE;
505   }
506 
507   Status = RtlQueryProcessDebugInformation(th32ProcessID,
508                                            RTL_DEBUG_QUERY_HEAPS | RTL_DEBUG_QUERY_HEAP_BLOCKS,
509                                            DebugInfo);
510 
511   if (NT_SUCCESS(Status))
512   {
513     Status = STATUS_NO_MORE_FILES;
514 
515     for (i = 0;
516          i != DebugInfo->Heaps->NumberOfHeaps;
517          i++)
518     {
519       Heap = &DebugInfo->Heaps->Heaps[i];
520 
521       if ((ULONG_PTR)Heap->BaseAddress == th32HeapID)
522       {
523         lphe->hHandle = (HANDLE)Heap->BaseAddress;
524         lphe->dwAddress = 0;
525         lphe->dwBlockSize = 0;
526         lphe->dwFlags = 0;
527         lphe->dwLockCount = 0;
528         lphe->dwResvd = 0;
529         lphe->th32ProcessID = th32ProcessID;
530         lphe->th32HeapID = (ULONG_PTR)Heap->BaseAddress;
531 
532         Block = (PRTLP_HEAP_ENTRY)Heap->Entries;
533         LastBlock = Block + Heap->NumberOfEntries;
534 
535         while (Block != LastBlock && (Block->Flags & PROCESS_HEAP_UNCOMMITTED_RANGE))
536         {
537           lphe->dwResvd++;
538           lphe->dwAddress = (ULONG_PTR)((ULONG_PTR)Block->Address + Heap->EntryOverhead);
539           Block++;
540         }
541 
542         if (Block != LastBlock && lphe->dwResvd != 0)
543         {
544           lphe->dwBlockSize =  Block->Size;
545 
546           if (Block->Flags & 0x2F1) /* FIXME */
547             lphe->dwFlags = LF32_FIXED;
548           else if (Block->Flags & 0x20) /* FIXME */
549             lphe->dwFlags = LF32_MOVEABLE;
550           else if (Block->Flags & 0x100) /* FIXME */
551             lphe->dwFlags = LF32_FREE;
552 
553           Status = STATUS_SUCCESS;
554         }
555 
556         break;
557       }
558     }
559   }
560 
561   RtlDestroyQueryDebugBuffer(DebugInfo);
562 
563   if (!NT_SUCCESS(Status))
564   {
565     BaseSetLastNTError(Status);
566     return FALSE;
567   }
568 
569   return TRUE;
570 }
571 
572 
573 /*
574  * @implemented
575  */
576 BOOL
577 WINAPI
Heap32Next(LPHEAPENTRY32 lphe)578 Heap32Next(LPHEAPENTRY32 lphe)
579 {
580   PRTL_DEBUG_INFORMATION DebugInfo;
581   PRTL_HEAP_INFORMATION Heap;
582   PRTLP_HEAP_ENTRY Block, LastBlock;
583   BOOLEAN FoundUncommitted = FALSE;
584   ULONG i;
585   NTSTATUS Status;
586 
587   CHECK_PARAM_SIZE(lphe, sizeof(HEAPENTRY32));
588 
589   DebugInfo = RtlCreateQueryDebugBuffer(0,
590                                         FALSE);
591   if (DebugInfo == NULL)
592   {
593     SetLastError(ERROR_NOT_ENOUGH_MEMORY);
594     return FALSE;
595   }
596 
597   Status = RtlQueryProcessDebugInformation(lphe->th32ProcessID,
598                                            RTL_DEBUG_QUERY_HEAPS | RTL_DEBUG_QUERY_HEAP_BLOCKS,
599                                            DebugInfo);
600 
601   if (NT_SUCCESS(Status))
602   {
603     Status = STATUS_NO_MORE_FILES;
604 
605     for (i = 0;
606          i != DebugInfo->Heaps->NumberOfHeaps;
607          i++)
608     {
609       Heap = &DebugInfo->Heaps->Heaps[i];
610 
611       if ((ULONG_PTR)Heap->BaseAddress == lphe->th32HeapID)
612       {
613         if (++lphe->dwResvd < Heap->NumberOfEntries)
614         {
615           lphe->dwFlags = 0;
616 
617           Block = (PRTLP_HEAP_ENTRY)Heap->Entries + lphe->dwResvd;
618           LastBlock = (PRTLP_HEAP_ENTRY)Heap->Entries + Heap->NumberOfEntries;
619 
620           while (Block < LastBlock && (Block->Flags & PROCESS_HEAP_UNCOMMITTED_RANGE))
621           {
622             lphe->dwResvd++;
623             lphe->dwAddress = (ULONG_PTR)((ULONG_PTR)Block->Address + Heap->EntryOverhead);
624             FoundUncommitted = TRUE;
625             Block++;
626           }
627 
628           if (Block < LastBlock)
629           {
630             if (!FoundUncommitted)
631               lphe->dwAddress += lphe->dwBlockSize;
632 
633             lphe->dwBlockSize =  Block->Size;
634 
635             if (Block->Flags & 0x2F1) /* FIXME */
636               lphe->dwFlags = LF32_FIXED;
637             else if (Block->Flags & 0x20) /* FIXME */
638               lphe->dwFlags = LF32_MOVEABLE;
639             else if (Block->Flags & 0x100) /* FIXME */
640               lphe->dwFlags = LF32_FREE;
641 
642             Status = STATUS_SUCCESS;
643           }
644         }
645 
646         break;
647       }
648     }
649   }
650 
651   RtlDestroyQueryDebugBuffer(DebugInfo);
652 
653   if (!NT_SUCCESS(Status))
654   {
655     BaseSetLastNTError(Status);
656     return FALSE;
657   }
658 
659   return TRUE;
660 
661 }
662 
663 
664 /*
665  * @implemented
666  */
667 BOOL
668 WINAPI
Heap32ListFirst(HANDLE hSnapshot,LPHEAPLIST32 lphl)669 Heap32ListFirst(HANDLE hSnapshot, LPHEAPLIST32 lphl)
670 {
671   PTH32SNAPSHOT Snapshot;
672   LARGE_INTEGER SOffset;
673   SIZE_T ViewSize;
674   NTSTATUS Status;
675 
676   CHECK_PARAM_SIZE(lphl, sizeof(HEAPLIST32));
677 
678   SOffset.QuadPart = 0;
679   ViewSize = 0;
680   Snapshot = NULL;
681 
682   Status = NtMapViewOfSection(hSnapshot,
683                               NtCurrentProcess(),
684                               (PVOID*)&Snapshot,
685                               0,
686                               0,
687                               &SOffset,
688                               &ViewSize,
689                               ViewShare,
690                               0,
691                               PAGE_READWRITE);
692   if(NT_SUCCESS(Status))
693   {
694     BOOL Ret;
695 
696     if(Snapshot->HeapListCount > 0)
697     {
698       LPHEAPLIST32 Entries = (LPHEAPLIST32)OffsetToPtr(Snapshot, Snapshot->HeapListOffset);
699       Snapshot->HeapListIndex = 1;
700       RtlCopyMemory(lphl, &Entries[0], sizeof(HEAPLIST32));
701       Ret = TRUE;
702     }
703     else
704     {
705       SetLastError(ERROR_NO_MORE_FILES);
706       Ret = FALSE;
707     }
708 
709     NtUnmapViewOfSection(NtCurrentProcess(), (PVOID)Snapshot);
710     return Ret;
711   }
712 
713   BaseSetLastNTError(Status);
714   return FALSE;
715 }
716 
717 
718 /*
719  * @implemented
720  */
721 BOOL
722 WINAPI
Heap32ListNext(HANDLE hSnapshot,LPHEAPLIST32 lphl)723 Heap32ListNext(HANDLE hSnapshot, LPHEAPLIST32 lphl)
724 {
725   PTH32SNAPSHOT Snapshot;
726   LARGE_INTEGER SOffset;
727   SIZE_T ViewSize;
728   NTSTATUS Status;
729 
730   CHECK_PARAM_SIZE(lphl, sizeof(HEAPLIST32));
731 
732   SOffset.QuadPart = 0;
733   ViewSize = 0;
734   Snapshot = NULL;
735 
736   Status = NtMapViewOfSection(hSnapshot,
737                               NtCurrentProcess(),
738                               (PVOID*)&Snapshot,
739                               0,
740                               0,
741                               &SOffset,
742                               &ViewSize,
743                               ViewShare,
744                               0,
745                               PAGE_READWRITE);
746   if(NT_SUCCESS(Status))
747   {
748     BOOL Ret;
749 
750     if(Snapshot->HeapListCount > 0 &&
751        Snapshot->HeapListIndex < Snapshot->HeapListCount)
752     {
753       LPHEAPLIST32 Entries = (LPHEAPLIST32)OffsetToPtr(Snapshot, Snapshot->HeapListOffset);
754       RtlCopyMemory(lphl, &Entries[Snapshot->HeapListIndex++], sizeof(HEAPLIST32));
755       Ret = TRUE;
756     }
757     else
758     {
759       SetLastError(ERROR_NO_MORE_FILES);
760       Ret = FALSE;
761     }
762 
763     NtUnmapViewOfSection(NtCurrentProcess(), (PVOID)Snapshot);
764     return Ret;
765   }
766 
767   BaseSetLastNTError(Status);
768   return FALSE;
769 }
770 
771 
772 /*
773  * @implemented
774  */
775 BOOL
776 WINAPI
Module32First(HANDLE hSnapshot,LPMODULEENTRY32 lpme)777 Module32First(HANDLE hSnapshot, LPMODULEENTRY32 lpme)
778 {
779   MODULEENTRY32W me;
780   BOOL Ret;
781 
782   CHECK_PARAM_SIZEA(lpme, sizeof(MODULEENTRY32));
783 
784   me.dwSize = sizeof(MODULEENTRY32W);
785 
786   Ret = Module32FirstW(hSnapshot, &me);
787   if(Ret)
788   {
789     lpme->th32ModuleID = me.th32ModuleID;
790     lpme->th32ProcessID = me.th32ProcessID;
791     lpme->GlblcntUsage = me.GlblcntUsage;
792     lpme->ProccntUsage = me.ProccntUsage;
793     lpme->modBaseAddr = me.modBaseAddr;
794     lpme->modBaseSize = me.modBaseSize;
795     lpme->hModule = me.hModule;
796 
797     WideCharToMultiByte(CP_ACP, 0, me.szModule, -1, lpme->szModule, sizeof(lpme->szModule), 0, 0);
798     WideCharToMultiByte(CP_ACP, 0, me.szExePath, -1, lpme->szExePath, sizeof(lpme->szExePath), 0, 0);
799   }
800 
801   return Ret;
802 }
803 
804 
805 /*
806  * @implemented
807  */
808 BOOL
809 WINAPI
Module32FirstW(HANDLE hSnapshot,LPMODULEENTRY32W lpme)810 Module32FirstW(HANDLE hSnapshot, LPMODULEENTRY32W lpme)
811 {
812   PTH32SNAPSHOT Snapshot;
813   LARGE_INTEGER SOffset;
814   SIZE_T ViewSize;
815   NTSTATUS Status;
816 
817   CHECK_PARAM_SIZE(lpme, sizeof(MODULEENTRY32W));
818 
819   SOffset.QuadPart = 0;
820   ViewSize = 0;
821   Snapshot = NULL;
822 
823   Status = NtMapViewOfSection(hSnapshot,
824                               NtCurrentProcess(),
825                               (PVOID*)&Snapshot,
826                               0,
827                               0,
828                               &SOffset,
829                               &ViewSize,
830                               ViewShare,
831                               0,
832                               PAGE_READWRITE);
833   if(NT_SUCCESS(Status))
834   {
835     BOOL Ret;
836 
837     if(Snapshot->ModuleListCount > 0)
838     {
839       LPMODULEENTRY32W Entries = (LPMODULEENTRY32W)OffsetToPtr(Snapshot, Snapshot->ModuleListOffset);
840       Snapshot->ModuleListIndex = 1;
841       RtlCopyMemory(lpme, &Entries[0], sizeof(MODULEENTRY32W));
842       Ret = TRUE;
843     }
844     else
845     {
846       SetLastError(ERROR_NO_MORE_FILES);
847       Ret = FALSE;
848     }
849 
850     NtUnmapViewOfSection(NtCurrentProcess(), (PVOID)Snapshot);
851     return Ret;
852   }
853 
854   BaseSetLastNTError(Status);
855   return FALSE;
856 }
857 
858 
859 /*
860  * @implemented
861  */
862 BOOL
863 WINAPI
Module32Next(HANDLE hSnapshot,LPMODULEENTRY32 lpme)864 Module32Next(HANDLE hSnapshot, LPMODULEENTRY32 lpme)
865 {
866   MODULEENTRY32W me;
867   BOOL Ret;
868 
869   CHECK_PARAM_SIZEA(lpme, sizeof(MODULEENTRY32));
870 
871   me.dwSize = sizeof(MODULEENTRY32W);
872 
873   Ret = Module32NextW(hSnapshot, &me);
874   if(Ret)
875   {
876     lpme->th32ModuleID = me.th32ModuleID;
877     lpme->th32ProcessID = me.th32ProcessID;
878     lpme->GlblcntUsage = me.GlblcntUsage;
879     lpme->ProccntUsage = me.ProccntUsage;
880     lpme->modBaseAddr = me.modBaseAddr;
881     lpme->modBaseSize = me.modBaseSize;
882     lpme->hModule = me.hModule;
883 
884     WideCharToMultiByte(CP_ACP, 0, me.szModule, -1, lpme->szModule, sizeof(lpme->szModule), 0, 0);
885     WideCharToMultiByte(CP_ACP, 0, me.szExePath, -1, lpme->szExePath, sizeof(lpme->szExePath), 0, 0);
886   }
887 
888   return Ret;
889 }
890 
891 
892 /*
893  * @implemented
894  */
895 BOOL
896 WINAPI
Module32NextW(HANDLE hSnapshot,LPMODULEENTRY32W lpme)897 Module32NextW(HANDLE hSnapshot, LPMODULEENTRY32W lpme)
898 {
899   PTH32SNAPSHOT Snapshot;
900   LARGE_INTEGER SOffset;
901   SIZE_T ViewSize;
902   NTSTATUS Status;
903 
904   CHECK_PARAM_SIZE(lpme, sizeof(MODULEENTRY32W));
905 
906   SOffset.QuadPart = 0;
907   ViewSize = 0;
908   Snapshot = NULL;
909 
910   Status = NtMapViewOfSection(hSnapshot,
911                               NtCurrentProcess(),
912                               (PVOID*)&Snapshot,
913                               0,
914                               0,
915                               &SOffset,
916                               &ViewSize,
917                               ViewShare,
918                               0,
919                               PAGE_READWRITE);
920   if(NT_SUCCESS(Status))
921   {
922     BOOL Ret;
923 
924     if((Snapshot->ModuleListCount > 0) &&
925        (Snapshot->ModuleListIndex < Snapshot->ModuleListCount))
926     {
927       LPMODULEENTRY32W Entries = (LPMODULEENTRY32W)OffsetToPtr(Snapshot, Snapshot->ModuleListOffset);
928       RtlCopyMemory(lpme, &Entries[Snapshot->ModuleListIndex++], sizeof(MODULEENTRY32W));
929       Ret = TRUE;
930     }
931     else
932     {
933       SetLastError(ERROR_NO_MORE_FILES);
934       Ret = FALSE;
935     }
936 
937     NtUnmapViewOfSection(NtCurrentProcess(), (PVOID)Snapshot);
938     return Ret;
939   }
940 
941   BaseSetLastNTError(Status);
942   return FALSE;
943 }
944 
945 
946 /*
947  * @implemented
948  */
949 BOOL
950 WINAPI
Process32First(HANDLE hSnapshot,LPPROCESSENTRY32 lppe)951 Process32First(HANDLE hSnapshot, LPPROCESSENTRY32 lppe)
952 {
953   PROCESSENTRY32W pe;
954   BOOL Ret;
955 
956   CHECK_PARAM_SIZEA(lppe, sizeof(PROCESSENTRY32));
957 
958   pe.dwSize = sizeof(PROCESSENTRY32W);
959 
960   Ret = Process32FirstW(hSnapshot, &pe);
961   if(Ret)
962   {
963     lppe->cntUsage = pe.cntUsage;
964     lppe->th32ProcessID = pe.th32ProcessID;
965     lppe->th32DefaultHeapID = pe.th32DefaultHeapID;
966     lppe->th32ModuleID = pe.th32ModuleID;
967     lppe->cntThreads = pe.cntThreads;
968     lppe->th32ParentProcessID = pe.th32ParentProcessID;
969     lppe->pcPriClassBase = pe.pcPriClassBase;
970     lppe->dwFlags = pe.dwFlags;
971 
972     WideCharToMultiByte(CP_ACP, 0, pe.szExeFile, -1, lppe->szExeFile, sizeof(lppe->szExeFile), 0, 0);
973   }
974 
975   return Ret;
976 }
977 
978 
979 /*
980  * @implemented
981  */
982 BOOL
983 WINAPI
Process32FirstW(HANDLE hSnapshot,LPPROCESSENTRY32W lppe)984 Process32FirstW(HANDLE hSnapshot, LPPROCESSENTRY32W lppe)
985 {
986   PTH32SNAPSHOT Snapshot;
987   LARGE_INTEGER SOffset;
988   SIZE_T ViewSize;
989   NTSTATUS Status;
990 
991   CHECK_PARAM_SIZE(lppe, sizeof(PROCESSENTRY32W));
992 
993   SOffset.QuadPart = 0;
994   ViewSize = 0;
995   Snapshot = NULL;
996 
997   Status = NtMapViewOfSection(hSnapshot,
998                               NtCurrentProcess(),
999                               (PVOID*)&Snapshot,
1000                               0,
1001                               0,
1002                               &SOffset,
1003                               &ViewSize,
1004                               ViewShare,
1005                               0,
1006                               PAGE_READWRITE);
1007   if(NT_SUCCESS(Status))
1008   {
1009     BOOL Ret;
1010 
1011     if(Snapshot->ProcessListCount > 0)
1012     {
1013       LPPROCESSENTRY32W Entries = (LPPROCESSENTRY32W)OffsetToPtr(Snapshot, Snapshot->ProcessListOffset);
1014 
1015       Snapshot->ProcessListIndex = 1;
1016       RtlCopyMemory(lppe, &Entries[0], sizeof(PROCESSENTRY32W));
1017       Ret = TRUE;
1018     }
1019     else
1020     {
1021 
1022       SetLastError(ERROR_NO_MORE_FILES);
1023       Ret = FALSE;
1024     }
1025 
1026     NtUnmapViewOfSection(NtCurrentProcess(), (PVOID)Snapshot);
1027     return Ret;
1028   }
1029 
1030   BaseSetLastNTError(Status);
1031   return FALSE;
1032 }
1033 
1034 
1035 /*
1036  * @implemented
1037  */
1038 BOOL
1039 WINAPI
Process32Next(HANDLE hSnapshot,LPPROCESSENTRY32 lppe)1040 Process32Next(HANDLE hSnapshot, LPPROCESSENTRY32 lppe)
1041 {
1042   PROCESSENTRY32W pe;
1043   BOOL Ret;
1044 
1045   CHECK_PARAM_SIZEA(lppe, sizeof(PROCESSENTRY32));
1046 
1047   pe.dwSize = sizeof(PROCESSENTRY32W);
1048 
1049   Ret = Process32NextW(hSnapshot, &pe);
1050   if(Ret)
1051   {
1052     lppe->cntUsage = pe.cntUsage;
1053     lppe->th32ProcessID = pe.th32ProcessID;
1054     lppe->th32DefaultHeapID = pe.th32DefaultHeapID;
1055     lppe->th32ModuleID = pe.th32ModuleID;
1056     lppe->cntThreads = pe.cntThreads;
1057     lppe->th32ParentProcessID = pe.th32ParentProcessID;
1058     lppe->pcPriClassBase = pe.pcPriClassBase;
1059     lppe->dwFlags = pe.dwFlags;
1060 
1061     WideCharToMultiByte(CP_ACP, 0, pe.szExeFile, -1, lppe->szExeFile, sizeof(lppe->szExeFile), 0, 0);
1062   }
1063 
1064   return Ret;
1065 }
1066 
1067 
1068 /*
1069  * @implemented
1070  */
1071 BOOL
1072 WINAPI
Process32NextW(HANDLE hSnapshot,LPPROCESSENTRY32W lppe)1073 Process32NextW(HANDLE hSnapshot, LPPROCESSENTRY32W lppe)
1074 {
1075   PTH32SNAPSHOT Snapshot;
1076   LARGE_INTEGER SOffset;
1077   SIZE_T ViewSize;
1078   NTSTATUS Status;
1079 
1080   CHECK_PARAM_SIZE(lppe, sizeof(PROCESSENTRY32W));
1081 
1082   SOffset.QuadPart = 0;
1083   ViewSize = 0;
1084   Snapshot = NULL;
1085 
1086   Status = NtMapViewOfSection(hSnapshot,
1087                               NtCurrentProcess(),
1088                               (PVOID*)&Snapshot,
1089                               0,
1090                               0,
1091                               &SOffset,
1092                               &ViewSize,
1093                               ViewShare,
1094                               0,
1095                               PAGE_READWRITE);
1096   if(NT_SUCCESS(Status))
1097   {
1098     BOOL Ret;
1099 
1100     if(Snapshot->ProcessListCount > 0 &&
1101        Snapshot->ProcessListIndex < Snapshot->ProcessListCount)
1102     {
1103       LPPROCESSENTRY32W Entries = (LPPROCESSENTRY32W)OffsetToPtr(Snapshot, Snapshot->ProcessListOffset);
1104       RtlCopyMemory(lppe, &Entries[Snapshot->ProcessListIndex++], sizeof(PROCESSENTRY32W));
1105       Ret = TRUE;
1106     }
1107     else
1108     {
1109       SetLastError(ERROR_NO_MORE_FILES);
1110       Ret = FALSE;
1111     }
1112 
1113     NtUnmapViewOfSection(NtCurrentProcess(), (PVOID)Snapshot);
1114     return Ret;
1115   }
1116 
1117   BaseSetLastNTError(Status);
1118   return FALSE;
1119 }
1120 
1121 
1122 /*
1123  * @implemented
1124  */
1125 BOOL
1126 WINAPI
Thread32First(HANDLE hSnapshot,LPTHREADENTRY32 lpte)1127 Thread32First(HANDLE hSnapshot, LPTHREADENTRY32 lpte)
1128 {
1129   PTH32SNAPSHOT Snapshot;
1130   LARGE_INTEGER SOffset;
1131   SIZE_T ViewSize;
1132   NTSTATUS Status;
1133 
1134   CHECK_PARAM_SIZE(lpte, sizeof(THREADENTRY32));
1135 
1136   SOffset.QuadPart = 0;
1137   ViewSize = 0;
1138   Snapshot = NULL;
1139 
1140   Status = NtMapViewOfSection(hSnapshot,
1141                               NtCurrentProcess(),
1142                               (PVOID*)&Snapshot,
1143                               0,
1144                               0,
1145                               &SOffset,
1146                               &ViewSize,
1147                               ViewShare,
1148                               0,
1149                               PAGE_READWRITE);
1150   if(NT_SUCCESS(Status))
1151   {
1152     BOOL Ret;
1153 
1154     if(Snapshot->ThreadListCount > 0)
1155     {
1156       LPTHREADENTRY32 Entries = (LPTHREADENTRY32)OffsetToPtr(Snapshot, Snapshot->ThreadListOffset);
1157       Snapshot->ThreadListIndex = 1;
1158       RtlCopyMemory(lpte, &Entries[0], sizeof(THREADENTRY32));
1159       Ret = TRUE;
1160     }
1161     else
1162     {
1163       SetLastError(ERROR_NO_MORE_FILES);
1164       Ret = FALSE;
1165     }
1166 
1167     NtUnmapViewOfSection(NtCurrentProcess(), (PVOID)Snapshot);
1168     return Ret;
1169   }
1170 
1171   BaseSetLastNTError(Status);
1172   return FALSE;
1173 }
1174 
1175 
1176 /*
1177  * @implemented
1178  */
1179 BOOL
1180 WINAPI
Thread32Next(HANDLE hSnapshot,LPTHREADENTRY32 lpte)1181 Thread32Next(HANDLE hSnapshot, LPTHREADENTRY32 lpte)
1182 {
1183   PTH32SNAPSHOT Snapshot;
1184   LARGE_INTEGER SOffset;
1185   SIZE_T ViewSize;
1186   NTSTATUS Status;
1187 
1188   CHECK_PARAM_SIZE(lpte, sizeof(THREADENTRY32));
1189 
1190   SOffset.QuadPart = 0;
1191   ViewSize = 0;
1192   Snapshot = NULL;
1193 
1194   Status = NtMapViewOfSection(hSnapshot,
1195                               NtCurrentProcess(),
1196                               (PVOID*)&Snapshot,
1197                               0,
1198                               0,
1199                               &SOffset,
1200                               &ViewSize,
1201                               ViewShare,
1202                               0,
1203                               PAGE_READWRITE);
1204   if(NT_SUCCESS(Status))
1205   {
1206     BOOL Ret;
1207 
1208     if(Snapshot->ThreadListCount > 0 &&
1209        Snapshot->ThreadListIndex < Snapshot->ThreadListCount)
1210     {
1211       LPTHREADENTRY32 Entries = (LPTHREADENTRY32)OffsetToPtr(Snapshot, Snapshot->ThreadListOffset);
1212       RtlCopyMemory(lpte, &Entries[Snapshot->ThreadListIndex++], sizeof(THREADENTRY32));
1213       Ret = TRUE;
1214     }
1215     else
1216     {
1217       SetLastError(ERROR_NO_MORE_FILES);
1218       Ret = FALSE;
1219     }
1220 
1221     NtUnmapViewOfSection(NtCurrentProcess(), (PVOID)Snapshot);
1222     return Ret;
1223   }
1224 
1225   BaseSetLastNTError(Status);
1226   return FALSE;
1227 }
1228 
1229 
1230 /*
1231  * @implemented
1232  */
1233 BOOL
1234 WINAPI
Toolhelp32ReadProcessMemory(DWORD th32ProcessID,LPCVOID lpBaseAddress,LPVOID lpBuffer,SIZE_T cbRead,SIZE_T * lpNumberOfBytesRead)1235 Toolhelp32ReadProcessMemory(DWORD th32ProcessID,  LPCVOID lpBaseAddress,
1236                             LPVOID lpBuffer, SIZE_T cbRead, SIZE_T* lpNumberOfBytesRead)
1237 {
1238   HANDLE hProcess = OpenProcess(PROCESS_VM_READ, FALSE, th32ProcessID);
1239   if(hProcess != NULL)
1240   {
1241     BOOL Ret = ReadProcessMemory(hProcess, lpBaseAddress, lpBuffer, cbRead, lpNumberOfBytesRead);
1242     CloseHandle(hProcess);
1243     return Ret;
1244   }
1245 
1246   return FALSE;
1247 }
1248 
1249 
1250 /*
1251  * @implemented
1252  */
1253 HANDLE
1254 WINAPI
CreateToolhelp32Snapshot(DWORD dwFlags,DWORD th32ProcessID)1255 CreateToolhelp32Snapshot(DWORD dwFlags, DWORD th32ProcessID)
1256 {
1257   PRTL_DEBUG_INFORMATION HeapDebug, ModuleDebug;
1258   PVOID ProcThrdInfo;
1259   SIZE_T ProcThrdInfoSize;
1260   NTSTATUS Status;
1261   HANDLE hSnapShotSection = NULL;
1262 
1263   if(th32ProcessID == 0)
1264   {
1265     th32ProcessID = GetCurrentProcessId();
1266   }
1267 
1268   /*
1269    * Get all information required for the snapshot
1270    */
1271   Status = TH32CreateSnapshot(dwFlags,
1272                               th32ProcessID,
1273                               &HeapDebug,
1274                               &ModuleDebug,
1275                               &ProcThrdInfo,
1276                               &ProcThrdInfoSize);
1277   if(!NT_SUCCESS(Status))
1278   {
1279     BaseSetLastNTError(Status);
1280     return NULL;
1281   }
1282 
1283   /*
1284    * Create a section handle and initialize the collected information
1285    */
1286   Status = TH32CreateSnapshotSectionInitialize(dwFlags,
1287                                                th32ProcessID,
1288                                                HeapDebug,
1289                                                ModuleDebug,
1290                                                ProcThrdInfo,
1291                                                &hSnapShotSection);
1292 
1293   /*
1294    * Free the temporarily allocated memory which is no longer needed
1295    */
1296   TH32FreeAllocatedResources(HeapDebug,
1297                              ModuleDebug,
1298                              ProcThrdInfo,
1299                              ProcThrdInfoSize);
1300 
1301   if(!NT_SUCCESS(Status))
1302   {
1303     BaseSetLastNTError(Status);
1304     return NULL;
1305   }
1306 
1307   return hSnapShotSection;
1308 }
1309 
1310 /* EOF */
1311