Home | Info | Community | Development | myReactOS | Contact Us
ReactOS Development > Doxygenswapout.c
Go to the documentation of this file.
00001 /* 00002 * Copyright (C) 1998-2005 ReactOS Team (and the authors from the programmers section) 00003 * 00004 * This program is free software; you can redistribute it and/or 00005 * modify it under the terms of the GNU General Public License 00006 * as published by the Free Software Foundation; either version 2 00007 * of the License, or (at your option) any later version. 00008 * 00009 * This program is distributed in the hope that it will be useful, 00010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00012 * GNU General Public License for more details. 00013 * 00014 * You should have received a copy of the GNU General Public License 00015 * along with this program; if not, write to the Free Software 00016 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 00017 * 00018 * 00019 * PROJECT: ReactOS kernel 00020 * FILE: ntoskrnl/mm/section/fault.c 00021 * PURPOSE: Consolidate fault handlers for sections 00022 * 00023 * PROGRAMMERS: Arty 00024 * Rex Jolliff 00025 * David Welch 00026 * Eric Kohl 00027 * Emanuele Aliberti 00028 * Eugene Ingerman 00029 * Casper Hornstrup 00030 * KJK::Hyperion 00031 * Guido de Jong 00032 * Ge van Geldorp 00033 * Royce Mitchell III 00034 * Filip Navara 00035 * Aleksey Bragin 00036 * Jason Filby 00037 * Thomas Weidenmueller 00038 * Gunnar Andre' Dalsnes 00039 * Mike Nordell 00040 * Alex Ionescu 00041 * Gregor Anich 00042 * Steven Edwards 00043 * Herve Poussineau 00044 */ 00045 00046 /* 00047 00048 This file implements page out infrastructure for cache type sections. This 00049 is implemented a little differently from the legacy mm because mapping in an 00050 address space and membership in a segment are considered separate. 00051 00052 The general strategy here is to try to remove all mappings as gently as 00053 possible, then to remove the page entry from the section itself as a final 00054 step. If at any time during the page out operation, the page is mapped in 00055 a new address space by a competing thread, the operation will abort before 00056 the segment page is finally removed, and the page will be naturally faulted 00057 back into any address spaces required in the normal way. 00058 00059 */ 00060 00061 /* INCLUDES *****************************************************************/ 00062 00063 #include <ntoskrnl.h> 00064 #include "newmm.h" 00065 #define NDEBUG 00066 #include <debug.h> 00067 00068 #define DPRINTC DPRINT 00069 00070 extern KEVENT MmWaitPageEvent; 00071 extern FAST_MUTEX RmapListLock; 00072 extern PMMWSL MmWorkingSetList; 00073 00074 FAST_MUTEX MiGlobalPageOperation; 00075 00076 /* 00077 00078 MmWithdrawSectionPage removes a page entry from the section segment, replacing 00079 it with a wait entry. The caller must replace the wait entry with a 0, when 00080 any required writing is done. The wait entry must remain until the page is 00081 written to protect against cases where a fault brings a stale copy of the page 00082 back before writing is complete. 00083 00084 */ 00085 PFN_NUMBER 00086 NTAPI 00087 MmWithdrawSectionPage(PMM_SECTION_SEGMENT Segment, 00088 PLARGE_INTEGER FileOffset, 00089 BOOLEAN *Dirty) 00090 { 00091 ULONG_PTR Entry; 00092 00093 DPRINT("MmWithdrawSectionPage(%x,%08x%08x,%x)\n", 00094 Segment, 00095 FileOffset->HighPart, 00096 FileOffset->LowPart, 00097 Dirty); 00098 00099 MmLockSectionSegment(Segment); 00100 Entry = MmGetPageEntrySectionSegment(Segment, FileOffset); 00101 00102 *Dirty = !!IS_DIRTY_SSE(Entry); 00103 00104 DPRINT("Withdraw %x (%x) of %wZ\n", 00105 FileOffset->LowPart, 00106 Entry, 00107 Segment->FileObject ? &Segment->FileObject->FileName : NULL); 00108 00109 if (!Entry) 00110 { 00111 DPRINT("Stoeled!\n"); 00112 MmUnlockSectionSegment(Segment); 00113 return 0; 00114 } 00115 else if (MM_IS_WAIT_PTE(Entry)) 00116 { 00117 DPRINT("WAIT\n"); 00118 MmUnlockSectionSegment(Segment); 00119 return MM_WAIT_ENTRY; 00120 } 00121 else if (Entry && !IS_SWAP_FROM_SSE(Entry)) 00122 { 00123 DPRINT("Page %x\n", PFN_FROM_SSE(Entry)); 00124 00125 *Dirty |= (Entry & 2); 00126 00127 MmSetPageEntrySectionSegment(Segment, 00128 FileOffset, 00129 MAKE_SWAP_SSE(MM_WAIT_ENTRY)); 00130 00131 MmUnlockSectionSegment(Segment); 00132 return PFN_FROM_SSE(Entry); 00133 } 00134 else 00135 { 00136 DPRINT1("SWAP ENTRY?! (%x:%08x%08x)\n", 00137 Segment, 00138 FileOffset->HighPart, 00139 FileOffset->LowPart); 00140 00141 ASSERT(FALSE); 00142 MmUnlockSectionSegment(Segment); 00143 return 0; 00144 } 00145 } 00146 00147 /* 00148 00149 This function determines whether the segment holds the very last reference to 00150 the page being considered and if so, writes it back or discards it as 00151 approriate. One small niggle here is that we might be holding the last 00152 reference to the section segment associated with this page. That happens 00153 when the segment is destroyed at the same time that an active swap operation 00154 is occurring, and all maps were already withdrawn. In that case, it's our 00155 responsiblity for finalizing the segment. 00156 00157 Note that in the current code, WriteZero is always TRUE because the section 00158 always backs a file. In the ultimate form of this code, it also writes back 00159 pages without necessarily evicting them. In reactos' trunk, this is vestigal. 00160 00161 */ 00162 00163 NTSTATUS 00164 NTAPI 00165 MmFinalizeSectionPageOut(PMM_SECTION_SEGMENT Segment, 00166 PLARGE_INTEGER FileOffset, 00167 PFN_NUMBER Page, 00168 BOOLEAN Dirty) 00169 { 00170 NTSTATUS Status = STATUS_SUCCESS; 00171 BOOLEAN WriteZero = FALSE, WritePage = FALSE; 00172 SWAPENTRY Swap = MmGetSavedSwapEntryPage(Page); 00173 00174 /* Bail early if the reference count isn't where we need it */ 00175 if (MmGetReferenceCountPage(Page) != 1) 00176 { 00177 DPRINT1("Cannot page out locked page %x with ref count %d\n", 00178 Page, 00179 MmGetReferenceCountPage(Page)); 00180 return STATUS_UNSUCCESSFUL; 00181 } 00182 00183 MmLockSectionSegment(Segment); 00184 (void)InterlockedIncrementUL(&Segment->ReferenceCount); 00185 00186 if (Dirty) 00187 { 00188 DPRINT("Finalize (dirty) Segment %x Page %x\n", Segment, Page); 00189 DPRINT("Segment->FileObject %x\n", Segment->FileObject); 00190 DPRINT("Segment->Flags %x\n", Segment->Flags); 00191 00192 WriteZero = TRUE; 00193 WritePage = TRUE; 00194 } 00195 else 00196 { 00197 WriteZero = TRUE; 00198 } 00199 00200 DPRINT("Status %x\n", Status); 00201 00202 MmUnlockSectionSegment(Segment); 00203 00204 if (WritePage) 00205 { 00206 DPRINT("MiWriteBackPage(Segment %x FileObject %x Offset %x)\n", 00207 Segment, 00208 Segment->FileObject, 00209 FileOffset->LowPart); 00210 00211 Status = MiWriteBackPage(Segment->FileObject, 00212 FileOffset, 00213 PAGE_SIZE, 00214 Page); 00215 } 00216 00217 MmLockSectionSegment(Segment); 00218 00219 if (WriteZero && NT_SUCCESS(Status)) 00220 { 00221 DPRINT("Setting page entry in segment %x:%x to swap %x\n", 00222 Segment, 00223 FileOffset->LowPart, 00224 Swap); 00225 00226 MmSetPageEntrySectionSegment(Segment, 00227 FileOffset, 00228 Swap ? MAKE_SWAP_SSE(Swap) : 0); 00229 } 00230 else 00231 { 00232 DPRINT("Setting page entry in segment %x:%x to page %x\n", 00233 Segment, 00234 FileOffset->LowPart, 00235 Page); 00236 00237 MmSetPageEntrySectionSegment(Segment, 00238 FileOffset, 00239 Page ? (Dirty ? DIRTY_SSE(MAKE_PFN_SSE(Page)) : MAKE_PFN_SSE(Page)) : 0); 00240 } 00241 00242 if (NT_SUCCESS(Status)) 00243 { 00244 DPRINT("Removing page %x for real\n", Page); 00245 MmSetSavedSwapEntryPage(Page, 0); 00246 MmReleasePageMemoryConsumer(MC_CACHE, Page); 00247 } 00248 00249 MmUnlockSectionSegment(Segment); 00250 00251 if (InterlockedDecrementUL(&Segment->ReferenceCount) == 0) 00252 { 00253 MmFinalizeSegment(Segment); 00254 } 00255 00256 /* Note: Writing may evict the segment... Nothing is guaranteed from here down */ 00257 MiSetPageEvent(Segment, FileOffset->LowPart); 00258 00259 DPRINT("Status %x\n", Status); 00260 return Status; 00261 } 00262 00263 /* 00264 00265 The slightly misnamed MmPageOutCacheSection removes a page from an address 00266 space in the manner of fault handlers found in fault.c. In the ultimate form 00267 of the code, this is one of the function pointers stored in a memory area 00268 to control how pages in that memory area are managed. 00269 00270 Also misleading is the call to MmReleasePageMemoryConsumer, which releases 00271 the reference held by this address space only. After all address spaces 00272 have had MmPageOutCacheSection succeed on them for the indicated page, 00273 then paging out of a cache page can continue. 00274 00275 */ 00276 00277 NTSTATUS 00278 NTAPI 00279 MmPageOutCacheSection(PMMSUPPORT AddressSpace, 00280 MEMORY_AREA* MemoryArea, 00281 PVOID Address, 00282 PBOOLEAN Dirty, 00283 PMM_REQUIRED_RESOURCES Required) 00284 { 00285 ULONG_PTR Entry; 00286 PFN_NUMBER OurPage; 00287 PEPROCESS Process = MmGetAddressSpaceOwner(AddressSpace); 00288 LARGE_INTEGER TotalOffset; 00289 PMM_SECTION_SEGMENT Segment; 00290 PVOID PAddress = MM_ROUND_DOWN(Address, PAGE_SIZE); 00291 00292 TotalOffset.QuadPart = (ULONG_PTR)PAddress - (ULONG_PTR)MemoryArea->StartingAddress + 00293 MemoryArea->Data.SectionData.ViewOffset.QuadPart; 00294 00295 Segment = MemoryArea->Data.SectionData.Segment; 00296 00297 MmLockSectionSegment(Segment); 00298 ASSERT(KeGetCurrentIrql() <= APC_LEVEL); 00299 00300 Entry = MmGetPageEntrySectionSegment(Segment, &TotalOffset); 00301 00302 if (MmIsPageSwapEntry(Process, PAddress)) 00303 { 00304 SWAPENTRY SwapEntry; 00305 MmGetPageFileMapping(Process, PAddress, &SwapEntry); 00306 MmUnlockSectionSegment(Segment); 00307 return SwapEntry == MM_WAIT_ENTRY ? STATUS_SUCCESS + 1 : STATUS_UNSUCCESSFUL; 00308 } 00309 00310 MmDeleteRmap(Required->Page[0], Process, Address); 00311 MmDeleteVirtualMapping(Process, Address, FALSE, Dirty, &OurPage); 00312 ASSERT(OurPage == Required->Page[0]); 00313 00314 /* Note: this releases the reference held by this address space only. */ 00315 MmReleasePageMemoryConsumer(MC_CACHE, Required->Page[0]); 00316 00317 MmUnlockSectionSegment(Segment); 00318 MiSetPageEvent(Process, Address); 00319 return STATUS_SUCCESS; 00320 } 00321 00322 /* 00323 00324 This function is called by rmap when spare pages are needed by the blancer. 00325 It attempts first to release the page from every address space in which it 00326 appears, and, after a final check that no competing thread has mapped the 00327 page again, uses MmFinalizeSectionPageOut to completely evict the page. If 00328 that's successful, then a suitable non-page map will be left in the segment 00329 page table, otherwise, the original page is replaced in the section page 00330 map. Failure may result from a variety of conditions, but always leaves 00331 the page mapped. 00332 00333 This code is like the other fault handlers, in that MmPageOutCacheSection has 00334 the option of returning either STATUS_SUCCESS + 1 to wait for a wait entry 00335 to disppear or to use the blocking callout facility by returning 00336 STATUS_MORE_PROCESSING_REQUIRED and placing a pointer to a function from 00337 reqtools.c in the MM_REQUIRED_RESOURCES struct. 00338 00339 */ 00340 00341 NTSTATUS 00342 NTAPI 00343 MmpPageOutPhysicalAddress(PFN_NUMBER Page) 00344 { 00345 BOOLEAN ProcRef = FALSE, PageDirty; 00346 PFN_NUMBER SectionPage = 0; 00347 PMM_RMAP_ENTRY entry; 00348 PMM_SECTION_SEGMENT Segment = NULL; 00349 LARGE_INTEGER FileOffset; 00350 PMEMORY_AREA MemoryArea; 00351 PMMSUPPORT AddressSpace = MmGetKernelAddressSpace(); 00352 BOOLEAN Dirty = FALSE; 00353 PVOID Address = NULL; 00354 PEPROCESS Process = NULL; 00355 NTSTATUS Status = STATUS_SUCCESS; 00356 MM_REQUIRED_RESOURCES Resources = { 0 }; 00357 00358 DPRINTC("Page out %x (ref ct %x)\n", Page, MmGetReferenceCountPage(Page)); 00359 00360 ExAcquireFastMutex(&MiGlobalPageOperation); 00361 if ((Segment = MmGetSectionAssociation(Page, &FileOffset))) 00362 { 00363 DPRINTC("Withdrawing page (%x) %x:%x\n", 00364 Page, 00365 Segment, 00366 FileOffset.LowPart); 00367 00368 SectionPage = MmWithdrawSectionPage(Segment, &FileOffset, &Dirty); 00369 DPRINTC("SectionPage %x\n", SectionPage); 00370 00371 if (SectionPage == MM_WAIT_ENTRY || SectionPage == 0) 00372 { 00373 DPRINT1("In progress page out %x\n", SectionPage); 00374 ExReleaseFastMutex(&MiGlobalPageOperation); 00375 return STATUS_UNSUCCESSFUL; 00376 } 00377 else 00378 { 00379 ASSERT(SectionPage == Page); 00380 } 00381 Resources.State = Dirty ? 1 : 0; 00382 } 00383 else 00384 { 00385 DPRINT("No segment association for %x\n", Page); 00386 } 00387 00388 00389 Dirty = MmIsDirtyPageRmap(Page); 00390 00391 DPRINTC("Trying to unmap all instances of %x\n", Page); 00392 ExAcquireFastMutex(&RmapListLock); 00393 entry = MmGetRmapListHeadPage(Page); 00394 00395 // Entry and Segment might be null here in the case that the page 00396 // is new and is in the process of being swapped in 00397 if (!entry && !Segment) 00398 { 00399 Status = STATUS_UNSUCCESSFUL; 00400 DPRINT1("Page %x is in transit\n", Page); 00401 ExReleaseFastMutex(&RmapListLock); 00402 goto bail; 00403 } 00404 00405 while (entry != NULL && NT_SUCCESS(Status)) 00406 { 00407 Process = entry->Process; 00408 Address = entry->Address; 00409 00410 DPRINTC("Process %x Address %x Page %x\n", Process, Address, Page); 00411 00412 if (RMAP_IS_SEGMENT(Address)) { 00413 entry = entry->Next; 00414 continue; 00415 } 00416 00417 if (Process && Address < MmSystemRangeStart) 00418 { 00419 /* Make sure we don't try to page out part of an exiting process */ 00420 if (PspIsProcessExiting(Process)) 00421 { 00422 DPRINT("bail\n"); 00423 ExReleaseFastMutex(&RmapListLock); 00424 goto bail; 00425 } 00426 ObReferenceObject(Process); 00427 ProcRef = TRUE; 00428 AddressSpace = &Process->Vm; 00429 } 00430 else 00431 { 00432 AddressSpace = MmGetKernelAddressSpace(); 00433 } 00434 ExReleaseFastMutex(&RmapListLock); 00435 00436 RtlZeroMemory(&Resources, sizeof(Resources)); 00437 00438 if ((((ULONG_PTR)Address) & 0xFFF) != 0) 00439 { 00440 KeBugCheck(MEMORY_MANAGEMENT); 00441 } 00442 00443 MmLockAddressSpace(AddressSpace); 00444 00445 do 00446 { 00447 MemoryArea = MmLocateMemoryAreaByAddress(AddressSpace, Address); 00448 if (MemoryArea == NULL || MemoryArea->DeleteInProgress) 00449 { 00450 Status = STATUS_UNSUCCESSFUL; 00451 MmUnlockAddressSpace(AddressSpace); 00452 DPRINTC("bail\n"); 00453 goto bail; 00454 } 00455 00456 DPRINTC("Type %x (%x -> %x)\n", 00457 MemoryArea->Type, 00458 MemoryArea->StartingAddress, 00459 MemoryArea->EndingAddress); 00460 00461 Resources.DoAcquisition = NULL; 00462 Resources.Page[0] = Page; 00463 00464 ASSERT(KeGetCurrentIrql() <= APC_LEVEL); 00465 00466 DPRINT("%x:%x, page %x %x\n", 00467 Process, 00468 Address, 00469 Page, 00470 Resources.Page[0]); 00471 00472 PageDirty = FALSE; 00473 00474 Status = MmPageOutCacheSection(AddressSpace, 00475 MemoryArea, 00476 Address, 00477 &PageDirty, 00478 &Resources); 00479 00480 Dirty |= PageDirty; 00481 DPRINT("%x\n", Status); 00482 00483 ASSERT(KeGetCurrentIrql() <= APC_LEVEL); 00484 00485 MmUnlockAddressSpace(AddressSpace); 00486 00487 if (Status == STATUS_SUCCESS + 1) 00488 { 00489 // Wait page ... the other guy has it, so we'll just fail for now 00490 DPRINT1("Wait entry ... can't continue\n"); 00491 Status = STATUS_UNSUCCESSFUL; 00492 goto bail; 00493 } 00494 else if (Status == STATUS_MORE_PROCESSING_REQUIRED) 00495 { 00496 DPRINTC("DoAcquisition %x\n", Resources.DoAcquisition); 00497 00498 Status = Resources.DoAcquisition(AddressSpace, 00499 MemoryArea, 00500 &Resources); 00501 00502 DPRINTC("Status %x\n", Status); 00503 if (!NT_SUCCESS(Status)) 00504 { 00505 DPRINT1("bail\n"); 00506 goto bail; 00507 } 00508 else Status = STATUS_MM_RESTART_OPERATION; 00509 } 00510 00511 MmLockAddressSpace(AddressSpace); 00512 } 00513 while (Status == STATUS_MM_RESTART_OPERATION); 00514 00515 MmUnlockAddressSpace(AddressSpace); 00516 00517 if (ProcRef) 00518 { 00519 ObDereferenceObject(Process); 00520 ProcRef = FALSE; 00521 } 00522 00523 ExAcquireFastMutex(&RmapListLock); 00524 ASSERT(!MM_IS_WAIT_PTE(MmGetPfnForProcess(Process, Address))); 00525 entry = MmGetRmapListHeadPage(Page); 00526 00527 DPRINTC("Entry %x\n", entry); 00528 } 00529 00530 ExReleaseFastMutex(&RmapListLock); 00531 00532 bail: 00533 DPRINTC("BAIL %x\n", Status); 00534 00535 if (Segment) 00536 { 00537 ULONG RefCount; 00538 00539 DPRINTC("About to finalize section page %x (%x:%x) Status %x %s\n", 00540 Page, 00541 Segment, 00542 FileOffset.LowPart, 00543 Status, 00544 Dirty ? "dirty" : "clean"); 00545 00546 if (!NT_SUCCESS(Status) || 00547 !NT_SUCCESS(Status = MmFinalizeSectionPageOut(Segment, 00548 &FileOffset, 00549 Page, 00550 Dirty))) 00551 { 00552 DPRINTC("Failed to page out %x, replacing %x at %x in segment %x\n", 00553 SectionPage, 00554 FileOffset.LowPart, 00555 Segment); 00556 00557 MmLockSectionSegment(Segment); 00558 00559 MmSetPageEntrySectionSegment(Segment, 00560 &FileOffset, 00561 Dirty ? MAKE_PFN_SSE(Page) : DIRTY_SSE(MAKE_PFN_SSE(Page))); 00562 00563 MmUnlockSectionSegment(Segment); 00564 } 00565 00566 /* Alas, we had the last reference */ 00567 if ((RefCount = InterlockedDecrementUL(&Segment->ReferenceCount)) == 0) 00568 MmFinalizeSegment(Segment); 00569 } 00570 00571 if (ProcRef) 00572 { 00573 DPRINTC("Dereferencing process...\n"); 00574 ObDereferenceObject(Process); 00575 } 00576 00577 ExReleaseFastMutex(&MiGlobalPageOperation); 00578 00579 DPRINTC("%s %x %x\n", 00580 NT_SUCCESS(Status) ? "Evicted" : "Spared", 00581 Page, 00582 Status); 00583 00584 return NT_SUCCESS(Status) ? STATUS_SUCCESS : STATUS_UNSUCCESSFUL; 00585 } 00586 00587 ULONG 00588 NTAPI 00589 MiCacheEvictPages(PMM_SECTION_SEGMENT Segment, 00590 ULONG Target) 00591 { 00592 ULONG_PTR Entry; 00593 ULONG Result = 0, i, j; 00594 NTSTATUS Status; 00595 PFN_NUMBER Page; 00596 LARGE_INTEGER Offset; 00597 00598 MmLockSectionSegment(Segment); 00599 00600 for (i = 0; i < RtlNumberGenericTableElements(&Segment->PageTable); i++) { 00601 00602 PCACHE_SECTION_PAGE_TABLE Element = RtlGetElementGenericTable(&Segment->PageTable, 00603 i); 00604 00605 ASSERT(Element); 00606 00607 Offset = Element->FileOffset; 00608 for (j = 0; j < ENTRIES_PER_ELEMENT; j++, Offset.QuadPart += PAGE_SIZE) { 00609 Entry = MmGetPageEntrySectionSegment(Segment, &Offset); 00610 if (Entry && !IS_SWAP_FROM_SSE(Entry)) { 00611 Page = PFN_FROM_SSE(Entry); 00612 MmUnlockSectionSegment(Segment); 00613 Status = MmpPageOutPhysicalAddress(Page); 00614 if (NT_SUCCESS(Status)) 00615 Result++; 00616 MmLockSectionSegment(Segment); 00617 } 00618 } 00619 } 00620 00621 MmUnlockSectionSegment(Segment); 00622 00623 return Result; 00624 } 00625 00626 extern LIST_ENTRY MiSegmentList; 00627 00628 // Interact with legacy balance manager for now 00629 // This can fall away when our section implementation supports 00630 // demand paging properly 00631 NTSTATUS 00632 MiRosTrimCache(ULONG Target, 00633 ULONG Priority, 00634 PULONG NrFreed) 00635 { 00636 ULONG Freed; 00637 PLIST_ENTRY Entry; 00638 PMM_SECTION_SEGMENT Segment; 00639 *NrFreed = 0; 00640 00641 DPRINT1("Need to trim %d cache pages\n", Target); 00642 for (Entry = MiSegmentList.Flink; 00643 *NrFreed < Target && Entry != &MiSegmentList; 00644 Entry = Entry->Flink) { 00645 Segment = CONTAINING_RECORD(Entry, MM_SECTION_SEGMENT, ListOfSegments); 00646 /* Defer to MM to try recovering pages from it */ 00647 Freed = MiCacheEvictPages(Segment, Target); 00648 *NrFreed += Freed; 00649 } 00650 DPRINT1("Evicted %d cache pages\n", Target); 00651 00652 if (!IsListEmpty(&MiSegmentList)) { 00653 Entry = MiSegmentList.Flink; 00654 RemoveEntryList(Entry); 00655 InsertTailList(&MiSegmentList, Entry); 00656 } 00657 00658 return STATUS_SUCCESS; 00659 } Generated on Sun May 27 2012 04:37:05 for ReactOS by
1.7.6.1
|