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.

1387 lines
32 KiB

  1. //+--------------------------------------------------------------------------
  2. //
  3. // Microsoft Windows
  4. // Copyright (C) Microsoft Corporation, 1996 - 1999
  5. //
  6. // File: view.cpp
  7. //
  8. // Contents: Cert Server Database interface implementation
  9. //
  10. //---------------------------------------------------------------------------
  11. #include <pch.cpp>
  12. #pragma hdrstop
  13. #include "csprop.h"
  14. #include "column.h"
  15. #include "enum.h"
  16. #include "db.h"
  17. #include "row.h"
  18. #include "view.h"
  19. #if DBG_CERTSRV
  20. #define THREAD_TIMEOUT INFINITE
  21. #else
  22. #define THREAD_TIMEOUT INFINITE // used to be 10000ms=10 seconds
  23. #endif
  24. #if DBG
  25. LONG g_cCertDBResultRow;
  26. LONG g_cCertDBResultRowTotal;
  27. #endif
  28. #ifdef DBG_CERTSRV_DEBUG_PRINT
  29. DWORD s_ssDB = DBG_SS_CERTDBI;
  30. #endif
  31. #if DBG_CERTSRV
  32. VOID
  33. dbDumpFileTime(
  34. IN DWORD dwSubSystemId,
  35. IN CHAR const *pszPrefix,
  36. IN FILETIME const *pft);
  37. #endif
  38. typedef struct
  39. {
  40. CERTSESSION *pcs;
  41. ICertDB *pdb;
  42. DWORD ccvr;
  43. CERTVIEWRESTRICTION const *acvr;
  44. DWORD ccolOut;
  45. DWORD const *acolOut;
  46. } THREAD_PARAM_OPEN;
  47. typedef struct
  48. {
  49. ULONG celt;
  50. CERTDBRESULTROW *rgelt;
  51. ULONG *pceltFetched;
  52. } THREAD_PARAM_NEXT;
  53. CEnumCERTDBRESULTROW::CEnumCERTDBRESULTROW(
  54. IN BOOL fThreading) :
  55. m_fThreading(fThreading)
  56. {
  57. DBGCODE(InterlockedIncrement(&g_cCertDBResultRow));
  58. DBGCODE(InterlockedIncrement(&g_cCertDBResultRowTotal));
  59. m_pdb = NULL;
  60. m_pcs = NULL;
  61. m_aRestriction = NULL;
  62. m_acolOut = NULL;
  63. m_cRef = 1;
  64. m_ieltMax = 0;
  65. m_hWorkThread = NULL;
  66. m_hViewEvent = NULL;
  67. m_hrThread = S_OK;
  68. m_hReturnEvent = NULL;
  69. m_enumViewCall = ENUMTHREAD_END;
  70. m_pThreadParam = NULL;
  71. //#if DBG_CERTSRV
  72. m_dwCallerThreadId = 0;
  73. //#endif
  74. }
  75. CEnumCERTDBRESULTROW::~CEnumCERTDBRESULTROW()
  76. {
  77. HRESULT hr;
  78. DBGCODE(InterlockedDecrement(&g_cCertDBResultRow));
  79. _Cleanup();
  80. }
  81. // The following is the worker thread procedure to handle calls.
  82. // All view calls will be made in this thread.
  83. DWORD WINAPI
  84. CEnumCERTDBRESULTROW::_ViewWorkThreadFunctionHelper(
  85. LPVOID lpParam)
  86. {
  87. HRESULT hr = S_OK;
  88. DBGPRINT((s_ssDB, "worker thread (tid=%d) is created.\n", GetCurrentThreadId()));
  89. if (NULL == lpParam)
  90. {
  91. hr = E_POINTER;
  92. _PrintError(hr, "null pointer, kill worker thread unexpectedly");
  93. ExitThread(hr);
  94. }
  95. // call real one
  96. return (((CEnumCERTDBRESULTROW*)lpParam)->_ViewWorkThreadFunction());
  97. }
  98. DWORD
  99. CEnumCERTDBRESULTROW::_ViewWorkThreadFunction(VOID)
  100. {
  101. HRESULT hr = S_OK;
  102. while (TRUE)
  103. {
  104. if (WAIT_OBJECT_0 == WaitForSingleObject(m_hViewEvent, INFINITE))
  105. {
  106. switch (m_enumViewCall)
  107. {
  108. case ENUMTHREAD_OPEN:
  109. // call open
  110. m_hrThread = _ThreadOpen(m_dwCallerThreadId);
  111. if (!SetEvent(m_hReturnEvent))
  112. {
  113. hr = myHLastError();
  114. _PrintError(hr, "SetEvent");
  115. }
  116. break;
  117. case ENUMTHREAD_NEXT:
  118. // call next
  119. m_hrThread = _ThreadNext(m_dwCallerThreadId);
  120. if (!SetEvent(m_hReturnEvent))
  121. {
  122. hr = myHLastError();
  123. _PrintError(hr, "SetEvent");
  124. }
  125. break;
  126. case ENUMTHREAD_CLEANUP:
  127. // call cleanup
  128. _ThreadCleanup(m_dwCallerThreadId);
  129. m_hrThread = S_OK;
  130. if (!SetEvent(m_hReturnEvent))
  131. {
  132. hr = myHLastError();
  133. _PrintError(hr, "SetEvent");
  134. }
  135. break;
  136. case ENUMTHREAD_END:
  137. DBGPRINT((s_ssDB, "End worker thread (tid=%d)\n", GetCurrentThreadId()));
  138. ExitThread(hr);
  139. break;
  140. default:
  141. // unexpected
  142. DBGPRINT((DBG_SS_CERTDB, "Unexpected event from (tid=%d)\n", m_dwCallerThreadId));
  143. m_hrThread = E_UNEXPECTED;
  144. if (!SetEvent(m_hReturnEvent))
  145. {
  146. hr = myHLastError();
  147. _PrintError(hr, "SetEvent");
  148. }
  149. CSASSERT(FALSE);
  150. break;
  151. }
  152. }
  153. }
  154. return(hr);
  155. }
  156. VOID
  157. CEnumCERTDBRESULTROW::_Cleanup()
  158. {
  159. HRESULT hr;
  160. if (!m_fThreading)
  161. {
  162. CSASSERT(NULL == m_hWorkThread);
  163. _ThreadCleanup(0); // no work thread, call directly
  164. }
  165. else
  166. {
  167. if (NULL != m_hWorkThread &&
  168. NULL != m_hViewEvent &&
  169. NULL != m_hReturnEvent)
  170. {
  171. // ask work thread to do clean up
  172. m_enumViewCall = ENUMTHREAD_CLEANUP;
  173. //#if DBG_CERTSRV
  174. m_dwCallerThreadId = GetCurrentThreadId();
  175. DBGPRINT((
  176. s_ssDB,
  177. "CEnumCERTDBRESULTROW::_Cleanup(tid=%d) (this=0x%x)\n",
  178. m_dwCallerThreadId,
  179. this));
  180. //#endif
  181. //set cleanup event
  182. if (!SetEvent(m_hViewEvent))
  183. {
  184. hr = myHLastError();
  185. _PrintError(hr, "SetEvent");
  186. }
  187. else
  188. {
  189. hr = _HandleThreadError();
  190. _PrintIfError(hr, "_HandleThreadError");
  191. }
  192. // ask the thread end
  193. m_enumViewCall = ENUMTHREAD_END;
  194. if (!SetEvent(m_hViewEvent))
  195. {
  196. hr = myHLastError();
  197. _PrintError(hr, "SetEvent(thread still alive");
  198. }
  199. else
  200. {
  201. if (WAIT_OBJECT_0 != WaitForSingleObject(m_hWorkThread, THREAD_TIMEOUT))
  202. {
  203. hr = myHLastError();
  204. _PrintError(hr, "Thread is not killed");
  205. }
  206. }
  207. if (GetExitCodeThread(m_hWorkThread, (DWORD *) &hr))
  208. {
  209. _PrintIfError(hr, "Work thread error");
  210. }
  211. m_pThreadParam = NULL; //may not be necessary, but safe
  212. }
  213. if (NULL != m_hWorkThread)
  214. {
  215. CloseHandle(m_hWorkThread);
  216. m_hWorkThread = NULL;
  217. }
  218. if (NULL != m_hViewEvent)
  219. {
  220. CloseHandle(m_hViewEvent);
  221. m_hViewEvent = NULL;
  222. }
  223. if (NULL != m_hReturnEvent)
  224. {
  225. CloseHandle(m_hReturnEvent);
  226. m_hReturnEvent = NULL;
  227. }
  228. }
  229. if (NULL != m_pdb)
  230. {
  231. m_pdb->Release();
  232. m_pdb = NULL;
  233. }
  234. }
  235. VOID
  236. CEnumCERTDBRESULTROW::_ThreadCleanup(DWORD dwCallerThreadID)
  237. {
  238. HRESULT hr;
  239. DWORD i;
  240. if (NULL != m_pdb)
  241. {
  242. if (NULL != m_pcs)
  243. {
  244. hr = ((CCertDB *) m_pdb)->CloseTables(m_pcs);
  245. _PrintIfError(hr, "CloseTables");
  246. hr = ((CCertDB *) m_pdb)->ReleaseSession(m_pcs);
  247. _PrintIfError(hr, "ReleaseSession");
  248. m_pcs = NULL;
  249. }
  250. if (NULL != m_aRestriction)
  251. {
  252. for (i = 0; i < m_cRestriction; i++)
  253. {
  254. if (NULL != m_aRestriction[i].pbValue)
  255. {
  256. LocalFree(m_aRestriction[i].pbValue);
  257. }
  258. }
  259. LocalFree(m_aRestriction);
  260. m_aRestriction = NULL;
  261. }
  262. if (NULL != m_acolOut)
  263. {
  264. LocalFree(m_acolOut);
  265. m_acolOut = NULL;
  266. }
  267. }
  268. }
  269. HRESULT
  270. CEnumCERTDBRESULTROW::_HandleThreadError()
  271. {
  272. HRESULT hr = S_OK;
  273. HANDLE ahEvents[] = { m_hReturnEvent, m_hWorkThread };
  274. // need to handle error
  275. DWORD dwWaitState = WaitForMultipleObjects(
  276. ARRAYSIZE(ahEvents),
  277. ahEvents,
  278. FALSE,
  279. THREAD_TIMEOUT);
  280. // reset
  281. m_pThreadParam = NULL;
  282. //#if DBG_CERTSRV
  283. //m_dwCallerThreadId = 0;
  284. //#endif
  285. if (WAIT_OBJECT_0 == dwWaitState)
  286. {
  287. // signaled from work thread
  288. hr = m_hrThread;
  289. }
  290. else if (WAIT_OBJECT_0 + 1 == dwWaitState)
  291. {
  292. // work thread ended unexpectedly
  293. hr = E_UNEXPECTED;
  294. _JumpError(hr, error, "work thread is ended unexpectedly");
  295. }
  296. else if (WAIT_TIMEOUT == dwWaitState)
  297. {
  298. hr = HRESULT_FROM_WIN32(ERROR_TIMEOUT);
  299. _JumpError(hr, error, "WaitForSingleObject(timeout)");
  300. }
  301. else if (WAIT_FAILED == dwWaitState)
  302. {
  303. hr = myHLastError();
  304. _JumpError(hr, error, "WaitForSingleObject");
  305. }
  306. error:
  307. return(hr);
  308. }
  309. // Truncate FILETIME to next lower minute and add lMinuteCount minutes (if !0)
  310. HRESULT
  311. myMakeExprDateMinuteRound(
  312. IN OUT FILETIME *pft,
  313. IN LONG lMinuteCount)
  314. {
  315. HRESULT hr;
  316. SYSTEMTIME st;
  317. #if DBG_CERTSRV
  318. dbDumpFileTime(DBG_SS_CERTDBI, "MinuteRound(IN): ", pft);
  319. #endif
  320. FileTimeToSystemTime(pft, &st);
  321. st.wSecond = 0;
  322. st.wMilliseconds = 0;
  323. if (!SystemTimeToFileTime(&st, pft))
  324. {
  325. hr = myHLastError();
  326. _JumpError(hr, error, "SystemTimeToFileTime");
  327. }
  328. if (0 != lMinuteCount)
  329. {
  330. myMakeExprDateTime(pft, lMinuteCount, ENUM_PERIOD_MINUTES);
  331. }
  332. #if DBG_CERTSRV
  333. dbDumpFileTime(DBG_SS_CERTDBI, "MinuteRound(OUT): ", pft);
  334. #endif
  335. hr = S_OK;
  336. error:
  337. return(hr);
  338. }
  339. HRESULT
  340. CEnumCERTDBRESULTROW::_SetTable(
  341. IN LONG ColumnIndex,
  342. OUT LONG *pColumnIndexDefault)
  343. {
  344. HRESULT hr;
  345. DWORD dwTable;
  346. LONG ColumnIndexDefault;
  347. if (0 > ColumnIndex)
  348. {
  349. switch (ColumnIndex)
  350. {
  351. case CV_COLUMN_LOG_DEFAULT:
  352. case CV_COLUMN_LOG_FAILED_DEFAULT:
  353. case CV_COLUMN_LOG_REVOKED_DEFAULT:
  354. case CV_COLUMN_QUEUE_DEFAULT:
  355. ColumnIndex = DTI_REQUESTTABLE;
  356. break;
  357. case CV_COLUMN_EXTENSION_DEFAULT:
  358. ColumnIndex = DTI_EXTENSIONTABLE;
  359. break;
  360. case CV_COLUMN_ATTRIBUTE_DEFAULT:
  361. ColumnIndex = DTI_ATTRIBUTETABLE;
  362. break;
  363. case CV_COLUMN_CRL_DEFAULT:
  364. ColumnIndex = DTI_CRLTABLE;
  365. break;
  366. default:
  367. hr = E_INVALIDARG;
  368. _JumpError(hr, error, "bad negative ColumnIndex");
  369. }
  370. }
  371. if (~(DTI_COLUMNMASK | DTI_TABLEMASK) & ColumnIndex)
  372. {
  373. hr = E_INVALIDARG;
  374. _JumpError(hr, error, "invalid bits");
  375. }
  376. switch (DTI_TABLEMASK & ColumnIndex)
  377. {
  378. case DTI_REQUESTTABLE:
  379. case DTI_CERTIFICATETABLE:
  380. dwTable = TABLE_REQCERTS;
  381. ColumnIndexDefault = DTI_REQUESTTABLE | DTR_REQUESTID;
  382. break;
  383. case DTI_EXTENSIONTABLE:
  384. dwTable = TABLE_EXTENSIONS;
  385. ColumnIndexDefault = DTI_EXTENSIONTABLE | DTE_REQUESTID;
  386. break;
  387. case DTI_ATTRIBUTETABLE:
  388. dwTable = TABLE_ATTRIBUTES;
  389. ColumnIndexDefault = DTI_ATTRIBUTETABLE | DTA_REQUESTID;
  390. break;
  391. case DTI_CRLTABLE:
  392. dwTable = TABLE_CRLS;
  393. ColumnIndexDefault = DTI_CRLTABLE | DTL_ROWID;
  394. break;
  395. default:
  396. hr = E_INVALIDARG;
  397. _JumpError(hr, error, "bad table");
  398. }
  399. if (CSF_TABLESET & m_pcs->SesFlags)
  400. {
  401. if ((CSF_TABLEMASK & m_pcs->SesFlags) != dwTable)
  402. {
  403. DBGPRINT((
  404. DBG_SS_CERTVIEW,
  405. "_SetTable: Table=%x <- %x\n",
  406. CSF_TABLEMASK & m_pcs->SesFlags,
  407. dwTable));
  408. hr = E_INVALIDARG;
  409. _JumpError(hr, error, "mixed tables");
  410. }
  411. }
  412. else
  413. {
  414. CSASSERT(0 == (CSF_TABLEMASK & m_pcs->SesFlags));
  415. m_pcs->SesFlags |= CSF_TABLESET | dwTable;
  416. }
  417. *pColumnIndexDefault = ColumnIndexDefault;
  418. hr = S_OK;
  419. error:
  420. return(hr);
  421. }
  422. HRESULT
  423. CEnumCERTDBRESULTROW::_SaveRestrictions(
  424. IN DWORD ccvrIn,
  425. IN CERTVIEWRESTRICTION const *acvrIn,
  426. IN LONG ColumnIndexDefault)
  427. {
  428. HRESULT hr;
  429. DWORD ccvrAlloc;
  430. CERTVIEWRESTRICTION const *pcvr;
  431. CERTVIEWRESTRICTION const *pcvrEnd;
  432. CERTVIEWRESTRICTION const *pcvrIndexed;
  433. CERTVIEWRESTRICTION *pcvrDst;
  434. BOOL fFoundSortOrder;
  435. BOOL fDefault;
  436. DWORD dwDefaultValue;
  437. DWORD Type;
  438. FILETIME ft;
  439. ccvrAlloc = ccvrIn;
  440. pcvrIndexed = NULL;
  441. fFoundSortOrder = FALSE;
  442. pcvrEnd = &acvrIn[ccvrIn];
  443. for (pcvr = acvrIn; pcvr < pcvrEnd; pcvr++) // for each restriction
  444. {
  445. fDefault = 0 > (LONG) pcvr->ColumnIndex;
  446. if (!fDefault)
  447. {
  448. hr = ((CCertDB *) m_pdb)->GetColumnType(pcvr->ColumnIndex, &Type);
  449. _JumpIfError(hr, error, "GetColumnType");
  450. }
  451. if (fDefault || (PROPFLAGS_INDEXED & Type))
  452. {
  453. if (!fFoundSortOrder && CVR_SORT_NONE != pcvr->SortOrder)
  454. {
  455. // if the first indexed column with sort order, save this one.
  456. fFoundSortOrder = TRUE;
  457. pcvrIndexed = pcvr;
  458. }
  459. else
  460. if (NULL == pcvrIndexed)
  461. {
  462. // if the first indexed column, save this one.
  463. pcvrIndexed = pcvr;
  464. }
  465. }
  466. if (CVR_SORT_NONE != pcvr->SortOrder && pcvrIndexed != pcvr)
  467. {
  468. hr = E_INVALIDARG;
  469. DBGPRINT((DBG_SS_CERTDB, "_SaveRestrictions(%x)\n", pcvr->ColumnIndex));
  470. _JumpError(hr, error, "multiple SortOrders or non-indexed column");
  471. }
  472. if (!fDefault &&
  473. PROPTYPE_DATE == (PROPTYPE_MASK & Type) &&
  474. CVR_SEEK_EQ == pcvr->SeekOperator)
  475. {
  476. ccvrAlloc++; // Turn Date == value into a range restriction
  477. }
  478. }
  479. if (NULL == pcvrIndexed)
  480. {
  481. ccvrAlloc++; // No indexed column: add RequestId >= 0
  482. }
  483. m_aRestriction = (CERTVIEWRESTRICTION *) LocalAlloc(
  484. LMEM_FIXED | LMEM_ZEROINIT,
  485. ccvrAlloc * sizeof(m_aRestriction[0]));
  486. if (NULL == m_aRestriction)
  487. {
  488. hr = E_OUTOFMEMORY;
  489. _JumpError(hr, error, "LocalAlloc");
  490. }
  491. m_cRestriction = ccvrAlloc;
  492. pcvrDst = m_aRestriction;
  493. // If no indexed restriction, add one.
  494. if (NULL == pcvrIndexed)
  495. {
  496. pcvrDst->ColumnIndex = ColumnIndexDefault;
  497. pcvrDst->SeekOperator = CVR_SEEK_NONE;
  498. pcvrDst->SortOrder = CVR_SORT_ASCEND;
  499. pcvrDst->cbValue = 0;
  500. pcvrDst->pbValue = NULL;
  501. pcvrDst++;
  502. }
  503. for (pcvr = acvrIn; pcvr < pcvrEnd; pcvr++)
  504. {
  505. CERTVIEWRESTRICTION const *pcvrSrc = pcvr;
  506. BYTE *pbValue;
  507. // Swap the first restriction with the first indexed restriction
  508. if (NULL != pcvrIndexed)
  509. {
  510. if (pcvrSrc == acvrIn)
  511. {
  512. pcvrSrc = pcvrIndexed;
  513. }
  514. else if (pcvrSrc == pcvrIndexed)
  515. {
  516. pcvrSrc = acvrIn;
  517. }
  518. }
  519. *pcvrDst = *pcvrSrc;
  520. if (pcvrSrc == pcvrIndexed && CVR_SORT_NONE == pcvrSrc->SortOrder)
  521. {
  522. pcvrDst->SortOrder = CVR_SORT_ASCEND;
  523. }
  524. pcvrDst->pbValue = NULL;
  525. fDefault = 0 > (LONG) pcvr->ColumnIndex;
  526. if (fDefault)
  527. {
  528. pcvrDst->SeekOperator = CVR_SEEK_GE; // default seek operator
  529. dwDefaultValue = 1; // default RequestId/Rowid
  530. switch (pcvr->ColumnIndex)
  531. {
  532. case CV_COLUMN_QUEUE_DEFAULT:
  533. case CV_COLUMN_LOG_DEFAULT:
  534. case CV_COLUMN_LOG_FAILED_DEFAULT:
  535. case CV_COLUMN_LOG_REVOKED_DEFAULT:
  536. pcvrDst->ColumnIndex = DTI_REQUESTTABLE | DTR_REQUESTDISPOSITION;
  537. if (CV_COLUMN_QUEUE_DEFAULT == pcvrDst->ColumnIndex)
  538. {
  539. dwDefaultValue = DB_DISP_QUEUE_MAX;
  540. pcvrDst->SeekOperator = CVR_SEEK_LE;
  541. }
  542. else if (CV_COLUMN_LOG_DEFAULT == pcvrDst->ColumnIndex)
  543. {
  544. dwDefaultValue = DB_DISP_LOG_MIN;
  545. }
  546. else if (CV_COLUMN_LOG_REVOKED_DEFAULT == pcvrDst->ColumnIndex)
  547. {
  548. dwDefaultValue = DB_DISP_REVOKED;
  549. pcvrDst->SeekOperator = CVR_SEEK_EQ;
  550. }
  551. else
  552. {
  553. dwDefaultValue = DB_DISP_LOG_FAILED_MIN;
  554. }
  555. break;
  556. case CV_COLUMN_EXTENSION_DEFAULT:
  557. pcvrDst->ColumnIndex = DTI_EXTENSIONTABLE | DTE_REQUESTID;
  558. break;
  559. case CV_COLUMN_ATTRIBUTE_DEFAULT:
  560. pcvrDst->ColumnIndex = DTI_ATTRIBUTETABLE | DTA_REQUESTID;
  561. break;
  562. case CV_COLUMN_CRL_DEFAULT:
  563. pcvrDst->ColumnIndex = DTI_CRLTABLE | DTL_ROWID;
  564. break;
  565. default:
  566. hr = E_INVALIDARG;
  567. _JumpError(hr, error, "bad default restriction column");
  568. break;
  569. }
  570. pcvrDst->cbValue = sizeof(dwDefaultValue);
  571. pbValue = (BYTE *) &dwDefaultValue;
  572. }
  573. else
  574. {
  575. // To handle rounding errors, modify date restrictions as follows:
  576. //
  577. // DateColumn == Constant ==> two restrictions:
  578. // DateColumn < Ceiling(Constant) &&
  579. // DateColumn >= Floor(Constant)
  580. //
  581. // DateColumn > Constant ==> DateColumn >= Ceiling(Constant)
  582. // DateColumn >= Constant ==> DateColumn >= Floor(Constant)
  583. //
  584. // DateColumn < Constant ==> DateColumn < Floor(Constant)
  585. // DateColumn <= Constant ==> DateColumn < Ceiling(Constant)
  586. hr = ((CCertDB *) m_pdb)->GetColumnType(
  587. pcvrDst->ColumnIndex,
  588. &Type);
  589. _JumpIfError(hr, error, "GetColumnType");
  590. pbValue = pcvrSrc->pbValue;
  591. if (PROPTYPE_DATE == (PROPTYPE_MASK & Type) &&
  592. 0 == (CVR_SEEK_NODELTA & pcvrDst->SeekOperator) &&
  593. CVR_SEEK_NONE != (CVR_SEEK_MASK & pcvrDst->SeekOperator))
  594. {
  595. LONG lMinuteCount = 0; // assume truncate to lower minute
  596. if(NULL == pcvrSrc->pbValue)
  597. {
  598. hr = E_INVALIDARG;
  599. _JumpError(hr, error, "restriction value is null");
  600. }
  601. ft = *(FILETIME *) pcvrSrc->pbValue;
  602. pbValue = (BYTE *) &ft;
  603. switch (CVR_SEEK_MASK & pcvrDst->SeekOperator)
  604. {
  605. FILETIME ftCeiling;
  606. case CVR_SEEK_EQ:
  607. ftCeiling = ft;
  608. hr = myMakeExprDateMinuteRound(&ftCeiling, 1);
  609. _JumpIfError(hr, error, "myMakeExprDateMinuteRound");
  610. pcvrDst->pbValue = (BYTE *) LocalAlloc(
  611. LMEM_FIXED,
  612. sizeof(ft));
  613. if (NULL == pcvrDst->pbValue)
  614. {
  615. hr = E_OUTOFMEMORY;
  616. _JumpError(hr, error, "LocalAlloc");
  617. }
  618. CopyMemory(pcvrDst->pbValue, &ftCeiling, pcvrDst->cbValue);
  619. pcvrDst->SeekOperator = CVR_SEEK_LT | CVR_SEEK_NODELTA;
  620. pcvrDst++;
  621. *pcvrDst = *pcvrSrc;
  622. pcvrDst->pbValue = NULL;
  623. pcvrDst->SeekOperator = CVR_SEEK_GE | CVR_SEEK_NODELTA;
  624. hr = myMakeExprDateMinuteRound(&ft, 0);
  625. _JumpIfError(hr, error, "myMakeExprDateMinuteRound");
  626. break;
  627. case CVR_SEEK_GT:
  628. lMinuteCount = 1; // round to next higher minute
  629. // FALL THROUGH
  630. case CVR_SEEK_GE:
  631. pcvrDst->SeekOperator = CVR_SEEK_GE | CVR_SEEK_NODELTA;
  632. hr = myMakeExprDateMinuteRound(&ft, lMinuteCount);
  633. _JumpIfError(hr, error, "myMakeExprDateMinuteRound");
  634. break;
  635. case CVR_SEEK_LE:
  636. lMinuteCount = 1; // round to next higher minute
  637. // FALL THROUGH
  638. case CVR_SEEK_LT:
  639. pcvrDst->SeekOperator = CVR_SEEK_LT | CVR_SEEK_NODELTA;
  640. hr = myMakeExprDateMinuteRound(&ft, lMinuteCount);
  641. _JumpIfError(hr, error, "myMakeExprDateMinuteRound");
  642. break;
  643. default:
  644. hr = E_INVALIDARG;
  645. _JumpError(hr, error, "invalid seek operator");
  646. }
  647. }
  648. }
  649. // either nonzero or SEEK_NONE
  650. CSASSERT((0 != pcvrDst->cbValue) || ((CVR_SEEK_MASK & pcvrDst->SeekOperator) == CVR_SEEK_NONE));
  651. if (0 != pcvrDst->cbValue)
  652. {
  653. pcvrDst->pbValue = (BYTE *) LocalAlloc(LMEM_FIXED, pcvrDst->cbValue);
  654. if (NULL == pcvrDst->pbValue)
  655. {
  656. hr = E_OUTOFMEMORY;
  657. _JumpError(hr, error, "alloc value");
  658. }
  659. CopyMemory(pcvrDst->pbValue, pbValue, pcvrDst->cbValue);
  660. }
  661. pcvrDst++;
  662. }
  663. CSASSERT(pcvrDst == &m_aRestriction[m_cRestriction]);
  664. #if DBG_CERTSRV
  665. pcvrEnd = &m_aRestriction[m_cRestriction];
  666. for (pcvr = m_aRestriction; pcvr < pcvrEnd; pcvr++)
  667. {
  668. ((CCertDB *) m_pdb)->DumpRestriction(
  669. DBG_SS_CERTDBI,
  670. SAFE_SUBTRACT_POINTERS(pcvr, m_aRestriction),
  671. pcvr);
  672. }
  673. #endif // DBG_CERTSRV
  674. hr = S_OK;
  675. error:
  676. return(hr);
  677. }
  678. HRESULT
  679. CEnumCERTDBRESULTROW::Open(
  680. IN CERTSESSION *pcs,
  681. IN ICertDB *pdb,
  682. IN DWORD ccvr,
  683. IN CERTVIEWRESTRICTION const *acvr,
  684. IN DWORD ccolOut,
  685. IN DWORD const *acolOut)
  686. {
  687. HRESULT hr;
  688. THREAD_PARAM_OPEN tpOpen;
  689. CSASSERT(NULL == m_hViewEvent);
  690. CSASSERT(NULL == m_hReturnEvent);
  691. CSASSERT(NULL == m_hWorkThread);
  692. if (NULL != m_hViewEvent ||
  693. NULL != m_hReturnEvent ||
  694. NULL != m_hWorkThread)
  695. {
  696. hr = E_UNEXPECTED;
  697. _JumpError(hr, error, "unexpected thread sync. state");
  698. }
  699. hr = ((CCertDB *) pdb)->TestShutDownState();
  700. _JumpIfError(hr, error, "TestShutDownState");
  701. // call cleanup before worker thread is created
  702. _Cleanup();
  703. tpOpen.pcs = pcs;
  704. tpOpen.pdb = pdb;
  705. tpOpen.ccvr = ccvr;
  706. tpOpen.acvr = acvr;
  707. tpOpen.ccolOut = ccolOut;
  708. tpOpen.acolOut = acolOut;
  709. m_pThreadParam = (void*)&tpOpen;
  710. //#if DBG_CERTSRV
  711. m_dwCallerThreadId = GetCurrentThreadId();
  712. DBGPRINT((s_ssDB, "CEnumCERTDBRESULTROW::Open(tid=%d) (this=0x%x)\n", m_dwCallerThreadId, this));
  713. //#endif
  714. if (m_fThreading)
  715. {
  716. m_hViewEvent = CreateEvent(
  717. NULL, //child inheritance
  718. FALSE, //manual reset
  719. FALSE, //initial signaled
  720. NULL); //name
  721. if (NULL == m_hViewEvent)
  722. {
  723. hr = myHLastError();
  724. _JumpError(hr, error, "CreateEvent");
  725. }
  726. m_hReturnEvent = CreateEvent(
  727. NULL, //child inheritance
  728. FALSE, //manual reset
  729. FALSE, //initial signaled
  730. NULL); //name
  731. if (NULL == m_hReturnEvent)
  732. {
  733. hr = myHLastError();
  734. _JumpError(hr, error, "CreateEvent");
  735. }
  736. m_hWorkThread = CreateThread(
  737. NULL, //no child inheritance
  738. 0, //use default stack size
  739. _ViewWorkThreadFunctionHelper, // thread function
  740. this, //pass this pointer
  741. 0, //run immediately
  742. &pcs->dwThreadId); //session thread id is overwritten
  743. if (NULL == m_hWorkThread)
  744. {
  745. hr = myHLastError();
  746. _JumpError(hr, error, "CreateThread");
  747. }
  748. m_enumViewCall = ENUMTHREAD_OPEN;
  749. //set open event
  750. if (!SetEvent(m_hViewEvent))
  751. {
  752. hr = myHLastError();
  753. _JumpError(hr, error, "SetEvent");
  754. }
  755. else
  756. {
  757. hr = _HandleThreadError();
  758. }
  759. }
  760. else
  761. {
  762. // don't go through worker thread
  763. hr = _ThreadOpen(0);
  764. }
  765. //hr = S_OK;
  766. error:
  767. return(hr);
  768. }
  769. HRESULT
  770. CEnumCERTDBRESULTROW::_ThreadOpen(DWORD dwCallerThreadID)
  771. {
  772. HRESULT hr;
  773. THREAD_PARAM_OPEN *ptpOpen = (THREAD_PARAM_OPEN *)m_pThreadParam;
  774. LONG ColumnIndexDefault = DTI_REQUESTTABLE | DTR_REQUESTID;
  775. DWORD i;
  776. DBGPRINT((s_ssDB, "CEnumCERTDBRESULTROW::ThreadOpen(tid=%d) from (tid=%d)\n", GetCurrentThreadId(), m_dwCallerThreadId));
  777. CSASSERT(NULL != ptpOpen);
  778. CSASSERTTHREAD(ptpOpen->pcs);
  779. if (NULL == ptpOpen->pcs ||
  780. NULL == ptpOpen->pdb ||
  781. (NULL == ptpOpen->acvr && 0 != ptpOpen->ccvr) ||
  782. NULL == ptpOpen->acolOut)
  783. {
  784. hr = E_POINTER;
  785. _JumpError(hr, error, "NULL parm");
  786. }
  787. m_fNoMoreData = FALSE;
  788. m_pcs = ptpOpen->pcs;
  789. m_pdb = ptpOpen->pdb;
  790. m_pdb->AddRef();
  791. m_ielt = 0;
  792. m_cskip = 0;
  793. CSASSERT(0 == m_pcs->cTransact);
  794. if (NULL != ptpOpen->acolOut)
  795. {
  796. for (i = 0; i < ptpOpen->ccolOut; i++)
  797. {
  798. hr = _SetTable(ptpOpen->acolOut[i], &ColumnIndexDefault);
  799. _JumpIfError(hr, error, "_SetTable");
  800. }
  801. }
  802. for (i = 0; i < ptpOpen->ccvr; i++)
  803. {
  804. hr = _SetTable(ptpOpen->acvr[i].ColumnIndex, &ColumnIndexDefault);
  805. _JumpIfError(hr, error, "_SetTable");
  806. }
  807. hr = _SaveRestrictions(ptpOpen->ccvr, ptpOpen->acvr, ColumnIndexDefault);
  808. _JumpIfError(hr, error, "_SaveRestrictions");
  809. m_ccolOut = ptpOpen->ccolOut;
  810. if (NULL != ptpOpen->acolOut)
  811. {
  812. m_acolOut = (DWORD *) LocalAlloc(
  813. LMEM_FIXED,
  814. sizeof(m_acolOut[0]) * m_ccolOut);
  815. if (NULL == m_acolOut)
  816. {
  817. hr = E_OUTOFMEMORY;
  818. _JumpError(hr, error, "alloc output columns");
  819. }
  820. CopyMemory(m_acolOut, ptpOpen->acolOut, sizeof(m_acolOut[0]) * m_ccolOut);
  821. }
  822. if (!(CSF_READONLY & ptpOpen->pcs->SesFlags))
  823. {
  824. hr = ((CCertDB *) m_pdb)->BeginTransaction(m_pcs, FALSE);
  825. _JumpIfError(hr, error, "BeginTransaction");
  826. }
  827. hr = ((CCertDB *) m_pdb)->OpenTables(m_pcs, &m_aRestriction[0]);
  828. _PrintIfError2(hr, "OpenTables", CERTSRV_E_PROPERTY_EMPTY);
  829. if (CERTSRV_E_PROPERTY_EMPTY == hr)
  830. {
  831. m_fNoMoreData = TRUE;
  832. m_ieltMax = 0;
  833. hr = S_OK;
  834. }
  835. _JumpIfError(hr, error, "OpenTables");
  836. error:
  837. return(hr);
  838. }
  839. STDMETHODIMP
  840. CEnumCERTDBRESULTROW::Next(
  841. /* [in] */ ULONG celt,
  842. /* [out] */ CERTDBRESULTROW *rgelt,
  843. /* [out] */ ULONG *pceltFetched)
  844. {
  845. HRESULT hr;
  846. THREAD_PARAM_NEXT tpNext;
  847. tpNext.celt = celt;
  848. tpNext.rgelt = rgelt;
  849. tpNext.pceltFetched = pceltFetched;
  850. m_pThreadParam = (void*)&tpNext;
  851. //#if DBG_CERTSRV
  852. m_dwCallerThreadId = GetCurrentThreadId();
  853. DBGPRINT((s_ssDB, "CEnumCERTDBRESULTROW::Next(tid=%d) (this=0x%x)\n", m_dwCallerThreadId, this));
  854. //#endif
  855. CSASSERT(NULL != m_pdb);
  856. if (NULL == m_pdb)
  857. {
  858. hr = E_UNEXPECTED;
  859. _JumpError(hr, error, "NULL m_pdb");
  860. }
  861. hr = ((CCertDB *) m_pdb)->TestShutDownState();
  862. _JumpIfError(hr, error, "TestShutDownState");
  863. if (m_fThreading)
  864. {
  865. CSASSERT(NULL != m_hViewEvent);
  866. CSASSERT(NULL != m_hReturnEvent);
  867. CSASSERT(NULL != m_hWorkThread);
  868. if (NULL == m_hViewEvent ||
  869. NULL == m_hReturnEvent ||
  870. NULL == m_hWorkThread)
  871. {
  872. hr = E_UNEXPECTED;
  873. _JumpError(hr, error, "unexpected thread sync. state");
  874. }
  875. m_enumViewCall = ENUMTHREAD_NEXT;
  876. // set next event
  877. if (!SetEvent(m_hViewEvent))
  878. {
  879. hr = myHLastError();
  880. _JumpError(hr, error, "SetEvent");
  881. }
  882. else
  883. {
  884. hr = _HandleThreadError();
  885. }
  886. }
  887. else
  888. {
  889. // don't go through worker thread
  890. hr = _ThreadNext(0);
  891. }
  892. //hr = S_OK;
  893. error:
  894. return(hr);
  895. }
  896. HRESULT
  897. CEnumCERTDBRESULTROW::_ThreadNext(DWORD dwCallerThreadID)
  898. {
  899. HRESULT hr;
  900. LONG cskip;
  901. LONG cskipped;
  902. THREAD_PARAM_NEXT *ptpNext = (THREAD_PARAM_NEXT *)m_pThreadParam;
  903. DBGPRINT((s_ssDB, "CEnumCERTDBRESULTROW::ThreadNext(tid=%d) from (tid=%d)\n", GetCurrentThreadId(), m_dwCallerThreadId));
  904. CSASSERT(NULL != ptpNext);
  905. DBGPRINT((
  906. DBG_SS_CERTDBI,
  907. "Trace: hr = penum->Next(%d, arow, &crow);\t_PrintIfError(hr, \"Next\");\n",
  908. ptpNext->celt));
  909. if (NULL == ptpNext->rgelt || NULL == ptpNext->pceltFetched)
  910. {
  911. hr = E_POINTER;
  912. _JumpError(hr, error, "NULL parm");
  913. }
  914. *ptpNext->pceltFetched = 0;
  915. if (NULL == m_pdb)
  916. {
  917. hr = E_UNEXPECTED;
  918. _JumpError(hr, error, "NULL m_pdb");
  919. }
  920. ZeroMemory(ptpNext->rgelt, ptpNext->celt * sizeof(ptpNext->rgelt[0]));
  921. CSASSERT(0 <= m_ielt);
  922. CSASSERT(0 <= m_ielt + m_cskip);
  923. DBGPRINT((
  924. DBG_SS_CERTDBI,
  925. "Next(celt=%d) ielt=%d, skip=%d\n",
  926. ptpNext->celt,
  927. m_ielt,
  928. m_cskip));
  929. hr = S_FALSE;
  930. if (m_fNoMoreData)
  931. {
  932. // We know no additional data can be returned until Reset is called or
  933. // until Skip is called with a negative skip count. Don't bother...
  934. _JumpError2(hr, error, "NoMoreData", S_FALSE);
  935. }
  936. // If we have previously computed the end of the data set, ...
  937. cskip = m_cskip;
  938. if (0 != m_ieltMax)
  939. {
  940. if (m_ielt + cskip >= m_ieltMax)
  941. {
  942. // The requested data lies past the computed end of the data set.
  943. CSASSERT(S_FALSE == hr);
  944. m_fNoMoreData = TRUE;
  945. _JumpError2(hr, error, "past end", S_FALSE);
  946. }
  947. DBGPRINT((
  948. DBG_SS_CERTDBI,
  949. "cskip = %d m_ielt = %d m_ieltMax = %d\n",
  950. cskip,
  951. m_ielt,
  952. m_ieltMax));
  953. if (0 > cskip && m_ielt > m_ieltMax)
  954. {
  955. // We're skiping backwards. If we started out past the end of the
  956. // data set, we must reduce the negative skip count passed to the
  957. // DB layer to position the index cursor correctly.
  958. cskip += m_ielt - m_ieltMax;
  959. DBGPRINT((
  960. DBG_SS_CERTDBI,
  961. "MODIFIED: cskip = %d m_ielt = %d m_ieltMax = %d\n",
  962. cskip,
  963. m_ielt,
  964. m_ieltMax));
  965. }
  966. }
  967. hr = ((CCertDB *) m_pdb)->EnumCertDBResultRowNext(
  968. m_pcs,
  969. m_cRestriction,
  970. m_aRestriction,
  971. m_ccolOut,
  972. m_acolOut,
  973. cskip,
  974. ptpNext->celt,
  975. ptpNext->rgelt,
  976. ptpNext->pceltFetched,
  977. &cskipped);
  978. if (S_FALSE == hr)
  979. {
  980. // Only set m_ieltMax the first time we run off the end, when we will
  981. // be guaranteed that we are moving forward through the DB index.
  982. // Otherwise the math is too complicated and would be redundant anyway.
  983. if (0 == m_ieltMax)
  984. {
  985. CSASSERT(0 <= cskip);
  986. CSASSERT(0 <= cskipped);
  987. m_ieltMax = m_ielt + cskipped;
  988. }
  989. DBGPRINT((
  990. DBG_SS_CERTDBI,
  991. "Next: ieltMax=%d ielt=%d, cskipped=%d\n",
  992. m_ieltMax,
  993. m_ielt,
  994. cskipped));
  995. m_fNoMoreData = TRUE;
  996. }
  997. else
  998. {
  999. _JumpIfError(hr, error, "EnumCertDBResultRowNext");
  1000. }
  1001. DBGPRINT((
  1002. DBG_SS_CERTDBI,
  1003. "Next: ielt=%d -> %d cskip=%d, *pceltFetched=%d\n",
  1004. m_ielt,
  1005. m_ielt + m_cskip + *ptpNext->pceltFetched,
  1006. m_cskip,
  1007. *ptpNext->pceltFetched));
  1008. m_ielt += m_cskip;
  1009. m_ielt += *ptpNext->pceltFetched;
  1010. m_cskip = 0;
  1011. error:
  1012. if (S_FALSE == hr)
  1013. {
  1014. CSASSERT(NULL != ptpNext->rgelt);
  1015. CSASSERT(NULL != ptpNext->pceltFetched);
  1016. CSASSERT(*ptpNext->pceltFetched < ptpNext->celt);
  1017. CERTDBRESULTROW *peltMaxIndex = &ptpNext->rgelt[*ptpNext->pceltFetched];
  1018. peltMaxIndex->rowid = m_ieltMax;
  1019. peltMaxIndex->ccol = ~m_ieltMax;
  1020. }
  1021. return(hr);
  1022. }
  1023. STDMETHODIMP
  1024. CEnumCERTDBRESULTROW::ReleaseResultRow(
  1025. /* [in] */ ULONG celt,
  1026. /* [in, out] */ CERTDBRESULTROW *rgelt)
  1027. {
  1028. HRESULT hr;
  1029. if (NULL == rgelt)
  1030. {
  1031. hr = E_POINTER;
  1032. _JumpError(hr, error, "NULL parm");
  1033. }
  1034. if (NULL == m_pdb)
  1035. {
  1036. hr = E_UNEXPECTED;
  1037. _JumpError(hr, error, "NULL m_pdb");
  1038. }
  1039. hr = ((CCertDB *) m_pdb)->ReleaseResultRow(celt, rgelt);
  1040. _JumpIfError(hr, error, "ReleaseResultRow");
  1041. error:
  1042. return(hr);
  1043. }
  1044. STDMETHODIMP
  1045. CEnumCERTDBRESULTROW::Skip(
  1046. /* [in] */ LONG celt,
  1047. /* [out] */ LONG *pielt)
  1048. {
  1049. HRESULT hr;
  1050. LONG cskipnew;
  1051. DBGPRINT((
  1052. DBG_SS_CERTDBI,
  1053. "Trace: hr = penum->Skip(%d, &irow);\t_PrintIfError(hr, \"Skip\");\n",
  1054. celt));
  1055. if (NULL == pielt)
  1056. {
  1057. hr = E_POINTER;
  1058. _JumpError(hr, error, "NULL parm");
  1059. }
  1060. cskipnew = m_cskip + celt;
  1061. DBGPRINT((
  1062. DBG_SS_CERTDBI,
  1063. "Skip(%d) ielt=%d: %d --> %d, skip=%d --> %d\n",
  1064. celt,
  1065. m_ielt,
  1066. m_ielt + m_cskip,
  1067. m_ielt + cskipnew,
  1068. m_cskip,
  1069. cskipnew));
  1070. CSASSERT(0 <= m_ielt);
  1071. if (0 > celt)
  1072. {
  1073. if (0 > m_ielt + cskipnew)
  1074. {
  1075. hr = E_INVALIDARG;
  1076. _JumpError(hr, error, "Skip back to before start");
  1077. }
  1078. m_fNoMoreData = FALSE;
  1079. }
  1080. *pielt = m_ielt + cskipnew;
  1081. m_cskip = cskipnew;
  1082. hr = S_OK;
  1083. error:
  1084. return(hr);
  1085. }
  1086. STDMETHODIMP
  1087. CEnumCERTDBRESULTROW::Reset(VOID)
  1088. {
  1089. HRESULT hr;
  1090. LONG iDummy;
  1091. DBGPRINT((
  1092. DBG_SS_CERTDBI,
  1093. "Trace: hr = penum->Reset();\t_PrintIfError(hr, \"Reset\");\n// "));
  1094. hr = Skip(-(m_ielt + m_cskip), &iDummy);
  1095. _JumpIfError(hr, error, "Skip");
  1096. CSASSERT(0 == iDummy);
  1097. error:
  1098. return(hr);
  1099. }
  1100. STDMETHODIMP
  1101. CEnumCERTDBRESULTROW::Clone(
  1102. /* [out] */ IEnumCERTDBRESULTROW **ppenum)
  1103. {
  1104. HRESULT hr;
  1105. LONG iDummy;
  1106. if (NULL == ppenum)
  1107. {
  1108. hr = E_POINTER;
  1109. _JumpError(hr, error, "NULL parm");
  1110. }
  1111. *ppenum = NULL;
  1112. if (NULL == m_pdb)
  1113. {
  1114. hr = E_UNEXPECTED;
  1115. _JumpError(hr, error, "NULL m_pdb");
  1116. }
  1117. hr = ((CCertDB *) m_pdb)->TestShutDownState();
  1118. _JumpIfError(hr, error, "TestShutDownState");
  1119. hr = ((CCertDB *) m_pdb)->OpenView(
  1120. m_cRestriction,
  1121. m_aRestriction,
  1122. m_ccolOut,
  1123. m_acolOut,
  1124. m_fThreading,
  1125. ppenum);
  1126. _JumpIfError(hr, error, "OpenView");
  1127. (*ppenum)->Skip(m_ielt + m_cskip, &iDummy);
  1128. error:
  1129. return(hr);
  1130. }
  1131. // IUnknown implementation
  1132. STDMETHODIMP
  1133. CEnumCERTDBRESULTROW::QueryInterface(
  1134. const IID& iid,
  1135. void **ppv)
  1136. {
  1137. HRESULT hr;
  1138. if (NULL == ppv)
  1139. {
  1140. hr = E_POINTER;
  1141. _JumpError(hr, error, "NULL parm");
  1142. }
  1143. if (iid == IID_IUnknown)
  1144. {
  1145. *ppv = static_cast<IEnumCERTDBRESULTROW *>(this);
  1146. }
  1147. else if (iid == IID_IEnumCERTDBRESULTROW)
  1148. {
  1149. *ppv = static_cast<IEnumCERTDBRESULTROW *>(this);
  1150. }
  1151. else
  1152. {
  1153. *ppv = NULL;
  1154. hr = E_NOINTERFACE;
  1155. _JumpError(hr, error, "IID");
  1156. }
  1157. reinterpret_cast<IUnknown *>(*ppv)->AddRef();
  1158. hr = S_OK;
  1159. error:
  1160. return(hr);
  1161. }
  1162. ULONG STDMETHODCALLTYPE
  1163. CEnumCERTDBRESULTROW::AddRef()
  1164. {
  1165. return(InterlockedIncrement(&m_cRef));
  1166. }
  1167. ULONG STDMETHODCALLTYPE
  1168. CEnumCERTDBRESULTROW::Release()
  1169. {
  1170. ULONG cRef = InterlockedDecrement(&m_cRef);
  1171. if (0 == cRef)
  1172. {
  1173. delete this;
  1174. }
  1175. return(cRef);
  1176. }
  1177. #if 0
  1178. STDMETHODIMP
  1179. CEnumCERTDBRESULTROW::InterfaceSupportsErrorInfo(
  1180. IN REFIID riid)
  1181. {
  1182. static const IID *arr[] =
  1183. {
  1184. &IID_IEnumCERTDBRESULTROW,
  1185. };
  1186. for (int i = 0; i < sizeof(arr)/sizeof(arr[0]); i++)
  1187. {
  1188. if (InlineIsEqualGUID(*arr[i], riid))
  1189. {
  1190. return(S_OK);
  1191. }
  1192. }
  1193. return(S_FALSE);
  1194. }
  1195. #endif