[NPFS]
[reactos.git] / reactos / drivers / filesystems / npfs / fsctrl.c
1 /*
2 * COPYRIGHT: See COPYING in the top level directory
3 * PROJECT: ReactOS kernel
4 * FILE: drivers/fs/np/fsctrl.c
5 * PURPOSE: Named pipe filesystem
6 * PROGRAMMER: David Welch <welch@cwcom.net>
7 * Eric Kohl
8 * Michael Martin
9 */
10
11 /* INCLUDES ******************************************************************/
12
13 #include "npfs.h"
14
15 #define NDEBUG
16 #include <debug.h>
17
18 /* FUNCTIONS *****************************************************************/
19
20 static DRIVER_CANCEL NpfsListeningCancelRoutine;
21 static VOID NTAPI
22 NpfsListeningCancelRoutine(IN PDEVICE_OBJECT DeviceObject,
23 IN PIRP Irp)
24 {
25 PNPFS_WAITER_ENTRY Waiter;
26
27 Waiter = (PNPFS_WAITER_ENTRY)&Irp->Tail.Overlay.DriverContext;
28
29 DPRINT("NpfsListeningCancelRoutine() called for <%wZ>\n",
30 &Waiter->Ccb->Fcb->PipeName);
31
32 IoReleaseCancelSpinLock(Irp->CancelIrql);
33
34
35 KeLockMutex(&Waiter->Ccb->Fcb->CcbListLock);
36 RemoveEntryList(&Waiter->Entry);
37 KeUnlockMutex(&Waiter->Ccb->Fcb->CcbListLock);
38
39 Irp->IoStatus.Status = STATUS_CANCELLED;
40 Irp->IoStatus.Information = 0;
41 IoCompleteRequest(Irp, IO_NO_INCREMENT);
42 }
43
44
45 static NTSTATUS
46 NpfsAddListeningServerInstance(PIRP Irp,
47 PNPFS_CCB Ccb)
48 {
49 PNPFS_WAITER_ENTRY Entry;
50 KIRQL oldIrql;
51
52 Entry = (PNPFS_WAITER_ENTRY)&Irp->Tail.Overlay.DriverContext;
53
54 Entry->Ccb = Ccb;
55
56 KeLockMutex(&Ccb->Fcb->CcbListLock);
57
58 IoMarkIrpPending(Irp);
59 InsertTailList(&Ccb->Fcb->WaiterListHead, &Entry->Entry);
60
61 IoAcquireCancelSpinLock(&oldIrql);
62 if (!Irp->Cancel)
63 {
64 (void)IoSetCancelRoutine(Irp, NpfsListeningCancelRoutine);
65 IoReleaseCancelSpinLock(oldIrql);
66 KeUnlockMutex(&Ccb->Fcb->CcbListLock);
67 return STATUS_PENDING;
68 }
69 IoReleaseCancelSpinLock(oldIrql);
70
71 RemoveEntryList(&Entry->Entry);
72
73 Irp->IoStatus.Status = STATUS_CANCELLED;
74 Irp->IoStatus.Information = 0;
75 IoCompleteRequest(Irp, IO_NO_INCREMENT);
76 KeUnlockMutex(&Ccb->Fcb->CcbListLock);
77
78 return STATUS_CANCELLED;
79 }
80
81
82 static NTSTATUS
83 NpfsConnectPipe(PIRP Irp,
84 PNPFS_CCB Ccb)
85 {
86 PIO_STACK_LOCATION IoStack;
87 PFILE_OBJECT FileObject;
88 ULONG Flags;
89 PLIST_ENTRY current_entry;
90 PNPFS_FCB Fcb;
91 PNPFS_CCB ClientCcb;
92 NTSTATUS Status;
93
94 DPRINT("NpfsConnectPipe()\n");
95
96 if (Ccb->PipeState == FILE_PIPE_CONNECTED_STATE)
97 {
98 KeResetEvent(&Ccb->ConnectEvent);
99 return STATUS_PIPE_CONNECTED;
100 }
101
102 if (Ccb->PipeState == FILE_PIPE_CLOSING_STATE)
103 return STATUS_PIPE_CLOSING;
104
105 DPRINT("Waiting for connection...\n");
106
107 Fcb = Ccb->Fcb;
108 IoStack = IoGetCurrentIrpStackLocation(Irp);
109 FileObject = IoStack->FileObject;
110 Flags = FileObject->Flags;
111
112 /* search for a listening client fcb */
113 KeLockMutex(&Fcb->CcbListLock);
114
115 current_entry = Fcb->ClientCcbListHead.Flink;
116 while (current_entry != &Fcb->ClientCcbListHead)
117 {
118 ClientCcb = CONTAINING_RECORD(current_entry,
119 NPFS_CCB,
120 CcbListEntry);
121
122 if (ClientCcb->PipeState == 0)
123 {
124 /* found a passive (waiting) client CCB */
125 DPRINT("Passive (waiting) client CCB found -- wake the client\n");
126 KeSetEvent(&ClientCcb->ConnectEvent, IO_NO_INCREMENT, FALSE);
127 break;
128 }
129
130 #if 0
131 if (ClientCcb->PipeState == FILE_PIPE_LISTENING_STATE)
132 {
133 /* found a listening client CCB */
134 DPRINT("Listening client CCB found -- connecting\n");
135
136 /* connect client and server CCBs */
137 Ccb->OtherSide = ClientCcb;
138 ClientCcb->OtherSide = Ccb;
139
140 /* set connected state */
141 Ccb->PipeState = FILE_PIPE_CONNECTED_STATE;
142 ClientCcb->PipeState = FILE_PIPE_CONNECTED_STATE;
143
144 KeUnlockMutex(&Fcb->CcbListLock);
145
146 /* FIXME: create and initialize data queues */
147
148 /* signal client's connect event */
149 DPRINT("Setting the ConnectEvent for %x\n", ClientCcb);
150 KeSetEvent(&ClientCcb->ConnectEvent, IO_NO_INCREMENT, FALSE);
151
152 return STATUS_PIPE_CONNECTED;
153 }
154 #endif
155
156 current_entry = current_entry->Flink;
157 }
158
159 /* no listening client fcb found */
160 DPRINT("No listening client fcb found -- waiting for client\n");
161
162 Ccb->PipeState = FILE_PIPE_LISTENING_STATE;
163
164 Status = NpfsAddListeningServerInstance(Irp, Ccb);
165
166 KeUnlockMutex(&Fcb->CcbListLock);
167
168 if (Flags & FO_SYNCHRONOUS_IO)
169 {
170 KeWaitForSingleObject(&Ccb->ConnectEvent,
171 UserRequest,
172 Irp->RequestorMode,
173 FALSE,
174 NULL);
175 }
176
177 DPRINT("NpfsConnectPipe() done (Status %lx)\n", Status);
178
179 return Status;
180 }
181
182
183 static NTSTATUS
184 NpfsDisconnectPipe(PNPFS_CCB Ccb)
185 {
186 NTSTATUS Status;
187 PNPFS_FCB Fcb;
188 PNPFS_CCB OtherSide;
189 BOOLEAN Server;
190
191 DPRINT("NpfsDisconnectPipe()\n");
192
193 Fcb = Ccb->Fcb;
194 KeLockMutex(&Fcb->CcbListLock);
195
196 if (Ccb->PipeState == FILE_PIPE_DISCONNECTED_STATE)
197 {
198 DPRINT("Pipe is already disconnected\n");
199 Status = STATUS_PIPE_DISCONNECTED;
200 }
201 else if ((!Ccb->OtherSide) && (Ccb->PipeState == FILE_PIPE_CONNECTED_STATE))
202 {
203 ExAcquireFastMutex(&Ccb->DataListLock);
204 Ccb->PipeState = FILE_PIPE_DISCONNECTED_STATE;
205 ExReleaseFastMutex(&Ccb->DataListLock);
206 Status = STATUS_SUCCESS;
207 }
208 else if (Ccb->PipeState == FILE_PIPE_CONNECTED_STATE)
209 {
210 Server = (Ccb->PipeEnd == FILE_PIPE_SERVER_END);
211 OtherSide = Ccb->OtherSide;
212 //Ccb->OtherSide = NULL;
213 Ccb->PipeState = FILE_PIPE_DISCONNECTED_STATE;
214 /* Lock the server first */
215 if (Server)
216 {
217 ExAcquireFastMutex(&Ccb->DataListLock);
218 ExAcquireFastMutex(&OtherSide->DataListLock);
219 }
220 else
221 {
222 ExAcquireFastMutex(&OtherSide->DataListLock);
223 ExAcquireFastMutex(&Ccb->DataListLock);
224 }
225 OtherSide->PipeState = FILE_PIPE_DISCONNECTED_STATE;
226 //OtherSide->OtherSide = NULL;
227 /*
228 * Signaling the write event. If is possible that an other
229 * thread waits for an empty buffer.
230 */
231 KeSetEvent(&OtherSide->ReadEvent, IO_NO_INCREMENT, FALSE);
232 KeSetEvent(&OtherSide->WriteEvent, IO_NO_INCREMENT, FALSE);
233 if (Server)
234 {
235 ExReleaseFastMutex(&OtherSide->DataListLock);
236 ExReleaseFastMutex(&Ccb->DataListLock);
237 }
238 else
239 {
240 ExReleaseFastMutex(&Ccb->DataListLock);
241 ExReleaseFastMutex(&OtherSide->DataListLock);
242 }
243 Status = STATUS_SUCCESS;
244 }
245 else if (Ccb->PipeState == FILE_PIPE_LISTENING_STATE)
246 {
247 PLIST_ENTRY Entry;
248 PNPFS_WAITER_ENTRY WaitEntry = NULL;
249 BOOLEAN Complete = FALSE;
250 PIRP Irp = NULL;
251
252 Entry = Ccb->Fcb->WaiterListHead.Flink;
253 while (Entry != &Ccb->Fcb->WaiterListHead)
254 {
255 WaitEntry = CONTAINING_RECORD(Entry, NPFS_WAITER_ENTRY, Entry);
256 if (WaitEntry->Ccb == Ccb)
257 {
258 RemoveEntryList(Entry);
259 Irp = CONTAINING_RECORD(Entry, IRP, Tail.Overlay.DriverContext);
260 Complete = (NULL == IoSetCancelRoutine(Irp, NULL));
261 break;
262 }
263 Entry = Entry->Flink;
264 }
265
266 if (Irp)
267 {
268 if (Complete)
269 {
270 Irp->IoStatus.Status = STATUS_PIPE_BROKEN;
271 Irp->IoStatus.Information = 0;
272 IoCompleteRequest(Irp, IO_NO_INCREMENT);
273 }
274 }
275 Ccb->PipeState = FILE_PIPE_DISCONNECTED_STATE;
276 Status = STATUS_SUCCESS;
277 }
278 else if (Ccb->PipeState == FILE_PIPE_CLOSING_STATE)
279 {
280 Status = STATUS_PIPE_CLOSING;
281 }
282 else
283 {
284 Status = STATUS_UNSUCCESSFUL;
285 }
286 KeUnlockMutex(&Fcb->CcbListLock);
287 return Status;
288 }
289
290
291 static NTSTATUS
292 NpfsWaitPipe(PIRP Irp,
293 PNPFS_CCB Ccb)
294 {
295 PLIST_ENTRY current_entry;
296 PNPFS_VCB Vcb;
297 PNPFS_FCB Fcb;
298 PNPFS_CCB ServerCcb;
299 PFILE_PIPE_WAIT_FOR_BUFFER WaitPipe;
300 LARGE_INTEGER TimeOut;
301 UNICODE_STRING PipeName;
302 NTSTATUS Status;
303
304 DPRINT("NpfsWaitPipe\n");
305
306 WaitPipe = (PFILE_PIPE_WAIT_FOR_BUFFER)Irp->AssociatedIrp.SystemBuffer;
307
308 /* Fail, if the CCB does not represent the root directory */
309 if (Ccb->Type != CCB_DIRECTORY)
310 return STATUS_ILLEGAL_FUNCTION;
311
312 /* Calculate the pipe name length and allocate the buffer */
313 PipeName.Length = WaitPipe->NameLength + sizeof(WCHAR);
314 PipeName.MaximumLength = PipeName.Length + sizeof(WCHAR);
315 PipeName.Buffer = ExAllocatePool(NonPagedPool, PipeName.MaximumLength);
316 if (PipeName.Buffer == NULL)
317 {
318 DPRINT1("Could not allocate memory for the pipe name!\n");
319 return STATUS_NO_MEMORY;
320 }
321
322 /* Copy the pipe name into the buffer, prepend a backslash and append a 0 character */
323 PipeName.Buffer[0] = L'\\';
324 RtlCopyMemory(&PipeName.Buffer[1],
325 &WaitPipe->Name[0],
326 WaitPipe->NameLength);
327 PipeName.Buffer[PipeName.Length / sizeof(WCHAR)] = 0;
328
329 DPRINT("Waiting for Pipe %wZ\n", &PipeName);
330
331 /* Get the VCB */
332 Vcb = Ccb->Fcb->Vcb;
333
334 /* Lock the pipe list */
335 KeLockMutex(&Vcb->PipeListLock);
336
337 /* File a pipe with the given name */
338 Fcb = NpfsFindPipe(Vcb,
339 &PipeName);
340
341 /* Unlock the pipe list */
342 KeUnlockMutex(&Vcb->PipeListLock);
343
344 /* Release the pipe name buffer */
345 ExFreePool(PipeName.Buffer);
346
347 /* Fail if not pipe was found */
348 if (Fcb == NULL)
349 {
350 DPRINT("No pipe found!\n", Fcb);
351 return STATUS_OBJECT_NAME_NOT_FOUND;
352 }
353
354 DPRINT("Fcb %p\n", Fcb);
355
356 /* search for listening server */
357 current_entry = Fcb->ServerCcbListHead.Flink;
358 while (current_entry != &Fcb->ServerCcbListHead)
359 {
360 ServerCcb = CONTAINING_RECORD(current_entry,
361 NPFS_CCB,
362 CcbListEntry);
363
364 if (ServerCcb->PipeState == FILE_PIPE_LISTENING_STATE)
365 {
366 /* found a listening server CCB */
367 DPRINT("Listening server CCB found -- connecting\n");
368
369 return STATUS_SUCCESS;
370 }
371
372 current_entry = current_entry->Flink;
373 }
374
375 /* No listening server fcb found */
376
377 /* If no timeout specified, use the default one */
378 if (WaitPipe->TimeoutSpecified)
379 TimeOut = WaitPipe->Timeout;
380 else
381 TimeOut = Fcb->TimeOut;
382
383 /* Wait for one */
384 Status = KeWaitForSingleObject(&Ccb->ConnectEvent,
385 UserRequest,
386 KernelMode,
387 FALSE,
388 &TimeOut);
389
390 DPRINT("KeWaitForSingleObject() returned (Status %lx)\n", Status);
391
392 return Status;
393 }
394
395
396 /*
397 * FUNCTION: Return current state of a pipe
398 * ARGUMENTS:
399 * Irp = Pointer to I/O request packet
400 * IrpSp = Pointer to current stack location of Irp
401 * RETURNS:
402 * Status of operation
403 */
404
405 /*
406 * FUNCTION: Peek at a pipe (get information about messages)
407 * ARGUMENTS:
408 * Irp = Pointer to I/O request packet
409 * IoStack = Pointer to current stack location of Irp
410 * RETURNS:
411 * Status of operation
412 */
413 static NTSTATUS
414 NpfsPeekPipe(PIRP Irp,
415 PIO_STACK_LOCATION IoStack)
416 {
417 ULONG OutputBufferLength;
418 ULONG ReturnLength = 0;
419 PFILE_PIPE_PEEK_BUFFER Reply;
420 PNPFS_FCB Fcb;
421 PNPFS_CCB Ccb;
422 NTSTATUS Status;
423 ULONG MessageCount = 0;
424 ULONG MessageLength;
425 ULONG ReadDataAvailable;
426 PVOID BufferPtr;
427
428 DPRINT("NpfsPeekPipe\n");
429
430 OutputBufferLength = IoStack->Parameters.DeviceIoControl.OutputBufferLength;
431 DPRINT("OutputBufferLength: %lu\n", OutputBufferLength);
432
433 /* Validate parameters */
434 if (OutputBufferLength < sizeof(FILE_PIPE_PEEK_BUFFER))
435 {
436 DPRINT1("Buffer too small\n");
437 return STATUS_INVALID_PARAMETER;
438 }
439
440 Ccb = IoStack->FileObject->FsContext2;
441 Reply = (PFILE_PIPE_PEEK_BUFFER)Irp->AssociatedIrp.SystemBuffer;
442 Fcb = Ccb->Fcb;
443
444
445 Reply->NamedPipeState = Ccb->PipeState;
446
447 Reply->ReadDataAvailable = Ccb->ReadDataAvailable;
448 DPRINT("ReadDataAvailable: %lu\n", Ccb->ReadDataAvailable);
449
450 ExAcquireFastMutex(&Ccb->DataListLock);
451 BufferPtr = Ccb->ReadPtr;
452 DPRINT("BufferPtr = %x\n", BufferPtr);
453 if (Ccb->Fcb->PipeType == FILE_PIPE_BYTE_STREAM_TYPE)
454 {
455 DPRINT("Byte Stream Mode\n");
456 Reply->MessageLength = Ccb->ReadDataAvailable;
457 DPRINT("Reply->MessageLength %lu\n",Reply->MessageLength );
458 MessageCount = 1;
459
460 if (Reply->Data[0] && (OutputBufferLength >= Ccb->ReadDataAvailable + FIELD_OFFSET(FILE_PIPE_PEEK_BUFFER, Data[0])))
461 {
462 ReturnLength = Ccb->ReadDataAvailable;
463 memcpy(&Reply->Data[0], (PVOID)BufferPtr, Ccb->ReadDataAvailable);
464 }
465 }
466 else
467 {
468 DPRINT("Message Mode\n");
469 ReadDataAvailable=Ccb->ReadDataAvailable;
470
471 if (ReadDataAvailable > 0)
472 {
473 memcpy(&Reply->MessageLength, BufferPtr, sizeof(ULONG));
474
475 while ((ReadDataAvailable > 0) && (BufferPtr < Ccb->WritePtr))
476 {
477 memcpy(&MessageLength, BufferPtr, sizeof(MessageLength));
478
479 ASSERT(MessageLength > 0);
480
481 DPRINT("MessageLength = %lu\n",MessageLength);
482 ReadDataAvailable -= MessageLength;
483 MessageCount++;
484
485 /* If its the first message, copy the Message if the size of buffer is large enough */
486 if (MessageCount==1)
487 {
488 if ((Reply->Data[0])
489 && (OutputBufferLength >= (MessageLength + FIELD_OFFSET(FILE_PIPE_PEEK_BUFFER, Data[0]))))
490 {
491 memcpy(&Reply->Data[0], (PVOID)((ULONG_PTR)BufferPtr + sizeof(MessageLength)), MessageLength);
492 ReturnLength = MessageLength;
493 }
494 }
495
496 BufferPtr =(PVOID)((ULONG_PTR)BufferPtr + MessageLength + sizeof(MessageLength));
497 DPRINT("BufferPtr = %x\n", BufferPtr);
498 DPRINT("ReadDataAvailable: %lu\n", ReadDataAvailable);
499 }
500
501 if (ReadDataAvailable != 0)
502 {
503 DPRINT1("Possible memory corruption.\n");
504 ASSERT(FALSE);
505 }
506 }
507 }
508 ExReleaseFastMutex(&Ccb->DataListLock);
509
510 Reply->NumberOfMessages = MessageCount;
511
512 Irp->IoStatus.Information = ReturnLength + FIELD_OFFSET(FILE_PIPE_PEEK_BUFFER, Data[0]);
513 Irp->IoStatus.Status = STATUS_SUCCESS;
514
515 Status = STATUS_SUCCESS;
516
517 DPRINT("NpfsPeekPipe done\n");
518
519 return Status;
520 }
521
522
523 NTSTATUS NTAPI
524 NpfsFileSystemControl(PDEVICE_OBJECT DeviceObject,
525 PIRP Irp)
526 {
527 PIO_STACK_LOCATION IoStack;
528 PFILE_OBJECT FileObject;
529 NTSTATUS Status;
530 PNPFS_VCB Vcb;
531 PNPFS_FCB Fcb;
532 PNPFS_CCB Ccb;
533
534 DPRINT("NpfsFileSystemContol(DeviceObject %p Irp %p)\n", DeviceObject, Irp);
535
536 Vcb = (PNPFS_VCB)DeviceObject->DeviceExtension;
537 IoStack = IoGetCurrentIrpStackLocation(Irp);
538 DPRINT("IoStack: %p\n", IoStack);
539 FileObject = IoStack->FileObject;
540 DPRINT("FileObject: %p\n", FileObject);
541 Ccb = FileObject->FsContext2;
542 DPRINT("CCB: %p\n", Ccb);
543 Fcb = Ccb->Fcb;
544 DPRINT("Pipe: %p\n", Fcb);
545 DPRINT("PipeName: %wZ\n", &Fcb->PipeName);
546
547 Irp->IoStatus.Information = 0;
548
549 switch (IoStack->Parameters.FileSystemControl.FsControlCode)
550 {
551 case FSCTL_PIPE_ASSIGN_EVENT:
552 DPRINT1("Assign event not implemented\n");
553 Status = STATUS_NOT_IMPLEMENTED;
554 break;
555
556 case FSCTL_PIPE_DISCONNECT:
557 DPRINT("Disconnecting pipe %wZ\n", &Fcb->PipeName);
558 Status = NpfsDisconnectPipe(Ccb);
559 break;
560
561 case FSCTL_PIPE_LISTEN:
562 DPRINT("Connecting pipe %wZ\n", &Fcb->PipeName);
563 Status = NpfsConnectPipe(Irp, Ccb);
564 break;
565
566 case FSCTL_PIPE_PEEK:
567 DPRINT("Peeking pipe %wZ\n", &Fcb->PipeName);
568 Status = NpfsPeekPipe(Irp, (PIO_STACK_LOCATION)IoStack);
569 break;
570
571 case FSCTL_PIPE_QUERY_EVENT:
572 DPRINT1("Query event not implemented\n");
573 Status = STATUS_NOT_IMPLEMENTED;
574 break;
575
576 case FSCTL_PIPE_TRANSCEIVE:
577 /* If you implement this, please remove the workaround in
578 lib/kernel32/file/npipe.c function TransactNamedPipe() */
579 DPRINT1("Transceive not implemented\n");
580 Status = STATUS_NOT_IMPLEMENTED;
581 break;
582
583 case FSCTL_PIPE_WAIT:
584 DPRINT("Waiting for pipe %wZ\n", &Fcb->PipeName);
585 Status = NpfsWaitPipe(Irp, Ccb);
586 break;
587
588 case FSCTL_PIPE_IMPERSONATE:
589 DPRINT1("Impersonate not implemented\n");
590 Status = STATUS_NOT_IMPLEMENTED;
591 break;
592
593 case FSCTL_PIPE_SET_CLIENT_PROCESS:
594 DPRINT1("Set client process not implemented\n");
595 Status = STATUS_NOT_IMPLEMENTED;
596 break;
597
598 case FSCTL_PIPE_QUERY_CLIENT_PROCESS:
599 DPRINT1("Query client process not implemented\n");
600 Status = STATUS_NOT_IMPLEMENTED;
601 break;
602
603 case FSCTL_PIPE_INTERNAL_READ:
604 DPRINT1("Internal read not implemented\n");
605 Status = STATUS_NOT_IMPLEMENTED;
606 break;
607
608 case FSCTL_PIPE_INTERNAL_WRITE:
609 DPRINT1("Internal write not implemented\n");
610 Status = STATUS_NOT_IMPLEMENTED;
611 break;
612
613 case FSCTL_PIPE_INTERNAL_TRANSCEIVE:
614 DPRINT1("Internal transceive not implemented\n");
615 Status = STATUS_NOT_IMPLEMENTED;
616 break;
617
618 case FSCTL_PIPE_INTERNAL_READ_OVFLOW:
619 DPRINT1("Internal read overflow not implemented\n");
620 Status = STATUS_NOT_IMPLEMENTED;
621 break;
622
623 default:
624 DPRINT1("Unrecognized IoControlCode: %x\n",
625 IoStack->Parameters.FileSystemControl.FsControlCode);
626 Status = STATUS_UNSUCCESSFUL;
627 }
628
629 if (Status != STATUS_PENDING)
630 {
631 Irp->IoStatus.Status = Status;
632
633 IoCompleteRequest(Irp, IO_NO_INCREMENT);
634 }
635
636 return Status;
637 }
638
639
640 NTSTATUS NTAPI
641 NpfsFlushBuffers(PDEVICE_OBJECT DeviceObject,
642 PIRP Irp)
643 {
644 /* FIXME: Implement */
645
646 Irp->IoStatus.Status = STATUS_SUCCESS;
647 Irp->IoStatus.Information = 0;
648
649 IoCompleteRequest(Irp, IO_NO_INCREMENT);
650
651 return STATUS_SUCCESS;
652 }
653
654 /* EOF */