Sync to trunk head (r40091)
[reactos.git] / reactos / drivers / network / afd / afd / main.c
1 /* $Id$
2 * COPYRIGHT: See COPYING in the top level directory
3 * PROJECT: ReactOS kernel
4 * FILE: drivers/net/afd/afd/main.c
5 * PURPOSE: Ancillary functions driver
6 * PROGRAMMER: Art Yerkes (ayerkes@speakeasy.net)
7 * UPDATE HISTORY:
8 * 20040630 Created
9 *
10 * Suggestions: Uniform naming (AfdXxx)
11 */
12
13 /* INCLUDES */
14
15 #include "afd.h"
16 #include "tdi_proto.h"
17 #include "tdiconn.h"
18 #include "debug.h"
19
20 #ifdef DBG
21
22 /* See debug.h for debug/trace constants */
23 //DWORD DebugTraceLevel = DEBUG_ULTRA;
24 DWORD DebugTraceLevel = 0;
25
26 #endif /* DBG */
27
28 void OskitDumpBuffer( PCHAR Data, UINT Len ) {
29 unsigned int i;
30
31 for( i = 0; i < Len; i++ ) {
32 if( i && !(i & 0xf) ) DbgPrint( "\n" );
33 if( !(i & 0xf) ) DbgPrint( "%08x: ", (UINT_PTR)(Data + i) );
34 DbgPrint( " %02x", Data[i] & 0xff );
35 }
36 DbgPrint("\n");
37 }
38
39 /* FUNCTIONS */
40
41 NTSTATUS NTAPI
42 DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath);
43
44 static NTSTATUS NTAPI
45 AfdCreateSocket(PDEVICE_OBJECT DeviceObject, PIRP Irp,
46 PIO_STACK_LOCATION IrpSp) {
47 PAFD_FCB FCB;
48 PFILE_OBJECT FileObject;
49 PAFD_DEVICE_EXTENSION DeviceExt;
50 PFILE_FULL_EA_INFORMATION EaInfo;
51 PAFD_CREATE_PACKET ConnectInfo = NULL;
52 ULONG EaLength;
53 PWCHAR EaInfoValue = NULL;
54 UINT Disposition, i;
55 NTSTATUS Status = STATUS_SUCCESS;
56
57 AFD_DbgPrint(MID_TRACE,
58 ("AfdCreate(DeviceObject %p Irp %p)\n", DeviceObject, Irp));
59
60 DeviceExt = DeviceObject->DeviceExtension;
61 FileObject = IrpSp->FileObject;
62 Disposition = (IrpSp->Parameters.Create.Options >> 24) & 0xff;
63
64 Irp->IoStatus.Information = 0;
65
66 EaInfo = Irp->AssociatedIrp.SystemBuffer;
67
68 if( EaInfo ) {
69 ConnectInfo = (PAFD_CREATE_PACKET)(EaInfo->EaName + EaInfo->EaNameLength + 1);
70 EaInfoValue = (PWCHAR)(((PCHAR)ConnectInfo) + sizeof(AFD_CREATE_PACKET));
71
72 EaLength = sizeof(FILE_FULL_EA_INFORMATION) +
73 EaInfo->EaNameLength +
74 EaInfo->EaValueLength;
75
76 AFD_DbgPrint(MID_TRACE,("EaInfo: %x, EaInfoValue: %x\n",
77 EaInfo, EaInfoValue));
78 }
79
80 AFD_DbgPrint(MID_TRACE,("About to allocate the new FCB\n"));
81
82 FCB = ExAllocatePool(NonPagedPool, sizeof(AFD_FCB));
83 if( FCB == NULL ) {
84 Irp->IoStatus.Status = STATUS_NO_MEMORY;
85 IoCompleteRequest(Irp, IO_NO_INCREMENT);
86 return STATUS_NO_MEMORY;
87 }
88
89 AFD_DbgPrint(MID_TRACE,("Initializing the new FCB @ %x (FileObject %x Flags %x)\n", FCB, FileObject, ConnectInfo ? ConnectInfo->EndpointFlags : 0));
90
91 RtlZeroMemory( FCB, sizeof( *FCB ) );
92
93 FCB->Flags = ConnectInfo ? ConnectInfo->EndpointFlags : 0;
94 FCB->State = SOCKET_STATE_CREATED;
95 FCB->FileObject = FileObject;
96 FCB->DeviceExt = DeviceExt;
97 FCB->Recv.Size = DEFAULT_RECEIVE_WINDOW_SIZE;
98 FCB->Send.Size = DEFAULT_SEND_WINDOW_SIZE;
99
100 KeInitializeSpinLock( &FCB->SpinLock );
101 ExInitializeFastMutex( &FCB->Mutex );
102 KeInitializeEvent( &FCB->StateLockedEvent, NotificationEvent, FALSE );
103
104 for( i = 0; i < MAX_FUNCTIONS; i++ ) {
105 InitializeListHead( &FCB->PendingIrpList[i] );
106 }
107
108 InitializeListHead( &FCB->DatagramList );
109 InitializeListHead( &FCB->PendingConnections );
110
111 AFD_DbgPrint(MID_TRACE,("%x: Checking command channel\n", FCB));
112
113 if( ConnectInfo ) {
114 FCB->TdiDeviceName.Length = ConnectInfo->SizeOfTransportName;
115 FCB->TdiDeviceName.MaximumLength = FCB->TdiDeviceName.Length;
116 FCB->TdiDeviceName.Buffer =
117 ExAllocatePool( NonPagedPool, FCB->TdiDeviceName.Length );
118
119 if( !FCB->TdiDeviceName.Buffer ) {
120 ExFreePool(FCB);
121 AFD_DbgPrint(MID_TRACE,("Could not copy target string\n"));
122 Irp->IoStatus.Status = STATUS_NO_MEMORY;
123 IoCompleteRequest( Irp, IO_NETWORK_INCREMENT );
124 return STATUS_NO_MEMORY;
125 }
126
127 RtlCopyMemory( FCB->TdiDeviceName.Buffer,
128 ConnectInfo->TransportName,
129 FCB->TdiDeviceName.Length );
130
131 AFD_DbgPrint(MID_TRACE,("Success: %s %wZ\n",
132 EaInfo->EaName, &FCB->TdiDeviceName));
133 } else {
134 AFD_DbgPrint(MID_TRACE,("Success: Control connection\n"));
135 }
136
137 FileObject->FsContext = FCB;
138
139 /* It seems that UDP sockets are writable from inception */
140 if( FCB->Flags & SGID_CONNECTIONLESS ) {
141 AFD_DbgPrint(MID_TRACE,("Packet oriented socket\n"));
142 /* Allocate our backup buffer */
143 FCB->Recv.Window = ExAllocatePool( NonPagedPool, FCB->Recv.Size );
144 if( !FCB->Recv.Window ) Status = STATUS_NO_MEMORY;
145 FCB->Send.Window = ExAllocatePool( NonPagedPool, FCB->Send.Size );
146 if( !FCB->Send.Window ) {
147 if( FCB->Recv.Window ) ExFreePool( FCB->Recv.Window );
148 Status = STATUS_NO_MEMORY;
149 }
150 /* A datagram socket is always sendable */
151 FCB->PollState |= AFD_EVENT_SEND;
152 PollReeval( FCB->DeviceExt, FCB->FileObject );
153 }
154
155 if( !NT_SUCCESS(Status) ) {
156 if( FCB->TdiDeviceName.Buffer ) ExFreePool( FCB->TdiDeviceName.Buffer );
157 ExFreePool( FCB );
158 FileObject->FsContext = NULL;
159 }
160
161 Irp->IoStatus.Status = Status;
162 IoCompleteRequest( Irp, IO_NETWORK_INCREMENT );
163
164 return Status;
165 }
166
167 VOID DestroySocket( PAFD_FCB FCB ) {
168 UINT i;
169 BOOLEAN ReturnEarly = FALSE;
170 PAFD_IN_FLIGHT_REQUEST InFlightRequest[IN_FLIGHT_REQUESTS];
171
172 AFD_DbgPrint(MIN_TRACE,("Called (%x)\n", FCB));
173
174 if( !SocketAcquireStateLock( FCB ) ) return;
175
176 FCB->State = SOCKET_STATE_CLOSED;
177
178 InFlightRequest[0] = &FCB->ListenIrp;
179 InFlightRequest[1] = &FCB->ReceiveIrp;
180 InFlightRequest[2] = &FCB->SendIrp;
181 InFlightRequest[3] = &FCB->ConnectIrp;
182
183 /* Return early here because we might be called in the mean time. */
184 if( FCB->Critical ||
185 FCB->ListenIrp.InFlightRequest ||
186 FCB->ReceiveIrp.InFlightRequest ||
187 FCB->SendIrp.InFlightRequest ||
188 FCB->ConnectIrp.InFlightRequest ) {
189 AFD_DbgPrint(MIN_TRACE,("Leaving socket alive (%x %x %x %x)\n",
190 FCB->ListenIrp.InFlightRequest,
191 FCB->ReceiveIrp.InFlightRequest,
192 FCB->SendIrp.InFlightRequest,
193 FCB->ConnectIrp.InFlightRequest));
194 ReturnEarly = TRUE;
195 }
196
197 /* After PoolReeval, this FCB should not be involved in any outstanding
198 * poll requests */
199
200 /* Cancel our pending requests */
201 for( i = 0; i < IN_FLIGHT_REQUESTS; i++ ) {
202 if( InFlightRequest[i]->InFlightRequest ) {
203 AFD_DbgPrint(MID_TRACE,("Cancelling in flight irp %d (%x)\n",
204 i, InFlightRequest[i]->InFlightRequest));
205 IoCancelIrp(InFlightRequest[i]->InFlightRequest);
206 InFlightRequest[i]->InFlightRequest = NULL;
207 }
208 }
209
210 SocketStateUnlock( FCB );
211
212 if( ReturnEarly ) return;
213
214 if( FCB->Recv.Window )
215 ExFreePool( FCB->Recv.Window );
216 if( FCB->Send.Window )
217 ExFreePool( FCB->Send.Window );
218 if( FCB->AddressFrom )
219 ExFreePool( FCB->AddressFrom );
220 if( FCB->LocalAddress )
221 ExFreePool( FCB->LocalAddress );
222 if( FCB->RemoteAddress )
223 ExFreePool( FCB->RemoteAddress );
224 if( FCB->TdiDeviceName.Buffer )
225 ExFreePool(FCB->TdiDeviceName.Buffer);
226
227 if (FCB->Connection.Object)
228 {
229 NtClose(FCB->Connection.Handle);
230 ObDereferenceObject(FCB->Connection.Object);
231 }
232 if (FCB->AddressFile.Object)
233 {
234 NtClose(FCB->AddressFile.Handle);
235 ObDereferenceObject(FCB->AddressFile.Object);
236 }
237
238 ExFreePool(FCB);
239 AFD_DbgPrint(MIN_TRACE,("Deleted (%x)\n", FCB));
240
241 AFD_DbgPrint(MIN_TRACE,("Leaving\n"));
242 }
243
244 static NTSTATUS NTAPI
245 AfdCloseSocket(PDEVICE_OBJECT DeviceObject, PIRP Irp,
246 PIO_STACK_LOCATION IrpSp)
247 {
248 PFILE_OBJECT FileObject = IrpSp->FileObject;
249 PAFD_FCB FCB = FileObject->FsContext;
250
251 AFD_DbgPrint(MID_TRACE,
252 ("AfdClose(DeviceObject %p Irp %p)\n", DeviceObject, Irp));
253
254 AFD_DbgPrint(MID_TRACE,("FCB %x\n", FCB));
255
256 FCB->PollState |= AFD_EVENT_CLOSE;
257 PollReeval( FCB->DeviceExt, FileObject );
258 KillSelectsForFCB( FCB->DeviceExt, FileObject, FALSE );
259
260 if( FCB->EventSelect ) ObDereferenceObject( FCB->EventSelect );
261
262 FileObject->FsContext = NULL;
263 DestroySocket( FCB );
264
265 Irp->IoStatus.Status = STATUS_SUCCESS;
266 Irp->IoStatus.Information = 0;
267 IoCompleteRequest(Irp, IO_NO_INCREMENT);
268
269 AFD_DbgPrint(MID_TRACE, ("Returning success.\n"));
270
271 return STATUS_SUCCESS;
272 }
273
274 static NTSTATUS NTAPI
275 AfdDisconnect(PDEVICE_OBJECT DeviceObject, PIRP Irp,
276 PIO_STACK_LOCATION IrpSp) {
277 PFILE_OBJECT FileObject = IrpSp->FileObject;
278 PAFD_FCB FCB = FileObject->FsContext;
279 PAFD_DISCONNECT_INFO DisReq;
280 IO_STATUS_BLOCK Iosb;
281 PTDI_CONNECTION_INFORMATION ConnInfo;
282 NTSTATUS Status;
283 USHORT Flags = 0;
284
285 if( !SocketAcquireStateLock( FCB ) ) return LostSocket( Irp );
286
287 if( !(DisReq = LockRequest( Irp, IrpSp )) )
288 return UnlockAndMaybeComplete( FCB, STATUS_NO_MEMORY,
289 Irp, 0, NULL );
290
291 if (NULL == FCB->RemoteAddress)
292 {
293 ConnInfo = NULL;
294 }
295 else
296 {
297 Status = TdiBuildNullConnectionInfo
298 ( &ConnInfo, FCB->RemoteAddress->Address[0].AddressType );
299
300 if( !NT_SUCCESS(Status) || !ConnInfo )
301 return UnlockAndMaybeComplete( FCB, STATUS_NO_MEMORY,
302 Irp, 0, NULL );
303 }
304
305 if( DisReq->DisconnectType & AFD_DISCONNECT_SEND )
306 Flags |= TDI_DISCONNECT_RELEASE;
307 if( DisReq->DisconnectType & AFD_DISCONNECT_RECV ||
308 DisReq->DisconnectType & AFD_DISCONNECT_ABORT )
309 Flags |= TDI_DISCONNECT_ABORT;
310
311 Status = TdiDisconnect( FCB->Connection.Object,
312 &DisReq->Timeout,
313 Flags,
314 &Iosb,
315 NULL,
316 NULL,
317 FCB->AddressFrom,
318 ConnInfo);
319
320 if (ConnInfo) ExFreePool( ConnInfo );
321
322 return UnlockAndMaybeComplete( FCB, Status, Irp, 0, NULL );
323 }
324
325 static NTSTATUS NTAPI
326 AfdDispatch(PDEVICE_OBJECT DeviceObject, PIRP Irp)
327 {
328 PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);
329 NTSTATUS Status = STATUS_SUCCESS;
330 #ifdef DBG
331 PFILE_OBJECT FileObject = IrpSp->FileObject;
332 #endif
333
334 AFD_DbgPrint(MID_TRACE,("AfdDispatch: %d\n", IrpSp->MajorFunction));
335 if( IrpSp->MajorFunction != IRP_MJ_CREATE) {
336 AFD_DbgPrint(MID_TRACE,("FO %x, IrpSp->FO %x\n",
337 FileObject, IrpSp->FileObject));
338 ASSERT(FileObject == IrpSp->FileObject);
339 }
340
341 Irp->IoStatus.Information = 0;
342
343 switch(IrpSp->MajorFunction)
344 {
345 /* opening and closing handles to the device */
346 case IRP_MJ_CREATE:
347 /* Mostly borrowed from the named pipe file system */
348 return AfdCreateSocket(DeviceObject, Irp, IrpSp);
349
350 case IRP_MJ_CLOSE:
351 /* Ditto the borrowing */
352 return AfdCloseSocket(DeviceObject, Irp, IrpSp);
353
354 /* write data */
355 case IRP_MJ_WRITE:
356 return AfdConnectedSocketWriteData( DeviceObject, Irp, IrpSp, TRUE );
357
358 /* read data */
359 case IRP_MJ_READ:
360 return AfdConnectedSocketReadData( DeviceObject, Irp, IrpSp, TRUE );
361
362 case IRP_MJ_DEVICE_CONTROL:
363 {
364 switch( IrpSp->Parameters.DeviceIoControl.IoControlCode ) {
365 case IOCTL_AFD_BIND:
366 return AfdBindSocket( DeviceObject, Irp, IrpSp );
367
368 case IOCTL_AFD_CONNECT:
369 return AfdStreamSocketConnect( DeviceObject, Irp, IrpSp );
370
371 case IOCTL_AFD_START_LISTEN:
372 return AfdListenSocket( DeviceObject, Irp, IrpSp );
373
374 case IOCTL_AFD_RECV:
375 return AfdConnectedSocketReadData( DeviceObject, Irp, IrpSp,
376 FALSE );
377
378 case IOCTL_AFD_SELECT:
379 return AfdSelect( DeviceObject, Irp, IrpSp );
380
381 case IOCTL_AFD_EVENT_SELECT:
382 return AfdEventSelect( DeviceObject, Irp, IrpSp );
383
384 case IOCTL_AFD_ENUM_NETWORK_EVENTS:
385 return AfdEnumEvents( DeviceObject, Irp, IrpSp );
386
387 case IOCTL_AFD_RECV_DATAGRAM:
388 return AfdPacketSocketReadData( DeviceObject, Irp, IrpSp );
389
390 case IOCTL_AFD_SEND:
391 return AfdConnectedSocketWriteData( DeviceObject, Irp, IrpSp,
392 FALSE );
393
394 case IOCTL_AFD_SEND_DATAGRAM:
395 return AfdPacketSocketWriteData( DeviceObject, Irp, IrpSp );
396
397 case IOCTL_AFD_GET_INFO:
398 return AfdGetInfo( DeviceObject, Irp, IrpSp );
399
400 case IOCTL_AFD_GET_CONTEXT:
401 return AfdGetContext( DeviceObject, Irp, IrpSp );
402
403 case IOCTL_AFD_SET_CONTEXT:
404 return AfdSetContext( DeviceObject, Irp, IrpSp );
405
406 case IOCTL_AFD_WAIT_FOR_LISTEN:
407 return AfdWaitForListen( DeviceObject, Irp, IrpSp );
408
409 case IOCTL_AFD_ACCEPT:
410 return AfdAccept( DeviceObject, Irp, IrpSp );
411
412 case IOCTL_AFD_DISCONNECT:
413 return AfdDisconnect( DeviceObject, Irp, IrpSp );
414
415 case IOCTL_AFD_GET_SOCK_NAME:
416 return AfdGetSockName( DeviceObject, Irp, IrpSp );
417
418 case IOCTL_AFD_GET_PEER_NAME:
419 return AfdGetPeerName( DeviceObject, Irp, IrpSp );
420
421 case IOCTL_AFD_GET_TDI_HANDLES:
422 AFD_DbgPrint(MIN_TRACE, ("IOCTL_AFD_GET_TDI_HANDLES\n"));
423 break;
424
425 case IOCTL_AFD_SET_INFO:
426 AFD_DbgPrint(MIN_TRACE, ("IOCTL_AFD_SET_INFO\n"));
427 break;
428
429 case IOCTL_AFD_SET_CONNECT_DATA:
430 AFD_DbgPrint(MIN_TRACE, ("IOCTL_AFD_SET_CONNECT_DATA\n"));
431 break;
432
433 case IOCTL_AFD_SET_CONNECT_OPTIONS:
434 AFD_DbgPrint(MIN_TRACE, ("IOCTL_AFD_SET_CONNECT_OPTIONS\n"));
435 break;
436
437 case IOCTL_AFD_SET_DISCONNECT_DATA:
438 AFD_DbgPrint(MIN_TRACE, ("IOCTL_AFD_SET_DISCONNECT_DATA\n"));
439 break;
440
441 case IOCTL_AFD_SET_DISCONNECT_OPTIONS:
442 AFD_DbgPrint(MIN_TRACE, ("IOCTL_AFD_SET_DISCONNECT_OPTIONS\n"));
443 break;
444
445 case IOCTL_AFD_GET_CONNECT_DATA:
446 AFD_DbgPrint(MIN_TRACE, ("IOCTL_AFD_GET_CONNECT_DATA\n"));
447 break;
448
449 case IOCTL_AFD_GET_CONNECT_OPTIONS:
450 AFD_DbgPrint(MIN_TRACE, ("IOCTL_AFD_GET_CONNECT_OPTIONS\n"));
451 break;
452
453 case IOCTL_AFD_GET_DISCONNECT_DATA:
454 AFD_DbgPrint(MIN_TRACE, ("IOCTL_AFD_GET_DISCONNECT_DATA\n"));
455 break;
456
457 case IOCTL_AFD_GET_DISCONNECT_OPTIONS:
458 AFD_DbgPrint(MIN_TRACE, ("IOCTL_AFD_GET_DISCONNECT_OPTIONS\n"));
459 break;
460
461 case IOCTL_AFD_SET_CONNECT_DATA_SIZE:
462 AFD_DbgPrint(MIN_TRACE, ("IOCTL_AFD_SET_CONNECT_DATA_SIZE\n"));
463 break;
464
465 case IOCTL_AFD_SET_CONNECT_OPTIONS_SIZE:
466 AFD_DbgPrint(MIN_TRACE, ("IOCTL_AFD_SET_CONNECT_OPTIONS_SIZE\n"));
467 break;
468
469 case IOCTL_AFD_SET_DISCONNECT_DATA_SIZE:
470 AFD_DbgPrint(MIN_TRACE, ("IOCTL_AFD_SET_DISCONNECT_DATA_SIZE\n"));
471 break;
472
473 case IOCTL_AFD_SET_DISCONNECT_OPTIONS_SIZE:
474 AFD_DbgPrint(MIN_TRACE, ("IOCTL_AFD_SET_DISCONNECT_OPTIONS_SIZE\n"));
475 break;
476
477 case IOCTL_AFD_DEFER_ACCEPT:
478 AFD_DbgPrint(MIN_TRACE, ("IOCTL_AFD_DEFER_ACCEPT\n"));
479 break;
480
481 case IOCTL_AFD_GET_PENDING_CONNECT_DATA:
482 AFD_DbgPrint(MIN_TRACE, ("IOCTL_AFD_GET_PENDING_CONNECT_DATA\n"));
483 break;
484
485 default:
486 Status = STATUS_NOT_IMPLEMENTED;
487 AFD_DbgPrint(MIN_TRACE, ("Unknown IOCTL (0x%x)\n",
488 IrpSp->Parameters.DeviceIoControl.
489 IoControlCode));
490 break;
491 }
492 break;
493 }
494
495 /* unsupported operations */
496 default:
497 {
498 Status = STATUS_NOT_IMPLEMENTED;
499 AFD_DbgPrint(MIN_TRACE,
500 ("Irp: Unknown Major code was %x\n",
501 IrpSp->MajorFunction));
502 break;
503 }
504 }
505
506 AFD_DbgPrint(MID_TRACE, ("Returning %x\n", Status));
507 Irp->IoStatus.Status = Status;
508 IoCompleteRequest(Irp, IO_NO_INCREMENT);
509
510 return (Status);
511 }
512
513 static VOID NTAPI
514 AfdUnload(PDRIVER_OBJECT DriverObject)
515 {
516 }
517
518 NTSTATUS NTAPI
519 DriverEntry(PDRIVER_OBJECT DriverObject, PUNICODE_STRING RegistryPath)
520 {
521 PDEVICE_OBJECT DeviceObject;
522 UNICODE_STRING wstrDeviceName = RTL_CONSTANT_STRING(L"\\Device\\Afd");
523 PAFD_DEVICE_EXTENSION DeviceExt;
524 NTSTATUS Status;
525
526 /* register driver routines */
527 DriverObject->MajorFunction[IRP_MJ_CLOSE] = AfdDispatch;
528 DriverObject->MajorFunction[IRP_MJ_CREATE] = AfdDispatch;
529 DriverObject->MajorFunction[IRP_MJ_WRITE] = AfdDispatch;
530 DriverObject->MajorFunction[IRP_MJ_READ] = AfdDispatch;
531 DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = AfdDispatch;
532 DriverObject->DriverUnload = AfdUnload;
533
534 Status = IoCreateDevice
535 ( DriverObject,
536 sizeof(AFD_DEVICE_EXTENSION),
537 &wstrDeviceName,
538 FILE_DEVICE_NAMED_PIPE,
539 0,
540 FALSE,
541 &DeviceObject );
542
543 /* failure */
544 if(!NT_SUCCESS(Status))
545 {
546 return (Status);
547 }
548
549 DeviceExt = DeviceObject->DeviceExtension;
550 KeInitializeSpinLock( &DeviceExt->Lock );
551 InitializeListHead( &DeviceExt->Polls );
552
553 AFD_DbgPrint(MID_TRACE,("Device created: object %x ext %x\n",
554 DeviceObject, DeviceExt));
555
556 return (Status);
557 }
558
559 /* EOF */