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.

812 lines
21 KiB

  1. //----------------------------------------------------------------------------
  2. //
  3. // Microsoft Windows
  4. // Copyright (C) Microsoft Corporation, 1992 - 2000.
  5. //
  6. // File: cumicurs.cxx
  7. //
  8. // Contents: Contains the UMI cursor object implementation. There are 2
  9. // ways to use this object. One is to initialize with an
  10. // IADsContainer pointer and the other is to use an IUmiQuery obj.
  11. //
  12. // History: 03-16-00 SivaramR Created (in WinNT)
  13. // 03-28-00 AjayR modified for LDAP.
  14. //
  15. //----------------------------------------------------------------------------
  16. #include "ldap.hxx"
  17. //+---------------------------------------------------------------------------
  18. // Function: CUmiCursor::CUmiCursor
  19. //
  20. // Synopsis: Constructor
  21. //
  22. // Arguments: None
  23. //
  24. // Returns: N/A
  25. //
  26. // Modifies: N/A
  27. //
  28. //----------------------------------------------------------------------------
  29. CUmiCursor::CUmiCursor():
  30. _pPropMgr(NULL),
  31. _ulErrorStatus(0),
  32. _pIID(NULL),
  33. _pContainer(NULL),
  34. _pEnum(NULL),
  35. _pSearchHelper(NULL),
  36. _fQuery(FALSE)
  37. {
  38. }
  39. //+---------------------------------------------------------------------------
  40. // Function: CUmiCursor::~CUmiCursor
  41. //
  42. // Synopsis: Destructor
  43. //
  44. // Arguments: None
  45. //
  46. // Returns: N/A
  47. //
  48. // Modifies: N/A
  49. //
  50. //----------------------------------------------------------------------------
  51. CUmiCursor::~CUmiCursor()
  52. {
  53. if (_pPropMgr) {
  54. delete _pPropMgr;
  55. }
  56. if (_pContainer) {
  57. _pContainer->Release();
  58. }
  59. if (_pIID){
  60. FreeADsMem(_pIID);
  61. }
  62. if (_pEnum) {
  63. _pEnum->Release();
  64. }
  65. if (_pSearchHelper) {
  66. delete _pSearchHelper;
  67. }
  68. }
  69. //+---------------------------------------------------------------------------
  70. // Function: CUmiCursor::CreateCursor (static constructor overloaded)
  71. //
  72. // Synopsis: Creats a UmiCursor object in the container mode.
  73. //
  74. // Arguments: *pCont - Pointer to container we are enumerating.
  75. // iid - IID requested on returned object.
  76. // ppInterface - Ptr to return value.
  77. //
  78. // Returns: HRESULT - S_OK or any failure error code.
  79. //
  80. // Modifies: ppInterface to point to new cursor object.
  81. //
  82. //----------------------------------------------------------------------------
  83. HRESULT
  84. CUmiCursor::CreateCursor(
  85. IUnknown *pCont,
  86. REFIID iid,
  87. LPVOID *ppInterface
  88. )
  89. {
  90. CUmiCursor *pCursor = NULL;
  91. CPropertyManager *pPropMgr = NULL;
  92. IADsContainer *pContainer = NULL;
  93. HRESULT hr = S_OK;
  94. ADsAssert(ppInterface);
  95. ADsAssert(pCont);
  96. pCursor = new CUmiCursor();
  97. if (!pCursor) {
  98. BAIL_ON_FAILURE(hr = E_OUTOFMEMORY);
  99. }
  100. //
  101. // Initialize the various params on this object.
  102. //
  103. hr = CPropertyManager::CreatePropertyManager(
  104. (IUnknown *) pCursor,
  105. NULL, // IADs ptr
  106. NULL, // pCreds
  107. IntfPropsCursor,
  108. &pPropMgr
  109. );
  110. BAIL_ON_FAILURE(hr);
  111. hr = pCont->QueryInterface(
  112. IID_IADsContainer,
  113. (void **) &pContainer
  114. );
  115. BAIL_ON_FAILURE(hr);
  116. hr = pCursor->QueryInterface(iid, ppInterface);
  117. BAIL_ON_FAILURE(hr);
  118. //
  119. // Ref on this object is now 2, release the additional ref.
  120. //
  121. pCursor->Release();
  122. pCursor->_pContainer = pContainer;
  123. pCursor->_pPropMgr = pPropMgr;
  124. pCursor->_pIID = NULL;
  125. pCursor->_ulErrorStatus = 0;
  126. RRETURN(S_OK);
  127. error:
  128. if (pCursor) {
  129. delete pCursor;
  130. }
  131. if (pPropMgr) {
  132. delete pPropMgr;
  133. }
  134. RRETURN(hr);
  135. }
  136. //+---------------------------------------------------------------------------
  137. // Function: CUmiCursor::CreateCursor (static constructor overloaded).
  138. //
  139. // Synopsis: Creats a UmiCursor object in the query mode. Most of the
  140. // params are needed for creating the search helper object.
  141. //
  142. // Arguments: pQuery - Query being executed.
  143. // pConnection - Connection being used for query.
  144. // pUnk - ???.
  145. // pszADsPath - Path of object query is being executed on.
  146. // pszLdapServer - Server of object being queried.
  147. // pszLdapDn - Dn of the object being searched.
  148. // cCredentials - Credentials to use for query.
  149. // dwPort - Port being used for connection.
  150. // iid - Cursor iid requested
  151. //
  152. // Returns: HRESULT - S_OK or any failure error code.
  153. //
  154. // Modifies: ppInterface to point to new cursor object.
  155. //
  156. //----------------------------------------------------------------------------
  157. HRESULT
  158. CUmiCursor::CreateCursor(
  159. IUmiQuery *pQuery,
  160. IUmiConnection *pConnection,
  161. IUnknown *pUnk,
  162. LPCWSTR pszADsPath,
  163. LPCWSTR pszLdapServer,
  164. LPCWSTR pszLdapDn,
  165. CCredentials cCredentials,
  166. DWORD dwPort,
  167. REFIID iid,
  168. LPVOID *ppInterface
  169. )
  170. {
  171. CUmiCursor *pCursor = NULL;
  172. CPropertyManager *pPropMgr = NULL;
  173. HRESULT hr = S_OK;
  174. CUmiSearchHelper *pSearchHelper = NULL;
  175. ADsAssert(ppInterface);
  176. hr = CUmiSearchHelper::CreateSearchHelper(
  177. pQuery,
  178. pConnection,
  179. pUnk,
  180. pszADsPath,
  181. pszLdapServer,
  182. pszLdapDn,
  183. cCredentials,
  184. dwPort,
  185. &pSearchHelper
  186. );
  187. BAIL_ON_FAILURE(hr);
  188. pCursor = new CUmiCursor();
  189. if (!pCursor) {
  190. BAIL_ON_FAILURE(hr = E_OUTOFMEMORY);
  191. }
  192. //
  193. // Initialize the various params on this object.
  194. //
  195. hr = CPropertyManager::CreatePropertyManager(
  196. (IUnknown *) pCursor,
  197. NULL, // pIADs
  198. NULL, // pCreds
  199. IntfPropsCursor,
  200. &pPropMgr
  201. );
  202. BAIL_ON_FAILURE(hr);
  203. hr = pCursor->QueryInterface(iid, ppInterface);
  204. BAIL_ON_FAILURE(hr);
  205. //
  206. // Ref on this object is now 2, release the additional ref.
  207. //
  208. pCursor->Release();
  209. pCursor->_pSearchHelper = pSearchHelper;
  210. pCursor->_pPropMgr = pPropMgr;
  211. pCursor->_pIID = NULL;
  212. pCursor->_ulErrorStatus = 0;
  213. pCursor->_fQuery = TRUE;
  214. RRETURN(S_OK);
  215. error:
  216. if (pCursor) {
  217. delete pCursor;
  218. }
  219. if (pSearchHelper) {
  220. delete pSearchHelper;
  221. }
  222. if (pPropMgr) {
  223. delete pPropMgr;
  224. }
  225. RRETURN(hr);
  226. }
  227. //+---------------------------------------------------------------------------
  228. // Function: CUmiCursor::QueryInterface (IUnknown interface).
  229. //
  230. // Synopsis: Standard QueryInterface function.
  231. //
  232. // Arguments: Self explanatory.
  233. //
  234. // Returns: S_OK on success or any suitable error code.
  235. //
  236. // Modifies: *ppInterface to return interface pointer
  237. //
  238. //----------------------------------------------------------------------------
  239. STDMETHODIMP
  240. CUmiCursor::QueryInterface(
  241. REFIID iid,
  242. LPVOID *ppInterface
  243. )
  244. {
  245. if (!ppInterface)
  246. RRETURN(E_INVALIDARG);
  247. if (IsEqualIID(iid, IID_IUnknown)) {
  248. *ppInterface = (IUmiCursor *) this;
  249. }
  250. else if (IsEqualIID(iid, IID_IUmiBaseObject)) {
  251. *ppInterface = (IUmiCursor *) this;
  252. }
  253. else if (IsEqualIID(iid, IID_IUmiPropList)) {
  254. *ppInterface = (IUmiCursor *) this;
  255. }
  256. else if (IsEqualIID(iid, IID_IUmiCursor)) {
  257. *ppInterface = (IUmiCursor *) this;
  258. }
  259. else {
  260. *ppInterface = NULL;
  261. RRETURN(E_NOINTERFACE);
  262. }
  263. AddRef();
  264. RRETURN(S_OK);
  265. }
  266. //+---------------------------------------------------------------------------
  267. // Function: CUmiCursor::GetLastStatus (IUmiBaseObject method).
  268. //
  269. // Synopsis: Returns only numeric status code from the last operation.
  270. //
  271. // Arguments: uFlags - Only 0 is supported for now.
  272. // puSpecificStatus - Returns status/error code.
  273. // riid - Not used.
  274. // pStatusObj - NULL, not used currently.
  275. //
  276. // Returns: UMI_S_NO_ERROR on success. Error code otherwise.
  277. //
  278. // Modifies: *puSpecificStatus to return appropriate status code.
  279. //
  280. //----------------------------------------------------------------------------
  281. STDMETHODIMP
  282. CUmiCursor::GetLastStatus(
  283. ULONG uFlags,
  284. ULONG *puSpecificStatus,
  285. REFIID riid,
  286. LPVOID *pStatusObj
  287. )
  288. {
  289. if (uFlags != 0) {
  290. RRETURN(UMI_E_INVALID_FLAGS);
  291. }
  292. if (!puSpecificStatus) {
  293. RRETURN(E_INVALIDARG);
  294. }
  295. if (pStatusObj) {
  296. //
  297. // Should we error out ?
  298. //
  299. *pStatusObj = NULL;
  300. }
  301. *puSpecificStatus = _ulErrorStatus;
  302. RRETURN(UMI_S_NO_ERROR);
  303. }
  304. //+---------------------------------------------------------------------------
  305. // Function: CUmiCursor::GetInterfacePropList (IUmiBaseObject method).
  306. //
  307. // Synopsis: Returns a pointer to the interface property list for
  308. // cursor object.
  309. //
  310. // Arguments: uFlags - Flags, only 0 is supported.
  311. // ppPropList - Return value.
  312. //
  313. // Returns: UMI_S_NO_ERROR on success. Error code otherwise.
  314. //
  315. // Modifies: *pPropList changed to IUmiPropList pointer
  316. //
  317. //----------------------------------------------------------------------------
  318. STDMETHODIMP
  319. CUmiCursor::GetInterfacePropList(
  320. ULONG uFlags,
  321. IUmiPropList **ppPropList
  322. )
  323. {
  324. HRESULT hr = UMI_S_NO_ERROR;
  325. SetLastStatus(0);
  326. if (uFlags != 0) {
  327. RRETURN(UMI_E_INVALID_FLAGS);
  328. }
  329. if (!ppPropList) {
  330. RRETURN(E_INVALIDARG);
  331. }
  332. hr = _pPropMgr->QueryInterface(IID_IUmiPropList, (void **)ppPropList);
  333. if (FAILED(hr)) {
  334. SetLastStatus(hr);
  335. }
  336. RRETURN(hr);
  337. }
  338. //+---------------------------------------------------------------------------
  339. // Function: CUmiCursor::SetLastStatus (internal private helper routine).
  340. //
  341. // Synopsis: Sets the status of the last operation. If the status is one
  342. // of the pre-defined error codes, then the status is just set to
  343. // 0 since we are not adding any value by returning the same
  344. // status as the error code.
  345. //
  346. // Arguments: ulStatus - Status to be set.
  347. //
  348. // Returns: Nothing
  349. //
  350. // Modifies: Internal member status variable.
  351. //
  352. //----------------------------------------------------------------------------
  353. void
  354. CUmiCursor::SetLastStatus(ULONG ulStatus)
  355. {
  356. this->_ulErrorStatus = ulStatus;
  357. return;
  358. }
  359. //+---------------------------------------------------------------------------
  360. // Function: CUmiCursor::SetIID (IUmiCursor method).
  361. //
  362. // Synopsis: Sets the interface to be requested off each item returned by
  363. // the enumerator. Default is IID_IUmiObject.
  364. //
  365. // Arguments: riid - IID of interface to request.
  366. //
  367. // Returns: UMI_S_NO_ERROR on success. Error code otherwise.
  368. //
  369. // Modifies: Nothing.
  370. //
  371. //----------------------------------------------------------------------------
  372. STDMETHODIMP
  373. CUmiCursor::SetIID(
  374. REFIID riid
  375. )
  376. {
  377. SetLastStatus(0);
  378. if (_fQuery) {
  379. RRETURN(this->_pSearchHelper->SetIID(riid));
  380. }
  381. else {
  382. if (!_pIID){
  383. _pIID = (IID *) AllocADsMem(sizeof(IID));
  384. if (!_pIID){
  385. RRETURN(E_OUTOFMEMORY);
  386. }
  387. }
  388. memcpy(_pIID, &riid, sizeof(IID));
  389. }
  390. RRETURN(UMI_S_NO_ERROR);
  391. }
  392. //+---------------------------------------------------------------------------
  393. // Function: CUmiCursor::Reset (IUmiCursor method).
  394. //
  395. // Synopsis: Resets the enumerator to restart from begining (this is for
  396. // likely to return an error for the IADsContainer case).
  397. //
  398. // Arguments: N/A.
  399. //
  400. // Returns: UMI_S_NO_ERROR on success. Error code otherwise.
  401. //
  402. // Modifies: N/A.
  403. //
  404. //----------------------------------------------------------------------------
  405. STDMETHODIMP
  406. CUmiCursor::Reset(void)
  407. {
  408. HRESULT hr = E_NOTIMPL;
  409. RRETURN(hr);
  410. //
  411. // Rest of the code can be invoked when we have proper support.
  412. //
  413. SetLastStatus(0);
  414. if (!_fQuery) {
  415. //
  416. // it is possible that _pEnum may be NULL here if the user
  417. // called Reset before calling Next()
  418. //
  419. if (!_pEnum) {
  420. RRETURN(UMI_S_NO_ERROR);
  421. }
  422. hr = _pEnum->Reset();
  423. BAIL_ON_FAILURE(hr);
  424. hr = S_OK;
  425. }
  426. else {
  427. hr = _pSearchHelper->Reset();
  428. }
  429. error:
  430. if (FAILED(hr)) {
  431. SetLastStatus(hr);
  432. hr = MapHrToUmiError(hr);
  433. }
  434. RRETURN(hr);
  435. }
  436. //+---------------------------------------------------------------------------
  437. // Function: CUmiCursor::GetFilter (internal private helper routine).
  438. //
  439. // Synopsis: Gets the filter from the interface property cache. If the
  440. // interface property was not set, an emty variant is returned.
  441. //
  442. // Arguments: pvFilter - ptr to variant for return value.
  443. //
  444. // Returns: UMI_S_NO_ERROR on success. Error code otherwise.
  445. //
  446. // Modifies: *pvFilter to return the filter.
  447. //
  448. //----------------------------------------------------------------------------
  449. HRESULT
  450. CUmiCursor::GetFilter(VARIANT *pvFilter)
  451. {
  452. HRESULT hr = UMI_S_NO_ERROR;
  453. UMI_PROPERTY_VALUES *pUmiProp = NULL;
  454. LPWSTR *ppszFilters = NULL;
  455. DWORD dwNumFilters = 0;
  456. ADsAssert(pvFilter);
  457. VariantInit(pvFilter);
  458. hr = _pPropMgr->Get(
  459. L"__FILTER",
  460. 0,
  461. &pUmiProp
  462. );
  463. if (hr == UMI_E_NOT_FOUND) {
  464. //
  465. // interface property was not set. Return empty variant.
  466. //
  467. RRETURN(hr);
  468. }
  469. BAIL_ON_FAILURE(hr);
  470. ADsAssert(pUmiProp->pPropArray->uType == UMI_TYPE_LPWSTR);
  471. //
  472. // Make sure we have data back and that it is not just NULL.
  473. // We will not get back a NULL string but instead an array with
  474. // one element that is NULL. That is as good as no filter.
  475. //
  476. if (pUmiProp->pPropArray->uCount
  477. && pUmiProp->pPropArray->pUmiValue
  478. && pUmiProp->pPropArray->pUmiValue->pszStrValue[0]
  479. ) {
  480. //
  481. // Valid filter is present.
  482. //
  483. ppszFilters = pUmiProp->pPropArray->pUmiValue->pszStrValue;
  484. dwNumFilters = pUmiProp->pPropArray->uCount;
  485. hr = ADsBuildVarArrayStr(ppszFilters, dwNumFilters, pvFilter);
  486. BAIL_ON_FAILURE(hr);
  487. }
  488. else {
  489. hr = UMI_E_NOT_FOUND;
  490. }
  491. error:
  492. if (pUmiProp) {
  493. _pPropMgr->FreeMemory(0, pUmiProp);
  494. }
  495. RRETURN(hr);
  496. }
  497. //+---------------------------------------------------------------------------
  498. // Function: CUmiCursor::Next (IUmiCursor method).
  499. //
  500. // Synopsis: Returns the next "n" item(s) in the enumeration sequence.
  501. //
  502. // Arguments: uNumRequested - Number of items requested.
  503. // pNumReturned - Returns actual number of objects returned.
  504. // ppObjects - Array of interface pointers of size
  505. // *pNumReturned.
  506. //
  507. // Returns: UMI_S_NO_ERROR on success. Error code otherwise.
  508. //
  509. // Modifies: *pNumReturned to return the number of objects returned
  510. // *ppObjects to return the interface pointers
  511. //
  512. //----------------------------------------------------------------------------
  513. STDMETHODIMP
  514. CUmiCursor::Next(
  515. ULONG uNumRequested,
  516. ULONG *puNumReturned,
  517. LPVOID *ppObjects
  518. )
  519. {
  520. HRESULT hr = S_OK;;
  521. VARIANT vFilter, *pvResults = NULL;
  522. ULONG ulIndex = 0, uNumReturned = 0, uNumResults = 0;
  523. IDispatch *pDisp = NULL;
  524. IUnknown **pUnkArr = NULL, *pTmpUnk = NULL;
  525. VARIANT vOldFilter;
  526. BOOL fReplaceFilter = FALSE;
  527. SetLastStatus(0);
  528. VariantInit(&vOldFilter);
  529. if ( (!puNumReturned) || (!ppObjects) ){
  530. RRETURN(E_INVALIDARG);
  531. }
  532. *puNumReturned = 0;
  533. *ppObjects = NULL;
  534. //
  535. // If this is a query then we need to get the results from the
  536. // query object.
  537. //
  538. if (_fQuery) {
  539. hr = _pSearchHelper->Next(
  540. uNumRequested,
  541. puNumReturned,
  542. ppObjects
  543. );
  544. //
  545. // MapHrToUmiError
  546. //
  547. RRETURN(hr);
  548. }
  549. //
  550. // If we get here this is a container enumerate.
  551. //
  552. VariantInit(&vFilter);
  553. if (!_pEnum) {
  554. //
  555. // first call to Next()
  556. //
  557. ADsAssert(_pContainer);
  558. //
  559. // check if the user set a filter on the cursor
  560. //
  561. hr = GetFilter(&vFilter);
  562. if (SUCCEEDED(hr)) {
  563. //
  564. // We need to get the old filter to restore it.
  565. //
  566. hr = _pContainer->get_Filter(&vOldFilter);
  567. if (SUCCEEDED(hr)) {
  568. fReplaceFilter = TRUE;
  569. }
  570. //
  571. // We have a valid filter that we need to set.
  572. //
  573. hr = _pContainer->put_Filter(vFilter);
  574. }
  575. else if (hr == UMI_E_NOT_FOUND) {
  576. //
  577. // Reset error as this one is expected, anything else we bail.
  578. //
  579. hr = S_OK;
  580. }
  581. //
  582. // Catch either GetFilter failure or put_Filter failure.
  583. //
  584. BAIL_ON_FAILURE(hr);
  585. hr = _pContainer->get__NewEnum((IUnknown **) &_pEnum);
  586. //
  587. // Restore old filter irrespective of ECODE.
  588. //
  589. if (fReplaceFilter) {
  590. _pContainer->put_Filter(vOldFilter);
  591. }
  592. BAIL_ON_FAILURE(hr);
  593. }
  594. //
  595. // allocate memory for variants to return objects
  596. //
  597. pvResults = (VARIANT *) AllocADsMem(uNumRequested * sizeof(VARIANT));
  598. if (!pvResults) {
  599. BAIL_ON_FAILURE(hr = E_OUTOFMEMORY);
  600. }
  601. hr = _pEnum->Next(
  602. uNumRequested,
  603. pvResults,
  604. &uNumReturned
  605. );
  606. BAIL_ON_FAILURE(hr);
  607. if (!uNumReturned) {
  608. //
  609. // This will handle the S_FALSE case.
  610. //
  611. goto error;
  612. }
  613. //
  614. // allocate memory for array of interface pointers to return
  615. //
  616. pUnkArr = (IUnknown **) AllocADsMem(uNumReturned * sizeof(IUnknown *));
  617. if (!pUnkArr) {
  618. BAIL_ON_FAILURE(hr = E_OUTOFMEMORY);
  619. }
  620. //
  621. // convert the V_DISPATCH variants to the requested interface properties
  622. //
  623. for(ulIndex = 0; ulIndex < uNumReturned; ulIndex++) {
  624. pDisp = V_DISPATCH(&pvResults[ulIndex]);
  625. if (_pIID) {
  626. hr = pDisp->QueryInterface(*_pIID, (void **) &pTmpUnk);
  627. }
  628. else {
  629. hr = pDisp->QueryInterface(IID_IUmiObject, (void **) &pTmpUnk);
  630. }
  631. //
  632. // Is this really correct ?
  633. //
  634. if (FAILED(hr)) {
  635. continue;
  636. }
  637. pUnkArr[uNumResults] = pTmpUnk;
  638. uNumResults++;
  639. }
  640. *puNumReturned = uNumResults;
  641. if (uNumResults > 0) {
  642. *ppObjects = pUnkArr;
  643. }
  644. else {
  645. FreeADsMem(pUnkArr);
  646. }
  647. error:
  648. VariantClear(&vFilter);
  649. VariantClear(&vOldFilter);
  650. if (pvResults) {
  651. for(ulIndex = 0; ulIndex < uNumReturned; ulIndex++) {
  652. VariantClear(&pvResults[ulIndex]);
  653. }
  654. FreeADsMem(pvResults);
  655. }
  656. if (FAILED(hr)) {
  657. SetLastStatus(hr);
  658. hr = MapHrToUmiError(hr);
  659. }
  660. RRETURN(hr);
  661. }
  662. //+---------------------------------------------------------------------------
  663. // Function: Count
  664. //
  665. // Synopsis: Counts the number of results returned by the enumerator.
  666. // Not implemented currently.
  667. //
  668. // Arguments:
  669. //
  670. // None
  671. //
  672. // Returns: E_NOTIMPL for now.
  673. //
  674. // Modifies: Nothing.
  675. //
  676. //----------------------------------------------------------------------------
  677. STDMETHODIMP CUmiCursor::Count(
  678. ULONG *puNumObjects
  679. )
  680. {
  681. SetLastStatus(0);
  682. RRETURN(E_NOTIMPL);
  683. }
  684. //+---------------------------------------------------------------------------
  685. // Function: Previous
  686. //
  687. // Synopsis: Returnss the previous object returned by the enumerator.
  688. // Not implemented currently.
  689. //
  690. // Arguments:
  691. //
  692. // None
  693. //
  694. // Returns: E_NOTIMPL for now.
  695. //
  696. // Modifies: Nothing.
  697. //
  698. //----------------------------------------------------------------------------
  699. STDMETHODIMP CUmiCursor::Previous(
  700. ULONG uFlags,
  701. LPVOID *pObj
  702. )
  703. {
  704. SetLastStatus(0);
  705. RRETURN(E_NOTIMPL);
  706. }