123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655 |
- #define UNP_READ_SIZE_MT 0x400000
- #define UNP_BLOCKS_PER_THREAD 2
- struct UnpackThreadDataList
- {
- UnpackThreadData *D;
- uint BlockCount;
- };
- THREAD_PROC(UnpackDecodeThread)
- {
- UnpackThreadDataList *DL=(UnpackThreadDataList *)Data;
- for (uint I=0;I<DL->BlockCount;I++)
- DL->D->UnpackPtr->UnpackDecode(DL->D[I]);
- }
- void Unpack::InitMT()
- {
- if (ReadBufMT==NULL)
- {
- // Even getbits32 can read up to 3 additional bytes after current
- // and our block header and table reading code can look much further.
- // Let's allocate the additional space here, so we do not need to check
- // bounds for every bit field access.
- const size_t Overflow=1024;
- ReadBufMT=new byte[UNP_READ_SIZE_MT+Overflow];
- memset(ReadBufMT,0,UNP_READ_SIZE_MT+Overflow);
- }
- if (UnpThreadData==NULL)
- {
- uint MaxItems=MaxUserThreads*UNP_BLOCKS_PER_THREAD;
- UnpThreadData=new UnpackThreadData[MaxItems];
- memset(UnpThreadData,0,sizeof(UnpackThreadData)*MaxItems);
- for (uint I=0;I<MaxItems;I++)
- {
- UnpackThreadData *CurData=UnpThreadData+I;
- if (CurData->Decoded==NULL)
- {
- // Typical number of items in RAR blocks does not exceed 0x4000.
- CurData->DecodedAllocated=0x4100;
- // It will be freed in the object destructor, not in this file.
- CurData->Decoded=(UnpackDecodedItem *)malloc(CurData->DecodedAllocated*sizeof(UnpackDecodedItem));
- if (CurData->Decoded==NULL)
- ErrHandler.MemoryError();
- }
- }
- }
- }
- void Unpack::Unpack5MT(bool Solid)
- {
- InitMT();
- UnpInitData(Solid);
- for (uint I=0;I<MaxUserThreads*UNP_BLOCKS_PER_THREAD;I++)
- {
- UnpackThreadData *CurData=UnpThreadData+I;
- CurData->LargeBlock=false;
- CurData->Incomplete=false;
- }
- UnpThreadData[0].BlockHeader=BlockHeader;
- UnpThreadData[0].BlockTables=BlockTables;
- uint LastBlockNum=0;
- int DataSize=0;
- int BlockStart=0;
- // 'true' if we found a block too large for multithreaded extraction,
- // so we switched to single threaded mode until the end of file.
- // Large blocks could cause too high memory use in multithreaded mode.
- bool LargeBlock=false;
- bool Done=false;
- while (!Done)
- {
- // Data amount, which is guaranteed to fit block header and tables,
- // so we can safely read them without additional checks.
- const int TooSmallToProcess=1024;
- int ReadSize=UnpIO->UnpRead(ReadBufMT+DataSize,(UNP_READ_SIZE_MT-DataSize)&~0xf);
- if (ReadSize<0)
- break;
- DataSize+=ReadSize;
- if (DataSize==0)
- break;
- // First read chunk can be small if we are near the end of volume
- // and we want it to fit block header and tables.
- if (ReadSize>0 && DataSize<TooSmallToProcess)
- continue;
- while (BlockStart<DataSize && !Done)
- {
- uint BlockNumber=0,BlockNumberMT=0;
- while (BlockNumber<MaxUserThreads*UNP_BLOCKS_PER_THREAD)
- {
- UnpackThreadData *CurData=UnpThreadData+BlockNumber;
- LastBlockNum=BlockNumber;
- CurData->UnpackPtr=this;
- // 'Incomplete' thread is present. This is a thread processing block
- // in the end of buffer, split between two read operations.
- if (CurData->Incomplete)
- CurData->DataSize=DataSize;
- else
- {
- CurData->Inp.SetExternalBuffer(ReadBufMT+BlockStart);
- CurData->Inp.InitBitInput();
- CurData->DataSize=DataSize-BlockStart;
- if (CurData->DataSize==0)
- break;
- CurData->DamagedData=false;
- CurData->HeaderRead=false;
- CurData->TableRead=false;
- }
- // We should not use 'last block in file' block flag here unless
- // we'll check the block size, because even if block is last in file,
- // it can exceed the current buffer and require more reading.
- CurData->NoDataLeft=(ReadSize==0);
- CurData->Incomplete=false;
- CurData->ThreadNumber=BlockNumber;
- if (!CurData->HeaderRead)
- {
- CurData->HeaderRead=true;
- if (!ReadBlockHeader(CurData->Inp,CurData->BlockHeader) ||
- !CurData->BlockHeader.TablePresent && !TablesRead5)
- {
- Done=true;
- break;
- }
- TablesRead5=true;
- }
- // To prevent too high memory use we switch to single threaded mode
- // if block exceeds this size. Typically RAR blocks do not exceed
- // 64 KB, so this protection should not affect most of valid archives.
- const int LargeBlockSize=0x20000;
- if (LargeBlock || CurData->BlockHeader.BlockSize>LargeBlockSize)
- LargeBlock=CurData->LargeBlock=true;
- else
- BlockNumberMT++; // Number of normal blocks processed in MT mode.
- BlockStart+=CurData->BlockHeader.HeaderSize+CurData->BlockHeader.BlockSize;
- BlockNumber++;
- int DataLeft=DataSize-BlockStart;
- if (DataLeft>=0 && CurData->BlockHeader.LastBlockInFile)
- break;
- // For second and following threads we move smaller blocks to buffer
- // start to ensure that we have enough data to fit block header
- // and tables.
- if (DataLeft<TooSmallToProcess)
- break;
- }
- //#undef USE_THREADS
- UnpackThreadDataList UTDArray[MaxPoolThreads];
- uint UTDArrayPos=0;
- uint MaxBlockPerThread=BlockNumberMT/MaxUserThreads;
- if (BlockNumberMT%MaxUserThreads!=0)
- MaxBlockPerThread++;
- // Decode all normal blocks until the first 'large' if any.
- for (uint CurBlock=0;CurBlock<BlockNumberMT;CurBlock+=MaxBlockPerThread)
- {
- UnpackThreadDataList *UTD=UTDArray+UTDArrayPos++;
- UTD->D=UnpThreadData+CurBlock;
- UTD->BlockCount=Min(MaxBlockPerThread,BlockNumberMT-CurBlock);
- #ifdef USE_THREADS
- if (BlockNumber==1)
- UnpackDecode(*UTD->D);
- else
- UnpThreadPool->AddTask(UnpackDecodeThread,(void*)UTD);
- #else
- for (uint I=0;I<UTD->BlockCount;I++)
- UnpackDecode(UTD->D[I]);
- #endif
- }
- if (BlockNumber==0)
- break;
- #ifdef USE_THREADS
- UnpThreadPool->WaitDone();
- #endif
- bool IncompleteThread=false;
- for (uint Block=0;Block<BlockNumber;Block++)
- {
- UnpackThreadData *CurData=UnpThreadData+Block;
- if (!CurData->LargeBlock && !ProcessDecoded(*CurData) ||
- CurData->LargeBlock && !UnpackLargeBlock(*CurData) ||
- CurData->DamagedData)
- {
- Done=true;
- break;
- }
- if (CurData->Incomplete)
- {
- int BufPos=int(CurData->Inp.InBuf+CurData->Inp.InAddr-ReadBufMT);
- if (DataSize<=BufPos) // Thread exceeded input buffer boundary.
- {
- Done=true;
- break;
- }
- IncompleteThread=true;
- memmove(ReadBufMT,ReadBufMT+BufPos,DataSize-BufPos);
- CurData->BlockHeader.BlockSize-=CurData->Inp.InAddr-CurData->BlockHeader.BlockStart;
- CurData->BlockHeader.HeaderSize=0;
- CurData->BlockHeader.BlockStart=0;
- CurData->Inp.InBuf=ReadBufMT;
- CurData->Inp.InAddr=0;
- if (Block!=0)
- {
- // Move the incomplete thread entry to the first position,
- // so we'll start processing from it. Preserve the original
- // buffer for decoded data.
- UnpackDecodedItem *Decoded=UnpThreadData[0].Decoded;
- uint DecodedAllocated=UnpThreadData[0].DecodedAllocated;
- UnpThreadData[0]=*CurData;
- UnpThreadData[0].Decoded=Decoded;
- UnpThreadData[0].DecodedAllocated=DecodedAllocated;
- CurData->Incomplete=false;
- }
- BlockStart=0;
- DataSize-=BufPos;
- break;
- }
- else
- if (CurData->BlockHeader.LastBlockInFile)
- {
- Done=true;
- break;
- }
- }
- if (IncompleteThread || Done)
- break; // Current buffer is done, read more data or quit.
- else
- {
- int DataLeft=DataSize-BlockStart;
- if (DataLeft<TooSmallToProcess)
- {
- if (DataLeft<0) // Invalid data, must not happen in valid archive.
- {
- Done=true;
- break;
- }
- // If we do not have incomplete thread and have some data
- // in the end of buffer, too small for single thread,
- // let's move it to beginning of next buffer.
- if (DataLeft>0)
- memmove(ReadBufMT,ReadBufMT+BlockStart,DataLeft);
- DataSize=DataLeft;
- BlockStart=0;
- break; // Current buffer is done, try to read more data.
- }
- }
- }
- }
- UnpPtr&=MaxWinMask; // ProcessDecoded and maybe others can leave UnpPtr > MaxWinMask here.
- UnpWriteBuf();
- BlockHeader=UnpThreadData[LastBlockNum].BlockHeader;
- BlockTables=UnpThreadData[LastBlockNum].BlockTables;
- }
- // Decode Huffman block and save decoded data to memory.
- void Unpack::UnpackDecode(UnpackThreadData &D)
- {
- if (!D.TableRead)
- {
- D.TableRead=true;
- if (!ReadTables(D.Inp,D.BlockHeader,D.BlockTables))
- {
- D.DamagedData=true;
- return;
- }
- }
- if (D.Inp.InAddr>D.BlockHeader.HeaderSize+D.BlockHeader.BlockSize)
- {
- D.DamagedData=true;
- return;
- }
- D.DecodedSize=0;
- int BlockBorder=D.BlockHeader.BlockStart+D.BlockHeader.BlockSize-1;
- // Reserve enough space even for filter entry.
- int DataBorder=D.DataSize-16;
- int ReadBorder=Min(BlockBorder,DataBorder);
- while (true)
- {
- if (D.Inp.InAddr>=ReadBorder)
- {
- if (D.Inp.InAddr>BlockBorder || D.Inp.InAddr==BlockBorder &&
- D.Inp.InBit>=D.BlockHeader.BlockBitSize)
- break;
- // If we do not have any more data in file to read, we must process
- // what we have until last byte. Otherwise we can return and append
- // more data to unprocessed few bytes.
- if ((D.Inp.InAddr>=DataBorder) && !D.NoDataLeft || D.Inp.InAddr>=D.DataSize)
- {
- D.Incomplete=true;
- break;
- }
- }
- if (D.DecodedSize>D.DecodedAllocated-8) // Filter can use several slots.
- {
- D.DecodedAllocated=D.DecodedAllocated*2;
- void *Decoded=realloc(D.Decoded,D.DecodedAllocated*sizeof(UnpackDecodedItem));
- if (Decoded==NULL)
- ErrHandler.MemoryError(); // D.Decoded will be freed in the destructor.
- D.Decoded=(UnpackDecodedItem *)Decoded;
- }
- UnpackDecodedItem *CurItem=D.Decoded+D.DecodedSize++;
- uint MainSlot=DecodeNumber(D.Inp,&D.BlockTables.LD);
- if (MainSlot<256)
- {
- if (D.DecodedSize>1)
- {
- UnpackDecodedItem *PrevItem=CurItem-1;
- if (PrevItem->Type==UNPDT_LITERAL && PrevItem->Length<3)
- {
- PrevItem->Length++;
- PrevItem->Literal[PrevItem->Length]=(byte)MainSlot;
- D.DecodedSize--;
- continue;
- }
- }
- CurItem->Type=UNPDT_LITERAL;
- CurItem->Literal[0]=(byte)MainSlot;
- CurItem->Length=0;
- continue;
- }
- if (MainSlot>=262)
- {
- uint Length=SlotToLength(D.Inp,MainSlot-262);
- uint DBits,Distance=1,DistSlot=DecodeNumber(D.Inp,&D.BlockTables.DD);
- if (DistSlot<4)
- {
- DBits=0;
- Distance+=DistSlot;
- }
- else
- {
- DBits=DistSlot/2 - 1;
- Distance+=(2 | (DistSlot & 1)) << DBits;
- }
- if (DBits>0)
- {
- if (DBits>=4)
- {
- if (DBits>4)
- {
- Distance+=((D.Inp.getbits32()>>(36-DBits))<<4);
- D.Inp.addbits(DBits-4);
- }
- uint LowDist=DecodeNumber(D.Inp,&D.BlockTables.LDD);
- Distance+=LowDist;
- }
- else
- {
- Distance+=D.Inp.getbits32()>>(32-DBits);
- D.Inp.addbits(DBits);
- }
- }
- if (Distance>0x100)
- {
- Length++;
- if (Distance>0x2000)
- {
- Length++;
- if (Distance>0x40000)
- Length++;
- }
- }
- CurItem->Type=UNPDT_MATCH;
- CurItem->Length=(ushort)Length;
- CurItem->Distance=Distance;
- continue;
- }
- if (MainSlot==256)
- {
- UnpackFilter Filter;
- ReadFilter(D.Inp,Filter);
- CurItem->Type=UNPDT_FILTER;
- CurItem->Length=Filter.Type;
- CurItem->Distance=Filter.BlockStart;
- CurItem=D.Decoded+D.DecodedSize++;
- CurItem->Type=UNPDT_FILTER;
- CurItem->Length=Filter.Channels;
- CurItem->Distance=Filter.BlockLength;
- continue;
- }
- if (MainSlot==257)
- {
- CurItem->Type=UNPDT_FULLREP;
- continue;
- }
- if (MainSlot<262)
- {
- CurItem->Type=UNPDT_REP;
- CurItem->Distance=MainSlot-258;
- uint LengthSlot=DecodeNumber(D.Inp,&D.BlockTables.RD);
- uint Length=SlotToLength(D.Inp,LengthSlot);
- CurItem->Length=(ushort)Length;
- continue;
- }
- }
- }
- // Process decoded Huffman block data.
- bool Unpack::ProcessDecoded(UnpackThreadData &D)
- {
- UnpackDecodedItem *Item=D.Decoded,*Border=D.Decoded+D.DecodedSize;
- while (Item<Border)
- {
- UnpPtr&=MaxWinMask;
- if (((WriteBorder-UnpPtr) & MaxWinMask)<MAX_INC_LZ_MATCH && WriteBorder!=UnpPtr)
- {
- UnpWriteBuf();
- if (WrittenFileSize>DestUnpSize)
- return false;
- }
- if (Item->Type==UNPDT_LITERAL)
- {
- #if defined(LITTLE_ENDIAN) && defined(ALLOW_MISALIGNED)
- if (Item->Length==3 && UnpPtr<MaxWinSize-4)
- {
- *(uint32 *)(Window+UnpPtr)=*(uint32 *)Item->Literal;
- UnpPtr+=4;
- }
- else
- #endif
- for (uint I=0;I<=Item->Length;I++)
- Window[UnpPtr++ & MaxWinMask]=Item->Literal[I];
- }
- else
- if (Item->Type==UNPDT_MATCH)
- {
- InsertOldDist(Item->Distance);
- LastLength=Item->Length;
- CopyString(Item->Length,Item->Distance);
- }
- else
- if (Item->Type==UNPDT_REP)
- {
- uint Distance=OldDist[Item->Distance];
- for (uint I=Item->Distance;I>0;I--)
- OldDist[I]=OldDist[I-1];
- OldDist[0]=Distance;
- LastLength=Item->Length;
- CopyString(Item->Length,Distance);
- }
- else
- if (Item->Type==UNPDT_FULLREP)
- {
- if (LastLength!=0)
- CopyString(LastLength,OldDist[0]);
- }
- else
- if (Item->Type==UNPDT_FILTER)
- {
- UnpackFilter Filter;
- Filter.Type=(byte)Item->Length;
- Filter.BlockStart=Item->Distance;
- Item++;
- Filter.Channels=(byte)Item->Length;
- Filter.BlockLength=Item->Distance;
- AddFilter(Filter);
- }
- Item++;
- }
- return true;
- }
- // For large blocks we decode and process in same function in single threaded
- // mode, so we do not need to store intermediate data in memory.
- bool Unpack::UnpackLargeBlock(UnpackThreadData &D)
- {
- if (!D.TableRead)
- {
- D.TableRead=true;
- if (!ReadTables(D.Inp,D.BlockHeader,D.BlockTables))
- {
- D.DamagedData=true;
- return false;
- }
- }
- if (D.Inp.InAddr>D.BlockHeader.HeaderSize+D.BlockHeader.BlockSize)
- {
- D.DamagedData=true;
- return false;
- }
- int BlockBorder=D.BlockHeader.BlockStart+D.BlockHeader.BlockSize-1;
- // Reserve enough space even for filter entry.
- int DataBorder=D.DataSize-16;
- int ReadBorder=Min(BlockBorder,DataBorder);
- while (true)
- {
- UnpPtr&=MaxWinMask;
- if (D.Inp.InAddr>=ReadBorder)
- {
- if (D.Inp.InAddr>BlockBorder || D.Inp.InAddr==BlockBorder &&
- D.Inp.InBit>=D.BlockHeader.BlockBitSize)
- break;
- // If we do not have any more data in file to read, we must process
- // what we have until last byte. Otherwise we can return and append
- // more data to unprocessed few bytes.
- if ((D.Inp.InAddr>=DataBorder) && !D.NoDataLeft || D.Inp.InAddr>=D.DataSize)
- {
- D.Incomplete=true;
- break;
- }
- }
- if (((WriteBorder-UnpPtr) & MaxWinMask)<MAX_INC_LZ_MATCH && WriteBorder!=UnpPtr)
- {
- UnpWriteBuf();
- if (WrittenFileSize>DestUnpSize)
- return false;
- }
- uint MainSlot=DecodeNumber(D.Inp,&D.BlockTables.LD);
- if (MainSlot<256)
- {
- Window[UnpPtr++]=(byte)MainSlot;
- continue;
- }
- if (MainSlot>=262)
- {
- uint Length=SlotToLength(D.Inp,MainSlot-262);
- uint DBits,Distance=1,DistSlot=DecodeNumber(D.Inp,&D.BlockTables.DD);
- if (DistSlot<4)
- {
- DBits=0;
- Distance+=DistSlot;
- }
- else
- {
- DBits=DistSlot/2 - 1;
- Distance+=(2 | (DistSlot & 1)) << DBits;
- }
- if (DBits>0)
- {
- if (DBits>=4)
- {
- if (DBits>4)
- {
- Distance+=((D.Inp.getbits32()>>(36-DBits))<<4);
- D.Inp.addbits(DBits-4);
- }
- uint LowDist=DecodeNumber(D.Inp,&D.BlockTables.LDD);
- Distance+=LowDist;
- }
- else
- {
- Distance+=D.Inp.getbits32()>>(32-DBits);
- D.Inp.addbits(DBits);
- }
- }
- if (Distance>0x100)
- {
- Length++;
- if (Distance>0x2000)
- {
- Length++;
- if (Distance>0x40000)
- Length++;
- }
- }
- InsertOldDist(Distance);
- LastLength=Length;
- CopyString(Length,Distance);
- continue;
- }
- if (MainSlot==256)
- {
- UnpackFilter Filter;
- if (!ReadFilter(D.Inp,Filter) || !AddFilter(Filter))
- break;
- continue;
- }
- if (MainSlot==257)
- {
- if (LastLength!=0)
- CopyString(LastLength,OldDist[0]);
- continue;
- }
- if (MainSlot<262)
- {
- uint DistNum=MainSlot-258;
- uint Distance=OldDist[DistNum];
- for (uint I=DistNum;I>0;I--)
- OldDist[I]=OldDist[I-1];
- OldDist[0]=Distance;
- uint LengthSlot=DecodeNumber(D.Inp,&D.BlockTables.RD);
- uint Length=SlotToLength(D.Inp,LengthSlot);
- LastLength=Length;
- CopyString(Length,Distance);
- continue;
- }
- }
- return true;
- }
|