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
22using namespace NWindows;
23using namespace NFile;
24using namespace NDir;
25
26static 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
34HRESULT 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
94CMultiOutStream::~CMultiOutStream()
95{
96  // we try to avoid exception in destructors
97  Destruct();
98}
99
100
101void 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*/
155bool 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
168bool 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
183FString 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
195HRESULT 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
210HRESULT 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
224HRESULT 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
262HRESULT 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
281HRESULT 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
312HRESULT 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
359HRESULT 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
406HRESULT 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/*
422call 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
432call 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
438Note: we don't remove zero sized first volume, if (_length == 0)
439*/
440
441HRESULT 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
511HRESULT 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
528bool 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
552Z7_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
591Z7_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
713Z7_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
740unsigned 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
762Z7_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