Leaked source code of windows server 2003
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.

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