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.

704 lines
18 KiB

  1. /*++
  2. Copyright (c) 2000 Microsoft Corporation
  3. Module Name:
  4. schemaextensions.cpp
  5. Abstract:
  6. This file contains the implementation of the CSchemaExtensions class.
  7. This is the only class that talks to the catalog.
  8. Author:
  9. MarcelV
  10. Revision History:
  11. Mohit Srivastava 28-Nov-00
  12. --*/
  13. #include "iisprov.h"
  14. #include "schemaextensions.h"
  15. #include "metabase.hxx"
  16. LPWSTR g_wszDatabaseName = L"Metabase";
  17. HRESULT GetMetabasePath(LPTSTR io_tszPath)
  18. /*++
  19. Synopsis:
  20. This beast was copied and modified from
  21. \%sdxroot%\iis\svcs\infocomm\metadata\dll\metasub.cxx
  22. Arguments: [io_tszPath] - must be at least size MAX_PATH
  23. Return Value:
  24. --*/
  25. {
  26. DBG_ASSERT(io_tszPath != NULL);
  27. HRESULT hresReturn = HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND);
  28. TCHAR tszBuffer[MAX_PATH] = {0};
  29. HKEY hkRegistryKey = NULL;
  30. DWORD dwRegReturn;
  31. DWORD dwType;
  32. DWORD dwSize = MAX_PATH * sizeof(TCHAR);
  33. dwRegReturn = RegOpenKey(
  34. HKEY_LOCAL_MACHINE,
  35. ADMIN_REG_KEY, // TEXT("SOFTWARE\\Microsoft\\INetMgr\\Parameters")
  36. &hkRegistryKey);
  37. if (dwRegReturn == ERROR_SUCCESS)
  38. {
  39. dwRegReturn = RegQueryValueEx(
  40. hkRegistryKey,
  41. MD_FILE_VALUE, // TEXT("MetadataFile")
  42. NULL,
  43. &dwType,
  44. (BYTE *) tszBuffer,
  45. &dwSize);
  46. if ((dwRegReturn == ERROR_SUCCESS) && dwType == (REG_SZ))
  47. {
  48. //
  49. // TODO: Change this error code
  50. //
  51. hresReturn = HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND);
  52. for(ULONG i = dwSize/sizeof(TCHAR)-1; i > 0; i--)
  53. {
  54. if(tszBuffer[i] == TEXT('\\'))
  55. {
  56. tszBuffer[i] = TEXT('\0');
  57. hresReturn = ERROR_SUCCESS;
  58. break;
  59. }
  60. }
  61. }
  62. RegCloseKey( hkRegistryKey );
  63. hkRegistryKey = NULL;
  64. }
  65. if (FAILED(hresReturn))
  66. {
  67. dwRegReturn = RegOpenKey(
  68. HKEY_LOCAL_MACHINE,
  69. SETUP_REG_KEY, // TEXT("SOFTWARE\\Microsoft\\InetStp")
  70. &hkRegistryKey);
  71. if (dwRegReturn == ERROR_SUCCESS)
  72. {
  73. dwSize = MAX_PATH * sizeof(TCHAR);
  74. dwRegReturn = RegQueryValueEx(hkRegistryKey,
  75. INSTALL_PATH_VALUE,
  76. NULL,
  77. &dwType,
  78. (BYTE *) tszBuffer,
  79. &dwSize);
  80. if ((dwRegReturn == ERROR_SUCCESS) && dwType == (REG_SZ))
  81. {
  82. hresReturn = HRESULT_FROM_WIN32(dwRegReturn);
  83. }
  84. RegCloseKey( hkRegistryKey );
  85. }
  86. else
  87. {
  88. hresReturn = HRESULT_FROM_WIN32(dwRegReturn);
  89. }
  90. }
  91. _tcscpy(io_tszPath, tszBuffer);
  92. if(FAILED(hresReturn))
  93. {
  94. DBGPRINTF((DBG_CONTEXT, "Could not get metabase path, hr=0x%x\n", hresReturn));
  95. }
  96. return hresReturn;
  97. }
  98. int __cdecl
  99. CompDBNames (const void * pDBMetaLHS, const void * pDBMetaRHS)
  100. {
  101. const tDATABASEMETARow *pLHS = static_cast<const tDATABASEMETARow *> (pDBMetaLHS );
  102. const tDATABASEMETARow *pRHS = static_cast<const tDATABASEMETARow *> (pDBMetaRHS );
  103. return _wcsicmp (pLHS->pInternalName, pRHS->pInternalName);
  104. }
  105. int __cdecl
  106. CompTableMeta (const void * pTableMetaLHS, const void * pTableMetaRHS)
  107. {
  108. const CTableMeta *pLHS = static_cast<const CTableMeta *> (pTableMetaLHS );
  109. const CTableMeta *pRHS = static_cast<const CTableMeta *> (pTableMetaRHS );
  110. return _wcsicmp (pLHS->TableMeta.pInternalName, pRHS->TableMeta.pInternalName);
  111. }
  112. int __cdecl
  113. CompTableDBName (const void * pTableMetaLHS, const void * pTableMetaRHS)
  114. {
  115. const CTableMeta *pLHS = static_cast<const CTableMeta *> (pTableMetaLHS );
  116. const CTableMeta *pRHS = static_cast<const CTableMeta *> (pTableMetaRHS );
  117. return _wcsicmp (pLHS->TableMeta.pDatabase, pRHS->TableMeta.pDatabase);
  118. }
  119. // sorted by table name and index
  120. int __cdecl
  121. CompColumnMetas (const void * pColumnMetaLHS, const void * pColumnMetaRHS)
  122. {
  123. const CColumnMeta *pLHS = static_cast<const CColumnMeta *> (pColumnMetaLHS );
  124. const CColumnMeta *pRHS = static_cast<const CColumnMeta *> (pColumnMetaRHS );
  125. int iCmp = _wcsicmp (pLHS->ColumnMeta.pTable, pRHS->ColumnMeta.pTable);
  126. if (iCmp != 0)
  127. {
  128. return iCmp;
  129. }
  130. return (*pLHS->ColumnMeta.pIndex) - (*pRHS->ColumnMeta.pIndex);
  131. }
  132. // sorted by table name and index
  133. int __cdecl
  134. CompTagMetas (const void * pTagMetaLHS, const void * pTagMetaRHS)
  135. {
  136. const tTAGMETARow *pLHS = static_cast<const tTAGMETARow *> (pTagMetaLHS );
  137. const tTAGMETARow *pRHS = static_cast<const tTAGMETARow *> (pTagMetaRHS );
  138. int iCmp = _wcsicmp (pLHS->pTable, pRHS->pTable);
  139. if (iCmp != 0)
  140. {
  141. return iCmp;
  142. }
  143. int iResult = (*pLHS->pColumnIndex) - (*pRHS->pColumnIndex);
  144. if (iResult != 0)
  145. {
  146. return iResult;
  147. }
  148. return (*pLHS->pValue) - (*pRHS->pValue);
  149. }
  150. CSchemaExtensions::CSchemaExtensions ()
  151. {
  152. m_paTableMetas = 0;
  153. m_cNrTables = 0;
  154. m_paColumnMetas = 0;
  155. m_cNrColumns = 0;
  156. m_paTags = 0;
  157. m_cNrTags = 0;
  158. m_pQueryCells = 0;
  159. m_cQueryCells = 0;
  160. m_wszBinFileName = 0;
  161. m_tszBinFilePath = 0;
  162. m_bBinFileLoaded = false;
  163. }
  164. CSchemaExtensions::~CSchemaExtensions()
  165. {
  166. if(m_bBinFileLoaded)
  167. {
  168. m_spIMbSchemaComp->ReleaseBinFileName(m_wszBinFileName);
  169. }
  170. delete [] m_paTableMetas;
  171. delete [] m_paColumnMetas;
  172. delete [] m_paTags;
  173. delete [] m_pQueryCells;
  174. delete [] m_wszBinFileName;
  175. delete [] m_tszBinFilePath;
  176. }
  177. HRESULT CSchemaExtensions::Initialize(bool i_bUseExtensions)
  178. {
  179. HRESULT hr = S_OK;
  180. ULONG cch = 0;
  181. InitializeSimpleTableDispenser();
  182. //
  183. // Get the dispenser
  184. //
  185. hr = GetSimpleTableDispenser (WSZ_PRODUCT_IIS, 0, &m_spDispenser);
  186. if(FAILED(hr))
  187. {
  188. DBGPRINTF((DBG_CONTEXT, "Could not get dispenser, hr=0x%x\n", hr));
  189. return hr;
  190. }
  191. //
  192. // Get the schema compiler interface from the dispenser which will help us
  193. // get the bin file name.
  194. //
  195. hr = m_spDispenser->QueryInterface(IID_IMetabaseSchemaCompiler,
  196. (LPVOID*)&m_spIMbSchemaComp);
  197. if(FAILED(hr))
  198. {
  199. DBGPRINTF((DBG_CONTEXT, "Couldn't get SchemaCompiler interface, hr=0x%x\n", hr));
  200. return hr;
  201. }
  202. //
  203. // Get the path of mbschema.xml
  204. //
  205. m_tszBinFilePath = new TCHAR[MAX_PATH+1];
  206. if(m_tszBinFilePath == NULL)
  207. {
  208. return E_OUTOFMEMORY;
  209. }
  210. hr = GetMetabasePath(m_tszBinFilePath);
  211. if(FAILED(hr))
  212. {
  213. DBGPRINTF((DBG_CONTEXT, "Couldn't get metabase path, hr=0x%x\n", hr));
  214. return hr;
  215. }
  216. //
  217. // Convert it to Unicode, Set Bin Path
  218. //
  219. #ifndef UNICODE
  220. // we want to convert an MBCS string in lpszA
  221. int nLen = MultiByteToWideChar(CP_ACP, 0,m_tszBinFilePath, -1, NULL, NULL);
  222. LPWSTR lpszW = new WCHAR[nLen];
  223. if(lpszW == NULL)
  224. {
  225. return E_OUTOFMEMORY;
  226. }
  227. if(MultiByteToWideChar(CP_ACP, 0, m_tszBinFilePath, -1, lpszW, nLen) == 0)
  228. {
  229. delete [] lpszW;
  230. hr = GetLastError();
  231. return HRESULT_FROM_WIN32(hr);
  232. }
  233. hr = m_spIMbSchemaComp->SetBinPath(lpszW);
  234. delete [] lpszW;
  235. #else
  236. hr = m_spIMbSchemaComp->SetBinPath(m_tszBinFilePath);
  237. #endif
  238. if(FAILED(hr))
  239. {
  240. return hr;
  241. }
  242. //
  243. // Get Bin FileName
  244. //
  245. hr = m_spIMbSchemaComp->GetBinFileName(NULL, &cch);
  246. if(FAILED(hr))
  247. {
  248. DBGPRINTF((DBG_CONTEXT, "Couldn't get schema bin filename size, hr=0x%x\n", hr));
  249. return hr;
  250. }
  251. m_wszBinFileName = new WCHAR[cch+1];
  252. if(m_wszBinFileName == NULL)
  253. {
  254. return E_OUTOFMEMORY;
  255. }
  256. hr = m_spIMbSchemaComp->GetBinFileName(m_wszBinFileName, &cch);
  257. if(FAILED(hr))
  258. {
  259. DBGPRINTF((DBG_CONTEXT, "Couldn't get schema bin filename, hr=0x%x\n", hr));
  260. return hr;
  261. }
  262. m_bBinFileLoaded = true;
  263. //
  264. // Set up the query cells
  265. //
  266. m_cQueryCells = 2;
  267. m_pQueryCells = new STQueryCell[m_cQueryCells];
  268. if(m_pQueryCells == NULL)
  269. {
  270. return E_OUTOFMEMORY;
  271. }
  272. if(i_bUseExtensions)
  273. {
  274. m_pQueryCells[0].pData = (LPVOID)m_wszBinFileName;
  275. }
  276. else
  277. {
  278. m_pQueryCells[0].pData = (LPVOID)NULL;
  279. }
  280. m_pQueryCells[0].eOperator = eST_OP_EQUAL;
  281. m_pQueryCells[0].iCell = iST_CELL_SCHEMAFILE;
  282. m_pQueryCells[0].dbType = DBTYPE_WSTR;
  283. m_pQueryCells[0].cbSize = 0;
  284. m_pQueryCells[1].pData = (void *) g_wszDatabaseName;
  285. m_pQueryCells[1].eOperator = eST_OP_EQUAL;
  286. m_pQueryCells[1].iCell = iCOLLECTION_META_Database;
  287. m_pQueryCells[1].dbType = DBTYPE_WSTR;
  288. m_pQueryCells[1].cbSize = 0;
  289. hr = GenerateIt();
  290. if(FAILED(hr))
  291. {
  292. DBGPRINTF((DBG_CONTEXT, "GenerateIt failed, hr=0x%x\n", hr));
  293. return hr;
  294. }
  295. return hr;
  296. }
  297. HRESULT CSchemaExtensions::GetMbSchemaTimeStamp(
  298. FILETIME* io_pFileTime) const
  299. {
  300. DBG_ASSERT(io_pFileTime != NULL);
  301. DBG_ASSERT(m_tszBinFilePath != NULL);
  302. HRESULT hr = S_OK;
  303. ULONG cchBinFilePath = _tcslen(m_tszBinFilePath);
  304. ULONG cchSchFileName = _tcslen(MD_SCHEMA_FILE_NAME);
  305. TCHAR* tszPathPlusName = new TCHAR[cchBinFilePath+1+cchSchFileName+1];
  306. TCHAR* tszCurPos = tszPathPlusName;
  307. if(tszPathPlusName == NULL)
  308. {
  309. return E_OUTOFMEMORY;
  310. }
  311. //
  312. // Copy the path
  313. //
  314. tszCurPos = tszPathPlusName;
  315. memcpy(tszCurPos, m_tszBinFilePath, sizeof(TCHAR)*(cchBinFilePath+1));
  316. //
  317. // Concat a \ if necessary, and the filename,
  318. //
  319. tszCurPos = tszPathPlusName + cchBinFilePath;
  320. if(m_tszBinFilePath[cchBinFilePath-1] != TEXT('\\'))
  321. {
  322. memcpy(tszCurPos, TEXT("\\"), sizeof(TCHAR)*2);
  323. tszCurPos++;
  324. }
  325. memcpy(tszCurPos, MD_SCHEMA_FILE_NAME, sizeof(TCHAR)*(cchSchFileName+1));
  326. //
  327. // Now get the file info
  328. //
  329. {
  330. WIN32_FIND_DATA FindFileData;
  331. memset(&FindFileData, 0, sizeof(WIN32_FIND_DATA));
  332. HANDLE hFindFile = FindFirstFile(tszPathPlusName, &FindFileData);
  333. if(hFindFile == INVALID_HANDLE_VALUE)
  334. {
  335. hr = GetLastError();
  336. hr = HRESULT_FROM_WIN32(hr);
  337. goto exit;
  338. }
  339. FindClose(hFindFile);
  340. //
  341. // Set out parameters if everything succeeded
  342. //
  343. memcpy(io_pFileTime, &FindFileData.ftLastWriteTime, sizeof(FILETIME));
  344. }
  345. exit:
  346. delete [] tszPathPlusName;
  347. return hr;
  348. }
  349. HRESULT
  350. CSchemaExtensions::GenerateIt ()
  351. {
  352. HRESULT hr = S_OK;
  353. hr = GetTables ();
  354. if (FAILED (hr))
  355. {
  356. return hr;
  357. }
  358. hr = GetColumns ();
  359. if (FAILED (hr))
  360. {
  361. return hr;
  362. }
  363. hr = GetTags ();
  364. if (FAILED (hr))
  365. {
  366. return hr;
  367. }
  368. hr = BuildInternalStructures ();
  369. if (FAILED (hr))
  370. {
  371. return hr;
  372. }
  373. return hr;
  374. }
  375. HRESULT
  376. CSchemaExtensions::GetTables ()
  377. {
  378. HRESULT hr = S_OK;
  379. ULONG one = 1;
  380. hr = m_spDispenser->GetTable (wszDATABASE_META, wszTABLE_TABLEMETA,
  381. m_pQueryCells, (void *)&one, eST_QUERYFORMAT_CELLS, 0, (void **) &m_spISTTableMeta);
  382. if (FAILED (hr))
  383. {
  384. return hr;
  385. }
  386. hr = m_spISTTableMeta->GetTableMeta (0, 0, &m_cNrTables, 0);
  387. if (FAILED (hr))
  388. {
  389. return hr;
  390. }
  391. if (m_cNrTables == 0)
  392. {
  393. return S_OK;
  394. }
  395. m_paTableMetas = new CTableMeta [m_cNrTables];
  396. if (m_paTableMetas == 0)
  397. {
  398. return E_OUTOFMEMORY;
  399. }
  400. for (ULONG idx =0; idx < m_cNrTables; ++idx)
  401. {
  402. hr = m_spISTTableMeta->GetColumnValues (idx, sizeof (tTABLEMETARow)/sizeof (ULONG *), 0, 0, (void **) &m_paTableMetas[idx].TableMeta);
  403. if (FAILED (hr))
  404. {
  405. return hr;
  406. }
  407. if (m_paTableMetas[idx].ColCount () > 0)
  408. {
  409. // set number of columns
  410. m_paTableMetas[idx].paColumns = new LPCColumnMeta[m_paTableMetas[idx].ColCount()];
  411. if (m_paTableMetas[idx].paColumns == 0)
  412. {
  413. return E_OUTOFMEMORY;
  414. }
  415. }
  416. }
  417. // and sort them by table name
  418. qsort (m_paTableMetas, m_cNrTables, sizeof (CTableMeta), CompTableMeta);
  419. return hr;
  420. }
  421. HRESULT
  422. CSchemaExtensions::GetColumns ()
  423. {
  424. HRESULT hr = S_OK;
  425. ULONG one = 1;
  426. hr = m_spDispenser->GetTable (wszDATABASE_META, wszTABLE_COLUMNMETA,
  427. m_pQueryCells, (void *)&one, eST_QUERYFORMAT_CELLS, 0, (void **) &m_spISTColumnMeta);
  428. if (FAILED (hr))
  429. {
  430. return hr;
  431. }
  432. hr = m_spISTColumnMeta->GetTableMeta (0, 0, &m_cNrColumns, 0);
  433. if (FAILED (hr))
  434. {
  435. return hr;
  436. }
  437. if (m_cNrColumns == 0)
  438. {
  439. return E_FAIL;
  440. }
  441. m_paColumnMetas = new CColumnMeta[m_cNrColumns];
  442. if (m_paColumnMetas == 0)
  443. {
  444. return E_OUTOFMEMORY;
  445. }
  446. ULONG acbSizes[cCOLUMNMETA_NumberOfColumns];
  447. for (ULONG idx =0; idx < m_cNrColumns; ++idx)
  448. {
  449. hr = m_spISTColumnMeta->GetColumnValues (idx, sizeof (tCOLUMNMETARow)/sizeof (ULONG *), 0, acbSizes, (void **) &m_paColumnMetas[idx].ColumnMeta);
  450. m_paColumnMetas[idx].cbDefaultValue = acbSizes[iCOLUMNMETA_DefaultValue];
  451. if (FAILED (hr))
  452. {
  453. return hr;
  454. }
  455. }
  456. qsort (m_paColumnMetas, m_cNrColumns, sizeof (CColumnMeta), CompColumnMetas);
  457. return hr;
  458. }
  459. HRESULT
  460. CSchemaExtensions::GetTags ()
  461. {
  462. HRESULT hr = S_OK;
  463. ULONG one = 1;
  464. hr = m_spDispenser->GetTable (wszDATABASE_META, wszTABLE_TAGMETA,
  465. m_pQueryCells, (void *)&one, eST_QUERYFORMAT_CELLS, 0, (void **) &m_spISTTagMeta);
  466. if (FAILED (hr))
  467. {
  468. return hr;
  469. }
  470. hr = m_spISTTagMeta->GetTableMeta (0, 0, &m_cNrTags, 0);
  471. if (FAILED (hr))
  472. {
  473. return hr;
  474. }
  475. if (m_cNrTags == 0)
  476. {
  477. return E_FAIL;
  478. }
  479. m_paTags = new tTAGMETARow[m_cNrTags];
  480. if (m_paTags == 0)
  481. {
  482. return E_OUTOFMEMORY;
  483. }
  484. for (ULONG idx =0; idx < m_cNrTags; ++idx)
  485. {
  486. hr = m_spISTTagMeta->GetColumnValues (idx, sizeof (tTAGMETARow)/sizeof (ULONG *), 0, 0, (void **) &m_paTags[idx]);
  487. if (FAILED (hr))
  488. {
  489. return hr;
  490. }
  491. }
  492. qsort (m_paTags, m_cNrTags, sizeof (tTAGMETARow), CompTagMetas);
  493. return hr;
  494. }
  495. HRESULT
  496. CSchemaExtensions::BuildInternalStructures ()
  497. {
  498. HRESULT hr = S_OK;
  499. // attach the tags to the tables
  500. ULONG idx = 0;
  501. while (idx < m_cNrTags)
  502. {
  503. // find the correct column
  504. CColumnMeta dummyColumnMeta;
  505. dummyColumnMeta.ColumnMeta.pTable = m_paTags[idx].pTable;
  506. dummyColumnMeta.ColumnMeta.pIndex = m_paTags[idx].pColumnIndex;
  507. // get column
  508. CColumnMeta *pColMeta = (CColumnMeta *) bsearch (&dummyColumnMeta,
  509. m_paColumnMetas,
  510. m_cNrColumns,
  511. sizeof (CColumnMeta),
  512. CompColumnMetas);
  513. DBG_ASSERT (pColMeta != NULL);
  514. DBG_ASSERT (wcscmp(pColMeta->ColumnMeta.pTable, m_paTags[idx].pTable) == 0 &&
  515. *pColMeta->ColumnMeta.pIndex == *m_paTags[idx].pColumnIndex);
  516. // get count
  517. ULONG iStartIdx = idx;
  518. pColMeta->cNrTags = 1;
  519. idx++; // skip over this element
  520. while ((idx < m_cNrTags) &&
  521. (wcscmp(pColMeta->ColumnMeta.pTable, m_paTags[idx].pTable) == 0) &&
  522. (*pColMeta->ColumnMeta.pIndex == *m_paTags[idx].pColumnIndex))
  523. {
  524. idx++;
  525. pColMeta->cNrTags += 1;
  526. }
  527. if (pColMeta->cNrTags > 0)
  528. {
  529. // allocate memory and copy the stuff
  530. pColMeta->paTags = new LPtTAGMETA[pColMeta->cNrTags];
  531. if (pColMeta->paTags == 0)
  532. {
  533. return E_OUTOFMEMORY;
  534. }
  535. for (ULONG tagIdx = 0; tagIdx < pColMeta->cNrTags; ++tagIdx)
  536. {
  537. pColMeta->paTags[tagIdx] = &m_paTags[iStartIdx + tagIdx];
  538. }
  539. }
  540. }
  541. // attach the columns to the tables
  542. for (idx=0; idx < m_cNrColumns; ++idx)
  543. {
  544. CTableMeta dummyTableMeta;
  545. dummyTableMeta.TableMeta.pInternalName = m_paColumnMetas[idx].ColumnMeta.pTable;
  546. // find table
  547. CTableMeta *pTableMeta = (CTableMeta *) bsearch (&dummyTableMeta, m_paTableMetas,
  548. m_cNrTables,
  549. sizeof (CTableMeta),
  550. CompTableMeta);
  551. DBG_ASSERT (pTableMeta != 0);
  552. DBG_ASSERT (wcscmp(pTableMeta->TableMeta.pInternalName, m_paColumnMetas[idx].ColumnMeta.pTable) == 0);
  553. // add Column to table
  554. ULONG iColumnIndex = *(m_paColumnMetas[idx].ColumnMeta.pIndex);
  555. DBG_ASSERT (iColumnIndex < pTableMeta->ColCount ());
  556. pTableMeta->paColumns[iColumnIndex] = &m_paColumnMetas[idx];
  557. }
  558. return hr;
  559. }
  560. CTableMeta* CSchemaExtensions::EnumTables(ULONG *io_idx)
  561. {
  562. DBG_ASSERT(io_idx != NULL);
  563. CTableMeta* pRet = NULL;
  564. while(1)
  565. {
  566. if(*io_idx < m_cNrTables)
  567. {
  568. pRet = &m_paTableMetas[*io_idx];
  569. if( _wcsicmp(pRet->TableMeta.pDatabase, g_wszDatabaseName) == 0 &&
  570. !(*pRet->TableMeta.pMetaFlags & fTABLEMETA_HIDDEN) )
  571. {
  572. *io_idx = 1 + *io_idx;
  573. return pRet;
  574. }
  575. else
  576. {
  577. *io_idx = 1 + *io_idx;
  578. }
  579. }
  580. else
  581. {
  582. *io_idx = 1 + *io_idx;
  583. return NULL;
  584. }
  585. }
  586. return NULL;
  587. }