Fixed typo.
[wine] / ole / stg_stream.c
1 /*
2  * Compound Storage (32 bit version)
3  * Stream implementation
4  *
5  * This file contains the implementation of the stream interface
6  * for streams contained in a compound storage.
7  *
8  * Copyright 1999 Francis Beaudet
9  * Copyright 1999 Thuy Nguyen
10  */
11 #include <assert.h>
12 #include <stdlib.h>
13 #include <stdio.h>
14 #include <string.h>
15
16 #include "winbase.h"
17 #include "winerror.h"
18 #include "debugtools.h"
19 #include "wine/obj_storage.h"
20
21 #include "storage32.h"
22
23 DEFAULT_DEBUG_CHANNEL(storage)
24
25
26 /*
27  * Virtual function table for the StgStreamImpl class.
28  */
29 static ICOM_VTABLE(IStream) StgStreamImpl_Vtbl =
30 {
31     ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
32     StgStreamImpl_QueryInterface,
33     StgStreamImpl_AddRef,
34     StgStreamImpl_Release,
35     StgStreamImpl_Read,
36     StgStreamImpl_Write,
37     StgStreamImpl_Seek,
38     StgStreamImpl_SetSize,
39     StgStreamImpl_CopyTo,
40     StgStreamImpl_Commit,
41     StgStreamImpl_Revert,
42     StgStreamImpl_LockRegion,
43     StgStreamImpl_UnlockRegion,
44     StgStreamImpl_Stat,
45     StgStreamImpl_Clone
46 };
47
48 /******************************************************************************
49 ** StgStreamImpl implementation
50 */
51
52 /***
53  * This is the constructor for the StgStreamImpl class.
54  *
55  * Params:
56  *    parentStorage - Pointer to the storage that contains the stream to open
57  *    ownerProperty - Index of the property that points to this stream.
58  */
59 StgStreamImpl* StgStreamImpl_Construct(
60                 StorageBaseImpl* parentStorage,
61                 ULONG              ownerProperty)
62 {
63   StgStreamImpl* newStream;
64
65   newStream = HeapAlloc(GetProcessHeap(), 0, sizeof(StgStreamImpl));
66   
67   if (newStream!=0)
68   {
69     /*
70      * Set-up the virtual function table and reference count.
71      */
72     newStream->lpvtbl = &StgStreamImpl_Vtbl;
73     newStream->ref    = 0;
74     
75     /*
76      * We want to nail-down the reference to the storage in case the
77      * stream out-lives the storage in the client application.
78      */
79     newStream->parentStorage = parentStorage;
80     IStorage_AddRef((IStorage*)newStream->parentStorage);
81     
82     newStream->ownerProperty = ownerProperty;
83     
84     /*
85      * Start the stream at the begining.
86      */
87     newStream->currentPosition.HighPart = 0;
88     newStream->currentPosition.LowPart = 0;
89     
90     /*
91      * Initialize the rest of the data.
92      */
93     newStream->streamSize.HighPart = 0;
94     newStream->streamSize.LowPart  = 0;
95     newStream->bigBlockChain       = 0;
96     newStream->smallBlockChain     = 0;
97     
98     /*
99      * Read the size from the property and determine if the blocks forming
100      * this stream are large or small.
101      */
102     StgStreamImpl_OpenBlockChain(newStream);
103   }
104   
105   return newStream;
106 }
107
108 /***
109  * This is the destructor of the StgStreamImpl class.
110  *
111  * This method will clean-up all the resources used-up by the given StgStreamImpl 
112  * class. The pointer passed-in to this function will be freed and will not
113  * be valid anymore.
114  */
115 void StgStreamImpl_Destroy(StgStreamImpl* This)
116 {
117   TRACE("(%p)\n", This);
118
119   /*
120    * Release the reference we are holding on the parent storage.
121    */
122   IStorage_Release((IStorage*)This->parentStorage);
123   This->parentStorage = 0;
124
125   /*
126    * Make sure we clean-up the block chain stream objects that we were using.
127    */
128   if (This->bigBlockChain != 0)
129   {
130     BlockChainStream_Destroy(This->bigBlockChain);
131     This->bigBlockChain = 0;
132   }
133
134   if (This->smallBlockChain != 0)
135   {
136     SmallBlockChainStream_Destroy(This->smallBlockChain);
137     This->smallBlockChain = 0;
138   }
139
140   /*
141    * Finally, free the memory used-up by the class.
142    */
143   HeapFree(GetProcessHeap(), 0, This);  
144 }
145
146 /***
147  * This implements the IUnknown method QueryInterface for this
148  * class
149  */
150 HRESULT WINAPI StgStreamImpl_QueryInterface(
151                   IStream*     iface,
152                   REFIID         riid,        /* [in] */          
153                   void**         ppvObject)   /* [iid_is][out] */ 
154 {
155   StgStreamImpl* const This=(StgStreamImpl*)iface;
156
157   /*
158    * Perform a sanity check on the parameters.
159    */
160   if (ppvObject==0)
161     return E_INVALIDARG;
162   
163   /*
164    * Initialize the return parameter.
165    */
166   *ppvObject = 0;
167   
168   /*
169    * Compare the riid with the interface IDs implemented by this object.
170    */
171   if (memcmp(&IID_IUnknown, riid, sizeof(IID_IUnknown)) == 0) 
172   {
173     *ppvObject = (IStream*)This;
174   }
175   else if (memcmp(&IID_IStream, riid, sizeof(IID_IStream)) == 0) 
176   {
177     *ppvObject = (IStream*)This;
178   }
179   
180   /*
181    * Check that we obtained an interface.
182    */
183   if ((*ppvObject)==0)
184     return E_NOINTERFACE;
185   
186   /*
187    * Query Interface always increases the reference count by one when it is
188    * successful
189    */
190   StgStreamImpl_AddRef(iface);
191   
192   return S_OK;;
193 }
194
195 /***
196  * This implements the IUnknown method AddRef for this
197  * class
198  */
199 ULONG WINAPI StgStreamImpl_AddRef(
200                 IStream* iface)
201 {
202   StgStreamImpl* const This=(StgStreamImpl*)iface;
203
204   This->ref++;
205   
206   return This->ref;
207 }
208
209 /***
210  * This implements the IUnknown method Release for this
211  * class
212  */
213 ULONG WINAPI StgStreamImpl_Release(
214                 IStream* iface)
215 {
216   StgStreamImpl* const This=(StgStreamImpl*)iface;
217
218   ULONG newRef;
219   
220   This->ref--;
221   
222   newRef = This->ref;
223   
224   /*
225    * If the reference count goes down to 0, perform suicide.
226    */
227   if (newRef==0)
228   {
229     StgStreamImpl_Destroy(This);
230   }
231   
232   return newRef;
233 }
234
235 /***
236  * This method will open the block chain pointed by the property
237  * that describes the stream.
238  * If the stream's size is null, no chain is opened.
239  */
240 void StgStreamImpl_OpenBlockChain(
241         StgStreamImpl* This)
242 {
243   StgProperty    curProperty;
244   BOOL         readSucessful;
245
246   /*
247    * Make sure no old object is staying behind.
248    */
249   if (This->smallBlockChain != 0)
250   {
251     SmallBlockChainStream_Destroy(This->smallBlockChain);
252     This->smallBlockChain = 0;
253   }
254
255   if (This->bigBlockChain != 0)
256   {
257     BlockChainStream_Destroy(This->bigBlockChain);
258     This->bigBlockChain = 0;
259   }
260
261   /*
262    * Read the information from the property.
263    */
264   readSucessful = StorageImpl_ReadProperty(This->parentStorage->ancestorStorage,
265                                              This->ownerProperty,
266                                              &curProperty);
267   
268   if (readSucessful)
269   {
270     This->streamSize = curProperty.size;
271     
272     /*
273      * This code supports only streams that are <32 bits in size.
274      */
275     assert(This->streamSize.HighPart == 0);
276     
277     if(curProperty.startingBlock == BLOCK_END_OF_CHAIN)
278     {
279       assert( (This->streamSize.HighPart == 0) && (This->streamSize.LowPart == 0) );
280     }
281     else
282     {
283       if ( (This->streamSize.HighPart == 0) &&
284            (This->streamSize.LowPart < LIMIT_TO_USE_SMALL_BLOCK) )
285       {
286         This->smallBlockChain = SmallBlockChainStream_Construct(
287                                                                 This->parentStorage->ancestorStorage,   
288                                                                 This->ownerProperty);
289       }
290       else
291       {
292         This->bigBlockChain = BlockChainStream_Construct(
293                                                          This->parentStorage->ancestorStorage,
294                                                          NULL,
295                                                          This->ownerProperty);
296       }
297     }
298   }
299 }
300
301 /***
302  * This method is part of the ISequentialStream interface.
303  *
304  * If reads a block of information from the stream at the current
305  * position. It then moves the current position at the end of the
306  * read block
307  *
308  * See the documentation of ISequentialStream for more info.
309  */
310 HRESULT WINAPI StgStreamImpl_Read( 
311                   IStream*     iface,
312                   void*          pv,        /* [length_is][size_is][out] */
313                   ULONG          cb,        /* [in] */                     
314                   ULONG*         pcbRead)   /* [out] */                    
315 {
316   StgStreamImpl* const This=(StgStreamImpl*)iface;
317
318   ULONG bytesReadBuffer;
319   ULONG bytesToReadFromBuffer;
320
321   TRACE("(%p, %p, %ld, %p)\n",
322         iface, pv, cb, pcbRead);
323
324   /* 
325    * If the caller is not interested in the nubmer of bytes read,
326    * we use another buffer to avoid "if" statements in the code.
327    */
328   if (pcbRead==0)
329     pcbRead = &bytesReadBuffer;
330   
331   /*
332    * Using the known size of the stream, calculate the number of bytes
333    * to read from the block chain
334    */
335   bytesToReadFromBuffer = MIN( This->streamSize.LowPart - This->currentPosition.LowPart, cb);
336   
337   /*
338    * Depending on the type of chain that was opened when the stream was constructed,
339    * we delegate the work to the method that read the block chains.
340    */
341   if (This->smallBlockChain!=0)
342   {
343     SmallBlockChainStream_ReadAt(This->smallBlockChain,
344                                  This->currentPosition,
345                                  bytesToReadFromBuffer,
346                                  pv,
347                                  pcbRead);
348     
349   }
350   else if (This->bigBlockChain!=0)
351   {
352     BlockChainStream_ReadAt(This->bigBlockChain,
353                             This->currentPosition,
354                             bytesToReadFromBuffer,
355                             pv,
356                             pcbRead);
357   }
358   else
359   {
360     /*
361      * Small and big block chains are both NULL. This case will happen
362      * when a stream starts with BLOCK_END_OF_CHAIN and has size zero.
363      */
364
365     *pcbRead = 0;
366     return S_OK;
367   }
368
369   /*
370    * We should always be able to read the proper amount of data from the
371    * chain.
372    */
373   assert(bytesToReadFromBuffer == *pcbRead);
374
375   /*
376    * Advance the pointer for the number of positions read.
377    */
378   This->currentPosition.LowPart += *pcbRead;
379   
380   /*
381    * The function returns S_OK if the buffer was filled completely
382    * it returns S_FALSE if the end of the stream is reached before the
383    * buffer is filled
384    */
385   if(*pcbRead == cb)
386     return S_OK;
387   
388   return S_FALSE;
389 }
390         
391 /***
392  * This method is part of the ISequentialStream interface.
393  *
394  * It writes a block of information to the stream at the current
395  * position. It then moves the current position at the end of the
396  * written block. If the stream is too small to fit the block,
397  * the stream is grown to fit.
398  *
399  * See the documentation of ISequentialStream for more info.
400  */
401 HRESULT WINAPI StgStreamImpl_Write(
402                   IStream*     iface,
403                   const void*    pv,          /* [size_is][in] */ 
404                   ULONG          cb,          /* [in] */          
405                   ULONG*         pcbWritten)  /* [out] */         
406 {
407   StgStreamImpl* const This=(StgStreamImpl*)iface;
408
409   ULARGE_INTEGER newSize;
410   ULONG bytesWritten = 0;
411
412   TRACE("(%p, %p, %ld, %p)\n",
413         iface, pv, cb, pcbWritten);
414   
415   /*
416    * If the caller is not interested in the number of bytes written,
417    * we use another buffer to avoid "if" statements in the code.
418    */
419   if (pcbWritten == 0)
420     pcbWritten = &bytesWritten;
421   
422   /*
423    * Initialize the out parameter
424    */
425   *pcbWritten = 0;
426
427   if (cb == 0)
428   {
429     return S_OK;
430   }
431   else
432   {
433     newSize.HighPart = 0;
434     newSize.LowPart = This->currentPosition.LowPart + cb;
435   }
436   
437   /*
438    * Verify if we need to grow the stream
439    */
440   if (newSize.LowPart > This->streamSize.LowPart)
441   {
442     /* grow stream */
443     IStream_SetSize(iface, newSize);
444   }
445   
446   /*
447    * Depending on the type of chain that was opened when the stream was constructed,
448    * we delegate the work to the method that readwrites to the block chains.
449    */
450   if (This->smallBlockChain!=0)
451   {
452     SmallBlockChainStream_WriteAt(This->smallBlockChain,
453                                   This->currentPosition,
454                                   cb,
455                                   pv,
456                                   pcbWritten);
457     
458   }
459   else if (This->bigBlockChain!=0)
460   {
461     BlockChainStream_WriteAt(This->bigBlockChain,
462                              This->currentPosition,
463                              cb,
464                              pv,
465                              pcbWritten);
466   }
467   else
468     assert(FALSE);
469   
470   /*
471    * Advance the position pointer for the number of positions written.
472    */
473   This->currentPosition.LowPart += *pcbWritten;
474   
475   return S_OK;
476 }
477
478 /***
479  * This method is part of the IStream interface.
480  *
481  * It will move the current stream pointer according to the parameters
482  * given.
483  *
484  * See the documentation of IStream for more info.
485  */        
486 HRESULT WINAPI StgStreamImpl_Seek( 
487                   IStream*      iface,
488                   LARGE_INTEGER   dlibMove,         /* [in] */ 
489                   DWORD           dwOrigin,         /* [in] */ 
490                   ULARGE_INTEGER* plibNewPosition) /* [out] */
491 {
492   StgStreamImpl* const This=(StgStreamImpl*)iface;
493
494   ULARGE_INTEGER newPosition;
495
496   TRACE("(%p, %ld, %ld, %p)\n",
497         iface, dlibMove.LowPart, dwOrigin, plibNewPosition);
498
499   /* 
500    * The caller is allowed to pass in NULL as the new position return value.
501    * If it happens, we assign it to a dynamic variable to avoid special cases
502    * in the code below.
503    */
504   if (plibNewPosition == 0)
505   {
506     plibNewPosition = &newPosition;
507   }
508
509   /*
510    * The file pointer is moved depending on the given "function"
511    * parameter.
512    */
513   switch (dwOrigin)
514   {
515     case STREAM_SEEK_SET:
516       plibNewPosition->HighPart = 0;
517       plibNewPosition->LowPart  = 0;
518       break;
519     case STREAM_SEEK_CUR:
520       *plibNewPosition = This->currentPosition;
521       break;
522     case STREAM_SEEK_END:
523       *plibNewPosition = This->streamSize;
524       break;
525     default:
526       return STG_E_INVALIDFUNCTION;
527   }
528
529   /*
530    * We don't support files with offsets of 64 bits.
531    */
532   assert(dlibMove.HighPart == 0);
533
534   /*
535    * Check if we end-up before the beginning of the file. That should trigger an
536    * error.
537    */
538   if ( (dlibMove.LowPart<0) && (plibNewPosition->LowPart < (ULONG)(-dlibMove.LowPart)) )
539   {
540     /*
541      * I don't know what error to send there.
542      */
543     return E_FAIL;
544   }
545
546   /*
547    * Move the actual file pointer
548    * If the file pointer ends-up after the end of the stream, the next Write operation will
549    * make the file larger. This is how it is documented.
550    */
551   plibNewPosition->LowPart += dlibMove.LowPart;
552   This->currentPosition = *plibNewPosition;
553  
554   return S_OK;
555 }
556
557 /***
558  * This method is part of the IStream interface.
559  *
560  * It will change the size of a stream.
561  *
562  * TODO: Switch from small blocks to big blocks and vice versa.
563  *
564  * See the documentation of IStream for more info.
565  */
566 HRESULT WINAPI StgStreamImpl_SetSize( 
567                                      IStream*      iface,
568                                      ULARGE_INTEGER  libNewSize)   /* [in] */ 
569 {
570   StgStreamImpl* const This=(StgStreamImpl*)iface;
571
572   StgProperty    curProperty;
573   BOOL         Success;
574
575   TRACE("(%p, %ld)\n", iface, libNewSize.LowPart);
576
577   /*
578    * As documented.
579    */
580   if (libNewSize.HighPart != 0)
581     return STG_E_INVALIDFUNCTION;
582   
583   if (This->streamSize.LowPart == libNewSize.LowPart)
584     return S_OK;
585
586   /*
587    * This will happen if we're creating a stream
588    */
589   if ((This->smallBlockChain == 0) && (This->bigBlockChain == 0))
590   {
591     if (libNewSize.LowPart < LIMIT_TO_USE_SMALL_BLOCK)
592     {
593       This->smallBlockChain = SmallBlockChainStream_Construct(
594                                     This->parentStorage->ancestorStorage,
595                                     This->ownerProperty);
596     }
597     else
598     {
599       This->bigBlockChain = BlockChainStream_Construct(
600                                 This->parentStorage->ancestorStorage,
601                                 NULL,
602                                 This->ownerProperty);
603     }
604   }
605
606   /*
607    * Read this stream's property to see if it's small blocks or big blocks
608    */
609   Success = StorageImpl_ReadProperty(This->parentStorage->ancestorStorage,
610                                        This->ownerProperty,
611                                        &curProperty); 
612   /*
613    * Determine if we have to switch from small to big blocks or vice versa
614    */  
615   if ( (This->smallBlockChain!=0) && 
616        (curProperty.size.LowPart < LIMIT_TO_USE_SMALL_BLOCK) )
617   {
618     if (libNewSize.LowPart >= LIMIT_TO_USE_SMALL_BLOCK)
619     {
620       /*
621        * Transform the small block chain into a big block chain
622        */
623       This->bigBlockChain = Storage32Impl_SmallBlocksToBigBlocks(
624                                 This->parentStorage->ancestorStorage,
625                                 &This->smallBlockChain);
626     }
627   }
628
629   if (This->smallBlockChain!=0)
630   {
631     Success = SmallBlockChainStream_SetSize(This->smallBlockChain, libNewSize);
632   }
633   else
634   {
635     Success = BlockChainStream_SetSize(This->bigBlockChain, libNewSize);
636   }
637
638   /*
639    * Write to the property the new information about this stream
640    */
641   Success = StorageImpl_ReadProperty(This->parentStorage->ancestorStorage,
642                                        This->ownerProperty,
643                                        &curProperty);
644
645   curProperty.size.HighPart = libNewSize.HighPart;
646   curProperty.size.LowPart = libNewSize.LowPart;
647   
648   if (Success)
649   {
650     StorageImpl_WriteProperty(This->parentStorage->ancestorStorage,
651                                 This->ownerProperty,
652                                 &curProperty);
653   }
654   
655   This->streamSize = libNewSize;
656   
657   return S_OK;
658 }
659         
660 /***
661  * This method is part of the IStream interface.
662  *
663  * It will copy the 'cb' Bytes to 'pstm' IStream.
664  *
665  * See the documentation of IStream for more info.
666  */
667 HRESULT WINAPI StgStreamImpl_CopyTo( 
668                                     IStream*      iface,
669                                     IStream*      pstm,         /* [unique][in] */ 
670                                     ULARGE_INTEGER  cb,           /* [in] */         
671                                     ULARGE_INTEGER* pcbRead,      /* [out] */        
672                                     ULARGE_INTEGER* pcbWritten)   /* [out] */        
673 {
674   HRESULT        hr = S_OK;
675   BYTE           tmpBuffer[128];
676   ULONG          bytesRead, bytesWritten, copySize;
677   ULARGE_INTEGER totalBytesRead;
678   ULARGE_INTEGER totalBytesWritten;
679
680   TRACE("(%p, %p, %ld, %p, %p)\n", 
681         iface, pstm, cb.LowPart, pcbRead, pcbWritten);
682
683   /*
684    * Sanity check
685    */
686   if ( pstm == 0 )
687     return STG_E_INVALIDPOINTER;
688
689   totalBytesRead.LowPart = totalBytesRead.HighPart = 0;
690   totalBytesWritten.LowPart = totalBytesWritten.HighPart = 0;
691
692   /*
693    * use stack to store data temporarly
694    * there is surely more performant way of doing it, for now this basic
695    * implementation will do the job
696    */
697   while ( cb.LowPart > 0 )
698   {
699     if ( cb.LowPart >= 128 )
700       copySize = 128;
701     else
702       copySize = cb.LowPart;
703     
704     IStream_Read(iface, tmpBuffer, copySize, &bytesRead);
705
706     totalBytesRead.LowPart += bytesRead;
707     
708     IStream_Write(pstm, tmpBuffer, bytesRead, &bytesWritten);
709
710     totalBytesWritten.LowPart += bytesWritten;
711
712     /*
713      * Check that read & write operations were succesfull
714      */
715     if (bytesRead != bytesWritten)
716     {
717       hr = STG_E_MEDIUMFULL;
718       break;
719     }
720     
721     if (bytesRead!=copySize)
722       cb.LowPart = 0;
723     else
724       cb.LowPart -= bytesRead;
725   }
726
727   /*
728    * Update number of bytes read and written
729    */
730   if (pcbRead)
731   {
732     pcbRead->LowPart = totalBytesRead.LowPart;
733     pcbRead->HighPart = totalBytesRead.HighPart;
734   }
735
736   if (pcbWritten)
737   {
738     pcbWritten->LowPart = totalBytesWritten.LowPart;
739     pcbWritten->HighPart = totalBytesWritten.HighPart;
740   }
741   return hr;
742 }
743
744 /***
745  * This method is part of the IStream interface.
746  *
747  * For streams contained in structured storages, this method
748  * does nothing. This is what the documentation tells us.
749  *
750  * See the documentation of IStream for more info.
751  */        
752 HRESULT WINAPI StgStreamImpl_Commit( 
753                   IStream*      iface,
754                   DWORD           grfCommitFlags)  /* [in] */ 
755 {
756   return S_OK;
757 }
758
759 /***
760  * This method is part of the IStream interface.
761  *
762  * For streams contained in structured storages, this method
763  * does nothing. This is what the documentation tells us.
764  *
765  * See the documentation of IStream for more info.
766  */        
767 HRESULT WINAPI StgStreamImpl_Revert( 
768                   IStream* iface)
769 {
770   return S_OK;
771 }
772
773 HRESULT WINAPI StgStreamImpl_LockRegion( 
774                                         IStream*     iface,
775                                         ULARGE_INTEGER libOffset,   /* [in] */ 
776                                         ULARGE_INTEGER cb,          /* [in] */ 
777                                         DWORD          dwLockType)  /* [in] */ 
778 {
779   FIXME("not implemented!\n");
780   return E_NOTIMPL;
781 }
782
783 HRESULT WINAPI StgStreamImpl_UnlockRegion( 
784                                           IStream*     iface,
785                                           ULARGE_INTEGER libOffset,   /* [in] */ 
786                                           ULARGE_INTEGER cb,          /* [in] */ 
787                                           DWORD          dwLockType)  /* [in] */ 
788 {
789   FIXME("not implemented!\n");
790   return E_NOTIMPL;
791 }
792
793 /***
794  * This method is part of the IStream interface.
795  *
796  * This method returns information about the current
797  * stream.
798  *
799  * See the documentation of IStream for more info.
800  */        
801 HRESULT WINAPI StgStreamImpl_Stat( 
802                   IStream*     iface,
803                   STATSTG*       pstatstg,     /* [out] */
804                   DWORD          grfStatFlag)  /* [in] */ 
805 {
806   StgStreamImpl* const This=(StgStreamImpl*)iface;
807
808   StgProperty    curProperty;
809   BOOL         readSucessful;
810   
811   /*
812    * Read the information from the property.
813    */
814   readSucessful = StorageImpl_ReadProperty(This->parentStorage->ancestorStorage,
815                                              This->ownerProperty,
816                                              &curProperty);
817   
818   if (readSucessful)
819   {
820     StorageUtl_CopyPropertyToSTATSTG(pstatstg, 
821                                      &curProperty, 
822                                      grfStatFlag);
823     
824     return S_OK;
825   }
826   
827   return E_FAIL;
828 }
829         
830 HRESULT WINAPI StgStreamImpl_Clone( 
831                                    IStream*     iface,
832                                    IStream**    ppstm) /* [out] */ 
833 {
834   FIXME("not implemented!\n");
835   return E_NOTIMPL;
836 }