1 /*
2 * PROJECT: ReactOS Drivers
3 * LICENSE: BSD - See COPYING.ARM in the top level directory
4 * FILE: drivers/sac/driver/concmd.c
5 * PURPOSE: Driver for the Server Administration Console (SAC) for EMS
6 * PROGRAMMERS: ReactOS Portable Systems Group
7 */
8
9 /* INCLUDES *******************************************************************/
10
11 #include "sacdrv.h"
12
13 #include <ndk/exfuncs.h>
14
15 /* GLOBALS ********************************************************************/
16
17 PVOID GlobalBuffer;
18 ULONG GlobalBufferSize;
19
20 /* FUNCTIONS ******************************************************************/
21
22 NTSTATUS
DoChannelListCommand(VOID)23 DoChannelListCommand(
24 VOID
25 )
26 {
27 return STATUS_NOT_IMPLEMENTED;
28 }
29
30 NTSTATUS
DoChannelCloseByNameCommand(IN PCHAR Count)31 DoChannelCloseByNameCommand(
32 IN PCHAR Count
33 )
34 {
35 return STATUS_NOT_IMPLEMENTED;
36 }
37
38 NTSTATUS
DoChannelCloseByIndexCommand(IN ULONG ChannelIndex)39 DoChannelCloseByIndexCommand(
40 IN ULONG ChannelIndex
41 )
42 {
43 return STATUS_NOT_IMPLEMENTED;
44 }
45
46 NTSTATUS
DoChannelSwitchByNameCommand(IN PCHAR Count)47 DoChannelSwitchByNameCommand(
48 IN PCHAR Count
49 )
50 {
51 return STATUS_NOT_IMPLEMENTED;
52 }
53
54 NTSTATUS
DoChannelSwitchByIndexCommand(IN ULONG ChannelIndex)55 DoChannelSwitchByIndexCommand(
56 IN ULONG ChannelIndex
57 )
58 {
59 return STATUS_NOT_IMPLEMENTED;
60 }
61
62 typedef struct _SAC_SYSTEM_INFORMATION
63 {
64 SYSTEM_BASIC_INFORMATION BasicInfo;
65 SYSTEM_TIMEOFDAY_INFORMATION TimeInfo;
66 SYSTEM_FILECACHE_INFORMATION CacheInfo;
67 SYSTEM_PERFORMANCE_INFORMATION PerfInfo;
68 ULONG RemainingSize;
69 ULONG ProcessDataOffset;
70 // SYSTEM_PAGEFILE_INFORMATION PageFileInfo;
71 // SYSTEM_PROCESS_INFORMATION ProcessInfo;
72 } SAC_SYSTEM_INFORMATION, *PSAC_SYSTEM_INFORMATION;
73
74 NTSTATUS
75 NTAPI
GetTListInfo(IN PSAC_SYSTEM_INFORMATION SacInfo,IN ULONG InputSize,OUT PULONG TotalSize)76 GetTListInfo(IN PSAC_SYSTEM_INFORMATION SacInfo,
77 IN ULONG InputSize,
78 OUT PULONG TotalSize)
79 {
80 NTSTATUS Status;
81 ULONG BufferLength, ReturnLength, RemainingSize;
82 PSYSTEM_PAGEFILE_INFORMATION PageFileInfo;
83 PSYSTEM_PROCESS_INFORMATION ProcessInfo;
84 ULONG_PTR P;
85 SAC_DBG(SAC_DBG_ENTRY_EXIT, "Entering.\n");
86
87 /* Assume failure */
88 *TotalSize = 0;
89
90 /* Bail out if the buffer is way too small */
91 if (InputSize < 4)
92 {
93 SAC_DBG(SAC_DBG_ENTRY_EXIT, "Exiting, no memory.\n");
94 return STATUS_NO_MEMORY;
95 }
96
97 /* Make sure it's at least big enough to hold the static structure */
98 BufferLength = InputSize - sizeof(SAC_SYSTEM_INFORMATION);
99 if (InputSize < sizeof(SAC_SYSTEM_INFORMATION))
100 {
101 SAC_DBG(SAC_DBG_ENTRY_EXIT, "Exiting, no memory (2).\n");
102 return STATUS_NO_MEMORY;
103 }
104
105 /* Query the time */
106 Status = ZwQuerySystemInformation(SystemTimeOfDayInformation,
107 &SacInfo->TimeInfo,
108 sizeof(SacInfo->TimeInfo),
109 NULL);
110 if (!NT_SUCCESS(Status))
111 {
112 SAC_DBG(SAC_DBG_ENTRY_EXIT, "Exiting, error.\n");
113 return Status;
114 }
115
116 /* Query basic information */
117 Status = ZwQuerySystemInformation(SystemBasicInformation,
118 &SacInfo->BasicInfo,
119 sizeof(SacInfo->BasicInfo),
120 NULL);
121 if (!NT_SUCCESS(Status))
122 {
123 SAC_DBG(SAC_DBG_ENTRY_EXIT, "Exiting, error (2).\n");
124 return Status;
125 }
126
127 /* Now query the pagefile information, which comes right after */
128 P = (ULONG_PTR)(SacInfo + 1);
129 PageFileInfo = (PSYSTEM_PAGEFILE_INFORMATION)P;
130 Status = ZwQuerySystemInformation(SystemPageFileInformation,
131 PageFileInfo,
132 BufferLength,
133 &ReturnLength);
134 if (!NT_SUCCESS(Status) || !(ReturnLength))
135 {
136 /* We failed -- is it because our buffer was too small? */
137 if (BufferLength < ReturnLength)
138 {
139 /* Bail out */
140 SAC_DBG(SAC_DBG_ENTRY_EXIT, "Exiting, no memory(5).\n");
141 return STATUS_NO_MEMORY;
142 }
143
144 /* Some other reason, assume the buffer is now full */
145 SacInfo->RemainingSize = 0;
146 }
147 else
148 {
149 /* This is the leftover data */
150 SacInfo->RemainingSize = InputSize - BufferLength;
151
152 /* This much has now been consumed, and where we are now */
153 BufferLength -= ReturnLength;
154 P += ReturnLength;
155
156 /* Are we out of memory? */
157 if ((LONG)BufferLength < 0)
158 {
159 /* Bail out */
160 SAC_DBG(SAC_DBG_ENTRY_EXIT, "Exiting, no memory(3).\n");
161 return STATUS_NO_MEMORY;
162 }
163
164 /* All good, loop the pagefile data now */
165 while (TRUE)
166 {
167 /* Is the pagefile name too big to fit? */
168 if (PageFileInfo->PageFileName.Length > (LONG)BufferLength)
169 {
170 /* Bail out */
171 SAC_DBG(SAC_DBG_ENTRY_EXIT, "Exiting, error(3).\n");
172 return STATUS_INFO_LENGTH_MISMATCH;
173 }
174
175 /* Copy the name into our own buffer */
176 RtlCopyMemory((PVOID)P,
177 PageFileInfo->PageFileName.Buffer,
178 PageFileInfo->PageFileName.Length);
179 PageFileInfo->PageFileName.Buffer = (PWCHAR)P;
180
181 /* Update buffer lengths and offset */
182 BufferLength -= PageFileInfo->PageFileName.Length;
183 P += PageFileInfo->PageFileName.Length;
184
185 /* Are we out of memory? */
186 if ((LONG)BufferLength < 0)
187 {
188 /* Bail out */
189 SAC_DBG(SAC_DBG_ENTRY_EXIT, "Exiting, no memory(4).\n");
190 return STATUS_NO_MEMORY;
191 }
192
193 /* If this was the only pagefile, break out */
194 if (!PageFileInfo->NextEntryOffset) break;
195
196 /* Otherwise, move to the next one */
197 PageFileInfo = (PVOID)((ULONG_PTR)PageFileInfo +
198 PageFileInfo->NextEntryOffset);
199 }
200 }
201
202 /* Next, query the file cache information */
203 Status = ZwQuerySystemInformation(SystemFileCacheInformation,
204 &SacInfo->CacheInfo,
205 sizeof(SacInfo->CacheInfo),
206 NULL);
207 if (!NT_SUCCESS(Status))
208 {
209 SAC_DBG(SAC_DBG_ENTRY_EXIT, "Exiting, error (4).\n");
210 return Status;
211 }
212
213 /* And then the performance information */
214 Status = ZwQuerySystemInformation(SystemPerformanceInformation,
215 &SacInfo->PerfInfo,
216 sizeof(SacInfo->PerfInfo),
217 NULL);
218 if (!NT_SUCCESS(Status))
219 {
220 SAC_DBG(SAC_DBG_ENTRY_EXIT, "Exiting, error(5).\n");
221 return Status;
222 }
223
224 /* Finally, align the buffer to query process and thread information */
225 P = ALIGN_UP(P, SYSTEM_PROCESS_INFORMATION);
226 RemainingSize = (ULONG_PTR)SacInfo + InputSize - P;
227
228 /* Are we out of memory? */
229 if ((LONG)RemainingSize < 0)
230 {
231 /* Bail out */
232 SAC_DBG(SAC_DBG_ENTRY_EXIT, "Exiting, no memory (6).\n");
233 return STATUS_NO_MEMORY;
234 }
235
236 /* Now query the processes and threads */
237 ProcessInfo = (PSYSTEM_PROCESS_INFORMATION)P;
238 Status = ZwQuerySystemInformation(SystemProcessInformation,
239 ProcessInfo,
240 RemainingSize,
241 &ReturnLength);
242 if (!NT_SUCCESS(Status))
243 {
244 SAC_DBG(SAC_DBG_ENTRY_EXIT, "Exiting, error(6).\n");
245 return Status;
246 }
247
248 /* The first process name will be right after this buffer */
249 P += ReturnLength;
250
251 /* The caller should look for process info over here */
252 SacInfo->ProcessDataOffset = InputSize - RemainingSize;
253
254 /* This is how much buffer data we have left -- are we out? */
255 BufferLength = RemainingSize - ReturnLength;
256 if ((LONG)BufferLength < 0)
257 {
258 /* Bail out */
259 SAC_DBG(SAC_DBG_ENTRY_EXIT, "Exiting, no memory(7).\n");
260 return STATUS_NO_MEMORY;
261 }
262
263 /* All good and ready to parse the process and thread list */
264 while (TRUE)
265 {
266 /* Does the process have a name? */
267 if (ProcessInfo->ImageName.Buffer)
268 {
269 /* Is the process name too big to fit? */
270 if ((LONG)BufferLength < ProcessInfo->ImageName.Length)
271 {
272 /* Bail out */
273 SAC_DBG(SAC_DBG_ENTRY_EXIT, "Exiting, error(7).\n");
274 return STATUS_INFO_LENGTH_MISMATCH;
275 }
276
277 /* Copy the name into our own buffer */
278 RtlCopyMemory((PVOID)P,
279 ProcessInfo->ImageName.Buffer,
280 ProcessInfo->ImageName.Length);
281 ProcessInfo->ImageName.Buffer = (PWCHAR)P;
282
283 /* Update buffer lengths and offset */
284 BufferLength -= ProcessInfo->ImageName.Length;
285 P += ProcessInfo->ImageName.Length;
286
287 /* Are we out of memory? */
288 if ((LONG)BufferLength < 0)
289 {
290 /* Bail out */
291 SAC_DBG(SAC_DBG_ENTRY_EXIT, "Exiting, no memory(8).\n");
292 return STATUS_NO_MEMORY;
293 }
294 }
295
296 /* If this was the only process, break out */
297 if (!ProcessInfo->NextEntryOffset) break;
298
299 /* Otherwise, move to the next one */
300 ProcessInfo = (PVOID)((ULONG_PTR)ProcessInfo +
301 ProcessInfo->NextEntryOffset);
302 }
303
304 /* All done! */
305 SAC_DBG(SAC_DBG_ENTRY_EXIT, "Exiting.\n");
306 *TotalSize = InputSize - BufferLength;
307 return STATUS_SUCCESS;
308 }
309
310 VOID
311 NTAPI
PrintTListInfo(IN PSAC_SYSTEM_INFORMATION SacInfo)312 PrintTListInfo(IN PSAC_SYSTEM_INFORMATION SacInfo)
313 {
314 SAC_DBG(SAC_DBG_ENTRY_EXIT, "Testing: %d %d %I64d\n",
315 SacInfo->BasicInfo.NumberOfPhysicalPages,
316 SacInfo->PerfInfo.AvailablePages,
317 SacInfo->TimeInfo.BootTime);
318 }
319
320 VOID
321 NTAPI
PutMore(OUT PBOOLEAN ScreenFull)322 PutMore(OUT PBOOLEAN ScreenFull)
323 {
324 *ScreenFull = FALSE;
325 }
326
327 BOOLEAN
RetrieveIpAddressFromString(IN PWCHAR IpString,OUT PULONG IpAddress)328 RetrieveIpAddressFromString(
329 IN PWCHAR IpString,
330 OUT PULONG IpAddress
331 )
332 {
333 return FALSE;
334 }
335
336 NTSTATUS
CallQueryIPIOCTL(IN HANDLE DriverHandle,IN PVOID DriverObject,IN HANDLE WaitEvent,IN PIO_STATUS_BLOCK IoStatusBlock,IN PVOID InputBuffer,IN ULONG InputBufferLength,IN PVOID OutputBuffer,IN ULONG OutputBufferLength,IN BOOLEAN PrintMessage,OUT PBOOLEAN MessagePrinted)337 CallQueryIPIOCTL(
338 IN HANDLE DriverHandle,
339 IN PVOID DriverObject,
340 IN HANDLE WaitEvent,
341 IN PIO_STATUS_BLOCK IoStatusBlock,
342 IN PVOID InputBuffer,
343 IN ULONG InputBufferLength,
344 IN PVOID OutputBuffer,
345 IN ULONG OutputBufferLength,
346 IN BOOLEAN PrintMessage,
347 OUT PBOOLEAN MessagePrinted
348 )
349 {
350 return STATUS_NOT_IMPLEMENTED;
351 }
352
353 VOID
354 NTAPI
DoRebootCommand(IN BOOLEAN Reboot)355 DoRebootCommand(IN BOOLEAN Reboot)
356 {
357 LARGE_INTEGER Timeout, TickCount;
358 NTSTATUS Status;
359 KEVENT Event;
360 SAC_DBG(SAC_DBG_ENTRY_EXIT, "SAC DoRebootCommand: Entering.\n");
361
362 /* Get the current time now, and setup a timeout in 1 second */
363 KeQueryTickCount(&TickCount);
364 Timeout.QuadPart = TickCount.QuadPart / (10000000 / KeQueryTimeIncrement());
365
366 /* Check if the timeout is small enough */
367 if (Timeout.QuadPart < 60 )
368 {
369 /* Show the prompt */
370 ConMgrSimpleEventMessage(Reboot ?
371 SAC_RESTART_PROMPT : SAC_SHUTDOWN_PROMPT,
372 TRUE);
373
374 /* Do the wait */
375 KeInitializeEvent(&Event, SynchronizationEvent, 0);
376 Timeout.QuadPart = -10000000 * (60 - Timeout.LowPart);
377 KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, &Timeout);
378 }
379
380 /* Do a shutdown or a reboot, based on the request */
381 Status = NtShutdownSystem(Reboot ? ShutdownReboot : ShutdownPowerOff);
382
383 /* Check if anyone in the command channel already allocated this */
384 if (!GlobalBuffer)
385 {
386 /* Allocate it */
387 GlobalBuffer = SacAllocatePool(PAGE_SIZE, GLOBAL_BLOCK_TAG);
388 if (!GlobalBuffer)
389 {
390 /* We need the global buffer, bail out without it*/
391 SacPutSimpleMessage(SAC_OUT_OF_MEMORY_PROMPT);
392 SAC_DBG(SAC_DBG_ENTRY_EXIT, "SAC DoRebootCommand: Exiting (1).\n");
393 return;
394 }
395
396 /* Set the size of the buffer */
397 GlobalBufferSize = PAGE_SIZE;
398 }
399
400 /* We came back from a reboot, this doesn't make sense, tell the user */
401 SacPutSimpleMessage(Reboot ? SAC_RESTART_FAIL_PROMPT : SAC_SHUTDOWN_FAIL_PROMPT);
402 swprintf(GlobalBuffer, GetMessage(SAC_FAIL_PROMPT), Status);
403 SacPutString(GlobalBuffer);
404 SAC_DBG(SAC_DBG_ENTRY_EXIT, "SAC DoRebootCommand: Exiting.\n");
405 }
406
407 VOID
408 NTAPI
DoFullInfoCommand(VOID)409 DoFullInfoCommand(VOID)
410 {
411 /* Flip the flag */
412 GlobalDoThreads = !GlobalDoThreads;
413
414 /* Print out the new state */
415 SacPutSimpleMessage(GlobalDoThreads ? 8 : 7);
416 }
417
418 VOID
419 NTAPI
DoPagingCommand(VOID)420 DoPagingCommand(VOID)
421 {
422 /* Flip the flag */
423 GlobalPagingNeeded = !GlobalPagingNeeded;
424
425 /* Print out the new state */
426 SacPutSimpleMessage(GlobalPagingNeeded ? 10 : 9);
427 }
428
429 VOID
430 NTAPI
DoSetTimeCommand(IN PCHAR InputTime)431 DoSetTimeCommand(IN PCHAR InputTime)
432 {
433 SAC_DBG(SAC_DBG_ENTRY_EXIT, "Entering\n");
434 }
435
436 VOID
437 NTAPI
DoKillCommand(IN PCHAR KillString)438 DoKillCommand(IN PCHAR KillString)
439 {
440 SAC_DBG(SAC_DBG_ENTRY_EXIT, "Entering\n");
441 }
442
443 VOID
444 NTAPI
DoLowerPriorityCommand(IN PCHAR PrioString)445 DoLowerPriorityCommand(IN PCHAR PrioString)
446 {
447 SAC_DBG(SAC_DBG_ENTRY_EXIT, "Entering\n");
448 }
449
450 VOID
451 NTAPI
DoRaisePriorityCommand(IN PCHAR PrioString)452 DoRaisePriorityCommand(IN PCHAR PrioString)
453 {
454 SAC_DBG(SAC_DBG_ENTRY_EXIT, "Entering\n");
455 }
456
457 VOID
458 NTAPI
DoLimitMemoryCommand(IN PCHAR LimitString)459 DoLimitMemoryCommand(IN PCHAR LimitString)
460 {
461 SAC_DBG(SAC_DBG_ENTRY_EXIT, "Entering\n");
462 }
463
464 VOID
465 NTAPI
DoCrashCommand(VOID)466 DoCrashCommand(VOID)
467 {
468 SAC_DBG(SAC_DBG_ENTRY_EXIT, "SAC DoCrashCommand: Entering.\n");
469
470 /* Crash the machine */
471 KeBugCheckEx(MANUALLY_INITIATED_CRASH, 0, 0, 0, 0);
472 __debugbreak();
473 }
474
475 VOID
476 NTAPI
DoMachineInformationCommand(VOID)477 DoMachineInformationCommand(VOID)
478 {
479 SAC_DBG(SAC_DBG_ENTRY_EXIT, "Entering\n");
480 }
481
482 VOID
483 NTAPI
DoChannelCommand(IN PCHAR ChannelString)484 DoChannelCommand(IN PCHAR ChannelString)
485 {
486 SAC_DBG(SAC_DBG_ENTRY_EXIT, "Entering\n");
487 }
488
489 VOID
490 NTAPI
DoCmdCommand(IN PCHAR InputString)491 DoCmdCommand(IN PCHAR InputString)
492 {
493 SAC_DBG(SAC_DBG_ENTRY_EXIT, "Entering\n");
494 }
495
496 VOID
497 NTAPI
DoLockCommand(VOID)498 DoLockCommand(VOID)
499 {
500 SAC_DBG(SAC_DBG_ENTRY_EXIT, "Entering\n");
501 }
502
503 FORCEINLINE
504 BOOLEAN
PrintHelpMessage(IN ULONG MessageId,IN OUT PULONG Count)505 PrintHelpMessage(IN ULONG MessageId,
506 IN OUT PULONG Count)
507 {
508 BOOLEAN ScreenFull;
509 ULONG NewCount;
510
511 /* Get the amount of lines this message will take */
512 NewCount = GetMessageLineCount(MessageId);
513 if ((NewCount + *Count) > SAC_VTUTF8_ROW_HEIGHT)
514 {
515 /* We are going to overflow the screen, wait for input */
516 PutMore(&ScreenFull);
517 if (ScreenFull) return FALSE;
518 *Count = 0;
519 }
520
521 /* Print out the message and update the amount of lines printed */
522 SacPutSimpleMessage(MessageId);
523 *Count += NewCount;
524 return TRUE;
525 }
526
527 VOID
528 NTAPI
DoHelpCommand(VOID)529 DoHelpCommand(VOID)
530 {
531 ULONG Count = 0;
532
533 /* Print out all the help messages */
534 if (!PrintHelpMessage(112, &Count)) return;
535 if (!PrintHelpMessage(12, &Count)) return;
536 if (!PrintHelpMessage(13, &Count)) return;
537 if (!PrintHelpMessage(14, &Count)) return;
538 if (!PrintHelpMessage(15, &Count)) return;
539 if (!PrintHelpMessage(16, &Count)) return;
540 if (!PrintHelpMessage(31, &Count)) return;
541 if (!PrintHelpMessage(18, &Count)) return;
542 if (!PrintHelpMessage(19, &Count)) return;
543 if (!PrintHelpMessage(32, &Count)) return;
544 if (!PrintHelpMessage(20, &Count)) return;
545 if (!PrintHelpMessage(21, &Count)) return;
546 if (!PrintHelpMessage(22, &Count)) return;
547 if (!PrintHelpMessage(23, &Count)) return;
548 if (!PrintHelpMessage(24, &Count)) return;
549 if (!PrintHelpMessage(25, &Count)) return;
550 if (!PrintHelpMessage(27, &Count)) return;
551 if (!PrintHelpMessage(28, &Count)) return;
552 if (!PrintHelpMessage(29, &Count)) return;
553 }
554
555 VOID
556 NTAPI
DoGetNetInfo(IN BOOLEAN DoPrint)557 DoGetNetInfo(IN BOOLEAN DoPrint)
558 {
559 SAC_DBG(SAC_DBG_ENTRY_EXIT, "Entering\n");
560 }
561
562 VOID
563 NTAPI
DoSetIpAddressCommand(IN PCHAR IpString)564 DoSetIpAddressCommand(IN PCHAR IpString)
565 {
566 SAC_DBG(SAC_DBG_ENTRY_EXIT, "Entering\n");
567 }
568
569 VOID
570 NTAPI
DoTlistCommand(VOID)571 DoTlistCommand(VOID)
572 {
573 NTSTATUS Status;
574 PVOID NewGlobalBuffer;
575 ULONG Size;
576 SAC_DBG(SAC_DBG_ENTRY_EXIT, "SAC DoTlistCommand: Entering.\n");
577
578 /* Check if a global buffer already exists */
579 if (!GlobalBuffer)
580 {
581 /* It doesn't, allocate one */
582 GlobalBuffer = SacAllocatePool(4096, GLOBAL_BLOCK_TAG);
583 if (GlobalBuffer)
584 {
585 /* Remember its current size */
586 GlobalBufferSize = 4096;
587 }
588 else
589 {
590 /* Out of memory, bail out */
591 SacPutSimpleMessage(11);
592 SAC_DBG(SAC_DBG_ENTRY_EXIT, "SAC DoTlistCommand: Exiting.\n");
593 return;
594 }
595 }
596
597 /* Loop as long as the buffer is too small */
598 while (TRUE)
599 {
600 /* Get the process list */
601 Status = GetTListInfo(GlobalBuffer, GlobalBufferSize, &Size);
602 if ((Status != STATUS_NO_MEMORY) &&
603 (Status != STATUS_INFO_LENGTH_MISMATCH))
604 {
605 /* It fits! Bail out */
606 break;
607 }
608
609 /* We need a new bigger buffer */
610 NewGlobalBuffer = SacAllocatePool(GlobalBufferSize + 4096,
611 GLOBAL_BLOCK_TAG);
612 if (!NewGlobalBuffer)
613 {
614 /* Out of memory, bail out */
615 SacPutSimpleMessage(11);
616 SAC_DBG(SAC_DBG_ENTRY_EXIT, "SAC DoTlistCommand: Exiting.\n");
617 return;
618 }
619
620 /* Free the old one, update state */
621 SacFreePool(GlobalBuffer);
622 GlobalBufferSize += 4096;
623 GlobalBuffer = NewGlobalBuffer;
624 }
625
626 /* Did we get here because we have the whole list? */
627 if (!NT_SUCCESS(Status))
628 {
629 /* Nope, print out a failure message */
630 SacPutSimpleMessage(68);
631 swprintf(GlobalBuffer, GetMessage(48), Status);
632 SacPutString(GlobalBuffer);
633 }
634 else
635 {
636 /* Yep, print out the list */
637 PrintTListInfo(GlobalBuffer);
638 }
639
640 SAC_DBG(SAC_DBG_ENTRY_EXIT, "SAC DoTlistCommand: Exiting.\n");
641 }
642