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.

673 lines
21 KiB

  1. /*++
  2. Copyright (c) 1996 Microsoft Corporation
  3. Abstract:
  4. This module provides functionality for ADs within spooler
  5. Author:
  6. Steve Wilson (NT) July 1997
  7. Revision History:
  8. --*/
  9. #include <precomp.h>
  10. #pragma hdrstop
  11. #include "dsprune.hxx"
  12. #include "clusspl.h"
  13. DWORD WINAPI DsUpdate(PDWORD pdwDelay);
  14. VOID ValidateDsProperties(PINIPRINTER pIniPrinter);
  15. HANDLE ghUpdateNow = NULL;
  16. extern DWORD dwUpdateFlag;
  17. extern "C" HANDLE ghDsUpdateThread;
  18. extern "C" DWORD gdwDsUpdateThreadId;
  19. HANDLE ghDsUpdateThread = NULL;
  20. DWORD gdwDsUpdateThreadId;
  21. BOOL gbInDomain;
  22. BOOL gdwLogDsEvents = LOG_ALL_EVENTS;
  23. DWORD
  24. SpawnDsUpdate(
  25. DWORD dwDelay
  26. )
  27. {
  28. DWORD dwError;
  29. PDWORD pdwDelay;
  30. SplInSem();
  31. if (!ghDsUpdateThread && !dwUpgradeFlag) {
  32. if (pdwDelay = (PDWORD) AllocSplMem(sizeof(DWORD))) {
  33. *pdwDelay = dwDelay;
  34. if(!(ghDsUpdateThread = CreateThread(NULL,
  35. 0,
  36. (LPTHREAD_START_ROUTINE) DsUpdate,
  37. (PVOID) pdwDelay,
  38. 0,
  39. &gdwDsUpdateThreadId))) {
  40. dwError = GetLastError();
  41. FreeSplMem(pdwDelay);
  42. } else {
  43. CloseHandle(ghDsUpdateThread);
  44. dwError = ERROR_SUCCESS;
  45. }
  46. } else {
  47. dwError = GetLastError();
  48. }
  49. } else {
  50. if (ghUpdateNow)
  51. SetEvent(ghUpdateNow);
  52. dwError = ERROR_BUSY;
  53. }
  54. return dwError;
  55. }
  56. BOOL
  57. DsUpdatePrinter(
  58. HANDLE h,
  59. PINIPRINTER pIniPrinter
  60. )
  61. {
  62. HANDLE hPrinter;
  63. PWSTR pszPrinterName = NULL;
  64. PDSUPDATEDATA pData = (PDSUPDATEDATA)h;
  65. DWORD dwAction;
  66. PRINTER_DEFAULTS Defaults;
  67. SplInSem();
  68. Defaults.pDatatype = NULL;
  69. Defaults.pDevMode = NULL;
  70. Defaults.DesiredAccess = PRINTER_ACCESS_ADMINISTER;
  71. // dwAction and DsKeyUpdateForeground are the foreground (client) thread's requested
  72. // action and state. DsKeyUpdate is the background (DsUpdate) thread's state
  73. // Foreground state always has priority over background, so sync up if needed.
  74. // When both the foreground and background actions are 0, then the publish state
  75. // is up to date.
  76. DBGMSG( DBG_EXEC, ("\nBACKGROUND UPDATE: Printer \"%ws\", dwAction = %x, DsKeyUpdate = %x, DsKeyUpdateForeground = %x, Attributes = %x\n",
  77. pIniPrinter->pName, pIniPrinter->dwAction, pIniPrinter->DsKeyUpdate, pIniPrinter->DsKeyUpdateForeground, pIniPrinter->Attributes ) );
  78. if (dwAction = pIniPrinter->dwAction) {
  79. pIniPrinter->dwAction = 0; // set to 0 so we know when client thread sets it
  80. pIniPrinter->DsKeyUpdate |= pIniPrinter->DsKeyUpdateForeground;
  81. pIniPrinter->DsKeyUpdateForeground = 0;
  82. //
  83. // Mask off possible conflicts in DS_KEY_PUBLISH, REPUBLISH, and UNPUBLISH actions
  84. //
  85. pIniPrinter->DsKeyUpdate &= ~(DS_KEY_PUBLISH | DS_KEY_REPUBLISH | DS_KEY_UNPUBLISH);
  86. if (dwAction == DSPRINT_PUBLISH) {
  87. pIniPrinter->DsKeyUpdate |= DS_KEY_PUBLISH;
  88. } else if (dwAction == DSPRINT_REPUBLISH) {
  89. pIniPrinter->DsKeyUpdate = DS_KEY_REPUBLISH;
  90. } else if (dwAction == DSPRINT_UNPUBLISH) {
  91. pIniPrinter->DsKeyUpdate = DS_KEY_UNPUBLISH;
  92. }
  93. } else {
  94. //
  95. // If DS_KEY_UPDATE_DRIVER is set by AddForm or DeleteForm foreground threads
  96. // in DsUpdateDriverKeys(). We have to copy that to pIniPrinter->DsKeyUpdate
  97. // even if dwAction is not set.
  98. //
  99. pIniPrinter->DsKeyUpdate |= (pIniPrinter->DsKeyUpdateForeground & DS_KEY_UPDATE_DRIVER);
  100. pIniPrinter->DsKeyUpdateForeground &= ~DS_KEY_UPDATE_DRIVER;
  101. }
  102. UpdatePrinterIni(pIniPrinter, UPDATE_DS_ONLY);
  103. if (pIniPrinter->DsKeyUpdate) {
  104. pData->bAllUpdated = FALSE;
  105. LeaveSplSem();
  106. // If this printer is pending deletion, delete by GUID because OpenPrinter
  107. // will fail.
  108. //
  109. // We check pIniPrinter->bDsPendingDeletion instead of
  110. // pIniPrinter->Status ~ PRINTER_PENDING_DELETION because PRINTER_PENDING_DELETION
  111. // is set in InternalDeletePrinter after it leaves Spooler CS. This gives the DS thread
  112. // a chance to check PRINTER_PENDING_DELETION flag before it is set.
  113. // The reason we cannot set PRINTER_PENDING_DELETION before we leave Spooler CS is because
  114. // we want OpenPrinter calls that come from PrinterDriverEvent to succeed.
  115. // See how InternalDeletePrinter sets the printer on PRINTER_NO_MORE_JOBS to reject incoming jobs
  116. // but accept OpenPrinter calls.
  117. //
  118. if (pIniPrinter->bDsPendingDeletion) {
  119. //
  120. // This will DECPRINTERREF to match DECPRINTERREF in SplDeletePrinter.
  121. // UnpublishByGUID won't call DeletePrinterCheck when it DECPRINTERREF.
  122. // RunForEachPrinter will do that.
  123. //
  124. UnpublishByGUID(pIniPrinter);
  125. } else {
  126. EnterSplSem();
  127. pszPrinterName = pszGetPrinterName( pIniPrinter, TRUE, NULL );
  128. LeaveSplSem();
  129. if (pszPrinterName) {
  130. if(LocalOpenPrinter(pszPrinterName, &hPrinter, &Defaults) == ROUTER_SUCCESS) {
  131. EnterSplSem();
  132. if( (pIniPrinter->DsKeyUpdate & DS_KEY_UPDATE_DRIVER) &&
  133. !(pIniPrinter->DsKeyUpdate & DS_KEY_REPUBLISH)) {
  134. //
  135. // We update the Registry with the Form data and then
  136. // set DS_KEY_PUBLISH. Eventually SetPrinterDS() would
  137. // update the DS.
  138. UpdateDsDriverKey(hPrinter);
  139. pIniPrinter->DsKeyUpdate &= ~DS_KEY_UPDATE_DRIVER;
  140. if(pIniPrinter->Attributes & PRINTER_ATTRIBUTE_PUBLISHED) {
  141. pIniPrinter->DsKeyUpdate |= DS_KEY_PUBLISH;
  142. }
  143. }
  144. if (pIniPrinter->DsKeyUpdate & DS_KEY_REPUBLISH) {
  145. SetPrinterDs(hPrinter, DSPRINT_UNPUBLISH, TRUE);
  146. // Unpublishing & republishing printer doesn't rewrite DS keys,
  147. // so on Republish, we should also rewrite DS keys so we know
  148. // everything is synched up
  149. SplDeletePrinterKey(hPrinter, SPLDS_DRIVER_KEY);
  150. SplDeletePrinterKey(hPrinter, SPLDS_SPOOLER_KEY);
  151. UpdateDsDriverKey(hPrinter);
  152. UpdateDsSpoolerKey(hPrinter, 0xffffffff);
  153. SetPrinterDs(hPrinter, DSPRINT_PUBLISH, TRUE);
  154. } else if (pIniPrinter->DsKeyUpdate & DS_KEY_UNPUBLISH) {
  155. SetPrinterDs(hPrinter, DSPRINT_UNPUBLISH, TRUE);
  156. } else if (pIniPrinter->DsKeyUpdate & DS_KEY_PUBLISH) {
  157. SetPrinterDs(hPrinter, DSPRINT_PUBLISH, TRUE);
  158. } else {
  159. //
  160. // If the printer is not published and DS_KEY_UPDATE_DRIVER
  161. // is set, then we will reach here and
  162. // DsKeyUpdate will have the DS_KEY_DRIVER set by
  163. // UpdateDsDriverKey(). So we just clear it here.
  164. //
  165. pIniPrinter->DsKeyUpdate = 0;
  166. UpdatePrinterIni(pIniPrinter, UPDATE_DS_ONLY);
  167. }
  168. LeaveSplSem();
  169. SplClosePrinter(hPrinter);
  170. }
  171. FreeSplStr(pszPrinterName);
  172. }
  173. }
  174. EnterSplSem();
  175. if (pIniPrinter->DsKeyUpdate) {
  176. pData->bSleep = TRUE; // Only sleep if the DS is down
  177. gdwLogDsEvents = LOG_INFO | LOG_SUCCESS; // Only report Warnings & Errors for first printer failures
  178. }
  179. }
  180. return TRUE;
  181. }
  182. BOOL
  183. DsUpdateSpooler(
  184. HANDLE h,
  185. PINISPOOLER pIniSpooler
  186. )
  187. {
  188. //
  189. // Only do this for local spoolers.
  190. //
  191. if (pIniSpooler->SpoolerFlags & SPL_TYPE_LOCAL) {
  192. RunForEachPrinter(pIniSpooler, h, DsUpdatePrinter);
  193. }
  194. return TRUE;
  195. }
  196. DWORD
  197. WINAPI
  198. DsUpdate(
  199. PDWORD pdwDelay
  200. )
  201. {
  202. DWORD dwSleep;
  203. DWORD dwError = ERROR_SUCCESS;
  204. HRESULT hr;
  205. DSUPDATEDATA Data;
  206. DWORD dwWaitTime = 0;
  207. SplOutSem();
  208. ghUpdateNow = CreateEvent((LPSECURITY_ATTRIBUTES) NULL, FALSE, FALSE, NULL);
  209. if (ghUpdateNow) {
  210. if (RegisterGPNotification(ghUpdateNow, TRUE)) {
  211. hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
  212. if (SUCCEEDED(hr)) {
  213. DBGMSG( DBG_EXEC, ("************** ENTER DSUPDATE\n" ) );
  214. //
  215. // Force initial sleep to be within 1 sec & 5 minutes
  216. //
  217. dwSleep = (*pdwDelay >= 1 && *pdwDelay < 300) ? *pdwDelay : 1;
  218. FreeSplMem(pdwDelay);
  219. if (dwSleep > 1) {
  220. Sleep(dwSleep*1000);
  221. }
  222. Data.dwSleepTime = dwSleep * 1000;
  223. gdwLogDsEvents = LOG_ALL_EVENTS;
  224. EnterSplSem();
  225. //
  226. // The logic of this loop changed from between Win2K to Whistler.
  227. // On Win2k, the DS background thread used to die if there were no DS
  228. // actions to be made.If the "Check published state" was changed,
  229. // Spooler couldn't reflect this change unless another DS action
  230. // created the DS thread.
  231. // On Whistler we keep it alive but sleeping.
  232. //
  233. do {
  234. Data.bAllUpdated = TRUE;
  235. Data.bSleep = FALSE;
  236. //
  237. // Run through and update each printer.
  238. //
  239. RunForEachSpooler(&Data, DsUpdateSpooler);
  240. //
  241. // If all printers are updated or the DS is not responding,
  242. // then put the DS thread to sleep.
  243. //
  244. if (Data.bAllUpdated || Data.bSleep) {
  245. dwWaitTime = GetDSSleepInterval(&Data);
  246. //
  247. // If the VerifyPublishedState Policy is set, then we need to verify
  248. // we're published based on the schedule specified by the policy.
  249. // However, if updating is failing, we should revert to the background
  250. // updating schedule rather than the "check published state" schedule.
  251. //
  252. LeaveSplSem();
  253. DBGMSG( DBG_EXEC, ("BACKGROUND UPDATE SLEEP: %d\n", dwWaitTime));
  254. dwError = WaitForSingleObject(ghUpdateNow, dwWaitTime);
  255. if (dwError == WAIT_FAILED) {
  256. //
  257. // There is one case when the DS thread can still die.
  258. // If this wait fails, we don't want the thread indefinitely spinning.
  259. //
  260. DBGMSG(DBG_WARNING, ("VerifyPublishedState Wait Failed: %d\n", GetLastError()));
  261. dwError = GetLastError();
  262. break;
  263. }
  264. EnterSplSem();
  265. //
  266. // If the "Check published state" policy is enabled,CheckPublishedPrinters will force the DS update
  267. // for published printers.If the object doesn't exist in DS, the printer is republished.(see GetPublishPoint)
  268. // The call returns 0 if there is the "check published state" policy
  269. // is disabled or it is enabled and there are no published printers.
  270. // We could actually break the loop and kill the thread in the case when we don't have published printers,
  271. // because we don't care for policy changes.
  272. //
  273. CheckPublishedPrinters();
  274. }
  275. } while (TRUE);
  276. LeaveSplSem();
  277. SplOutSem();
  278. CoUninitialize();
  279. } else {
  280. dwError = HRESULT_CODE(hr);
  281. }
  282. UnregisterGPNotification(ghUpdateNow);
  283. } else {
  284. dwError = GetLastError();
  285. }
  286. CloseHandle(ghUpdateNow);
  287. ghUpdateNow = NULL;
  288. } else {
  289. dwError = GetLastError();
  290. }
  291. DBGMSG(DBG_EXEC, ("************ LEAVE DSUPDATE\n"));
  292. ghDsUpdateThread = NULL;
  293. return dwError;
  294. }
  295. VOID
  296. ValidateDsProperties(
  297. PINIPRINTER pIniPrinter
  298. )
  299. {
  300. // Properties not generated by driver, spooler, or user should be checked here.
  301. // Currently, that means only the Server name. Note that we republish the object
  302. // if the server name changes, so the UNCName property gets fixed as well.
  303. DWORD dwError = ERROR_SUCCESS;
  304. BOOL dwAction = 0;
  305. HRESULT hr;
  306. SplInSem();
  307. PINISPOOLER pIniSpooler = pIniPrinter->pIniSpooler;
  308. WCHAR pData[INTERNET_MAX_HOST_NAME_LENGTH + 3];
  309. DWORD cbNeeded;
  310. DWORD dwType;
  311. struct hostent *pHostEnt;
  312. HKEY hKey = NULL;
  313. dwError = OpenPrinterKey(pIniPrinter, KEY_READ | KEY_WRITE, &hKey, SPLDS_SPOOLER_KEY, TRUE);
  314. if (dwError != ERROR_SUCCESS) {
  315. pIniPrinter->DsKeyUpdate = DS_KEY_REPUBLISH;
  316. return;
  317. }
  318. INCPRINTERREF(pIniPrinter);
  319. LeaveSplSem();
  320. // Set to publish by default. This will verify that the printer is published, but won't
  321. // write anything if there's nothing to update.
  322. pIniPrinter->DsKeyUpdate |= DS_KEY_PUBLISH;
  323. // Check Server Name
  324. //
  325. // If we were unable to find a DNS name for this machine, then gethostbyname failed.
  326. // In this case, let's just treat this attribute as being correct. If other attributes
  327. // cause us to update, we'll probably fail because the network is down or something. But
  328. // then again, we also might succeed.
  329. //
  330. if (pIniSpooler->pszFullMachineName) {
  331. cbNeeded = (INTERNET_MAX_HOST_NAME_LENGTH + 3)*sizeof *pData;
  332. dwType = REG_SZ;
  333. dwError = SplRegQueryValue( hKey,
  334. SPLDS_SERVER_NAME,
  335. &dwType,
  336. (PBYTE) pData,
  337. &cbNeeded,
  338. pIniSpooler);
  339. if (dwError != ERROR_SUCCESS || wcscmp((PWSTR) pData, pIniSpooler->pszFullMachineName)) {
  340. pIniPrinter->DsKeyUpdate = DS_KEY_REPUBLISH;
  341. goto error;
  342. }
  343. }
  344. // Check Short Server Name
  345. cbNeeded = (INTERNET_MAX_HOST_NAME_LENGTH + 3)*sizeof *pData;
  346. dwType = REG_SZ;
  347. dwError = SplRegQueryValue( hKey,
  348. SPLDS_SHORT_SERVER_NAME,
  349. &dwType,
  350. (PBYTE) pData,
  351. &cbNeeded,
  352. pIniSpooler);
  353. if (dwError != ERROR_SUCCESS || wcscmp((PWSTR) pData, pIniSpooler->pMachineName + 2)) {
  354. pIniPrinter->DsKeyUpdate = DS_KEY_REPUBLISH;
  355. goto error;
  356. }
  357. // Check Version Number
  358. cbNeeded = (INTERNET_MAX_HOST_NAME_LENGTH + 3)*sizeof *pData;
  359. dwType = REG_DWORD;
  360. dwError = SplRegQueryValue( hKey,
  361. SPLDS_VERSION_NUMBER,
  362. &dwType,
  363. (PBYTE) pData,
  364. &cbNeeded,
  365. pIniSpooler);
  366. if (dwError != ERROR_SUCCESS || *((PDWORD) pData) != DS_PRINTQUEUE_VERSION_WIN2000) {
  367. pIniPrinter->DsKeyUpdate = DS_KEY_REPUBLISH;
  368. goto error;
  369. }
  370. // Check Immortal flag
  371. cbNeeded = (INTERNET_MAX_HOST_NAME_LENGTH + 3)*sizeof *pData;
  372. dwType = REG_DWORD;
  373. dwError = SplRegQueryValue( hKey,
  374. SPLDS_FLAGS,
  375. &dwType,
  376. (PBYTE) pData,
  377. &cbNeeded,
  378. pIniSpooler);
  379. if (dwError != ERROR_SUCCESS) {
  380. pIniPrinter->DsKeyUpdate = DS_KEY_REPUBLISH;
  381. goto error;
  382. } else if (*((PDWORD) pData) != (DWORD) pIniSpooler->bImmortal) {
  383. dwError = SplRegSetValue( hKey,
  384. SPLDS_FLAGS,
  385. dwType,
  386. (PBYTE) &pIniSpooler->bImmortal,
  387. sizeof pIniSpooler->bImmortal,
  388. pIniSpooler);
  389. if (dwError == ERROR_SUCCESS) {
  390. pIniPrinter->DsKeyUpdate |= DS_KEY_SPOOLER;
  391. }
  392. else {
  393. pIniPrinter->DsKeyUpdate = DS_KEY_REPUBLISH;
  394. goto error;
  395. }
  396. }
  397. error:
  398. if (hKey)
  399. SplRegCloseKey(hKey, pIniSpooler);
  400. EnterSplSem();
  401. DECPRINTERREF(pIniPrinter);
  402. SplInSem();
  403. return;
  404. }
  405. extern "C" VOID
  406. InitializeDS(
  407. PINISPOOLER pIniSpooler
  408. )
  409. {
  410. PINIPRINTER pIniPrinter = NULL;
  411. PDSROLE_PRIMARY_DOMAIN_INFO_BASIC pDsRole = NULL;
  412. DWORD dwError = ERROR_SUCCESS;
  413. SYSTEMTIME SystemTime;
  414. DWORD dwDelay = 0;
  415. // Verify that we're in a domain
  416. dwError = DsRoleGetPrimaryDomainInformation(NULL, DsRolePrimaryDomainInfoBasic, (PBYTE *) &pDsRole);
  417. if (pDsRole) {
  418. gbInDomain = (dwError == ERROR_SUCCESS &&
  419. pDsRole->MachineRole != DsRole_RoleStandaloneServer &&
  420. pDsRole->MachineRole != DsRole_RoleStandaloneWorkstation);
  421. DsRoleFreeMemory((PVOID) pDsRole);
  422. } else {
  423. gbInDomain = FALSE;
  424. }
  425. if (gbInDomain) {
  426. // Check if we need to update the ds
  427. EnterSplSem();
  428. // Get spooler policies
  429. pIniSpooler->bImmortal = ImmortalPolicy();
  430. BOOL bPublishProhibited = PrinterPublishProhibited();
  431. // Run through all the printers and see if any need updating
  432. for (pIniPrinter = pIniSpooler->pIniPrinter ; pIniPrinter ; pIniPrinter = pIniPrinter->pNext) {
  433. // PublishProhibited not only prohibits new publishing, but also
  434. // removes currently published printers
  435. if (bPublishProhibited) {
  436. pIniPrinter->Attributes &= ~PRINTER_ATTRIBUTE_PUBLISHED;
  437. }
  438. if (pIniPrinter->Attributes & PRINTER_ATTRIBUTE_PUBLISHED) {
  439. // Verify properties not changed by driver or spooler
  440. ValidateDsProperties(pIniPrinter);
  441. } else if (pIniPrinter->pszObjectGUID) { // State is unpublished, but we haven't deleted
  442. // the PrintQueue from the DS yet.
  443. pIniPrinter->DsKeyUpdate = DS_KEY_UNPUBLISH;
  444. } else {
  445. pIniPrinter->DsKeyUpdate = 0;
  446. }
  447. if (!dwDelay && (pIniPrinter->DsKeyUpdate || pIniPrinter->DsKeyUpdateForeground)) {
  448. // Initially sleep a random amount of time
  449. // This keeps network traffic down if there's been a power outage
  450. GetSystemTime(&SystemTime);
  451. srand((unsigned) SystemTime.wMilliseconds);
  452. // 100 different sleep times from 1 sec - 100 sec
  453. // Typical time to publish printer is 5 seconds. Updates and deletes are just a couple seconds
  454. dwDelay = (rand()%100) + 1;
  455. }
  456. }
  457. if (dwDelay)
  458. SpawnDsUpdate(dwDelay);
  459. LeaveSplSem();
  460. if (ThisMachineIsADC()) {
  461. GetSystemTime(&SystemTime);
  462. srand((unsigned) SystemTime.wMilliseconds);
  463. DWORD dwPruningInterval = PruningInterval();
  464. if (dwPruningInterval == INFINITE)
  465. dwPruningInterval = DEFAULT_PRUNING_INTERVAL;
  466. if (dwPruningInterval)
  467. SpawnDsPrune(rand()%dwPruningInterval);
  468. else
  469. SpawnDsPrune(0);
  470. }
  471. }
  472. ServerThreadPolicy(gbInDomain);
  473. return;
  474. }
  475. BOOL
  476. DsUpdateDriverKeys(
  477. HANDLE h,
  478. PINIPRINTER pIniPrinter
  479. )
  480. {
  481. //
  482. // For now, we need this only when a new user defined form is added/deleted.
  483. // For this case, UpdateDsDriverKey is not called before call SetPrinterDS
  484. // We set all pIniPrinters->DsKeyUpdateForeground with DS_KEY_UPDATE_DRIVER so that
  485. // on DsUpdatePrinter we know that UpdateDsDriverKey must be called before
  486. // we try to publish the printer.
  487. //
  488. SplInSem();
  489. pIniPrinter->DsKeyUpdateForeground |= DS_KEY_UPDATE_DRIVER;
  490. return TRUE;
  491. }
  492. BOOL
  493. DsUpdateAllDriverKeys(
  494. HANDLE h,
  495. PINISPOOLER pIniSpooler
  496. )
  497. {
  498. HANDLE hToken = NULL;
  499. //
  500. // Only do this for local spoolers.
  501. //
  502. if (pIniSpooler->SpoolerFlags & SPL_TYPE_LOCAL) {
  503. RunForEachPrinter(pIniSpooler, h, DsUpdateDriverKeys);
  504. }
  505. hToken = RevertToPrinterSelf(); // All DS accesses are done by LocalSystem account
  506. SpawnDsUpdate(1);
  507. if (hToken)
  508. ImpersonatePrinterClient(hToken);
  509. return TRUE;
  510. }