diff options
Diffstat (limited to 'CPP/7zip/Common/MultiOutStream.cpp')
-rw-r--r-- | CPP/7zip/Common/MultiOutStream.cpp | 849 |
1 files changed, 849 insertions, 0 deletions
diff --git a/CPP/7zip/Common/MultiOutStream.cpp b/CPP/7zip/Common/MultiOutStream.cpp new file mode 100644 index 0000000..8efb977 --- /dev/null +++ b/CPP/7zip/Common/MultiOutStream.cpp | |||
@@ -0,0 +1,849 @@ | |||
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 | |||
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 | |||
94 | CMultiOutStream::~CMultiOutStream() | ||
95 | { | ||
96 | // we try to avoid exception in destructors | ||
97 | Destruct(); | ||
98 | } | ||
99 | |||
100 | |||
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 | */ | ||
155 | 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 | |||
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 | ||
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 | ||
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 | |||
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 | |||
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 | |||
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 | |||
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 | |||
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 | |||
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 | |||
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 | |||
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 | |||
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 | |||
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 | |||
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 | |||
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 | |||
740 | 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 | |||
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 | } | ||