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.

709 lines
18 KiB

  1. //*****************************************************************************
  2. //
  3. // WBEMTSS.CPP
  4. //
  5. // Copyright (c) 1996-1999, Microsoft Corporation, All rights reserved
  6. //
  7. // This file implements the classes used by the Timer Subsystem.
  8. //
  9. // Classes implemented:
  10. //
  11. // 26-Nov-96 raymcc Draft
  12. // 28-Dec-96 a-richm Alpha PDK Release
  13. // 12-Apr-97 a-levn Extensive changes
  14. //
  15. //*****************************************************************************
  16. #include "precomp.h"
  17. #include <stdio.h>
  18. #include "ess.h"
  19. #include "wbemtss.h"
  20. CCritSec CWBEMTimerInstruction::mstatic_cs;
  21. CWBEMTimerInstruction::CWBEMTimerInstruction()
  22. : m_lRefCount(1), m_bSkipIfPassed(FALSE), m_pNamespace(NULL),
  23. m_pGenerator(NULL), m_bRemoved(FALSE)
  24. {
  25. }
  26. CWBEMTimerInstruction::~CWBEMTimerInstruction()
  27. {
  28. if(m_pNamespace) m_pNamespace->Release();
  29. }
  30. CWbemTime CWBEMTimerInstruction::GetFirstFiringTime() const
  31. {
  32. CWbemTime FirstTime = ComputeFirstFiringTime();
  33. if(FirstTime.IsZero())
  34. {
  35. // Instruction says: fire now
  36. // ==========================
  37. FirstTime = CWbemTime::GetCurrentTime();
  38. }
  39. else if(SkipIfPassed())
  40. {
  41. FirstTime = SkipMissed(FirstTime);
  42. }
  43. return FirstTime;
  44. }
  45. CWbemTime CWBEMTimerInstruction::GetStartingFiringTime(CWbemTime OldTime) const
  46. {
  47. //
  48. // If SkipIfPassed is set, we need to set the starting firing time to the
  49. // next one after current
  50. //
  51. if(SkipIfPassed())
  52. return SkipMissed(OldTime);
  53. //
  54. // Otherwise, just leave it be --- the firing logic will figure out how many
  55. // we must have missed
  56. //
  57. return OldTime;
  58. }
  59. CWbemTime CWBEMTimerInstruction::SkipMissed(IN CWbemTime OldTime,
  60. OUT long* plMissedFiringCount) const
  61. {
  62. long lMissedCount = 0;
  63. CWbemTime Firing = OldTime;
  64. CWbemTime CurrentTime = CWbemTime::GetCurrentTime();
  65. while(Firing < CurrentTime)
  66. {
  67. Firing = ComputeNextFiringTime(Firing);
  68. lMissedCount++;
  69. }
  70. if(SkipIfPassed())
  71. lMissedCount = 0;
  72. if(plMissedFiringCount)
  73. *plMissedFiringCount = lMissedCount;
  74. return Firing;
  75. }
  76. CWbemTime CWBEMTimerInstruction::GetNextFiringTime(IN CWbemTime LastFiringTime,
  77. OUT long* plMissedFiringCount) const
  78. {
  79. CWbemTime NextFiring = ComputeNextFiringTime(LastFiringTime);
  80. NextFiring = SkipMissed(NextFiring, plMissedFiringCount);
  81. return NextFiring;
  82. }
  83. HRESULT CWBEMTimerInstruction::CheckObject(IWbemClassObject* pInst)
  84. {
  85. HRESULT hres;
  86. VARIANT v;
  87. VariantInit(&v);
  88. CClearMe cm(&v);
  89. hres = pInst->Get(L"SkipIfPassed", 0, &v, NULL, NULL);
  90. if(FAILED(hres))
  91. return hres;
  92. if(V_VT(&v) != VT_BOOL)
  93. return WBEM_E_INVALID_OBJECT;
  94. hres = pInst->Get(L"__CLASS", 0, &v, NULL, NULL);
  95. if(FAILED(hres))
  96. return hres;
  97. if(V_VT(&v) != VT_BSTR)
  98. return WBEM_E_INVALID_OBJECT;
  99. if(!_wcsicmp(V_BSTR(&v),
  100. CAbsoluteTimerInstruction::GetWbemClassName()))
  101. {
  102. return CAbsoluteTimerInstruction::CheckObject(pInst);
  103. }
  104. else if(!_wcsicmp(V_BSTR(&v),
  105. CIntervalTimerInstruction::GetWbemClassName()))
  106. {
  107. return CIntervalTimerInstruction::CheckObject(pInst);
  108. }
  109. else if(!_wcsicmp(V_BSTR(&v),
  110. CRecurringTimerInstruction::GetWbemClassName()))
  111. {
  112. return CRecurringTimerInstruction::CheckObject(pInst);
  113. }
  114. else
  115. {
  116. return WBEM_E_INVALID_CLASS;
  117. }
  118. }
  119. HRESULT CWBEMTimerInstruction::LoadFromWbemObject(
  120. LPCWSTR wszNamespace,
  121. ADDREF IWbemServices* pNamespace,
  122. CWinMgmtTimerGenerator* pGenerator,
  123. IN IWbemClassObject* pObject,
  124. OUT RELEASE_ME CWBEMTimerInstruction*& pInstruction)
  125. {
  126. HRESULT hres;
  127. VARIANT v;
  128. VariantInit(&v);
  129. CClearMe cm(&v);
  130. hres = pObject->Get(L"__CLASS", 0, &v, NULL, NULL);
  131. if(FAILED(hres)) return hres;
  132. if(V_VT(&v) != VT_BSTR) return WBEM_E_INVALID_OBJECT;
  133. if(!_wcsicmp(V_BSTR(&v), CAbsoluteTimerInstruction::GetWbemClassName()))
  134. {
  135. pInstruction = _new CAbsoluteTimerInstruction;
  136. }
  137. else if(!_wcsicmp(V_BSTR(&v), CIntervalTimerInstruction::GetWbemClassName()))
  138. {
  139. pInstruction = _new CIntervalTimerInstruction;
  140. }
  141. else if(!_wcsicmp(V_BSTR(&v),CRecurringTimerInstruction::GetWbemClassName()))
  142. {
  143. pInstruction = _new CRecurringTimerInstruction;
  144. }
  145. else
  146. {
  147. return WBEM_E_INVALID_CLASS;
  148. }
  149. if(pInstruction == NULL)
  150. return WBEM_E_OUT_OF_MEMORY;
  151. pInstruction->m_wsNamespace = wszNamespace;
  152. pInstruction->m_pGenerator = pGenerator;
  153. pInstruction->m_pNamespace = pNamespace;
  154. if(pNamespace) pNamespace->AddRef();
  155. VariantClear(&v);
  156. hres = pObject->Get(L"TimerId", 0, &v, NULL, NULL);
  157. if(FAILED(hres)) return hres;
  158. if(V_VT(&v) != VT_BSTR) return WBEM_E_INVALID_OBJECT;
  159. pInstruction->m_wsTimerId = V_BSTR(&v);
  160. VariantClear(&v);
  161. hres = pObject->Get(L"SkipIfPassed", 0, &v, NULL, NULL);
  162. if(FAILED(hres)) return hres;
  163. if(V_VT(&v) != VT_BOOL) return WBEM_E_INVALID_OBJECT;
  164. pInstruction->m_bSkipIfPassed = (V_BOOL(&v) != VARIANT_FALSE);
  165. return pInstruction->LoadFromWbemObject(pObject);
  166. }
  167. HRESULT CWBEMTimerInstruction::Fire(long lNumTimes, CWbemTime NextFiringTime)
  168. {
  169. // Notify the sink
  170. // ===============
  171. HRESULT hres = m_pGenerator->FireInstruction(this, lNumTimes);
  172. return hres;
  173. }
  174. HRESULT CWBEMTimerInstruction::StoreNextFiring(CWbemTime When)
  175. {
  176. SCODE sc;
  177. // Create an instance of the NextFiring class
  178. // ==========================================
  179. IWbemClassObject* pClass = NULL;
  180. sc = m_pNamespace->GetObject(L"__TimerNextFiring", 0, NULL, &pClass, NULL);
  181. if(FAILED(sc)) return sc;
  182. CReleaseMe rm0(pClass);
  183. IWbemClassObject* pInstance = NULL;
  184. sc = pClass->SpawnInstance(0, &pInstance);
  185. if(FAILED(sc)) return sc;
  186. CReleaseMe rm1(pInstance);
  187. // Set the timer id
  188. // ================
  189. VARIANT varID;
  190. V_VT(&varID) = VT_BSTR;
  191. V_BSTR(&varID) = SysAllocString(m_wsTimerId);
  192. if(V_BSTR(&varID) == NULL)
  193. return WBEM_E_OUT_OF_MEMORY;
  194. sc = pInstance->Put(L"TimerID", 0, &varID, 0);
  195. VariantClear(&varID);
  196. if(FAILED(sc))
  197. return sc;
  198. // Set the next firing time
  199. // ========================
  200. VARIANT varNext;
  201. V_VT(&varNext) = VT_BSTR;
  202. V_BSTR(&varNext) = SysAllocStringLen(NULL, 100);
  203. if(V_BSTR(&varNext) == NULL)
  204. return WBEM_E_OUT_OF_MEMORY;
  205. swprintf(V_BSTR(&varNext), L"%I64d", When.Get100nss());
  206. sc = pInstance->Put(L"NextEvent64BitTime", 0, &varNext, 0);
  207. VariantClear(&varNext);
  208. if(FAILED(sc))
  209. return sc;
  210. //
  211. // Save the instance in the repository using an internal API
  212. //
  213. IWbemInternalServices* pIntServ = NULL;
  214. sc = m_pNamespace->QueryInterface(IID_IWbemInternalServices,
  215. (void**)&pIntServ);
  216. if(FAILED(sc))
  217. {
  218. ERRORTRACE((LOG_ESS, "Unable to aquire internal services from core: "
  219. "0x%X\n", sc));
  220. return sc;
  221. }
  222. CReleaseMe rm2(pIntServ);
  223. sc = pIntServ->InternalPutInstance(pInstance);
  224. return sc;
  225. }
  226. HRESULT CWBEMTimerInstruction::MarkForRemoval()
  227. {
  228. CInCritSec incs(&mstatic_cs);
  229. m_bRemoved = TRUE;
  230. LPWSTR wszPath = _new WCHAR[wcslen(m_wsTimerId)+100];
  231. if(wszPath == NULL)
  232. return WBEM_E_OUT_OF_MEMORY;
  233. swprintf(wszPath, L"__TimerNextFiring=\"%S\"", (LPCWSTR)m_wsTimerId);
  234. HRESULT hres = m_pNamespace->DeleteInstance(wszPath, 0, NULL, NULL);
  235. delete [] wszPath;
  236. return hres;
  237. }
  238. CWbemTime CAbsoluteTimerInstruction::ComputeFirstFiringTime() const
  239. {
  240. return m_When;
  241. }
  242. CWbemTime CAbsoluteTimerInstruction::ComputeNextFiringTime(
  243. CWbemTime LastFiringTime) const
  244. {
  245. return CWbemTime::GetInfinity();
  246. }
  247. // static
  248. HRESULT CAbsoluteTimerInstruction::CheckObject(IWbemClassObject* pInst)
  249. {
  250. //
  251. // Check if EventDateTime is actually a date, and not an interval
  252. //
  253. VARIANT v;
  254. VariantInit(&v);
  255. CClearMe cm(&v);
  256. HRESULT hres = pInst->Get(L"EventDateTime", 0, &v, NULL, NULL);
  257. if(FAILED(hres)) return hres;
  258. if(V_VT(&v) != VT_BSTR)
  259. return WBEM_E_ILLEGAL_NULL;
  260. //
  261. // Check for * --- invalid
  262. //
  263. if(wcschr(V_BSTR(&v), L'*'))
  264. return WBEM_E_INVALID_PROPERTY;
  265. //
  266. // Check for ':' --- interval --- invalid
  267. //
  268. if(V_BSTR(&v)[21] == L':')
  269. return WBEM_E_INVALID_PROPERTY_TYPE;
  270. return WBEM_S_NO_ERROR;
  271. }
  272. HRESULT CAbsoluteTimerInstruction::LoadFromWbemObject(IWbemClassObject* pObject)
  273. {
  274. VARIANT v;
  275. VariantInit(&v);
  276. HRESULT hres = pObject->Get(L"EventDateTime", 0, &v, NULL, NULL);
  277. if(FAILED(hres)) return hres;
  278. if(V_VT(&v) != VT_BSTR) return WBEM_E_INVALID_OBJECT;
  279. BOOL bRes = m_When.SetDMTF(V_BSTR(&v));
  280. VariantClear(&v);
  281. return (bRes ? WBEM_S_NO_ERROR : WBEM_E_INVALID_OBJECT);
  282. }
  283. HRESULT CAbsoluteTimerInstruction::Fire(long lNumTimes,
  284. CWbemTime NextFiringTime)
  285. {
  286. // Fire it
  287. // =======
  288. HRESULT hres = CWBEMTimerInstruction::Fire(lNumTimes, NextFiringTime);
  289. {
  290. CInCritSec incs(&mstatic_cs);
  291. if(!m_bRemoved)
  292. {
  293. // Save the next firing time in WinMgmt
  294. // ====================================
  295. StoreNextFiring(NextFiringTime);
  296. }
  297. }
  298. return hres;
  299. }
  300. CWbemTime CIntervalTimerInstruction::ComputeFirstFiringTime() const
  301. {
  302. if(!m_Start.IsZero())
  303. return m_Start;
  304. else
  305. {
  306. // Indicate that current time should be used
  307. return CWbemTime::GetCurrentTime() + m_Interval;
  308. }
  309. }
  310. CWbemTime CIntervalTimerInstruction::ComputeNextFiringTime(
  311. CWbemTime LastFiringTime) const
  312. {
  313. if(m_Interval.IsZero())
  314. {
  315. return CWbemTime::GetInfinity();
  316. }
  317. return LastFiringTime + m_Interval;
  318. }
  319. HRESULT CIntervalTimerInstruction::LoadFromWbemObject(IWbemClassObject* pObject)
  320. {
  321. VARIANT v;
  322. VariantInit(&v);
  323. HRESULT hres = pObject->Get(L"IntervalBetweenEvents", 0, &v, NULL, NULL);
  324. if(FAILED(hres)) return hres;
  325. if(V_VT(&v) != VT_I4 || V_I4(&v) == 0)
  326. return WBEM_E_INVALID_OBJECT;
  327. m_Interval.SetMilliseconds(V_I4(&v));
  328. return S_OK;
  329. }
  330. CWinMgmtTimerGenerator::CWinMgmtTimerGenerator(CEss* pEss) : CTimerGenerator(),
  331. m_pEss(pEss)
  332. {
  333. }
  334. HRESULT CWinMgmtTimerGenerator::LoadTimerEventObject(
  335. LPCWSTR wszNamespace,
  336. IWbemServices* pNamespace,
  337. IWbemClassObject * pInstObject,
  338. IWbemClassObject * pNextFiring)
  339. {
  340. CWBEMTimerInstruction* pInst;
  341. CWbemTime When;
  342. HRESULT hres;
  343. hres = CWBEMTimerInstruction::LoadFromWbemObject(wszNamespace, pNamespace,
  344. this, pInstObject, pInst);
  345. if(FAILED(hres)) return hres;
  346. if(pNextFiring)
  347. {
  348. VARIANT v;
  349. VariantInit(&v);
  350. pNextFiring->Get(L"NextEvent64BitTime", 0 ,&v, NULL, NULL);
  351. if(V_VT(&v) != VT_BSTR)
  352. {
  353. delete pInst;
  354. return WBEM_E_FAILED;
  355. }
  356. __int64 i64;
  357. swscanf(V_BSTR(&v), L"%I64d", &i64);
  358. VariantClear(&v);
  359. When.Set100nss(i64);
  360. //
  361. // Ask the instruction to determine what the real first firing time
  362. // should be, given the fact what it was planned to be before we shut
  363. // down
  364. //
  365. When = pInst->GetStartingFiringTime(When);
  366. }
  367. else
  368. {
  369. When = CWbemTime::GetZero();
  370. }
  371. // Remove old
  372. // ==========
  373. VARIANT vID;
  374. VariantInit(&vID);
  375. hres = pInstObject->Get(TIMER_ID_PROPNAME, 0, &vID, NULL, NULL);
  376. if(FAILED(hres)) return hres;
  377. Remove(wszNamespace, V_BSTR(&vID));
  378. VariantClear(&vID);
  379. hres = Set(pInst, When);
  380. pInst->Release();
  381. return hres;
  382. }
  383. HRESULT CWinMgmtTimerGenerator::CheckTimerInstruction(IWbemClassObject* pInst)
  384. {
  385. return CWBEMTimerInstruction::CheckObject(pInst);
  386. }
  387. HRESULT CWinMgmtTimerGenerator::LoadTimerEventObject(
  388. LPCWSTR wszNamespace,
  389. IWbemClassObject * pInstObject)
  390. {
  391. IWbemServices* pNamespace;
  392. HRESULT hres = m_pEss->GetNamespacePointer(wszNamespace,TRUE,&pNamespace);
  393. if(FAILED(hres))
  394. return hres;
  395. hres = LoadTimerEventObject(wszNamespace, pNamespace, pInstObject);
  396. pNamespace->Release();
  397. return hres;
  398. }
  399. SCODE CWinMgmtTimerGenerator::LoadTimerEventQueue(LPCWSTR wszNamespace,
  400. IWbemServices* pNamespace)
  401. {
  402. SCODE sc;
  403. ULONG uRet;
  404. WCHAR pwcsCount[4] = L"";
  405. int iInstanceCount = 1;
  406. IEnumWbemClassObject* pEnum;
  407. sc = pNamespace->CreateInstanceEnum(L"__TimerInstruction",
  408. WBEM_FLAG_DEEP, NULL,
  409. &pEnum);
  410. if(FAILED(sc)) return sc;
  411. while (1)
  412. {
  413. IWbemClassObject* pInstruction;
  414. sc = pEnum->Next( WBEM_INFINITE, 1, &pInstruction, &uRet);
  415. if(FAILED(sc)) return sc;
  416. if(sc != WBEM_S_NO_ERROR)
  417. break;
  418. // Get the next firing object
  419. // ==========================
  420. VARIANT vID;
  421. VariantInit(&vID);
  422. sc = pInstruction->Get(L"TimerID", 0, &vID, NULL, NULL);
  423. if(FAILED(sc)) return sc;
  424. LPWSTR wszPath = _new WCHAR[wcslen(V_BSTR(&vID)) + 100];
  425. if(wszPath == NULL)
  426. return WBEM_E_OUT_OF_MEMORY;
  427. swprintf(wszPath, L"__TimerNextFiring.TimerID=\"%s\"", V_BSTR(&vID));
  428. VariantClear(&vID);
  429. IWbemClassObject* pNextFiring = 0;
  430. if(FAILED(pNamespace->GetObject(wszPath, 0, NULL, &pNextFiring, NULL)))
  431. {
  432. pNextFiring = NULL;
  433. }
  434. delete [] wszPath;
  435. LoadTimerEventObject(wszNamespace, pNamespace, pInstruction,
  436. pNextFiring);
  437. if(pNextFiring) pNextFiring->Release();
  438. pInstruction->Release();
  439. }
  440. pEnum->Release();
  441. return WBEM_S_NO_ERROR;
  442. }
  443. HRESULT CWinMgmtTimerGenerator::Remove(LPCWSTR wszNamespace, LPCWSTR wszId)
  444. {
  445. CIdTest test(wszNamespace, wszId);
  446. return CTimerGenerator::Remove(&test);
  447. }
  448. BOOL CWinMgmtTimerGenerator::CIdTest::operator()(CTimerInstruction* pInst)
  449. {
  450. if(pInst->GetInstructionType() != INSTTYPE_WBEM)
  451. return FALSE;
  452. CWBEMTimerInstruction* pWbemInst = (CWBEMTimerInstruction*)pInst;
  453. if(wcscmp(m_wszId, pWbemInst->GetTimerId()))
  454. return FALSE;
  455. if(_wcsicmp(m_wszNamespace, pWbemInst->GetNamespace()))
  456. return FALSE;
  457. return TRUE;
  458. }
  459. HRESULT CWinMgmtTimerGenerator::Remove(LPCWSTR wszNamespace)
  460. {
  461. CNamespaceTest test(wszNamespace);
  462. return CTimerGenerator::Remove(&test);
  463. }
  464. BOOL CWinMgmtTimerGenerator::CNamespaceTest::operator()(
  465. CTimerInstruction* pInst)
  466. {
  467. if(pInst->GetInstructionType() != INSTTYPE_WBEM)
  468. return FALSE;
  469. CWBEMTimerInstruction* pWbemInst = (CWBEMTimerInstruction*)pInst;
  470. if(_wcsicmp(m_wszNamespace, pWbemInst->GetNamespace()))
  471. return FALSE;
  472. return TRUE;
  473. }
  474. HRESULT CWinMgmtTimerGenerator::FireInstruction(
  475. CWBEMTimerInstruction* pInst, long lNumFirings)
  476. {
  477. HRESULT hres;
  478. CEventRepresentation Event;
  479. Event.type = e_EventTypeTimer;
  480. Event.wsz1 = (LPWSTR)pInst->GetNamespace();
  481. Event.wsz2 = (LPWSTR)pInst->GetTimerId();
  482. Event.wsz3 = NULL;
  483. Event.dw1 = (DWORD)lNumFirings;
  484. // Create the actual IWbemClassObject representing the event
  485. // ========================================================
  486. Event.nObjects = 1;
  487. Event.apObjects = _new IWbemClassObject*[1];
  488. if(Event.apObjects == NULL)
  489. return WBEM_E_OUT_OF_MEMORY;
  490. CVectorDeleteMe<IWbemClassObject*> vdm1(Event.apObjects);
  491. IWbemClassObject* pClass = // internal
  492. CEventRepresentation::GetEventClass(m_pEss, e_EventTypeTimer);
  493. if(pClass == NULL)
  494. return WBEM_E_OUT_OF_MEMORY;
  495. hres = pClass->SpawnInstance(0, &(Event.apObjects[0]));
  496. if(FAILED(hres))
  497. return hres;
  498. CReleaseMe rm1(Event.apObjects[0]);
  499. VARIANT v;
  500. VariantInit(&v);
  501. V_VT(&v) = VT_BSTR;
  502. V_BSTR(&v) = SysAllocString(pInst->GetTimerId());
  503. if(V_BSTR(&v) == NULL)
  504. return WBEM_E_OUT_OF_MEMORY;
  505. hres = Event.apObjects[0]->Put(L"TimerId", 0, &v, 0);
  506. VariantClear(&v);
  507. if(FAILED(hres))
  508. return hres;
  509. V_VT(&v) = VT_I4;
  510. V_I4(&v) = lNumFirings;
  511. hres = Event.apObjects[0]->Put(L"NumFirings", 0, &v, 0);
  512. VariantClear(&v);
  513. if(FAILED(hres))
  514. return hres;
  515. // Decorate it
  516. // ===========
  517. hres = m_pEss->DecorateObject(Event.apObjects[0], pInst->GetNamespace());
  518. if(FAILED(hres))
  519. return hres;
  520. // Give it to the ESS
  521. // ==================
  522. hres = m_pEss->ProcessEvent(Event, 0);
  523. // ignore error
  524. return WBEM_S_NO_ERROR;
  525. }
  526. HRESULT CWinMgmtTimerGenerator::Shutdown()
  527. {
  528. // Get the base class to shut everything down
  529. // ==========================================
  530. HRESULT hres = CTimerGenerator::Shutdown();
  531. hres = SaveAndRemove((LONG)FALSE);
  532. return hres;
  533. }
  534. HRESULT CWinMgmtTimerGenerator::SaveAndRemove(LONG lIsSystemShutDown)
  535. {
  536. // Store next firing times for all the instructions in the list
  537. // ============================================================
  538. CTimerInstruction* pInst;
  539. CWbemTime NextTime;
  540. while(m_Queue.Dequeue(pInst, NextTime) == S_OK)
  541. {
  542. // Convert to the right class
  543. // ==========================
  544. if(pInst->GetInstructionType() == INSTTYPE_WBEM)
  545. {
  546. CWBEMTimerInstruction* pWbemInst = (CWBEMTimerInstruction*)pInst;
  547. pWbemInst->StoreNextFiring(NextTime);
  548. }
  549. if (!lIsSystemShutDown)
  550. {
  551. pInst->Release();
  552. }
  553. }
  554. return S_OK;
  555. }
  556. void CWinMgmtTimerGenerator::DumpStatistics(FILE* f, long lFlags)
  557. {
  558. fprintf(f, "%d timer instructions in queue\n",
  559. m_Queue.GetNumInstructions());
  560. }