2002-10-01 Casper S. Hornstrup <chorns@users.sourceforge.net>
[reactos.git] / reactos / ntoskrnl / mm / pagefile.c
1 /*
2 * ReactOS kernel
3 * Copyright (C) 1998, 1999, 2000, 2001 ReactOS Team
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 */
19 /* $Id: pagefile.c,v 1.27 2002/10/01 19:27:23 chorns Exp $
20 *
21 * PROJECT: ReactOS kernel
22 * FILE: ntoskrnl/mm/pagefile.c
23 * PURPOSE: Paging file functions
24 * PROGRAMMER: David Welch (welch@mcmail.com)
25 * UPDATE HISTORY:
26 * Created 22/05/98
27 */
28
29 /* INCLUDES *****************************************************************/
30
31 #include <ddk/ntddk.h>
32 #include <internal/io.h>
33 #include <internal/mm.h>
34 #include <napi/core.h>
35 #include <internal/ps.h>
36
37 #define NDEBUG
38 #include <internal/debug.h>
39
40 /* TYPES *********************************************************************/
41
42 typedef struct _PAGINGFILE
43 {
44 LIST_ENTRY PagingFileListEntry;
45 PFILE_OBJECT FileObject;
46 LARGE_INTEGER MaximumSize;
47 LARGE_INTEGER CurrentSize;
48 ULONG FreePages;
49 ULONG UsedPages;
50 PULONG AllocMap;
51 KSPIN_LOCK AllocMapLock;
52 ULONG AllocMapSize;
53 } PAGINGFILE, *PPAGINGFILE;
54
55 /* GLOBALS *******************************************************************/
56
57 #define MAX_PAGING_FILES (32)
58
59 /* List of paging files, both used and free */
60 static PPAGINGFILE PagingFileList[MAX_PAGING_FILES];
61
62 /* Lock for examining the list of paging files */
63 static KSPIN_LOCK PagingFileListLock;
64
65 /* Number of paging files */
66 static ULONG MiPagingFileCount;
67
68 /* Number of pages that are available for swapping */
69 static ULONG MiFreeSwapPages;
70
71 /* Number of pages that have been allocated for swapping */
72 static ULONG MiUsedSwapPages;
73
74 /*
75 * Number of pages that have been reserved for swapping but not yet allocated
76 */
77 static ULONG MiReservedSwapPages;
78
79 /*
80 * Ratio between reserved and available swap pages, e.g. setting this to five
81 * forces one swap page to be available for every five swap pages that are
82 * reserved. Setting this to zero turns off commit checking altogether.
83 */
84 #define MM_PAGEFILE_COMMIT_RATIO (1)
85
86 /*
87 * Number of pages that can be used for potentially swapable memory without
88 * pagefile space being reserved. The intention is that this allows smss
89 * to start up and create page files while ordinarily having a commit
90 * ratio of one.
91 */
92 #define MM_PAGEFILE_COMMIT_GRACE (256)
93
94 static PVOID MmCoreDumpPageFrame;
95 static PULONG MmCoreDumpBlockMap;
96 static ULONG MmCoreDumpSize;
97 static PULONG MmCoreDumpBlockMap = NULL;
98 static MM_DUMP_POINTERS MmCoreDumpDeviceFuncs;
99 ULONG MmCoreDumpType;
100
101 /*
102 * Translate between a swap entry and a file and offset pair.
103 */
104 #define FILE_FROM_ENTRY(i) ((i) >> 24)
105 #define OFFSET_FROM_ENTRY(i) (((i) & 0xffffff) - 1)
106 #define ENTRY_FROM_FILE_OFFSET(i, j) (((i) << 24) | ((j) + 1))
107
108 static BOOLEAN MmSwapSpaceMessage = FALSE;
109
110 /* FUNCTIONS *****************************************************************/
111
112 VOID
113 MmShowOutOfSpaceMessagePagingFile(VOID)
114 {
115 if (!MmSwapSpaceMessage)
116 {
117 DPRINT1("MM: Out of swap space.\n");
118 MmSwapSpaceMessage = TRUE;
119 }
120 }
121
122 NTSTATUS MmWriteToSwapPage(SWAPENTRY SwapEntry, PMDL Mdl)
123 {
124 ULONG i, offset;
125 LARGE_INTEGER file_offset;
126 IO_STATUS_BLOCK Iosb;
127 NTSTATUS Status;
128 KEVENT Event;
129
130 if (SwapEntry == 0)
131 {
132 KeBugCheck(0);
133 return(STATUS_UNSUCCESSFUL);
134 }
135
136 i = FILE_FROM_ENTRY(SwapEntry);
137 offset = OFFSET_FROM_ENTRY(SwapEntry);
138
139 if (i > MAX_PAGING_FILES)
140 {
141 DPRINT1("Bad swap entry 0x%.8X\n", SwapEntry);
142 KeBugCheck(0);
143 }
144 if (PagingFileList[i]->FileObject == NULL ||
145 PagingFileList[i]->FileObject->DeviceObject == NULL)
146 {
147 DPRINT1("Bad paging file 0x%.8X\n", SwapEntry);
148 KeBugCheck(0);
149 }
150
151 file_offset.QuadPart = offset * 4096;
152 KeInitializeEvent(&Event, NotificationEvent, FALSE);
153 Status = IoPageWrite(PagingFileList[i]->FileObject,
154 Mdl,
155 &file_offset,
156 &Event,
157 &Iosb);
158 if (Status == STATUS_PENDING)
159 {
160 KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL);
161 return(Iosb.Status);
162 }
163 return(Status);
164 }
165
166 NTSTATUS MmReadFromSwapPage(SWAPENTRY SwapEntry, PMDL Mdl)
167 {
168 ULONG i, offset;
169 LARGE_INTEGER file_offset;
170 IO_STATUS_BLOCK Iosb;
171 NTSTATUS Status;
172 KEVENT Event;
173
174 if (SwapEntry == 0)
175 {
176 KeBugCheck(0);
177 return(STATUS_UNSUCCESSFUL);
178 }
179
180 i = FILE_FROM_ENTRY(SwapEntry);
181 offset = OFFSET_FROM_ENTRY(SwapEntry);
182
183 if (i > MAX_PAGING_FILES)
184 {
185 DPRINT1("Bad swap entry 0x%.8X\n", SwapEntry);
186 KeBugCheck(0);
187 }
188 if (PagingFileList[i]->FileObject == NULL ||
189 PagingFileList[i]->FileObject->DeviceObject == NULL)
190 {
191 DPRINT1("Bad paging file 0x%.8X\n", SwapEntry);
192 KeBugCheck(0);
193 }
194
195 file_offset.QuadPart = offset * 4096;
196 KeInitializeEvent(&Event, NotificationEvent, FALSE);
197 Status = IoPageRead(PagingFileList[i]->FileObject,
198 Mdl,
199 &file_offset,
200 &Event,
201 &Iosb);
202 if (Status == STATUS_PENDING)
203 {
204 KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL);
205 return(Iosb.Status);
206 }
207 return(Status);
208 }
209
210 VOID
211 MmInitPagingFile(VOID)
212 {
213 ULONG i;
214
215 KeInitializeSpinLock(&PagingFileListLock);
216
217 MiFreeSwapPages = 0;
218 MiUsedSwapPages = 0;
219 MiReservedSwapPages = 0;
220
221 for (i = 0; i < MAX_PAGING_FILES; i++)
222 {
223 PagingFileList[i] = NULL;
224 }
225 MiPagingFileCount = 0;
226
227 /*
228 * Initialize the crash dump support.
229 */
230 MmCoreDumpPageFrame = MmAllocateSection(PAGE_SIZE);
231 if (MmCoreDumpType == MM_CORE_DUMP_TYPE_FULL)
232 {
233 MmCoreDumpSize = MmStats.NrTotalPages * 4096 + 1024 * 1024;
234 }
235 else
236 {
237 MmCoreDumpSize = 1024 * 1024;
238 }
239 }
240
241 BOOLEAN
242 MmReserveSwapPages(ULONG Nr)
243 {
244 KIRQL oldIrql;
245 ULONG MiAvailSwapPages;
246
247 KeAcquireSpinLock(&PagingFileListLock, &oldIrql);
248 MiAvailSwapPages =
249 (MiFreeSwapPages * MM_PAGEFILE_COMMIT_RATIO) + MM_PAGEFILE_COMMIT_GRACE;
250 MiReservedSwapPages = MiReservedSwapPages + Nr;
251 if (MM_PAGEFILE_COMMIT_RATIO != 0 && MiAvailSwapPages < MiReservedSwapPages)
252 {
253 KeReleaseSpinLock(&PagingFileListLock, oldIrql);
254 return(FALSE);
255 }
256 KeReleaseSpinLock(&PagingFileListLock, oldIrql);
257 return(TRUE);
258 }
259
260 VOID
261 MmDereserveSwapPages(ULONG Nr)
262 {
263 KIRQL oldIrql;
264
265 KeAcquireSpinLock(&PagingFileListLock, &oldIrql);
266 MiReservedSwapPages = MiReservedSwapPages - Nr;
267 KeReleaseSpinLock(&PagingFileListLock, oldIrql);
268 }
269
270 static ULONG
271 MiAllocPageFromPagingFile(PPAGINGFILE PagingFile)
272 {
273 KIRQL oldIrql;
274 ULONG i, j;
275
276 KeAcquireSpinLock(&PagingFile->AllocMapLock, &oldIrql);
277
278 for (i = 0; i < PagingFile->AllocMapSize; i++)
279 {
280 for (j = 0; j < 32; j++)
281 {
282 if (!(PagingFile->AllocMap[i] & (1 << j)))
283 {
284 break;
285 }
286 }
287 if (j == 32)
288 {
289 continue;
290 }
291 PagingFile->AllocMap[i] |= (1 << j);
292 PagingFile->UsedPages++;
293 PagingFile->FreePages--;
294 KeReleaseSpinLock(&PagingFile->AllocMapLock, oldIrql);
295 return((i * 32) + j);
296 }
297
298 KeReleaseSpinLock(&PagingFile->AllocMapLock, oldIrql);
299 return(0xFFFFFFFF);
300 }
301
302 VOID
303 MmFreeSwapPage(SWAPENTRY Entry)
304 {
305 ULONG i;
306 ULONG off;
307 KIRQL oldIrql;
308
309 i = FILE_FROM_ENTRY(Entry);
310 off = OFFSET_FROM_ENTRY(Entry);
311
312 KeAcquireSpinLock(&PagingFileListLock, &oldIrql);
313 if (PagingFileList[i] == NULL)
314 {
315 KeBugCheck(0);
316 }
317 KeAcquireSpinLockAtDpcLevel(&PagingFileList[i]->AllocMapLock);
318
319 PagingFileList[i]->AllocMap[off / 32] &= (~(1 << (off % 32)));
320
321 PagingFileList[i]->FreePages++;
322 PagingFileList[i]->UsedPages--;
323
324 MiFreeSwapPages++;
325 MiUsedSwapPages--;
326
327 KeReleaseSpinLockFromDpcLevel(&PagingFileList[i]->AllocMapLock);
328 KeReleaseSpinLock(&PagingFileListLock, oldIrql);
329 }
330
331 BOOLEAN
332 MmIsAvailableSwapPage(VOID)
333 {
334 return(MiFreeSwapPages > 0);
335 }
336
337 SWAPENTRY
338 MmAllocSwapPage(VOID)
339 {
340 KIRQL oldIrql;
341 ULONG i;
342 ULONG off;
343 SWAPENTRY entry;
344
345 KeAcquireSpinLock(&PagingFileListLock, &oldIrql);
346
347 if (MiFreeSwapPages == 0)
348 {
349 KeReleaseSpinLock(&PagingFileListLock, oldIrql);
350 return(0);
351 }
352
353 for (i = 0; i < MAX_PAGING_FILES; i++)
354 {
355 if (PagingFileList[i] != NULL &&
356 PagingFileList[i]->FreePages >= 1)
357 {
358 off = MiAllocPageFromPagingFile(PagingFileList[i]);
359 if (off == 0xFFFFFFFF)
360 {
361 KeBugCheck(0);
362 KeReleaseSpinLock(&PagingFileListLock, oldIrql);
363 return(STATUS_UNSUCCESSFUL);
364 }
365 MiUsedSwapPages++;
366 MiFreeSwapPages--;
367 KeReleaseSpinLock(&PagingFileListLock, oldIrql);
368
369 entry = ENTRY_FROM_FILE_OFFSET(i, off);
370 return(entry);
371 }
372 }
373
374 KeReleaseSpinLock(&PagingFileListLock, oldIrql);
375 KeBugCheck(0);
376 return(0);
377 }
378
379 NTSTATUS STDCALL
380 MmDumpToPagingFile(ULONG BugCode,
381 ULONG BugCodeParameter1,
382 ULONG BugCodeParameter2,
383 ULONG BugCodeParameter3,
384 ULONG BugCodeParameter4,
385 PKTRAP_FRAME TrapFrame)
386 {
387 PMM_CORE_DUMP_HEADER Headers;
388 PVOID Context;
389 NTSTATUS Status;
390 UCHAR MdlBase[sizeof(MDL) + sizeof(PVOID)];
391 PMDL Mdl = (PMDL)MdlBase;
392 PETHREAD Thread = PsGetCurrentThread();
393 ULONG StackSize;
394 PULONG MdlMap;
395 ULONG NextOffset = 0;
396 ULONG i;
397
398 if (MmCoreDumpBlockMap == NULL)
399 {
400 return(STATUS_UNSUCCESSFUL);
401 }
402
403 DbgPrint("MM: Dumping core");
404
405 /* Prepare the dump headers. */
406 Headers = (PMM_CORE_DUMP_HEADER)MmCoreDumpPageFrame;
407 Headers->Magic = MM_CORE_DUMP_HEADER_MAGIC;
408 Headers->Version = MM_CORE_DUMP_HEADER_VERSION;
409 Headers->Type = MmCoreDumpType;
410 if (TrapFrame != NULL)
411 {
412 if (!(TrapFrame->Eflags & (1 << 17)))
413 {
414 memcpy(&Headers->TrapFrame, TrapFrame,
415 sizeof(KTRAP_FRAME) - (4 * sizeof(DWORD)));
416 }
417 else
418 {
419 memcpy(&Headers->TrapFrame, TrapFrame, sizeof(KTRAP_FRAME));
420 }
421 }
422 Headers->BugCheckCode = BugCode;
423 Headers->BugCheckParameters[0] = BugCodeParameter1;
424 Headers->BugCheckParameters[1] = BugCodeParameter2;
425 Headers->BugCheckParameters[2] = BugCodeParameter3;
426 Headers->BugCheckParameters[3] = BugCodeParameter4;
427 Headers->FaultingStackBase = (PVOID)Thread->Tcb.StackLimit;
428 Headers->FaultingStackSize = StackSize =
429 (ULONG)(Thread->Tcb.StackBase - Thread->Tcb.StackLimit);
430 Headers->PhysicalMemorySize = MmStats.NrTotalPages * PAGE_SIZE;
431
432 /* Initialize the dump device. */
433 Context = MmCoreDumpDeviceFuncs.Context;
434 Status = MmCoreDumpDeviceFuncs.DeviceInit(Context);
435 if (!NT_SUCCESS(Status))
436 {
437 DPRINT1("MM: Failed to initialize core dump device.\n");
438 return(Status);
439 }
440
441 /* Initialize the MDL. */
442 Mdl->Next = NULL;
443 Mdl->Size = sizeof(MDL) + sizeof(PVOID);
444 Mdl->MdlFlags = MDL_SOURCE_IS_NONPAGED_POOL;
445 Mdl->Process = NULL;
446 Mdl->MappedSystemVa = MmCoreDumpPageFrame;
447 Mdl->StartVa = NULL;
448 Mdl->ByteCount = PAGE_SIZE;
449 Mdl->ByteOffset = 0;
450 MdlMap = (PULONG)(Mdl + 1);
451
452 /* Dump the header. */
453 MdlMap[0] = MmGetPhysicalAddress(MmCoreDumpPageFrame).u.LowPart;
454 Status = MmCoreDumpDeviceFuncs.DeviceWrite(Context,
455 MmCoreDumpBlockMap[NextOffset],
456 Mdl);
457 if (!NT_SUCCESS(Status))
458 {
459 DPRINT1("MM: Failed to write core dump header\n.");
460 }
461 NextOffset++;
462 DbgPrint(".");
463
464 /* Write out the kernel mode stack of the faulting thread. */
465 for (i = 0; i < (StackSize / PAGE_SIZE); i++)
466 {
467 Mdl->MappedSystemVa = (PVOID)(Thread->Tcb.StackLimit + (i * PAGE_SIZE));
468 MdlMap[0] = MmGetPhysicalAddress(Mdl->MappedSystemVa).u.LowPart;
469 Status =
470 MmCoreDumpDeviceFuncs.DeviceWrite(Context,
471 MmCoreDumpBlockMap[NextOffset],
472 Mdl);
473 if (!NT_SUCCESS(Status))
474 {
475 DPRINT1("MM: Failed to write page to core dump.\n");
476 return(Status);
477 }
478 DbgPrint(".");
479 NextOffset++;
480 }
481
482 /* Write out the contents of physical memory. */
483 if (MmCoreDumpType == MM_CORE_DUMP_TYPE_FULL)
484 {
485 for (i = 0; i < MmStats.NrTotalPages; i++)
486 {
487 LARGE_INTEGER PhysicalAddress;
488 PhysicalAddress.QuadPart = i * PAGE_SIZE;
489 MdlMap[0] = i * PAGE_SIZE;
490 MmCreateVirtualMappingForKernel(MmCoreDumpPageFrame,
491 PAGE_READWRITE,
492 PhysicalAddress);
493 Status =
494 MmCoreDumpDeviceFuncs.DeviceWrite(Context,
495 MmCoreDumpBlockMap[NextOffset],
496 Mdl);
497 if (!NT_SUCCESS(Status))
498 {
499 DPRINT1("MM: Failed to write page to core dump.\n");
500 return(Status);
501 }
502 DbgPrint(".");
503 NextOffset++;
504 }
505 }
506
507 DbgPrint("\n");
508 return(STATUS_SUCCESS);
509 }
510
511 NTSTATUS STDCALL
512 NtCreatePagingFile(IN PUNICODE_STRING FileName,
513 IN PLARGE_INTEGER InitialSize,
514 IN PLARGE_INTEGER MaximumSize,
515 IN ULONG Reserved)
516 {
517 NTSTATUS Status;
518 OBJECT_ATTRIBUTES ObjectAttributes;
519 HANDLE FileHandle;
520 IO_STATUS_BLOCK IoStatus;
521 PFILE_OBJECT FileObject;
522 PPAGINGFILE PagingFile;
523 KIRQL oldIrql;
524 ULONG AllocMapSize;
525 ULONG i;
526
527 DPRINT("NtCreatePagingFile(FileName %wZ, InitialSize %I64d)\n",
528 FileName, InitialSize->QuadPart);
529
530 if (MiPagingFileCount >= MAX_PAGING_FILES)
531 {
532 return(STATUS_TOO_MANY_PAGING_FILES);
533 }
534
535 InitializeObjectAttributes(&ObjectAttributes,
536 FileName,
537 0,
538 NULL,
539 NULL);
540
541 Status = IoCreateFile(&FileHandle,
542 FILE_ALL_ACCESS,
543 &ObjectAttributes,
544 &IoStatus,
545 NULL,
546 0,
547 0,
548 FILE_OPEN_IF,
549 FILE_SYNCHRONOUS_IO_NONALERT,
550 NULL,
551 0,
552 CreateFileTypeNone,
553 NULL,
554 SL_OPEN_PAGING_FILE);
555 if (!NT_SUCCESS(Status))
556 {
557 return(Status);
558 }
559
560 Status = NtSetInformationFile(FileHandle,
561 &IoStatus,
562 InitialSize,
563 sizeof(LARGE_INTEGER),
564 FileAllocationInformation);
565 if (!NT_SUCCESS(Status))
566 {
567 ZwClose(FileHandle);
568 return(Status);
569 }
570
571 Status = ObReferenceObjectByHandle(FileHandle,
572 FILE_ALL_ACCESS,
573 IoFileObjectType,
574 UserMode,
575 (PVOID*)&FileObject,
576 NULL);
577 if (!NT_SUCCESS(Status))
578 {
579 NtClose(FileHandle);
580 return(Status);
581 }
582
583 PagingFile = ExAllocatePool(NonPagedPool, sizeof(*PagingFile));
584 if (PagingFile == NULL)
585 {
586 ObDereferenceObject(FileObject);
587 NtClose(FileHandle);
588 return(STATUS_NO_MEMORY);
589 }
590
591 PagingFile->FileObject = FileObject;
592 PagingFile->MaximumSize.QuadPart = MaximumSize->QuadPart;
593 PagingFile->CurrentSize.QuadPart = InitialSize->QuadPart;
594 PagingFile->FreePages = InitialSize->QuadPart / PAGE_SIZE;
595 PagingFile->UsedPages = 0;
596 KeInitializeSpinLock(&PagingFile->AllocMapLock);
597
598 AllocMapSize = (PagingFile->FreePages / 32) + 1;
599 PagingFile->AllocMap = ExAllocatePool(NonPagedPool,
600 AllocMapSize * sizeof(ULONG));
601 PagingFile->AllocMapSize = AllocMapSize;
602
603 if (PagingFile->AllocMap == NULL)
604 {
605 ExFreePool(PagingFile);
606 ObDereferenceObject(FileObject);
607 NtClose(FileHandle);
608 return(STATUS_NO_MEMORY);
609 }
610
611 KeAcquireSpinLock(&PagingFileListLock, &oldIrql);
612 for (i = 0; i < MAX_PAGING_FILES; i++)
613 {
614 if (PagingFileList[i] == NULL)
615 {
616 PagingFileList[i] = PagingFile;
617 break;
618 }
619 }
620 MiFreeSwapPages = MiFreeSwapPages + PagingFile->FreePages;
621 MiPagingFileCount++;
622 KeReleaseSpinLock(&PagingFileListLock, oldIrql);
623
624 /* Check whether this pagefile can be a crash dump target. */
625 if (PagingFile->CurrentSize.QuadPart >= MmCoreDumpSize &&
626 MmCoreDumpBlockMap != NULL)
627 {
628 MmCoreDumpBlockMap =
629 ExAllocatePool(NonPagedPool,
630 (MmCoreDumpSize / PAGE_SIZE) * sizeof(ULONG));
631 if (MmCoreDumpBlockMap == NULL)
632 {
633 DPRINT1("Failed to allocate block map.\n");
634 NtClose(FileHandle);
635 return(STATUS_SUCCESS);
636 }
637 Status = ZwFsControlFile(FileHandle,
638 NULL,
639 NULL,
640 NULL,
641 &IoStatus,
642 FSCTL_GET_DUMP_BLOCK_MAP,
643 &MmCoreDumpSize,
644 sizeof(ULONG),
645 MmCoreDumpBlockMap,
646 (MmCoreDumpSize / PAGE_SIZE) * sizeof(ULONG));
647 if (!NT_SUCCESS(Status))
648 {
649 DPRINT1("Failed to get dump block map (Status %X)\n", Status);
650 NtClose(FileHandle);
651 ExFreePool(MmCoreDumpBlockMap);
652 MmCoreDumpBlockMap = NULL;
653 return(STATUS_SUCCESS);
654 }
655 Status = ZwDeviceIoControlFile(FileHandle,
656 NULL,
657 NULL,
658 NULL,
659 &IoStatus,
660 IOCTL_GET_DUMP_POINTERS,
661 NULL,
662 0,
663 &MmCoreDumpDeviceFuncs,
664 sizeof(MmCoreDumpDeviceFuncs));
665 if (!NT_SUCCESS(Status))
666 {
667 DPRINT1("Failed to get dump block map (Status %X)\n", Status);
668 NtClose(FileHandle);
669 ExFreePool(MmCoreDumpBlockMap);
670 MmCoreDumpBlockMap = NULL;
671 return(STATUS_SUCCESS);
672 }
673 }
674 NtClose(FileHandle);
675
676 MmSwapSpaceMessage = FALSE;
677
678 return(STATUS_SUCCESS);
679 }
680
681
682 /* EOF */
683
684
685
686
687
688
689