Windows NT 4.0 source code leak
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

860 lines
20 KiB

4 years ago
  1. /*++
  2. Copyright (c) 1991 Microsoft Corporation
  3. Module Name:
  4. Registry.c
  5. Abstract:
  6. This contains all routines necessary to load the lana number to device pathname
  7. mapping and the Lana Enum record.
  8. Author:
  9. Colin Watson (colinw) 14-Mar-1992
  10. Revision History:
  11. Notes:
  12. The fcb holds an area for registry workspace. this is where the strings
  13. used to hold the DriverNames will be held in a single allocation.
  14. build with -DUTILITY to run as a test application.
  15. --*/
  16. #include "Nb.h"
  17. //#include <zwapi.h>
  18. //#include <stdlib.h>
  19. #include <crt\stdlib.h>
  20. typedef struct _LANA_MAP {
  21. BOOLEAN Enum;
  22. UCHAR Lana;
  23. } LANA_MAP, *PLANA_MAP;
  24. #define DEFAULT_VALUE_SIZE 4096
  25. #define ROUNDUP_TO_LONG(x) (((x) + sizeof(PVOID) - 1) & ~(sizeof(PVOID) - 1))
  26. #ifdef UTILITY
  27. #define ZwClose NtClose
  28. #define ZwCreateKey NtCreateKey
  29. #define ZwOpenKey NtOpenKey
  30. #define ZwQueryValueKey NtQueryValueKey
  31. #define ExFreePool free
  32. #endif
  33. //
  34. // Local functions used to access the registry.
  35. //
  36. NTSTATUS
  37. NbOpenRegistry(
  38. IN PUNICODE_STRING BaseName,
  39. OUT PHANDLE LinkageHandle,
  40. OUT PHANDLE ParametersHandle
  41. );
  42. VOID
  43. NbCloseRegistry(
  44. IN HANDLE LinkageHandle,
  45. IN HANDLE ParametersHandle
  46. );
  47. NTSTATUS
  48. NbReadLinkageInformation(
  49. IN HANDLE LinkageHandle,
  50. IN HANDLE ParametersHandle,
  51. IN HANDLE InfoLinkageHandle,
  52. IN HANDLE InfoParametersHandle,
  53. IN PFCB pfcb
  54. );
  55. ULONG
  56. NbReadSingleParameter(
  57. IN HANDLE ParametersHandle,
  58. IN PWCHAR ValueName,
  59. IN LONG DefaultValue
  60. );
  61. BOOLEAN
  62. NbCheckLana (
  63. PUNICODE_STRING DeviceName
  64. );
  65. #ifdef ALLOC_PRAGMA
  66. #pragma alloc_text(PAGE, GetIrpStackSize)
  67. #pragma alloc_text(PAGE, ReadRegistry)
  68. #pragma alloc_text(PAGE, NbFreeRegistryInfo)
  69. #pragma alloc_text(PAGE, NbOpenRegistry)
  70. #pragma alloc_text(PAGE, NbCloseRegistry)
  71. #pragma alloc_text(PAGE, NbReadLinkageInformation)
  72. #pragma alloc_text(PAGE, NbReadSingleParameter)
  73. #pragma alloc_text(PAGE, NbCheckLana)
  74. #endif
  75. CCHAR
  76. GetIrpStackSize(
  77. IN PUNICODE_STRING RegistryPath,
  78. IN CCHAR DefaultValue
  79. )
  80. /*++
  81. Routine Description:
  82. This routine is called by NbCreateDeviceContext to get the IRP
  83. stack size to be "exported" by the NetBIOS device.
  84. Arguments:
  85. RegistryPath - The name of Nb's node in the registry.
  86. DefaultValue - IRP stack size to be used if no registry value present.
  87. Return Value:
  88. CCHAR - IRP stack size to be stored in the device object.
  89. --*/
  90. {
  91. HANDLE LinkageHandle;
  92. HANDLE ParametersHandle;
  93. NTSTATUS Status;
  94. ULONG stackSize;
  95. PAGED_CODE();
  96. Status = NbOpenRegistry (RegistryPath, &LinkageHandle, &ParametersHandle);
  97. if (Status != STATUS_SUCCESS) {
  98. return DefaultValue;
  99. }
  100. //
  101. // Read the stack size value from the registry.
  102. //
  103. stackSize = NbReadSingleParameter(
  104. ParametersHandle,
  105. REGISTRY_IRP_STACK_SIZE,
  106. DefaultValue );
  107. if ( stackSize > 255 ) {
  108. stackSize = 255;
  109. }
  110. NbCloseRegistry (LinkageHandle, ParametersHandle);
  111. return (CCHAR)stackSize;
  112. }
  113. NTSTATUS
  114. ReadRegistry(
  115. IN PDEVICE_CONTEXT DeviceContext,
  116. IN PFCB NewFcb
  117. )
  118. /*++
  119. Routine Description:
  120. This routine is called by Nb to get information from the registry,
  121. starting at RegistryPath to get the parameters.
  122. Arguments:
  123. DeviceContext - Supplies RegistryPath. The name of Nb's node in the registry.
  124. NewFcb - Destination for the configuration information.
  125. Return Value:
  126. NTSTATUS - STATUS_SUCCESS if everything OK, STATUS_INSUFFICIENT_RESOURCES
  127. otherwise.
  128. --*/
  129. {
  130. HANDLE LinkageHandle;
  131. HANDLE ParametersHandle;
  132. HANDLE InfoLinkageHandle;
  133. HANDLE InfoParametersHandle;
  134. NTSTATUS Status;
  135. UNICODE_STRING InfoPath;
  136. PAGED_CODE();
  137. NewFcb->RegistrySpace = NULL; // No registry workspace.
  138. RtlInitUnicodeString(&InfoPath, REGISTRY_NETBIOS_INFORMATION);
  139. NewFcb->LanaEnum.length = 0;
  140. Status = NbOpenRegistry (&DeviceContext->RegistryPath, &LinkageHandle, &ParametersHandle);
  141. if (Status != STATUS_SUCCESS) {
  142. return STATUS_UNSUCCESSFUL;
  143. }
  144. Status = NbOpenRegistry (&InfoPath, &InfoLinkageHandle, &InfoParametersHandle);
  145. if (Status != STATUS_SUCCESS) {
  146. NbCloseRegistry (LinkageHandle, ParametersHandle);
  147. return STATUS_UNSUCCESSFUL;
  148. }
  149. //
  150. // Read in the NDIS binding information (if none is present
  151. // the array will be filled with all known drivers).
  152. //
  153. Status = NbReadLinkageInformation (
  154. LinkageHandle,
  155. ParametersHandle,
  156. InfoLinkageHandle,
  157. InfoParametersHandle,
  158. NewFcb);
  159. NbCloseRegistry (LinkageHandle, ParametersHandle);
  160. NbCloseRegistry (InfoLinkageHandle, InfoParametersHandle);
  161. return Status;
  162. }
  163. VOID
  164. NbFreeRegistryInfo (
  165. IN PFCB pfcb
  166. )
  167. /*++
  168. Routine Description:
  169. This routine is called by Nb to get free any storage that was allocated
  170. by NbConfigureTransport in producing the specified CONFIG_DATA structure.
  171. Arguments:
  172. ConfigurationInfo - A pointer to the configuration information structure.
  173. Return Value:
  174. None.
  175. --*/
  176. {
  177. PAGED_CODE();
  178. if ( pfcb->RegistrySpace != NULL ) {
  179. ExFreePool( pfcb->RegistrySpace );
  180. pfcb->RegistrySpace = NULL;
  181. }
  182. }
  183. NTSTATUS
  184. NbOpenRegistry(
  185. IN PUNICODE_STRING BaseName,
  186. OUT PHANDLE LinkageHandle,
  187. OUT PHANDLE ParametersHandle
  188. )
  189. /*++
  190. Routine Description:
  191. This routine is called by Nb to open the registry. If the registry
  192. tree exists, then it opens it and returns an error. If not, it
  193. creates the appropriate keys in the registry, opens it, and
  194. returns STATUS_SUCCESS.
  195. NOTE: If the key "ClearRegistry" exists in ntuser.cfg, then
  196. this routine will remove any existing registry values for Nb
  197. (but still create the tree if it doesn't exist) and return
  198. FALSE.
  199. Arguments:
  200. BaseName - Where in the registry to start looking for the information.
  201. LinkageHandle - Returns the handle used to read linkage information.
  202. ParametersHandle - Returns the handle used to read other
  203. parameters.
  204. Return Value:
  205. The status of the request.
  206. --*/
  207. {
  208. HANDLE NbConfigHandle;
  209. NTSTATUS Status;
  210. HANDLE LinkHandle;
  211. HANDLE ParamHandle;
  212. PWSTR LinkageString = REGISTRY_LINKAGE;
  213. PWSTR ParametersString = REGISTRY_PARAMETERS;
  214. UNICODE_STRING LinkageKeyName;
  215. UNICODE_STRING ParametersKeyName;
  216. OBJECT_ATTRIBUTES TmpObjectAttributes;
  217. ULONG Disposition;
  218. PAGED_CODE();
  219. //
  220. // Open the registry for the initial string.
  221. //
  222. InitializeObjectAttributes(
  223. &TmpObjectAttributes,
  224. BaseName, // name
  225. OBJ_CASE_INSENSITIVE, // attributes
  226. NULL, // root
  227. NULL // security descriptor
  228. );
  229. Status = ZwCreateKey(
  230. &NbConfigHandle,
  231. KEY_WRITE,
  232. &TmpObjectAttributes,
  233. 0, // title index
  234. NULL, // class
  235. 0, // create options
  236. &Disposition); // disposition
  237. if (!NT_SUCCESS(Status)) {
  238. return STATUS_UNSUCCESSFUL;
  239. }
  240. //
  241. // Open the Nb linkages key.
  242. //
  243. RtlInitUnicodeString (&LinkageKeyName, LinkageString);
  244. InitializeObjectAttributes(
  245. &TmpObjectAttributes,
  246. &LinkageKeyName, // name
  247. OBJ_CASE_INSENSITIVE, // attributes
  248. NbConfigHandle, // root
  249. NULL // security descriptor
  250. );
  251. Status = ZwOpenKey(
  252. &LinkHandle,
  253. KEY_READ,
  254. &TmpObjectAttributes);
  255. if (!NT_SUCCESS(Status)) {
  256. ZwClose (NbConfigHandle);
  257. return Status;
  258. }
  259. //
  260. // Now open the parameters key.
  261. //
  262. RtlInitUnicodeString (&ParametersKeyName, ParametersString);
  263. InitializeObjectAttributes(
  264. &TmpObjectAttributes,
  265. &ParametersKeyName, // name
  266. OBJ_CASE_INSENSITIVE, // attributes
  267. NbConfigHandle, // root
  268. NULL // security descriptor
  269. );
  270. Status = ZwOpenKey(
  271. &ParamHandle,
  272. KEY_READ,
  273. &TmpObjectAttributes);
  274. if (!NT_SUCCESS(Status)) {
  275. ZwClose (LinkHandle);
  276. ZwClose (NbConfigHandle);
  277. return Status;
  278. }
  279. *LinkageHandle = LinkHandle;
  280. *ParametersHandle = ParamHandle;
  281. //
  282. // All keys successfully opened or created.
  283. //
  284. ZwClose (NbConfigHandle);
  285. return STATUS_SUCCESS;
  286. } /* NbOpenRegistry */
  287. VOID
  288. NbCloseRegistry(
  289. IN HANDLE LinkageHandle,
  290. IN HANDLE ParametersHandle
  291. )
  292. /*++
  293. Routine Description:
  294. This routine is called by Nb to close the registry. It closes
  295. the handles passed in and does any other work needed.
  296. Arguments:
  297. LinkageHandle - The handle used to read linkage information.
  298. ParametersHandle - The handle used to read other parameters.
  299. Return Value:
  300. None.
  301. --*/
  302. {
  303. PAGED_CODE();
  304. ZwClose (LinkageHandle);
  305. ZwClose (ParametersHandle);
  306. } /* NbCloseRegistry */
  307. NTSTATUS
  308. NbReadLinkageInformation(
  309. IN HANDLE LinkageHandle,
  310. IN HANDLE ParametersHandle,
  311. IN HANDLE InfoLinkageHandle,
  312. IN HANDLE InfoParametersHandle,
  313. IN PFCB pfcb
  314. )
  315. /*++
  316. Routine Description:
  317. This routine is called by Nb to read its linkage information
  318. from the registry. If there is none present, then ConfigData
  319. is filled with a list of all the adapters that are known
  320. to Nb.
  321. Arguments:
  322. LinkageHandle - Supplies the Linkage key in netbios
  323. ParametersHandle
  324. InfoLinkageHandle - Supplies the Linkage key in NetBIOSInformation
  325. InfoParametersHandle
  326. pfcb - Describes Nb's current configuration.
  327. Return Value:
  328. Status
  329. --*/
  330. {
  331. PWSTR BindName = REGISTRY_BIND;
  332. UNICODE_STRING BindString;
  333. NTSTATUS Status;
  334. PKEY_VALUE_FULL_INFORMATION Value = NULL;
  335. ULONG ValueSize;
  336. PWSTR LanaMapName = REGISTRY_LANA_MAP;
  337. UNICODE_STRING LanaMapString;
  338. PLANA_MAP pLanaMap;
  339. ULONG BytesWritten;
  340. UINT ConfigBindings = 0;
  341. PWSTR CurBindValue;
  342. UINT index;
  343. PAGED_CODE();
  344. pfcb->MaxLana = NbReadSingleParameter( InfoParametersHandle, REGISTRY_MAX_LANA, -1 );
  345. if (pfcb->MaxLana > MAXIMUM_LANA) {
  346. return STATUS_INVALID_PARAMETER;
  347. }
  348. //
  349. // Read the "Bind" key.
  350. //
  351. RtlInitUnicodeString (&BindString, BindName);
  352. #ifdef UTILITY
  353. Value = malloc( DEFAULT_VALUE_SIZE);
  354. #else
  355. Value = ExAllocatePoolWithTag(PagedPool, DEFAULT_VALUE_SIZE, 'rSBN');
  356. #endif
  357. if ( Value == NULL ) {
  358. return STATUS_INSUFFICIENT_RESOURCES;
  359. }
  360. ValueSize = DEFAULT_VALUE_SIZE;
  361. pfcb->RegistrySpace = NULL;
  362. try {
  363. Status = ZwQueryValueKey(
  364. LinkageHandle,
  365. &BindString,
  366. KeyValueFullInformation,
  367. Value,
  368. ValueSize,
  369. &BytesWritten
  370. );
  371. if ( Status == STATUS_BUFFER_OVERFLOW) {
  372. ExFreePool( Value );
  373. // Now request with exactly the right size
  374. ValueSize = BytesWritten;
  375. #ifdef UTILITY
  376. Value = malloc( ValueSize);
  377. #else
  378. Value = ExAllocatePoolWithTag(PagedPool, ValueSize, 'rSBN');
  379. #endif
  380. if ( Value == NULL ) {
  381. try_return( Status = STATUS_INSUFFICIENT_RESOURCES);
  382. }
  383. Status = ZwQueryValueKey(
  384. LinkageHandle,
  385. &BindString,
  386. KeyValueFullInformation,
  387. Value,
  388. ValueSize,
  389. &BytesWritten
  390. );
  391. }
  392. if (!NT_SUCCESS(Status)) {
  393. try_return( Status );
  394. }
  395. if ( BytesWritten == 0 ) {
  396. try_return( Status = STATUS_ILL_FORMED_SERVICE_ENTRY);
  397. }
  398. //
  399. // Alloc space for Registry stuff as well as pDriverName array.
  400. //
  401. #ifdef UTILITY
  402. pfcb->RegistrySpace = malloc(ROUNDUP_TO_LONG(BytesWritten - Value->DataOffset) +
  403. (sizeof(UNICODE_STRING) * (pfcb->MaxLana+1)));
  404. #else
  405. pfcb->RegistrySpace = ExAllocatePoolWithTag(PagedPool,
  406. ROUNDUP_TO_LONG(BytesWritten - Value->DataOffset) +
  407. (sizeof(UNICODE_STRING) * (pfcb->MaxLana+1)), 'rSBN');
  408. #endif
  409. if ( pfcb->RegistrySpace == NULL ) {
  410. try_return( Status = STATUS_INSUFFICIENT_RESOURCES);
  411. }
  412. RtlMoveMemory(pfcb->RegistrySpace,
  413. (PUCHAR)Value + Value->DataOffset,
  414. BytesWritten - Value->DataOffset);
  415. pfcb->pDriverName =
  416. (PUNICODE_STRING) ((PBYTE) pfcb->RegistrySpace +
  417. ROUNDUP_TO_LONG(BytesWritten-Value->DataOffset));
  418. //
  419. // Read the "LanaMap" key into Storage.
  420. //
  421. RtlInitUnicodeString (&LanaMapString, LanaMapName);
  422. Status = ZwQueryValueKey(
  423. LinkageHandle,
  424. &LanaMapString,
  425. KeyValueFullInformation,
  426. Value,
  427. ValueSize,
  428. &BytesWritten
  429. );
  430. if (!NT_SUCCESS(Status)) {
  431. try_return( Status );
  432. }
  433. if ( BytesWritten == 0 ) {
  434. try_return( Status = STATUS_ILL_FORMED_SERVICE_ENTRY);
  435. }
  436. // Point pLanaMap at the data from the registry.
  437. pLanaMap = (PLANA_MAP)((PUCHAR)Value + Value->DataOffset);
  438. //
  439. // For each binding, initialize the drivername string.
  440. //
  441. for ( index = 0 ; index <= pfcb->MaxLana ; index++ ) {
  442. // Initialize unused drivernames to NULL name
  443. RtlInitUnicodeString (&pfcb->pDriverName[index], NULL);
  444. }
  445. CurBindValue = (PWCHAR)pfcb->RegistrySpace;
  446. #if DBG
  447. DbgPrint ("NETBIOS: Enumerating lanas ...\n");
  448. #endif
  449. while (*CurBindValue != 0) {
  450. if ((ConfigBindings > pfcb->MaxLana) ||
  451. (pLanaMap[ConfigBindings].Lana > pfcb->MaxLana)) {
  452. try_return( Status = STATUS_INVALID_PARAMETER);
  453. }
  454. RtlInitUnicodeString (
  455. &pfcb->pDriverName[pLanaMap[ConfigBindings].Lana],
  456. CurBindValue);
  457. if ( pLanaMap[ConfigBindings].Enum != FALSE ) {
  458. if (NbCheckLana (
  459. &pfcb->pDriverName[pLanaMap[ConfigBindings].Lana])) {
  460. //
  461. // Record that the lana number is enabled
  462. //
  463. pfcb->LanaEnum.lana[pfcb->LanaEnum.length] =
  464. pLanaMap[ConfigBindings].Lana;
  465. pfcb->LanaEnum.length++;
  466. #if DBG
  467. DbgPrint ("NETBIOS: Lana %d (%ls) added OK.\n",
  468. pLanaMap[ConfigBindings].Lana, CurBindValue);
  469. #endif
  470. }
  471. #if DBG
  472. else
  473. DbgPrint ("NETBIOS: Lana's %d %ls could not be opened.\n",
  474. pLanaMap[ConfigBindings].Lana, CurBindValue);
  475. #endif
  476. }
  477. #if DBG
  478. else
  479. DbgPrint ("NETBIOS: Lana %d (%ls) is disabled.\n",
  480. pLanaMap[ConfigBindings].Lana, CurBindValue);
  481. #endif
  482. ++ConfigBindings;
  483. //
  484. // Now advance the "Bind" value.
  485. //
  486. CurBindValue += wcslen(CurBindValue) + 1;
  487. }
  488. try_return( Status = STATUS_SUCCESS);
  489. try_exit:NOTHING;
  490. } finally {
  491. if ( !NT_SUCCESS(Status) ) {
  492. ExFreePool( pfcb->RegistrySpace );
  493. pfcb->RegistrySpace = NULL;
  494. }
  495. if ( Value != NULL ) {
  496. ExFreePool( Value );
  497. }
  498. }
  499. return Status;
  500. } /* NbReadLinkageInformation */
  501. ULONG
  502. NbReadSingleParameter(
  503. IN HANDLE ParametersHandle,
  504. IN PWCHAR ValueName,
  505. IN LONG DefaultValue
  506. )
  507. /*++
  508. Routine Description:
  509. This routine is called by Nb to read a single parameter
  510. from the registry. If the parameter is found it is stored
  511. in Data.
  512. Arguments:
  513. ParametersHandle - A pointer to the open registry.
  514. ValueName - The name of the value to search for.
  515. DefaultValue - The default value.
  516. Return Value:
  517. The value to use; will be the default if the value is not
  518. found or is not in the correct range.
  519. --*/
  520. {
  521. ULONG InformationBuffer[16]; // declare ULONG to get it aligned
  522. PKEY_VALUE_FULL_INFORMATION Information =
  523. (PKEY_VALUE_FULL_INFORMATION)InformationBuffer;
  524. UNICODE_STRING ValueKeyName;
  525. ULONG InformationLength;
  526. LONG ReturnValue;
  527. NTSTATUS Status;
  528. PAGED_CODE();
  529. RtlInitUnicodeString (&ValueKeyName, ValueName);
  530. Status = ZwQueryValueKey(
  531. ParametersHandle,
  532. &ValueKeyName,
  533. KeyValueFullInformation,
  534. (PVOID)Information,
  535. sizeof (InformationBuffer),
  536. &InformationLength);
  537. if ((Status == STATUS_SUCCESS) && (Information->DataLength == sizeof(ULONG))) {
  538. RtlMoveMemory(
  539. (PVOID)&ReturnValue,
  540. ((PUCHAR)Information) + Information->DataOffset,
  541. sizeof(ULONG));
  542. if (ReturnValue < 0) {
  543. ReturnValue = DefaultValue;
  544. }
  545. } else {
  546. ReturnValue = DefaultValue;
  547. }
  548. return ReturnValue;
  549. } /* NbReadSingleParameter */
  550. BOOLEAN
  551. NbCheckLana (
  552. PUNICODE_STRING DeviceName
  553. )
  554. /*++
  555. Routine Description:
  556. This routine uses the transport to create an entry in the NetBIOS
  557. table with the value of "Name". It will re-use an existing entry if
  558. "Name" already exists.
  559. Note: This synchronous call may take a number of seconds. If this matters
  560. then the caller should specify ASYNCH and a post routine so that it is
  561. performed by the thread created by the netbios dll routines.
  562. If pdncb == NULL then a special handle is returned that is capable of
  563. administering the transport. For example to execute an ASTAT.
  564. Arguments:
  565. FileHandle - Pointer to where the filehandle is to be returned.
  566. *Object - Pointer to where the file object pointer is to be stored
  567. pfcb - supplies the device names for the lana number.
  568. LanNumber - supplies the network adapter to be opened.
  569. pdncb - Pointer to either an NCB or NULL.
  570. Return Value:
  571. The function value is the status of the operation.
  572. --*/
  573. {
  574. IO_STATUS_BLOCK IoStatusBlock;
  575. NTSTATUS Status;
  576. OBJECT_ATTRIBUTES ObjectAttributes;
  577. HANDLE FileHandle;
  578. PAGED_CODE();
  579. InitializeObjectAttributes (
  580. &ObjectAttributes,
  581. DeviceName,
  582. 0,
  583. NULL,
  584. NULL);
  585. Status = ZwCreateFile (
  586. &FileHandle,
  587. GENERIC_READ | GENERIC_WRITE, // desired access.
  588. &ObjectAttributes, // object attributes.
  589. &IoStatusBlock, // returned status information.
  590. NULL, // Allocation size (unused).
  591. FILE_ATTRIBUTE_NORMAL, // file attributes.
  592. FILE_SHARE_WRITE,
  593. FILE_CREATE,
  594. 0, // create options.
  595. NULL,
  596. 0
  597. );
  598. if ( NT_SUCCESS( Status )) {
  599. Status = IoStatusBlock.Status;
  600. }
  601. // Obtain a referenced pointer to the file object.
  602. if (NT_SUCCESS( Status )) {
  603. NTSTATUS localstatus;
  604. localstatus = ZwClose( FileHandle);
  605. ASSERT(NT_SUCCESS(localstatus));
  606. return TRUE;
  607. }
  608. else {
  609. return FALSE;
  610. }
  611. }
  612. #ifdef UTILITY
  613. void
  614. _cdecl
  615. main (argc, argv)
  616. int argc;
  617. char *argv[];
  618. {
  619. DEVICE_CONTEXT DeviceContext;
  620. FCB NewFcb;
  621. RtlInitUnicodeString(&DeviceContext.RegistryPath, L"\\Registry\\Machine\\System\\CurrentControlSet\\Services\\Netbios");
  622. ReadRegistry(
  623. &DeviceContext,
  624. &NewFcb
  625. );
  626. }
  627. #endif