• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // MultiOutStream.cpp
2 
3 #include "StdAfx.h"
4 
5 // #define DEBUG_VOLUMES
6 
7 #ifdef DEBUG_VOLUMES
8 #include <stdio.h>
9   #define PRF(x) x;
10 #else
11   #define PRF(x)
12 #endif
13 
14 #include "../../Common/ComTry.h"
15 
16 #include "../../Windows/FileDir.h"
17 #include "../../Windows/FileFind.h"
18 #include "../../Windows/System.h"
19 
20 #include "MultiOutStream.h"
21 
22 using namespace NWindows;
23 using namespace NFile;
24 using namespace NDir;
25 
26 static const unsigned k_NumVols_MAX = k_VectorSizeMax - 1;
27       // 2; // for debug
28 
29 /*
30 #define UPDATE_HRES(hres, x) \
31   { const HRESULT res2 = (x); if (hres == SZ_OK) hres = res2; }
32 */
33 
Destruct()34 HRESULT CMultiOutStream::Destruct()
35 {
36   COM_TRY_BEGIN
37   HRESULT hres = S_OK;
38   HRESULT hres3 = S_OK;
39 
40   while (!Streams.IsEmpty())
41   {
42     try
43     {
44       HRESULT hres2;
45       if (NeedDelete)
46       {
47         /* we could call OptReOpen_and_SetSize() to test that we try to delete correct file,
48            but we cannot guarantee that (RealSize) will be correct after Write() or another failures.
49            And we still want to delete files even for such cases.
50            So we don't check for OptReOpen_and_SetSize() here: */
51         // if (OptReOpen_and_SetSize(Streams.Size() - 1, 0) == S_OK)
52         hres2 = CloseStream_and_DeleteFile(Streams.Size() - 1);
53       }
54       else
55       {
56         hres2 = CloseStream(Streams.Size() - 1);
57       }
58       if (hres == S_OK)
59         hres = hres2;
60     }
61     catch(...)
62     {
63       hres3 = E_OUTOFMEMORY;
64     }
65 
66     {
67       /* Stream was released in CloseStream_*() above already, and it was removed from linked list
68          it's some unexpected case, if Stream is still attached here.
69          So the following code is optional: */
70       CVolStream &s = Streams.Back();
71       if (s.Stream)
72       {
73         if (hres3 == S_OK)
74           hres3 = E_FAIL;
75         s.Stream.Detach();
76         /* it will be not failure, even if we call RemoveFromLinkedList()
77            twice for same CVolStream in this Destruct() function */
78         RemoveFromLinkedList(Streams.Size() - 1);
79       }
80     }
81     Streams.DeleteBack();
82     // Delete_LastStream_Records();
83   }
84 
85   if (hres == S_OK)
86     hres = hres3;
87   if (hres == S_OK && NumListItems != 0)
88     hres = E_FAIL;
89   return hres;
90   COM_TRY_END
91 }
92 
93 
~CMultiOutStream()94 CMultiOutStream::~CMultiOutStream()
95 {
96   // we try to avoid exception in destructors
97   Destruct();
98 }
99 
100 
Init(const CRecordVector<UInt64> & sizes)101 void CMultiOutStream::Init(const CRecordVector<UInt64> &sizes)
102 {
103   Streams.Clear();
104   InitLinkedList();
105   Sizes = sizes;
106   NeedDelete = true;
107   MTime_Defined = false;
108   FinalVol_WasReopen = false;
109   NumOpenFiles_AllowedMax = NSystem::Get_File_OPEN_MAX_Reduced_for_3_tasks();
110 
111   _streamIndex = 0;
112   _offsetPos = 0;
113   _absPos = 0;
114   _length = 0;
115   _absLimit = (UInt64)(Int64)-1;
116 
117   _restrict_Begin = 0;
118   _restrict_End = (UInt64)(Int64)-1;
119   _restrict_Global = 0;
120 
121   UInt64 sum = 0;
122   unsigned i = 0;
123   for (i = 0; i < Sizes.Size(); i++)
124   {
125     if (i >= k_NumVols_MAX)
126     {
127       _absLimit = sum;
128       break;
129     }
130     const UInt64 size = Sizes[i];
131     const UInt64 next = sum + size;
132     if (next < sum)
133       break;
134     sum = next;
135   }
136 
137   // if (Sizes.IsEmpty()) throw "no volume sizes";
138   const UInt64 size = Sizes.Back();
139   if (size == 0)
140     throw "zero size last volume";
141 
142   if (i == Sizes.Size())
143     if ((_absLimit - sum) / size >= (k_NumVols_MAX - i))
144       _absLimit = sum + (k_NumVols_MAX - i) * size;
145 }
146 
147 
148 /* IsRestricted():
149    we must call only if volume is full (s.RealSize==VolSize) or finished.
150    the function doesn't use VolSize and it uses s.RealSize instead.
151    it returns true  : if stream is restricted, and we can't close that stream
152    it returns false : if there is no restriction, and we can close that stream
153  Note: (RealSize == 0) (empty volume) on restriction bounds are supposed as non-restricted
154 */
IsRestricted(const CVolStream & s) const155 bool CMultiOutStream::IsRestricted(const CVolStream &s) const
156 {
157   if (s.Start < _restrict_Global)
158     return true;
159   if (_restrict_Begin == _restrict_End)
160     return false;
161   if (_restrict_Begin <= s.Start)
162     return _restrict_End > s.Start;
163   return _restrict_Begin < s.Start + s.RealSize;
164 }
165 
166 /*
167 // this function check also _length and volSize
168 bool CMultiOutStream::IsRestricted_for_Close(unsigned index) const
169 {
170   const CVolStream &s = Streams[index];
171   if (_length <= s.Start) // we don't close streams after the end, because we still can write them later
172     return true;
173   // (_length > s.Start)
174   const UInt64 volSize = GetVolSize_for_Stream(index);
175   if (volSize == 0)
176     return IsRestricted_Empty(s);
177   if (_length - s.Start < volSize)
178     return true;
179   return IsRestricted(s);
180 }
181 */
182 
GetFilePath(unsigned index)183 FString CMultiOutStream::GetFilePath(unsigned index)
184 {
185   FString name;
186   name.Add_UInt32(index + 1);
187   while (name.Len() < 3)
188     name.InsertAtFront(FTEXT('0'));
189   name.Insert(0, Prefix);
190   return name;
191 }
192 
193 
194 // we close stream, but we still keep item in Streams[] vector
CloseStream(unsigned index)195 HRESULT CMultiOutStream::CloseStream(unsigned index)
196 {
197   CVolStream &s = Streams[index];
198   if (s.Stream)
199   {
200     RINOK(s.StreamSpec->Close())
201     // the following two commands must be called together:
202     s.Stream.Release();
203     RemoveFromLinkedList(index);
204   }
205   return S_OK;
206 }
207 
208 
209 // we close stream and delete file, but we still keep item in Streams[] vector
CloseStream_and_DeleteFile(unsigned index)210 HRESULT CMultiOutStream::CloseStream_and_DeleteFile(unsigned index)
211 {
212   PRF(printf("\n====== %u, CloseStream_AndDelete \n", index));
213   RINOK(CloseStream(index))
214   FString path = GetFilePath(index);
215   path += Streams[index].Postfix;
216   // we can checki that file exist
217   // if (NFind::DoesFileExist_Raw(path))
218   if (!DeleteFileAlways(path))
219     return GetLastError_noZero_HRESULT();
220   return S_OK;
221 }
222 
223 
CloseStream_and_FinalRename(unsigned index)224 HRESULT CMultiOutStream::CloseStream_and_FinalRename(unsigned index)
225 {
226   PRF(printf("\n====== %u, CloseStream_and_FinalRename \n", index));
227   CVolStream &s = Streams[index];
228   // HRESULT res = S_OK;
229   bool mtime_WasSet = false;
230   if (MTime_Defined && s.Stream)
231   {
232     if (s.StreamSpec->SetMTime(&MTime))
233       mtime_WasSet = true;
234     // else res = GetLastError_noZero_HRESULT();
235   }
236 
237   RINOK(CloseStream(index))
238   if (s.Postfix.IsEmpty()) // if Postfix is empty, the path is already final
239     return S_OK;
240   const FString path = GetFilePath(index);
241   FString tempPath = path;
242   tempPath += s.Postfix;
243 
244   if (MTime_Defined && !mtime_WasSet)
245   {
246     if (!SetDirTime(tempPath, NULL, NULL, &MTime))
247     {
248       // res = GetLastError_noZero_HRESULT();
249     }
250   }
251   if (!MyMoveFile(tempPath, path))
252     return GetLastError_noZero_HRESULT();
253   /* we clear CVolStream::Postfix. So we will not use Temp path
254      anymore for this stream, and we will work only with final path */
255   s.Postfix.Empty();
256   // we can ignore set_mtime error or we can return it
257   return S_OK;
258   // return res;
259 }
260 
261 
PrepareToOpenNew()262 HRESULT CMultiOutStream::PrepareToOpenNew()
263 {
264   if (NumListItems < NumOpenFiles_AllowedMax)
265     return S_OK;
266   /* when we create zip archive: in most cases we need only starting
267      data of restricted region for rewriting zip's local header.
268      So here we close latest created volume (from Head), and we try to
269      keep oldest volumes that will be used for header rewriting later. */
270   const int index = Head;
271   if (index == -1)
272     return E_FAIL;
273   PRF(printf("\n== %u, PrepareToOpenNew::CloseStream, NumListItems =%u \n", index, NumListItems));
274   /* we don't expect non-restricted stream here in normal cases (if _restrict_Global was not changed).
275      if there was non-restricted stream, it should be closed before */
276   // if (!IsRestricted_for_Close(index)) return CloseStream_and_FinalRename(index);
277   return CloseStream((unsigned)index);
278 }
279 
280 
CreateNewStream(UInt64 newSize)281 HRESULT CMultiOutStream::CreateNewStream(UInt64 newSize)
282 {
283   PRF(printf("\n== %u, CreateNewStream, size =%u \n", Streams.Size(), (unsigned)newSize));
284 
285   if (Streams.Size() >= k_NumVols_MAX)
286     return E_INVALIDARG; // E_OUTOFMEMORY
287 
288   RINOK(PrepareToOpenNew())
289   CVolStream s;
290   s.StreamSpec = new COutFileStream;
291   s.Stream = s.StreamSpec;
292   const FString path = GetFilePath(Streams.Size());
293 
294   if (NFind::DoesFileExist_Raw(path))
295     return HRESULT_FROM_WIN32(ERROR_ALREADY_EXISTS);
296   if (!CreateTempFile2(path, false, s.Postfix, &s.StreamSpec->File))
297     return GetLastError_noZero_HRESULT();
298 
299   s.Start = GetGlobalOffset_for_NewStream();
300   s.Pos = 0;
301   s.RealSize = 0;
302 
303   const unsigned index = Streams.Add(s);
304   InsertToLinkedList(index);
305 
306   if (newSize != 0)
307     return s.SetSize2(newSize);
308   return S_OK;
309 }
310 
311 
CreateStreams_If_Required(unsigned streamIndex)312 HRESULT CMultiOutStream::CreateStreams_If_Required(unsigned streamIndex)
313 {
314   // UInt64 lastStreamSize = 0;
315   for (;;)
316   {
317     const unsigned numStreamsBefore = Streams.Size();
318     if (streamIndex < numStreamsBefore)
319       return S_OK;
320     UInt64 newSize;
321     if (streamIndex == numStreamsBefore)
322     {
323       // it's final volume that will be used for real writing.
324       /* SetSize(_offsetPos) is not required,
325       because the file Size will be set later by calling Seek() with Write() */
326       newSize = 0; // lastStreamSize;
327     }
328     else
329     {
330       // it's intermediate volume. So we need full volume size
331       newSize = GetVolSize_for_Stream(numStreamsBefore);
332     }
333 
334     RINOK(CreateNewStream(newSize))
335 
336     // optional check
337     if (numStreamsBefore + 1 != Streams.Size()) return E_FAIL;
338 
339     if (streamIndex != numStreamsBefore)
340     {
341       // it's intermediate volume. So we can close it, if it's non-restricted
342       bool isRestricted;
343       {
344         const CVolStream &s = Streams[numStreamsBefore];
345         if (newSize == 0)
346           isRestricted = IsRestricted_Empty(s);
347         else
348           isRestricted = IsRestricted(s);
349       }
350       if (!isRestricted)
351       {
352         RINOK(CloseStream_and_FinalRename(numStreamsBefore))
353       }
354     }
355   }
356 }
357 
358 
ReOpenStream(unsigned streamIndex)359 HRESULT CMultiOutStream::ReOpenStream(unsigned streamIndex)
360 {
361   PRF(printf("\n====== %u, ReOpenStream \n", streamIndex));
362   RINOK(PrepareToOpenNew())
363   CVolStream &s = Streams[streamIndex];
364 
365   FString path = GetFilePath(streamIndex);
366   path += s.Postfix;
367 
368   s.StreamSpec = new COutFileStream;
369   s.Stream = s.StreamSpec;
370   s.Pos = 0;
371 
372   HRESULT hres;
373   if (s.StreamSpec->Open(path, OPEN_EXISTING))
374   {
375     if (s.Postfix.IsEmpty())
376     {
377       /* it's unexpected case that we open finished volume.
378          It can mean that the code for restriction is incorrect */
379       FinalVol_WasReopen = true;
380     }
381     UInt64 realSize = 0;
382     hres = s.StreamSpec->GetSize(&realSize);
383     if (hres == S_OK)
384     {
385       if (realSize == s.RealSize)
386       {
387         InsertToLinkedList(streamIndex);
388         return S_OK;
389       }
390       // file size was changed between Close() and ReOpen()
391       // we must release Stream to be consistent with linked list
392       hres = E_FAIL;
393     }
394   }
395   else
396     hres = GetLastError_noZero_HRESULT();
397   s.Stream.Release();
398   s.StreamSpec = NULL;
399   return hres;
400 }
401 
402 
403 /* Sets size of stream, if new size is not equal to old size (RealSize).
404    If stream was closed and size change is required, it reopens the stream. */
405 
OptReOpen_and_SetSize(unsigned index,UInt64 size)406 HRESULT CMultiOutStream::OptReOpen_and_SetSize(unsigned index, UInt64 size)
407 {
408   CVolStream &s = Streams[index];
409   if (size == s.RealSize)
410     return S_OK;
411   if (!s.Stream)
412   {
413     RINOK(ReOpenStream(index))
414   }
415   PRF(printf("\n== %u, OptReOpen_and_SetSize, size =%u RealSize = %u\n", index, (unsigned)size, (unsigned)s.RealSize));
416   // comment it to debug tail after data
417   return s.SetSize2(size);
418 }
419 
420 
421 /*
422 call Normalize_finalMode(false), if _length was changed.
423   for all streams starting after _length:
424     - it sets zero size
425     - it still keeps file open
426   Note: after _length reducing with CMultiOutStream::SetSize() we can
427     have very big number of empty streams at the end of Streams[] list.
428     And Normalize_finalMode() will runs all these empty streams of Streams[] vector.
429     So it can be ineffective, if we call Normalize_finalMode() many
430     times after big reducing of (_length).
431 
432 call Normalize_finalMode(true) to set final presentations of all streams
433   for all streams starting after _length:
434     - it sets zero size
435     - it removes file
436     - it removes CVolStream object from Streams[] vector
437 
438 Note: we don't remove zero sized first volume, if (_length == 0)
439 */
440 
Normalize_finalMode(bool finalMode)441 HRESULT CMultiOutStream::Normalize_finalMode(bool finalMode)
442 {
443   PRF(printf("\n== Normalize_finalMode: _length =%d \n", (unsigned)_length));
444 
445   unsigned i = Streams.Size();
446 
447   UInt64 offset = 0;
448 
449   /* At first we normalize (reduce or increase) the sizes of all existing
450      streams in Streams[] that can be affected by changed _length.
451      And we remove tailing zero-size streams, if (finalMode == true) */
452   while (i != 0)
453   {
454     offset = Streams[--i].Start; // it's last item in Streams[]
455     // we don't want to remove first volume
456     if (offset < _length || i == 0)
457     {
458       const UInt64 volSize = GetVolSize_for_Stream(i);
459       UInt64 size = _length - offset; // (size != 0) here
460       if (size > volSize)
461         size = volSize;
462       RINOK(OptReOpen_and_SetSize(i, size))
463       if (_length - offset <= volSize)
464         return S_OK;
465       // _length - offset > volSize
466       offset += volSize;
467       // _length > offset
468       break;
469       // UPDATE_HRES(res, OptReOpen_and_SetSize(i, size));
470     }
471 
472     /* we Set Size of stream to zero even for (finalMode==true), although
473        that stream will be deleted in next commands */
474     // UPDATE_HRES(res, OptReOpen_and_SetSize(i, 0));
475     RINOK(OptReOpen_and_SetSize(i, 0))
476     if (finalMode)
477     {
478       RINOK(CloseStream_and_DeleteFile(i))
479       /* CVolStream::Stream was released above already, and it was
480          removed from linked list. So we don't need to update linked list
481          structure, when we delete last item in Streams[] */
482       Streams.DeleteBack();
483       // Delete_LastStream_Records();
484     }
485   }
486 
487   /* now we create new zero-filled streams to cover all data up to _length */
488 
489   if (_length == 0)
490     return S_OK;
491 
492   // (offset) is start offset of next stream after existing Streams[]
493 
494   for (;;)
495   {
496     // _length > offset
497     const UInt64 volSize = GetVolSize_for_Stream(Streams.Size());
498     UInt64 size = _length - offset; // (size != 0) here
499     if (size > volSize)
500       size = volSize;
501     RINOK(CreateNewStream(size))
502     if (_length - offset <= volSize)
503       return S_OK;
504     // _length - offset > volSize)
505     offset += volSize;
506     // _length > offset
507   }
508 }
509 
510 
FinalFlush_and_CloseFiles(unsigned & numTotalVolumesRes)511 HRESULT CMultiOutStream::FinalFlush_and_CloseFiles(unsigned &numTotalVolumesRes)
512 {
513   // at first we remove unused zero-sized streams after _length
514   HRESULT res = Normalize_finalMode(true);
515   numTotalVolumesRes = Streams.Size();
516   FOR_VECTOR (i, Streams)
517   {
518     const HRESULT res2 = CloseStream_and_FinalRename(i);
519     if (res == S_OK)
520       res = res2;
521   }
522   if (NumListItems != 0 && res == S_OK)
523     res = E_FAIL;
524   return res;
525 }
526 
527 
SetMTime_Final(const CFiTime & mTime)528 bool CMultiOutStream::SetMTime_Final(const CFiTime &mTime)
529 {
530   // we will set mtime only if new value differs from previous
531   if (!FinalVol_WasReopen && MTime_Defined && Compare_FiTime(&MTime, &mTime) == 0)
532     return true;
533   bool res = true;
534   FOR_VECTOR (i, Streams)
535   {
536     CVolStream &s = Streams[i];
537     if (s.Stream)
538     {
539       if (!s.StreamSpec->SetMTime(&mTime))
540         res = false;
541     }
542     else
543     {
544       if (!SetDirTime(GetFilePath(i), NULL, NULL, &mTime))
545         res = false;
546     }
547   }
548   return res;
549 }
550 
551 
Z7_COM7F_IMF(CMultiOutStream::SetSize (UInt64 newSize))552 Z7_COM7F_IMF(CMultiOutStream::SetSize(UInt64 newSize))
553 {
554   COM_TRY_BEGIN
555   if ((Int64)newSize < 0)
556     return HRESULT_WIN32_ERROR_NEGATIVE_SEEK;
557   if (newSize > _absLimit)
558   {
559     /* big seek value was sent to SetSize() or to Seek()+Write().
560        It can mean one of two situations:
561          1) some incorrect code called it with big seek value.
562          2) volume size was small, and we have too big number of volumes
563     */
564     /* in Windows SetEndOfFile() can return:
565        ERROR_NEGATIVE_SEEK:     for >= (1 << 63)
566        ERROR_INVALID_PARAMETER: for >  (16 TiB - 64 KiB)
567        ERROR_DISK_FULL:         for <= (16 TiB - 64 KiB)
568     */
569     // return E_FAIL;
570     // return E_OUTOFMEMORY;
571     return E_INVALIDARG;
572   }
573 
574   if (newSize > _length)
575   {
576     // we don't expect such case. So we just define global restriction */
577     _restrict_Global = newSize;
578   }
579   else if (newSize < _restrict_Global)
580     _restrict_Global = newSize;
581 
582   PRF(printf("\n== SetSize, size =%u \n", (unsigned)newSize));
583 
584   _length = newSize;
585   return Normalize_finalMode(false);
586 
587   COM_TRY_END
588 }
589 
590 
Z7_COM7F_IMF(CMultiOutStream::Write (const void * data,UInt32 size,UInt32 * processedSize))591 Z7_COM7F_IMF(CMultiOutStream::Write(const void *data, UInt32 size, UInt32 *processedSize))
592 {
593   COM_TRY_BEGIN
594   if (processedSize)
595     *processedSize = 0;
596   if (size == 0)
597     return S_OK;
598 
599   if (_absPos > _length)
600   {
601     // it create data only up to _absPos.
602     // but we still can need additional new streams, if _absPos at range of volume
603     RINOK(SetSize(_absPos))
604   }
605 
606   while (size != 0)
607   {
608     UInt64 volSize;
609     {
610       if (_streamIndex < Sizes.Size() - 1)
611       {
612         volSize = Sizes[_streamIndex];
613         if (_offsetPos >= volSize)
614         {
615           _offsetPos -= volSize;
616           _streamIndex++;
617           continue;
618         }
619       }
620       else
621       {
622         volSize = Sizes[Sizes.Size() - 1];
623         if (_offsetPos >= volSize)
624         {
625           const UInt64 v = _offsetPos / volSize;
626           if (v >= ((UInt32)(Int32)-1) - _streamIndex)
627             return E_INVALIDARG;
628             // throw 202208;
629           _streamIndex += (unsigned)v;
630           _offsetPos -= (unsigned)v * volSize;
631         }
632         if (_streamIndex >= k_NumVols_MAX)
633           return E_INVALIDARG;
634       }
635     }
636 
637     // (_offsetPos < volSize) here
638 
639     /* we can need to create one or more streams here,
640        vol_size for some streams is allowed to be 0.
641        Also we close some new created streams, if they are non-restricted */
642     // file Size will be set later by calling Seek() with Write()
643 
644     /* the case (_absPos > _length) was processed above with SetSize(_absPos),
645        so here it's expected. that we can create optional zero-size streams and then _streamIndex */
646     RINOK(CreateStreams_If_Required(_streamIndex))
647 
648     CVolStream &s = Streams[_streamIndex];
649 
650     PRF(printf("\n%d, == Write : Pos = %u, RealSize = %u size =%u \n",
651         _streamIndex, (unsigned)s.Pos, (unsigned)s.RealSize, size));
652 
653     if (!s.Stream)
654     {
655       RINOK(ReOpenStream(_streamIndex))
656     }
657     if (_offsetPos != s.Pos)
658     {
659       RINOK(s.Stream->Seek((Int64)_offsetPos, STREAM_SEEK_SET, NULL))
660       s.Pos = _offsetPos;
661     }
662 
663     UInt32 curSize = size;
664     {
665       const UInt64 rem = volSize - _offsetPos;
666       if (curSize > rem)
667         curSize = (UInt32)rem;
668     }
669     // curSize != 0
670     UInt32 realProcessed = 0;
671 
672     HRESULT hres = s.Stream->Write(data, curSize, &realProcessed);
673 
674     data = (const void *)((const Byte *)data + realProcessed);
675     size -= realProcessed;
676     s.Pos += realProcessed;
677     _offsetPos += realProcessed;
678     _absPos += realProcessed;
679     if (_length < _absPos)
680       _length = _absPos;
681     if (s.RealSize < _offsetPos)
682       s.RealSize = _offsetPos;
683     if (processedSize)
684       *processedSize += realProcessed;
685 
686     if (s.Pos == volSize)
687     {
688       bool isRestricted;
689       if (volSize == 0)
690         isRestricted = IsRestricted_Empty(s);
691       else
692         isRestricted = IsRestricted(s);
693       if (!isRestricted)
694       {
695         const HRESULT res2 = CloseStream_and_FinalRename(_streamIndex);
696         if (hres == S_OK)
697           hres = res2;
698       }
699       _streamIndex++;
700       _offsetPos = 0;
701     }
702 
703     RINOK(hres)
704     if (realProcessed == 0 && curSize != 0)
705       return E_FAIL;
706     // break;
707   }
708   return S_OK;
709   COM_TRY_END
710 }
711 
712 
Z7_COM7F_IMF(CMultiOutStream::Seek (Int64 offset,UInt32 seekOrigin,UInt64 * newPosition))713 Z7_COM7F_IMF(CMultiOutStream::Seek(Int64 offset, UInt32 seekOrigin, UInt64 *newPosition))
714 {
715   PRF(printf("\n-- Seek seekOrigin=%u Seek =%u\n", seekOrigin, (unsigned)offset));
716 
717   switch (seekOrigin)
718   {
719     case STREAM_SEEK_SET: break;
720     case STREAM_SEEK_CUR: offset += _absPos; break;
721     case STREAM_SEEK_END: offset += _length; break;
722     default: return STG_E_INVALIDFUNCTION;
723   }
724   if (offset < 0)
725     return HRESULT_WIN32_ERROR_NEGATIVE_SEEK;
726   if ((UInt64)offset != _absPos)
727   {
728     _absPos = (UInt64)offset;
729     _offsetPos = (UInt64)offset;
730     _streamIndex = 0;
731   }
732   if (newPosition)
733     *newPosition = (UInt64)offset;
734   return S_OK;
735 }
736 
737 
738 // result value will be saturated to (UInt32)(Int32)-1
739 
GetStreamIndex_for_Offset(UInt64 offset,UInt64 & relOffset) const740 unsigned CMultiOutStream::GetStreamIndex_for_Offset(UInt64 offset, UInt64 &relOffset) const
741 {
742   const unsigned last = Sizes.Size() - 1;
743   for (unsigned i = 0; i < last; i++)
744   {
745     const UInt64 size = Sizes[i];
746     if (offset < size)
747     {
748       relOffset = offset;
749       return i;
750     }
751     offset -= size;
752   }
753   const UInt64 size = Sizes[last];
754   const UInt64 v = offset / size;
755   if (v >= ((UInt32)(Int32)-1) - last)
756     return (UInt32)(Int32)-1; // saturation
757   relOffset = offset - (unsigned)v * size;
758   return last + (unsigned)(v);
759 }
760 
761 
Z7_COM7F_IMF(CMultiOutStream::SetRestriction (UInt64 begin,UInt64 end))762 Z7_COM7F_IMF(CMultiOutStream::SetRestriction(UInt64 begin, UInt64 end))
763 {
764   COM_TRY_BEGIN
765 
766   // begin = end = 0; // for debug
767 
768   PRF(printf("\n==================== CMultiOutStream::SetRestriction %u, %u\n", (unsigned)begin, (unsigned)end));
769   if (begin > end)
770   {
771     // these value are FAILED values.
772     return E_FAIL;
773     // return E_INVALIDARG;
774     /*
775     // or we can ignore error with 3 ways: no change, non-restricted, saturation:
776     end = begin;             // non-restricted
777     end = (UInt64)(Int64)-1; // saturation:
778     return S_OK;
779     */
780   }
781   UInt64 b = _restrict_Begin;
782   UInt64 e = _restrict_End;
783   _restrict_Begin = begin;
784   _restrict_End = end;
785 
786   if (b == e)    // if there were no restriction before
787     return S_OK; // no work to derestrict now.
788 
789   /* [b, e) is previous restricted region. So all volumes that
790      intersect that [b, e) region are candidats for derestriction */
791 
792   if (begin != end) // if there is new non-empty restricted region
793   {
794     /* Now we will try to reduce or change (b) and (e) bounds
795        to reduce main loop that checks volumes for derestriction.
796        We still use one big derestriction region in main loop, although
797        in some cases we could have two smaller derestriction regions.
798        Also usually restriction region cannot move back from previous start position,
799        so (b <= begin) is expected here for normal cases */
800     if (b == begin) // if same low bounds
801       b = end;      // we need to derestrict only after the end of new restricted region
802     if (e == end)   // if same high bounds
803       e = begin;    // we need to derestrict only before the begin of new restricted region
804   }
805 
806   if (b > e) //  || b == (UInt64)(Int64)-1
807     return S_OK;
808 
809   /* Here we close finished volumes that are not restricted anymore.
810      We close (low number) volumes at first. */
811 
812   UInt64 offset;
813   unsigned index = GetStreamIndex_for_Offset(b, offset);
814 
815   for (; index < Streams.Size(); index++)
816   {
817     {
818       const CVolStream &s = Streams[index];
819       if (_length <= s.Start)
820         break; // we don't close streams after _length
821       // (_length > s.Start)
822       const UInt64 volSize = GetVolSize_for_Stream(index);
823       if (volSize == 0)
824       {
825         if (e < s.Start)
826           break;
827         // we don't close empty stream, if next byte [s.Start, s.Start] is restricted
828         if (IsRestricted_Empty(s))
829           continue;
830       }
831       else
832       {
833         if (e <= s.Start)
834           break;
835         // we don't close non full streams
836         if (_length - s.Start < volSize)
837           break;
838         // (volSize == s.RealSize) is expected here. So no need to check it
839         // if (volSize != s.RealSize) break;
840         if (IsRestricted(s))
841           continue;
842       }
843     }
844     RINOK(CloseStream_and_FinalRename(index))
845   }
846 
847   return S_OK;
848   COM_TRY_END
849 }
850