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.

695 lines
18 KiB

  1. /******************************************************************************
  2. *
  3. * Copyright (c) 2000 Microsoft Corporation
  4. *
  5. * Module Name:
  6. * srconfig.cpp
  7. *
  8. * Abstract:
  9. * CSRConfig methods
  10. *
  11. * Revision History:
  12. * Brijesh Krishnaswami (brijeshk) 04/15/2000
  13. * created
  14. *
  15. *****************************************************************************/
  16. #include "precomp.h"
  17. extern "C"
  18. {
  19. #include <powrprof.h>
  20. }
  21. CSRConfig *g_pSRConfig; // the global instance
  22. CSRConfig::CSRConfig()
  23. {
  24. m_hSRInitEvent = m_hSRStopEvent = m_hIdleRequestEvent = NULL;
  25. m_hIdleStartEvent = m_hIdleStopEvent = NULL;
  26. m_hFilter = NULL;
  27. ::GetSystemDrive(m_szSystemDrive);
  28. m_fCleanup = FALSE;
  29. m_fSafeMode = FALSE;
  30. m_fReset = FALSE;
  31. m_dwFifoDisabledNum = 0;
  32. m_dwFreezeThawLogCount = 0;
  33. lstrcpy(m_szGuid, L"");
  34. }
  35. CSRConfig::~CSRConfig()
  36. {
  37. if (m_hSRInitEvent)
  38. CloseHandle(m_hSRInitEvent);
  39. if (m_hSRStopEvent)
  40. CloseHandle(m_hSRStopEvent);
  41. if (m_hIdleRequestEvent)
  42. CloseHandle(m_hIdleRequestEvent);
  43. CloseFilter();
  44. }
  45. void
  46. CSRConfig::SetDefaults()
  47. {
  48. m_dwDisableSR = FALSE;
  49. m_dwDisableSR_GroupPolicy = 0xFFFFFFFF; // not configured
  50. m_dwFirstRun = SR_FIRSTRUN_NO;
  51. m_dwTimerInterval = SR_DEFAULT_TIMERINTERVAL;
  52. m_dwIdleInterval = SR_DEFAULT_IDLEINTERVAL;
  53. m_dwCompressionBurst = SR_DEFAULT_COMPRESSIONBURST;
  54. m_dwRPSessionInterval = SR_DEFAULT_RPSESSIONINTERVAL;
  55. m_dwDSMax = SR_DEFAULT_DSMAX;
  56. m_dwDSMin = SR_DEFAULT_DSMIN;
  57. m_dwRPGlobalInterval = SR_DEFAULT_RPGLOBALINTERVAL;
  58. m_dwRPLifeInterval = SR_DEFAULT_RPLIFEINTERVAL;
  59. m_dwThawInterval = SR_DEFAULT_THAW_INTERVAL;
  60. m_dwDiskPercent = SR_DEFAULT_DISK_PERCENT;
  61. m_dwTestBroadcast = 0;
  62. m_dwCreateFirstRunRp = 1;
  63. }
  64. void
  65. CSRConfig::ReadAll()
  66. {
  67. HKEY hKeyCur = NULL, hKeyCfg = NULL, hKeyFilter = NULL, hKey = NULL;
  68. HKEY hKeyGP = NULL;
  69. DWORD dwRc;
  70. POWER_POLICY pp;
  71. GLOBAL_POWER_POLICY gpp;
  72. UINT uiPowerScheme = 0;
  73. TENTER("CSRConfig::ReadAll");
  74. // open the group policy key, ignore failures if key doesn't exist
  75. RegOpenKeyEx (HKEY_LOCAL_MACHINE,
  76. s_cszGroupPolicy,
  77. 0,
  78. KEY_READ,
  79. &hKeyGP);
  80. // open SystemRestore regkeys
  81. dwRc = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
  82. s_cszSRRegKey,
  83. 0,
  84. KEY_READ,
  85. &hKeyCur);
  86. if (ERROR_SUCCESS != dwRc)
  87. {
  88. TRACE(0, "! RegOpenKeyEx on %S : %ld", s_cszSRRegKey, dwRc);
  89. goto done;
  90. }
  91. dwRc = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
  92. s_cszSRCfgRegKey,
  93. 0,
  94. KEY_READ,
  95. &hKeyCfg);
  96. if (ERROR_SUCCESS != dwRc)
  97. {
  98. TRACE(0, "! RegOpenKeyEx on %S : %ld", s_cszSRCfgRegKey, dwRc);
  99. goto done;
  100. }
  101. // open the filter regkey
  102. dwRc = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
  103. s_cszFilterRegKey,
  104. 0,
  105. KEY_READ,
  106. &hKeyFilter);
  107. if (ERROR_SUCCESS != dwRc)
  108. {
  109. TRACE(0, "! RegOpenKeyEx on %S : %ld", s_cszFilterRegKey, dwRc);
  110. goto done;
  111. }
  112. RegReadDWORD(hKeyCur, s_cszDisableSR, &m_dwDisableSR);
  113. RegReadDWORD(hKeyCur, s_cszTestBroadcast, &m_dwTestBroadcast);
  114. // read the group policy values to be enforced
  115. // read the corresponding local setting also
  116. if (hKeyGP != NULL)
  117. RegReadDWORD(hKeyGP, s_cszDisableSR, &m_dwDisableSR_GroupPolicy);
  118. // read firstrun - 1 means firstrun, 0 means not
  119. RegReadDWORD(hKeyFilter, s_cszFirstRun, &m_dwFirstRun);
  120. // if firstrun, read other values from Cfg subkey
  121. // else read other values from main regkey
  122. hKey = (m_dwFirstRun == SR_FIRSTRUN_YES) ? hKeyCfg : hKeyCur;
  123. RegReadDWORD(hKey, s_cszDSMin, &m_dwDSMin);
  124. RegReadDWORD(hKey, s_cszDSMax, &m_dwDSMax);
  125. RegReadDWORD(hKey, s_cszRPSessionInterval, &m_dwRPSessionInterval);
  126. RegReadDWORD(hKey, s_cszRPGlobalInterval, &m_dwRPGlobalInterval);
  127. RegReadDWORD(hKey, s_cszRPLifeInterval, &m_dwRPLifeInterval);
  128. RegReadDWORD(hKey, s_cszCompressionBurst, &m_dwCompressionBurst);
  129. RegReadDWORD(hKey, s_cszTimerInterval, &m_dwTimerInterval);
  130. RegReadDWORD(hKey, s_cszDiskPercent, &m_dwDiskPercent);
  131. RegReadDWORD(hKey, s_cszThawInterval, &m_dwThawInterval);
  132. if (GetCurrentPowerPolicies (&gpp, &pp))
  133. {
  134. m_dwIdleInterval = max(pp.user.IdleTimeoutAc, pp.user.IdleTimeoutDc);
  135. m_dwIdleInterval = max(SR_DEFAULT_IDLEINTERVAL, m_dwIdleInterval*2);
  136. }
  137. done:
  138. if (hKeyCur)
  139. RegCloseKey(hKeyCur);
  140. if (hKeyCfg)
  141. RegCloseKey(hKeyCfg);
  142. if (hKeyFilter)
  143. RegCloseKey(hKeyFilter);
  144. if (hKeyGP)
  145. RegCloseKey (hKeyGP);
  146. TLEAVE();
  147. }
  148. // upon firstrun, write the current values to the current location
  149. void
  150. CSRConfig::WriteAll()
  151. {
  152. TENTER("CSRConfig::WriteAll");
  153. HKEY hKey = NULL;
  154. HKEY hKeyCfg = NULL;
  155. DWORD dwRc;
  156. // open SystemRestore regkey
  157. dwRc = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
  158. s_cszSRRegKey,
  159. 0,
  160. KEY_WRITE,
  161. &hKey);
  162. if (ERROR_SUCCESS != dwRc)
  163. {
  164. TRACE(0, "! RegOpenKeyEx on %S : %ld", s_cszSRRegKey, dwRc);
  165. goto done;
  166. }
  167. RegWriteDWORD(hKey, s_cszDisableSR, &m_dwDisableSR);
  168. RegWriteDWORD(hKey, s_cszDSMin, &m_dwDSMin);
  169. RegWriteDWORD(hKey, s_cszDSMax, &m_dwDSMax);
  170. RegWriteDWORD(hKey, s_cszRPSessionInterval, &m_dwRPSessionInterval);
  171. RegWriteDWORD(hKey, s_cszRPGlobalInterval, &m_dwRPGlobalInterval);
  172. RegWriteDWORD(hKey, s_cszRPLifeInterval, &m_dwRPLifeInterval);
  173. RegWriteDWORD(hKey, s_cszCompressionBurst, &m_dwCompressionBurst);
  174. RegWriteDWORD(hKey, s_cszTimerInterval, &m_dwTimerInterval);
  175. RegWriteDWORD(hKey, s_cszDiskPercent, &m_dwDiskPercent);
  176. RegWriteDWORD(hKey, s_cszThawInterval, &m_dwThawInterval);
  177. // secure our keys from other users
  178. if (ERROR_SUCCESS == RegOpenKeyEx(HKEY_LOCAL_MACHINE,
  179. s_cszSRCfgRegKey,
  180. 0,
  181. KEY_WRITE,
  182. &hKeyCfg))
  183. {
  184. dwRc = SetAclInObject(hKeyCfg, SE_REGISTRY_KEY,KEY_ALL_ACCESS,KEY_READ, TRUE);
  185. RegCloseKey (hKeyCfg);
  186. hKeyCfg = NULL;
  187. if (ERROR_SUCCESS != dwRc)
  188. {
  189. TRACE(0, "! SetAclInObject %S : %ld", s_cszSRCfgRegKey, dwRc);
  190. goto done;
  191. }
  192. }
  193. dwRc = SetAclInObject(hKey, SE_REGISTRY_KEY, KEY_ALL_ACCESS, KEY_READ, TRUE);
  194. if (ERROR_SUCCESS != dwRc)
  195. {
  196. TRACE(0, "! SetAclInObject %S : %ld", s_cszSRRegKey, dwRc);
  197. goto done;
  198. }
  199. done:
  200. TLEAVE();
  201. if (hKey)
  202. RegCloseKey(hKey);
  203. }
  204. DWORD
  205. CSRConfig::SetMachineGuid()
  206. {
  207. TENTER("CSRConfig::SetMachineGuid");
  208. HKEY hKey = NULL;
  209. DWORD dwErr, dwType, dwSize;
  210. WCHAR szGuid[GUID_STRLEN];
  211. LPWSTR pszGuid = NULL;
  212. // read the machine guid from the cfg regkey
  213. pszGuid = GetMachineGuid();
  214. if (! pszGuid)
  215. {
  216. GUID guid;
  217. UuidCreate(&guid);
  218. dwErr = RegOpenKeyEx (HKEY_LOCAL_MACHINE,
  219. s_cszSRCfgRegKey, 0,
  220. KEY_WRITE, &hKey);
  221. if (dwErr != ERROR_SUCCESS)
  222. goto Err;
  223. if (0 == StringFromGUID2 (guid, szGuid, sizeof(szGuid)/sizeof(WCHAR)))
  224. goto Err;
  225. dwErr = RegSetValueEx (hKey, s_cszSRMachineGuid,
  226. 0, REG_SZ, (BYTE *) szGuid,
  227. (lstrlen(szGuid) + 1) * sizeof(WCHAR));
  228. if (dwErr != ERROR_SUCCESS)
  229. goto Err;
  230. pszGuid = (LPWSTR) szGuid;
  231. }
  232. RegCloseKey(hKey);
  233. hKey = NULL;
  234. // then copy it to the filter regkey
  235. dwErr = RegOpenKeyEx (HKEY_LOCAL_MACHINE,
  236. s_cszFilterRegKey, 0,
  237. KEY_WRITE, &hKey);
  238. if (dwErr != ERROR_SUCCESS)
  239. goto Err;
  240. dwErr = RegSetValueEx (hKey, s_cszSRMachineGuid,
  241. 0, REG_SZ, (BYTE *) pszGuid,
  242. (lstrlen(pszGuid) + 1) * sizeof(WCHAR));
  243. Err:
  244. if (hKey)
  245. RegCloseKey(hKey);
  246. TLEAVE();
  247. return dwErr;
  248. }
  249. // method to set firstrun key in the registry and update member
  250. DWORD
  251. CSRConfig::SetFirstRun(DWORD dwValue)
  252. {
  253. HKEY hKeyFilter = NULL;
  254. DWORD dwRc = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
  255. s_cszFilterRegKey,
  256. 0,
  257. KEY_WRITE,
  258. &hKeyFilter);
  259. if (ERROR_SUCCESS != dwRc)
  260. goto done;
  261. dwRc = RegWriteDWORD(hKeyFilter, s_cszFirstRun, &dwValue);
  262. if (ERROR_SUCCESS != dwRc)
  263. goto done;
  264. m_dwFirstRun = dwValue;
  265. done:
  266. if (hKeyFilter)
  267. RegCloseKey(hKeyFilter);
  268. return dwRc;
  269. }
  270. DWORD
  271. CSRConfig::SetDisableFlag(DWORD dwValue)
  272. {
  273. HKEY hKeySR = NULL;
  274. DWORD dwRc = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
  275. s_cszSRRegKey,
  276. 0,
  277. KEY_WRITE,
  278. &hKeySR);
  279. if (ERROR_SUCCESS != dwRc)
  280. goto done;
  281. dwRc = RegWriteDWORD(hKeySR, s_cszDisableSR, &dwValue);
  282. if (ERROR_SUCCESS != dwRc)
  283. goto done;
  284. m_dwDisableSR = dwValue;
  285. done:
  286. if (hKeySR)
  287. RegCloseKey(hKeySR);
  288. return dwRc;
  289. }
  290. DWORD
  291. CSRConfig::SetCreateFirstRunRp(DWORD dwValue)
  292. {
  293. HKEY hKeySR = NULL;
  294. DWORD dwRc = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
  295. s_cszSRRegKey,
  296. 0,
  297. KEY_WRITE,
  298. &hKeySR);
  299. if (ERROR_SUCCESS != dwRc)
  300. goto done;
  301. dwRc = RegWriteDWORD(hKeySR, s_cszCreateFirstRunRp, &dwValue);
  302. if (ERROR_SUCCESS != dwRc)
  303. goto done;
  304. m_dwCreateFirstRunRp = dwValue;
  305. done:
  306. if (hKeySR)
  307. RegCloseKey(hKeySR);
  308. return dwRc;
  309. }
  310. DWORD
  311. CSRConfig::GetCreateFirstRunRp()
  312. {
  313. HKEY hKeySR = NULL;
  314. DWORD dwRc = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
  315. s_cszSRRegKey,
  316. 0,
  317. KEY_READ,
  318. &hKeySR);
  319. if (ERROR_SUCCESS != dwRc)
  320. goto done;
  321. dwRc = RegReadDWORD(hKeySR, s_cszCreateFirstRunRp, &m_dwCreateFirstRunRp);
  322. done:
  323. if (hKeySR)
  324. RegCloseKey(hKeySR);
  325. return m_dwCreateFirstRunRp;
  326. }
  327. // add datastore to regkey backup exclude list
  328. DWORD
  329. CSRConfig::AddBackupRegKey()
  330. {
  331. HKEY hKey = NULL;
  332. WCHAR szPath[MAX_PATH];
  333. DWORD dwRc = RegOpenKeyEx(HKEY_LOCAL_MACHINE,
  334. L"System\\CurrentControlSet\\Control\\BackupRestore\\FilesNotToBackup",
  335. 0,
  336. KEY_WRITE,
  337. &hKey);
  338. if (ERROR_SUCCESS != dwRc)
  339. goto done;
  340. MakeRestorePath(szPath, L"\\", L"* /s");
  341. szPath[lstrlen(szPath)+1] = L'\0'; // second null terminator for multi sz
  342. dwRc = RegSetValueEx (hKey,
  343. L"System Restore",
  344. 0,
  345. REG_MULTI_SZ,
  346. (LPBYTE) szPath,
  347. (lstrlen(szPath) + 2) * sizeof(WCHAR));
  348. if (ERROR_SUCCESS != dwRc)
  349. goto done;
  350. done:
  351. if (hKey)
  352. RegCloseKey(hKey);
  353. return dwRc;
  354. }
  355. void
  356. CSRConfig::RegisterTestMessages()
  357. {
  358. HWINSTA hwinstaUser = NULL;
  359. HDESK hdeskUser = NULL;
  360. DWORD dwThreadId;
  361. HWINSTA hwinstaSave = NULL;
  362. HDESK hdeskSave = NULL;
  363. DWORD dwRc;
  364. TENTER("RegisterTestMessages");
  365. //
  366. // save current values
  367. //
  368. GetDesktopWindow();
  369. hwinstaSave = GetProcessWindowStation();
  370. dwThreadId = GetCurrentThreadId();
  371. hdeskSave = GetThreadDesktop(dwThreadId);
  372. //
  373. // change desktop and winstation to interactive user
  374. //
  375. hwinstaUser = OpenWindowStation(L"WinSta0", FALSE, MAXIMUM_ALLOWED);
  376. if (hwinstaUser == NULL)
  377. {
  378. dwRc = GetLastError();
  379. trace(0, "! OpenWindowStation : %ld", dwRc);
  380. goto done;
  381. }
  382. SetProcessWindowStation(hwinstaUser);
  383. hdeskUser = OpenDesktop(L"Default", 0, FALSE, MAXIMUM_ALLOWED);
  384. if (hdeskUser == NULL)
  385. {
  386. dwRc = GetLastError();
  387. trace(0, "! OpenDesktop : %ld", dwRc);
  388. goto done;
  389. }
  390. SetThreadDesktop(hdeskUser);
  391. //
  392. // register the test messages
  393. //
  394. m_uiTMFreeze = RegisterWindowMessage(s_cszTM_Freeze);
  395. m_uiTMThaw = RegisterWindowMessage(s_cszTM_Thaw);
  396. m_uiTMFifoStart = RegisterWindowMessage(s_cszTM_FifoStart);
  397. m_uiTMFifoStop = RegisterWindowMessage(s_cszTM_FifoStop);
  398. m_uiTMEnable = RegisterWindowMessage(s_cszTM_Enable);
  399. m_uiTMDisable = RegisterWindowMessage(s_cszTM_Disable);
  400. m_uiTMCompressStart = RegisterWindowMessage(s_cszTM_CompressStart);
  401. m_uiTMCompressStop = RegisterWindowMessage(s_cszTM_CompressStop);
  402. done:
  403. //
  404. // restore old values
  405. //
  406. if (hdeskSave)
  407. SetThreadDesktop(hdeskSave);
  408. if (hwinstaSave)
  409. SetProcessWindowStation(hwinstaSave);
  410. //
  411. // close opened handles
  412. //
  413. if (hdeskUser)
  414. CloseDesktop(hdeskUser);
  415. if (hwinstaUser)
  416. CloseWindowStation(hwinstaUser);
  417. TLEAVE();
  418. }
  419. DWORD
  420. CSRConfig::Initialize()
  421. {
  422. DWORD dwRc = ERROR_INTERNAL_ERROR;
  423. WCHAR szDat[MAX_PATH];
  424. TENTER("CSRConfig::Initialize");
  425. // read all config values from registry
  426. // use default if not able to read value
  427. SetDefaults();
  428. ReadAll();
  429. // is this safe mode?
  430. if (0 != GetSystemMetrics(SM_CLEANBOOT))
  431. {
  432. TRACE(0, "This is safemode");
  433. m_fSafeMode = TRUE;
  434. }
  435. // if _filelst.cfg does not exist, then consider this firstrun
  436. MakeRestorePath(szDat, GetSystemDrive(), s_cszFilelistDat);
  437. if (-1 == GetFileAttributes(szDat))
  438. {
  439. TRACE(0, "%S does not exist", s_cszFilelistDat);
  440. SetFirstRun(SR_FIRSTRUN_YES);
  441. }
  442. if (m_dwFirstRun == SR_FIRSTRUN_YES)
  443. {
  444. SetMachineGuid();
  445. WriteAll();
  446. AddBackupRegKey();
  447. }
  448. TRACE(0, "%SFirstRun", m_dwFirstRun == SR_FIRSTRUN_YES ? L"" : L"Not ");
  449. // create events
  450. // give read access to everyone so that clients can wait on them
  451. // SR init
  452. m_hSRInitEvent = CreateEvent(NULL, FALSE, FALSE, s_cszSRInitEvent);
  453. if (! m_hSRInitEvent)
  454. {
  455. TRACE(0, "! CreateEvent on %S : %ld", s_cszSRInitEvent, GetLastError());
  456. goto done;
  457. }
  458. dwRc = SetAclInObject(m_hSRInitEvent,
  459. SE_KERNEL_OBJECT,
  460. STANDARD_RIGHTS_ALL | GENERIC_ALL,
  461. STANDARD_RIGHTS_READ | GENERIC_READ | SYNCHRONIZE,
  462. FALSE);
  463. if (dwRc != ERROR_SUCCESS)
  464. {
  465. TRACE(0, "! SetAclInObject : %ld", dwRc);
  466. goto done;
  467. }
  468. // SR stop
  469. m_hSRStopEvent = CreateEvent(NULL, TRUE, FALSE, s_cszSRStopEvent);
  470. if (! m_hSRStopEvent)
  471. {
  472. TRACE(0, "! CreateEvent on %S : %ld", s_cszSRStopEvent, GetLastError());
  473. goto done;
  474. }
  475. else if (GetLastError() == ERROR_ALREADY_EXISTS)
  476. {
  477. TRACE(0, "Stop Event already exists!");
  478. ResetEvent (m_hSRStopEvent);
  479. }
  480. dwRc = SetAclInObject(m_hSRStopEvent,
  481. SE_KERNEL_OBJECT,
  482. STANDARD_RIGHTS_ALL | GENERIC_ALL,
  483. STANDARD_RIGHTS_READ | GENERIC_READ | SYNCHRONIZE,
  484. FALSE);
  485. if (dwRc != ERROR_SUCCESS)
  486. {
  487. TRACE(0, "! SetAclInObject : %ld", dwRc);
  488. goto done;
  489. }
  490. // idle request
  491. m_hIdleRequestEvent = CreateEvent(NULL, FALSE, FALSE, s_cszIdleRequestEvent);
  492. if (! m_hIdleRequestEvent)
  493. {
  494. TRACE(0, "! CreateEvent on %S : %ld", s_cszIdleRequestEvent, GetLastError());
  495. goto done;
  496. }
  497. dwRc = SetAclInObject(m_hIdleRequestEvent,
  498. SE_KERNEL_OBJECT,
  499. STANDARD_RIGHTS_ALL | GENERIC_ALL,
  500. STANDARD_RIGHTS_READ | GENERIC_READ | SYNCHRONIZE,
  501. FALSE);
  502. if (dwRc != ERROR_SUCCESS)
  503. {
  504. TRACE(0, "! SetAclInObject : %ld", dwRc);
  505. goto done;
  506. }
  507. //
  508. // register test messages
  509. //
  510. if (m_dwTestBroadcast)
  511. {
  512. RegisterTestMessages();
  513. }
  514. dwRc = ERROR_SUCCESS;
  515. done:
  516. TLEAVE();
  517. return dwRc;
  518. }
  519. //
  520. // function to check if system is running on battery
  521. //
  522. BOOL CSRConfig::IsSystemOnBattery()
  523. {
  524. tenter("CSRConfig::IsSystemOnBattery");
  525. BOOL fRc = FALSE;
  526. SYSTEM_POWER_STATUS sps;
  527. if (FALSE == GetSystemPowerStatus(&sps))
  528. {
  529. trace(0, "! GetSystemPowerStatus : %ld", GetLastError());
  530. goto done;
  531. }
  532. fRc = (sps.ACLineStatus == 0);
  533. done:
  534. trace(0, "System %S battery", fRc ? L"on" : L"not on");
  535. tleave();
  536. return fRc;
  537. }
  538. DWORD CSRConfig::OpenFilter()
  539. {
  540. return SrCreateControlHandle(SR_OPTION_OVERLAPPED, &m_hFilter);
  541. }
  542. void CSRConfig::CloseFilter()
  543. {
  544. if (m_hFilter)
  545. {
  546. CloseHandle(m_hFilter);
  547. m_hFilter = NULL;
  548. }
  549. }