Source code of Windows XP (NT5)
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.

757 lines
21 KiB

  1. /*++
  2. Copyright (c) 1991 Microsoft Corporation
  3. Module Name:
  4. brconfig.c
  5. Abstract:
  6. This module contains the Browser service configuration routines.
  7. Author:
  8. Rita Wong (ritaw) 22-May-1991
  9. Revision History:
  10. --*/
  11. #include "precomp.h"
  12. #pragma hdrstop
  13. //-------------------------------------------------------------------//
  14. // //
  15. // Global variables //
  16. // //
  17. //-------------------------------------------------------------------//
  18. //
  19. // Browser configuration information structure which holds the
  20. // computername, primary domain, browser config buffer, and a resource
  21. // to serialize access to the whole thing.
  22. //
  23. BRCONFIGURATION_INFO BrInfo = {0};
  24. BR_BROWSER_FIELDS BrFields[] = {
  25. {WKSTA_KEYWORD_MAINTAINSRVLST, (LPDWORD) &BrInfo.MaintainServerList,
  26. 1,(DWORD)-1, 0, TriValueType, 0, NULL},
  27. {BROWSER_CONFIG_BACKUP_RECOVERY_TIME, &BrInfo.BackupBrowserRecoveryTime,
  28. BACKUP_BROWSER_RECOVERY_TIME, 0, 0xffffffff, DWordType, 0, NULL},
  29. {L"CacheHitLimit", &BrInfo.CacheHitLimit,
  30. // {BROWSER_CONFIG_CACHE_HIT_LIMIT, &BrInfo.CacheHitLimit,
  31. CACHED_BROWSE_RESPONSE_HIT_LIMIT, 0, 0x100, DWordType, 0, NULL },
  32. {L"CacheResponseSize", &BrInfo.NumberOfCachedResponses,
  33. // {BROWSER_CONFIG_CACHE_HIT_LIMIT, &BrInfo.CacheHitLimit,
  34. CACHED_BROWSE_RESPONSE_LIMIT, 0, MAXULONG, DWordType, 0, NULL },
  35. {L"QueryDriverFrequency", &BrInfo.DriverQueryFrequency,
  36. BROWSER_QUERY_DRIVER_FREQUENCY, 0, 15*60, DWordType, 0, NULL },
  37. {L"DirectHostBinding", (LPDWORD)&BrInfo.DirectHostBinding,
  38. 0, 0, 0, MultiSzType, 0, BrChangeDirectHostBinding },
  39. {L"UnboundBindings", (LPDWORD)&BrInfo.UnboundBindings,
  40. 0, 0, 0, MultiSzType, 0, NULL },
  41. {L"MasterPeriodicity", (LPDWORD)&BrInfo.MasterPeriodicity,
  42. MASTER_PERIODICITY, 5*60, 0x7fffffff/1000, DWordType, 0, BrChangeMasterPeriodicity },
  43. {L"BackupPeriodicity", (LPDWORD)&BrInfo.BackupPeriodicity,
  44. BACKUP_PERIODICITY, 5*60, 0x7fffffff/1000, DWordType, 0, NULL },
  45. #if DBG
  46. {L"BrowserDebug", (LPDWORD) &BrInfo.BrowserDebug,
  47. 0, 0, 0xffffffff,DWordType, 0, NULL},
  48. {L"BrowserDebugLimit", (LPDWORD) &BrInfo.BrowserDebugFileLimit,
  49. 10000*1024, 0, 0xffffffff,DWordType, 0, NULL},
  50. #endif
  51. {NULL, NULL, 0, 0, 0, BooleanType, 0, NULL}
  52. };
  53. ULONG
  54. NumberOfServerEnumerations = {0};
  55. ULONG
  56. NumberOfDomainEnumerations = {0};
  57. ULONG
  58. NumberOfOtherEnumerations = {0};
  59. ULONG
  60. NumberOfMissedGetBrowserListRequests = {0};
  61. CRITICAL_SECTION
  62. BrowserStatisticsLock = {0};
  63. NET_API_STATUS
  64. BrGetBrowserConfiguration(
  65. VOID
  66. )
  67. {
  68. NET_API_STATUS status;
  69. NT_PRODUCT_TYPE NtProductType;
  70. try {
  71. //
  72. // Initialize the resource for serializing access to configuration
  73. // information.
  74. //
  75. try{
  76. InitializeCriticalSection(&BrInfo.ConfigCritSect);
  77. }
  78. except ( EXCEPTION_EXECUTE_HANDLER ){
  79. return NERR_NoNetworkResource;
  80. }
  81. //
  82. // Lock config information structure for write access since we are
  83. // initializing the data in the structure.
  84. //
  85. EnterCriticalSection( &BrInfo.ConfigCritSect );
  86. //
  87. // Set pointer to configuration fields structure
  88. //
  89. BrInfo.BrConfigFields = BrFields;
  90. //
  91. // Determine our product type.
  92. //
  93. RtlGetNtProductType(&NtProductType);
  94. BrInfo.IsLanmanNt = (NtProductType == NtProductLanManNt);
  95. //
  96. // Read from the config file the browser configuration fields
  97. //
  98. status = BrReadBrowserConfigFields( TRUE );
  99. if (status != NERR_Success) {
  100. try_return ( status );
  101. }
  102. if (BrInfo.IsLanmanNt) {
  103. BrInfo.MaintainServerList = 1;
  104. }
  105. #ifdef ENABLE_PSEUDO_BROWSER
  106. BrInfo.PseudoServerLevel = GetBrowserPseudoServerLevel();
  107. #endif
  108. //
  109. // Don't let the user define define an incompatible master/backup periodicity.
  110. //
  111. if ( BrInfo.MasterPeriodicity > BrInfo.BackupPeriodicity ) {
  112. BrInfo.BackupPeriodicity = BrInfo.MasterPeriodicity;
  113. }
  114. try_exit:NOTHING;
  115. } finally {
  116. // else
  117. // Leave config file open because we need to read transport names from it.
  118. //
  119. LeaveCriticalSection(&BrInfo.ConfigCritSect);
  120. }
  121. return status;
  122. }
  123. #define REPORT_KEYWORD_IGNORED( lptstrKeyword ) \
  124. { \
  125. LPWSTR SubString[1]; \
  126. SubString[0] = lptstrKeyword; \
  127. BrLogEvent(EVENT_BROWSER_ILLEGAL_CONFIG, NERR_Success, 1, SubString); \
  128. NetpKdPrint(( \
  129. "[Browser] *ERROR* Tried to set keyword '" FORMAT_LPTSTR \
  130. "' with invalid value.\n" \
  131. "This error is ignored.\n", \
  132. lptstrKeyword )); \
  133. }
  134. NET_API_STATUS
  135. BrReadBrowserConfigFields(
  136. IN BOOL InitialCall
  137. )
  138. /*++
  139. Routine Description:
  140. This function assigns each browser/redir configuration field to the default
  141. value if it is not specified in the configuration file or if the value
  142. specified in the configuration file is invalid. Otherwise it overrides
  143. the default value with the value found in the configuration file.
  144. Arguments:
  145. InitialCall - True if this call was made during initialization
  146. Return Value:
  147. None.
  148. --*/
  149. {
  150. NET_API_STATUS status;
  151. LPNET_CONFIG_HANDLE BrowserSection;
  152. DWORD i;
  153. LPTSTR KeywordValueBuffer;
  154. DWORD KeywordValueStringLength;
  155. DWORD KeywordValue;
  156. DWORD OldKeywordValue;
  157. //
  158. // Open config file and get handle to the [LanmanBrowser] section
  159. //
  160. if ((status = NetpOpenConfigData(
  161. &BrowserSection,
  162. NULL, // Local
  163. SECT_NT_BROWSER,
  164. TRUE // want read-only access
  165. )) != NERR_Success) {
  166. return status;
  167. }
  168. for (i = 0; BrInfo.BrConfigFields[i].Keyword != NULL; i++) {
  169. BOOL ParameterChanged = FALSE;
  170. //
  171. // Skip this parameter if it can't change dynamically and
  172. // this isn't the initial call.
  173. //
  174. if ( !InitialCall && BrInfo.BrConfigFields[i].DynamicChangeRoutine == NULL ) {
  175. continue;
  176. }
  177. switch (BrInfo.BrConfigFields[i].DataType) {
  178. case MultiSzType:
  179. status = NetpGetConfigTStrArray(
  180. BrowserSection,
  181. BrInfo.BrConfigFields[i].Keyword,
  182. (LPTSTR_ARRAY *)(BrInfo.BrConfigFields[i].FieldPtr));
  183. if ((status != NO_ERROR) && (status != NERR_CfgParamNotFound)) {
  184. REPORT_KEYWORD_IGNORED( BrInfo.BrConfigFields[i].Keyword );
  185. }
  186. break;
  187. case BooleanType:
  188. status = NetpGetConfigBool(
  189. BrowserSection,
  190. BrInfo.BrConfigFields[i].Keyword,
  191. BrInfo.BrConfigFields[i].Default,
  192. (LPBOOL)(BrInfo.BrConfigFields[i].FieldPtr)
  193. );
  194. if ((status != NO_ERROR) && (status != NERR_CfgParamNotFound)) {
  195. REPORT_KEYWORD_IGNORED( BrInfo.BrConfigFields[i].Keyword );
  196. }
  197. break;
  198. case TriValueType:
  199. //
  200. // Assign default configuration value
  201. //
  202. *(BrInfo.BrConfigFields[i].FieldPtr) = BrInfo.BrConfigFields[i].Default;
  203. if (NetpGetConfigValue(
  204. BrowserSection,
  205. BrInfo.BrConfigFields[i].Keyword,
  206. &KeywordValueBuffer
  207. ) != NERR_Success) {
  208. continue;
  209. }
  210. KeywordValueStringLength = STRLEN(KeywordValueBuffer);
  211. if (STRICMP(KeywordValueBuffer, KEYWORD_YES) == 0) {
  212. *(BrInfo.BrConfigFields[i].FieldPtr) = 1;
  213. } else if (STRICMP(KeywordValueBuffer, KEYWORD_TRUE) == 0) {
  214. *(BrInfo.BrConfigFields[i].FieldPtr) = 1;
  215. } else if (STRICMP(KeywordValueBuffer, KEYWORD_NO) == 0) {
  216. *(BrInfo.BrConfigFields[i].FieldPtr) = (DWORD) -1;
  217. } else if (STRICMP(KeywordValueBuffer, KEYWORD_FALSE) == 0) {
  218. *(BrInfo.BrConfigFields[i].FieldPtr) = (DWORD) -1;
  219. } else if (STRICMP(KeywordValueBuffer, TEXT("AUTO")) == 0) {
  220. *(BrInfo.BrConfigFields[i].FieldPtr) = 0;
  221. }
  222. else {
  223. REPORT_KEYWORD_IGNORED( BrInfo.BrConfigFields[i].Keyword );
  224. }
  225. NetApiBufferFree(KeywordValueBuffer);
  226. break;
  227. case DWordType:
  228. OldKeywordValue = *(LPDWORD)BrInfo.BrConfigFields[i].FieldPtr;
  229. if (NetpGetConfigDword(
  230. BrowserSection,
  231. BrInfo.BrConfigFields[i].Keyword,
  232. BrInfo.BrConfigFields[i].Default,
  233. (LPDWORD)(BrInfo.BrConfigFields[i].FieldPtr)
  234. ) != NERR_Success) {
  235. continue;
  236. }
  237. KeywordValue = *(LPDWORD)BrInfo.BrConfigFields[i].FieldPtr;
  238. //
  239. // Don't allow too large or small a value.
  240. //
  241. if (KeywordValue < BrInfo.BrConfigFields[i].Minimum) {
  242. BrPrint(( BR_CRITICAL, "%ws value out of range %lu (%lu-%lu)\n",
  243. BrInfo.BrConfigFields[i].Keyword, KeywordValue,
  244. BrInfo.BrConfigFields[i].Minimum,
  245. BrInfo.BrConfigFields[i].Maximum
  246. ));
  247. KeywordValue =
  248. *(LPDWORD)BrInfo.BrConfigFields[i].FieldPtr =
  249. BrInfo.BrConfigFields[i].Minimum;
  250. }
  251. if (KeywordValue > BrInfo.BrConfigFields[i].Maximum) {
  252. BrPrint(( BR_CRITICAL, "%ws value out of range %lu (%lu-%lu)\n",
  253. BrInfo.BrConfigFields[i].Keyword, KeywordValue,
  254. BrInfo.BrConfigFields[i].Minimum,
  255. BrInfo.BrConfigFields[i].Maximum
  256. ));
  257. KeywordValue =
  258. *(LPDWORD)BrInfo.BrConfigFields[i].FieldPtr =
  259. BrInfo.BrConfigFields[i].Maximum;
  260. }
  261. //
  262. // Test if the parameter has actually changed
  263. //
  264. if ( OldKeywordValue != KeywordValue ) {
  265. ParameterChanged = TRUE;
  266. }
  267. break;
  268. default:
  269. NetpAssert(FALSE);
  270. }
  271. //
  272. // If this is a dynamic parameter change,
  273. // and this isn't the initial call.
  274. // notify that this parameter changed.
  275. //
  276. if ( !InitialCall && ParameterChanged ) {
  277. BrInfo.BrConfigFields[i].DynamicChangeRoutine();
  278. }
  279. }
  280. status = NetpCloseConfigData(BrowserSection);
  281. if (BrInfo.DirectHostBinding != NULL &&
  282. !NetpIsTStrArrayEmpty(BrInfo.DirectHostBinding)) {
  283. BrPrint(( BR_INIT,"DirectHostBinding length: %ld\n",NetpTStrArrayEntryCount(BrInfo.DirectHostBinding)));
  284. if (NetpTStrArrayEntryCount(BrInfo.DirectHostBinding) % 2 != 0) {
  285. status = ERROR_INVALID_PARAMETER;
  286. }
  287. }
  288. return status;
  289. }
  290. VOID
  291. BrDeleteConfiguration (
  292. DWORD BrInitState
  293. )
  294. {
  295. if (BrInfo.DirectHostBinding != NULL) {
  296. NetApiBufferFree(BrInfo.DirectHostBinding);
  297. }
  298. if (BrInfo.UnboundBindings != NULL) {
  299. NetApiBufferFree(BrInfo.UnboundBindings);
  300. }
  301. DeleteCriticalSection(&BrInfo.ConfigCritSect);
  302. UNREFERENCED_PARAMETER(BrInitState);
  303. }
  304. NET_API_STATUS
  305. BrChangeDirectHostBinding(
  306. VOID
  307. )
  308. /*++
  309. Routine Description (BrChnageDirectHostBinding):
  310. Handle a change in DirectHostBinding entry in the registry based on
  311. Registry notification.
  312. This is used so that when NwLnkNb transport is created via PnP, we should
  313. also create NwLnkIpx (current usage).
  314. The binding is refreshed in BrReadBrowserConfigFields above.
  315. Arguments:
  316. None.
  317. Return Value:
  318. None.
  319. --*/
  320. {
  321. NET_API_STATUS NetStatus = NERR_Success;
  322. NetStatus = BrChangeConfigValue(
  323. L"DirectHostBinding",
  324. MultiSzType,
  325. NULL,
  326. &(BrInfo.DirectHostBinding),
  327. TRUE );
  328. if ( NetStatus == NERR_Success ) {
  329. //
  330. // DirectHostBinding sepcified. Verify consistency
  331. //
  332. EnterCriticalSection ( &BrInfo.ConfigCritSect );
  333. if (BrInfo.DirectHostBinding != NULL &&
  334. !NetpIsTStrArrayEmpty(BrInfo.DirectHostBinding)) {
  335. BrPrint(( BR_INIT,"DirectHostBinding length: %ld\n",NetpTStrArrayEntryCount(BrInfo.DirectHostBinding)));
  336. if (NetpTStrArrayEntryCount(BrInfo.DirectHostBinding) % 2 != 0) {
  337. NetApiBufferFree(BrInfo.DirectHostBinding);
  338. BrInfo.DirectHostBinding = NULL;
  339. // we fail on invalid specifications
  340. NetStatus = ERROR_INVALID_PARAMETER;
  341. }
  342. }
  343. LeaveCriticalSection ( &BrInfo.ConfigCritSect );
  344. }
  345. return NetStatus;
  346. }
  347. NET_API_STATUS
  348. BrChangeConfigValue(
  349. LPWSTR pszKeyword IN,
  350. DATATYPE dataType IN,
  351. PVOID pDefault IN,
  352. PVOID *ppData OUT,
  353. BOOL bFree IN
  354. )
  355. /*++
  356. Routine Description:
  357. Reads in the registry value for browser registry Entry
  358. Arguments:
  359. pszKeyword -- keyword relative to browser param section
  360. dataType -- the type of the data to get from netapi lib.
  361. pDefault -- Default value (to pass to reg calls).
  362. pData -- data read from the registry.
  363. Return Value:
  364. Net api error code
  365. --*/
  366. {
  367. NET_API_STATUS status = STATUS_SUCCESS;
  368. LPNET_CONFIG_HANDLE BrowserSection = NULL;
  369. LPTSTR KeywordValueBuffer;
  370. DWORD KeywordValueStringLength;
  371. PVOID pData = NULL;
  372. ASSERT ( ppData );
  373. EnterCriticalSection ( &BrInfo.ConfigCritSect );
  374. //
  375. // Open config file and get handle to the [LanmanBrowser] section
  376. //
  377. if ((status = NetpOpenConfigData(
  378. &BrowserSection,
  379. NULL, // Local
  380. SECT_NT_BROWSER,
  381. TRUE // want read-only access
  382. )) != NERR_Success) {
  383. goto Cleanup;
  384. }
  385. switch (dataType) {
  386. case MultiSzType:
  387. {
  388. LPTSTR_ARRAY lpValues = NULL;
  389. status = NetpGetConfigTStrArray(
  390. BrowserSection,
  391. pszKeyword,
  392. (LPTSTR_ARRAY *)(&lpValues));
  393. if ((status != NO_ERROR) && (status != NERR_CfgParamNotFound)) {
  394. REPORT_KEYWORD_IGNORED( pszKeyword );
  395. }
  396. else {
  397. pData = (PVOID)lpValues;
  398. }
  399. break;
  400. }
  401. case BooleanType:
  402. {
  403. //
  404. // Note : This case is unused at the moment.
  405. //
  406. BOOL bData;
  407. status = NetpGetConfigBool(
  408. BrowserSection,
  409. pszKeyword,
  410. *(LPBOOL)pDefault,
  411. &bData
  412. );
  413. if ((status != NO_ERROR) && (status != NERR_CfgParamNotFound)) {
  414. REPORT_KEYWORD_IGNORED( pszKeyword );
  415. }
  416. else
  417. {
  418. // store bool value in ptr.
  419. // caller is responsible for consistent semantics translation.
  420. pData = IntToPtr((int)bData);
  421. }
  422. break;
  423. }
  424. case TriValueType:
  425. {
  426. //
  427. // Assign default configuration value
  428. //
  429. if (NetpGetConfigValue(
  430. BrowserSection,
  431. pszKeyword,
  432. &KeywordValueBuffer
  433. ) != NERR_Success) {
  434. REPORT_KEYWORD_IGNORED( pszKeyword );
  435. }
  436. KeywordValueStringLength = STRLEN(KeywordValueBuffer);
  437. if (STRICMP(KeywordValueBuffer, KEYWORD_YES) == 0) {
  438. pData = (LPVOID)1;
  439. } else if (STRICMP(KeywordValueBuffer, KEYWORD_TRUE) == 0) {
  440. pData = (LPVOID)1;
  441. } else if (STRICMP(KeywordValueBuffer, KEYWORD_NO) == 0) {
  442. pData = (LPVOID) -1;
  443. } else if (STRICMP(KeywordValueBuffer, KEYWORD_FALSE) == 0) {
  444. pData = (LPVOID) -1;
  445. } else if (STRICMP(KeywordValueBuffer, TEXT("AUTO")) == 0) {
  446. pData = (LPVOID)0;
  447. }
  448. else {
  449. // assign the value pointed by pDefault to pData
  450. pData = ULongToPtr((*(LPDWORD)pDefault));
  451. REPORT_KEYWORD_IGNORED( pszKeyword );
  452. }
  453. NetApiBufferFree(KeywordValueBuffer);
  454. break;
  455. }
  456. case DWordType:
  457. {
  458. DWORD dwTmp;
  459. if (NetpGetConfigDword(
  460. BrowserSection,
  461. pszKeyword,
  462. *(LPDWORD)pDefault,
  463. &dwTmp
  464. ) != NERR_Success) {
  465. REPORT_KEYWORD_IGNORED( pszKeyword );
  466. }
  467. else {
  468. pData = ULongToPtr(dwTmp);
  469. }
  470. break;
  471. }
  472. default:
  473. NetpAssert(FALSE);
  474. }
  475. Cleanup:
  476. // Close config, & leave CS
  477. NetpCloseConfigData(BrowserSection);
  478. // optionaly free data & set return buffer
  479. if ( status == STATUS_SUCCESS )
  480. {
  481. if ( bFree && *ppData )
  482. {
  483. NetApiBufferFree( *ppData );
  484. }
  485. *ppData = pData;
  486. }
  487. LeaveCriticalSection ( &BrInfo.ConfigCritSect );
  488. return status;
  489. }
  490. #if DBG
  491. NET_API_STATUS
  492. BrUpdateDebugInformation(
  493. IN LPWSTR SystemKeyName,
  494. IN LPWSTR ValueName,
  495. IN LPTSTR TransportName,
  496. IN LPTSTR ServerName OPTIONAL,
  497. IN DWORD ServiceStatus
  498. )
  499. /*++
  500. Routine Description:
  501. This routine will stick debug information in the registry about the last
  502. time the browser retrieved information from the remote server.
  503. Arguments:
  504. Return Value:
  505. None.
  506. --*/
  507. {
  508. WCHAR TotalKeyName[MAX_PATH];
  509. ULONG Disposition;
  510. HKEY Key;
  511. ULONG Status;
  512. SYSTEMTIME LocalTime;
  513. WCHAR LastUpdateTime[100];
  514. //
  515. // Build the key name:
  516. //
  517. // HKEY_LOCAL_MACHINE:System\CurrentControlSet\Services\Browser\Debug\<Transport>\SystemKeyName
  518. //
  519. wcscpy(TotalKeyName, L"System\\CurrentControlSet\\Services\\Browser\\Debug");
  520. wcscat(TotalKeyName, TransportName);
  521. wcscat(TotalKeyName, L"\\");
  522. wcscat(TotalKeyName, SystemKeyName);
  523. if ((Status = RegCreateKeyEx(HKEY_LOCAL_MACHINE, TotalKeyName, 0,
  524. L"BrowserDebugInformation",
  525. REG_OPTION_NON_VOLATILE,
  526. KEY_WRITE,
  527. NULL,
  528. &Key,
  529. &Disposition)) != ERROR_SUCCESS) {
  530. BrPrint(( BR_CRITICAL,"Unable to create key to log debug information: %lx\n", Status));
  531. return Status;
  532. }
  533. if (ARGUMENT_PRESENT(ServerName)) {
  534. if ((Status = RegSetValueEx(Key, ValueName, 0, REG_SZ, (LPBYTE)ServerName, (wcslen(ServerName)+1) * sizeof(WCHAR))) != ERROR_SUCCESS) {
  535. BrPrint(( BR_CRITICAL,
  536. "Unable to set value of ServerName value to %ws: %lx\n",
  537. ServerName, Status));
  538. RegCloseKey(Key);
  539. return Status;
  540. }
  541. } else {
  542. if ((Status = RegSetValueEx(Key, ValueName, 0, REG_DWORD, (LPBYTE)&ServiceStatus, sizeof(ULONG))) != ERROR_SUCCESS) {
  543. BrPrint(( BR_CRITICAL,"Unable to set value of ServerName value to %ws: %lx\n", ServerName, Status));
  544. RegCloseKey(Key);
  545. return Status;
  546. }
  547. }
  548. GetLocalTime(&LocalTime);
  549. swprintf(LastUpdateTime, L"%d/%d/%d %d:%d:%d:%d", LocalTime.wDay,
  550. LocalTime.wMonth,
  551. LocalTime.wYear,
  552. LocalTime.wHour,
  553. LocalTime.wMinute,
  554. LocalTime.wSecond,
  555. LocalTime.wMilliseconds);
  556. if ((Status = RegSetValueEx(Key, L"LastUpdateTime", 0, REG_SZ, (LPBYTE)&LastUpdateTime, (wcslen(LastUpdateTime) + 1)*sizeof(WCHAR))) != ERROR_SUCCESS) {
  557. BrPrint(( BR_CRITICAL,"Unable to set value of LastUpdateTime value to %s: %lx\n", LastUpdateTime, Status));
  558. }
  559. RegCloseKey(Key);
  560. return Status;
  561. }
  562. #endif