1 //  This file is part of par2cmdline (a PAR 2.0 compatible file verification and
2 //  repair tool). See http://parchive.sourceforge.net for details of PAR 2.0.
3 //
4 //  Copyright (c) 2003 Peter Brian Clements
5 //
6 //  par2cmdline is free software; you can redistribute it and/or modify
7 //  it under the terms of the GNU General Public License as published by
8 //  the Free Software Foundation; either version 2 of the License, or
9 //  (at your option) any later version.
10 //
11 //  par2cmdline is distributed in the hope that it will be useful,
12 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
13 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 //  GNU General Public License for more details.
15 //
16 //  You should have received a copy of the GNU General Public License
17 //  along with this program; if not, write to the Free Software
18 //  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
19 
20 #include "par2cmdline.h"
21 
22 #ifdef _MSC_VER
23 #ifdef _DEBUG
24 #undef THIS_FILE
25 static char THIS_FILE[]=__FILE__;
26 #define new DEBUG_NEW
27 #endif
28 #endif
29 
Par2CreatorSourceFile(void)30 Par2CreatorSourceFile::Par2CreatorSourceFile(void)
31 {
32   descriptionpacket = 0;
33   verificationpacket = 0;
34   diskfile = 0;
35   blockcount = 0;
36   //diskfilename;
37   //parfilename;
38   contextfull = 0;
39 }
40 
~Par2CreatorSourceFile(void)41 Par2CreatorSourceFile::~Par2CreatorSourceFile(void)
42 {
43   delete descriptionpacket;
44   delete verificationpacket;
45   delete diskfile;
46   delete contextfull;
47 }
48 
49 // Open the source file, compute the MD5 Hash of the whole file and the first
50 // 16k of the file, and then compute the FileId and store the results
51 // in a file description packet and a file verification packet.
52 
Open(CommandLine::NoiseLevel noiselevel,const CommandLine::ExtraFile & extrafile,u64 blocksize,bool deferhashcomputation)53 bool Par2CreatorSourceFile::Open(CommandLine::NoiseLevel noiselevel, const CommandLine::ExtraFile &extrafile, u64 blocksize, bool deferhashcomputation)
54 {
55   // Get the filename and filesize
56   diskfilename = extrafile.FileName();
57   filesize = extrafile.FileSize();
58 
59   // Work out how many blocks the file will be sliced into
60   blockcount = (u32)((filesize + blocksize-1) / blocksize);
61 
62   // Determine what filename to record in the PAR2 files
63   string::size_type where;
64   if (string::npos != (where = diskfilename.find_last_of('\\')) ||
65       string::npos != (where = diskfilename.find_last_of('/')))
66   {
67     parfilename = diskfilename.substr(where+1);
68   }
69   else
70   {
71     parfilename = diskfilename;
72   }
73 
74   // Create the Description and Verification packets
75   descriptionpacket = new DescriptionPacket;
76   descriptionpacket->Create(parfilename, filesize);
77 
78   verificationpacket = new VerificationPacket;
79   verificationpacket->Create(blockcount);
80 
81   // Create the diskfile object
82   diskfile  = new DiskFile;
83 
84   // Open the source file
85   if (!diskfile->Open(diskfilename, filesize))
86     return false;
87 
88   // Do we want to defer the computation of the full file hash, and
89   // the block crc and hashes. This is only permitted if there
90   // is sufficient memory available to create all recovery blocks
91   // in one pass of the source files (i.e. chunksize == blocksize)
92   if (deferhashcomputation)
93   {
94     // Initialise a buffer to read the first 16k of the source file
95     size_t buffersize = 16 * 1024;
96     if (buffersize > filesize)
97       buffersize = (size_t)filesize;
98     char *buffer = new char[buffersize];
99 
100     // Read the data from the file
101     if (!diskfile->Read(0, buffer, buffersize))
102     {
103       diskfile->Close();
104       delete [] buffer;
105       return false;
106     }
107 
108     // Compute the hash of the data read from the file
109     MD5Context context;
110     context.Update(buffer, buffersize);
111     delete [] buffer;
112     MD5Hash hash;
113     context.Final(hash);
114 
115     // Store the hash in the descriptionpacket and compute the file id
116     descriptionpacket->Hash16k(hash);
117 
118     // Compute the fileid and store it in the verification packet.
119     descriptionpacket->ComputeFileId();
120     verificationpacket->FileId(descriptionpacket->FileId());
121 
122     // Allocate an MD5 context for computing the file hash
123     // during the recovery data generation phase
124     contextfull = new MD5Context;
125   }
126   else
127   {
128     // Initialise a buffer to read the source file
129     size_t buffersize = 1024*1024;
130     if (buffersize > min(blocksize,filesize))
131       buffersize = (size_t)min(blocksize,filesize);
132     char *buffer = new char[buffersize];
133 
134     // Get ready to start reading source file to compute the hashes and crcs
135     u64 offset = 0;
136     u32 blocknumber = 0;
137     u64 need = blocksize;
138 
139     MD5Context filecontext;
140     MD5Context blockcontext;
141     u32        blockcrc = 0;
142 
143     // Whilst we have not reached the end of the file
144     while (offset < filesize)
145     {
146       // Work out how much we can read
147       size_t want = (size_t)min(filesize-offset, (u64)buffersize);
148 
149       // Read some data from the file into the buffer
150       if (!diskfile->Read(offset, buffer, want))
151       {
152         diskfile->Close();
153         delete [] buffer;
154         return false;
155       }
156 
157       // If the new data passes the 16k boundary, compute the 16k hash for the file
158       if (offset < 16384 && offset + want >= 16384)
159       {
160         filecontext.Update(buffer, (size_t)(16384-offset));
161 
162         MD5Context temp = filecontext;
163         MD5Hash hash;
164         temp.Final(hash);
165 
166         // Store the 16k hash in the file description packet
167         descriptionpacket->Hash16k(hash);
168 
169         if (offset + want > 16384)
170         {
171           filecontext.Update(&buffer[16384-offset], (size_t)(offset+want)-16384);
172         }
173       }
174       else
175       {
176         filecontext.Update(buffer, want);
177       }
178 
179       // Get ready to update block hashes and crcs
180       u32 used = 0;
181 
182       // Whilst we have not used all of the data we just read
183       while (used < want)
184       {
185         // How much of it can we use for the current block
186         u32 use = (u32)min(need, (u64)(want-used));
187 
188         blockcrc = ~0 ^ CRCUpdateBlock(~0 ^ blockcrc, use, &buffer[used]);
189         blockcontext.Update(&buffer[used], use);
190 
191         used += use;
192         need -= use;
193 
194         // Have we finished the current block
195         if (need == 0)
196         {
197           MD5Hash blockhash;
198           blockcontext.Final(blockhash);
199 
200           // Store the block hash and block crc in the file verification packet.
201           verificationpacket->SetBlockHashAndCRC(blocknumber, blockhash, blockcrc);
202 
203           blocknumber++;
204 
205           // More blocks
206           if (blocknumber < blockcount)
207           {
208             need = blocksize;
209 
210             blockcontext.Reset();
211             blockcrc = 0;
212           }
213         }
214       }
215 
216       if (noiselevel > CommandLine::nlQuiet)
217       {
218         // Display progress
219         u32 oldfraction = (u32)(1000 * offset / filesize);
220         offset += want;
221         u32 newfraction = (u32)(1000 * offset / filesize);
222         if (oldfraction != newfraction)
223         {
224           cout << newfraction/10 << '.' << newfraction%10 << "%\r" << flush;
225         }
226       }
227     }
228 
229     // Did we finish the last block
230     if (need > 0)
231     {
232       blockcrc = ~0 ^ CRCUpdateBlock(~0 ^ blockcrc, (size_t)need);
233       blockcontext.Update((size_t)need);
234 
235       MD5Hash blockhash;
236       blockcontext.Final(blockhash);
237 
238       // Store the block hash and block crc in the file verification packet.
239       verificationpacket->SetBlockHashAndCRC(blocknumber, blockhash, blockcrc);
240 
241       blocknumber++;
242 
243       need = 0;
244     }
245 
246     // Finish computing the file hash.
247     MD5Hash filehash;
248     filecontext.Final(filehash);
249 
250     // Store the file hash in the file description packet.
251     descriptionpacket->HashFull(filehash);
252 
253     // Did we compute the 16k hash.
254     if (offset < 16384)
255     {
256       // Store the 16k hash in the file description packet.
257       descriptionpacket->Hash16k(filehash);
258     }
259 
260     delete [] buffer;
261 
262     // Compute the fileid and store it in the verification packet.
263     descriptionpacket->ComputeFileId();
264     verificationpacket->FileId(descriptionpacket->FileId());
265   }
266 
267   return true;
268 }
269 
Close(void)270 void Par2CreatorSourceFile::Close(void)
271 {
272   diskfile->Close();
273 }
274 
275 
RecordCriticalPackets(list<CriticalPacket * > & criticalpackets)276 void Par2CreatorSourceFile::RecordCriticalPackets(list<CriticalPacket*> &criticalpackets)
277 {
278   // Add the file description packet and file verification packet to
279   // the critical packet list.
280   criticalpackets.push_back(descriptionpacket);
281   criticalpackets.push_back(verificationpacket);
282 }
283 
CompareLess(const Par2CreatorSourceFile * const & left,const Par2CreatorSourceFile * const & right)284 bool Par2CreatorSourceFile::CompareLess(const Par2CreatorSourceFile* const &left, const Par2CreatorSourceFile* const &right)
285 {
286   // Sort source files based on fileid
287   return left->descriptionpacket->FileId() < right->descriptionpacket->FileId();
288 }
289 
FileId(void) const290 const MD5Hash& Par2CreatorSourceFile::FileId(void) const
291 {
292   // Get the file id hash
293   return descriptionpacket->FileId();
294 }
295 
InitialiseSourceBlocks(vector<DataBlock>::iterator & sourceblock,u64 blocksize)296 void Par2CreatorSourceFile::InitialiseSourceBlocks(vector<DataBlock>::iterator &sourceblock, u64 blocksize)
297 {
298   for (u32 blocknum=0; blocknum<blockcount; blocknum++)
299   {
300     // Configure each source block to an appropriate offset and length within the source file.
301     sourceblock->SetLocation(diskfile,                                       // file
302                              blocknum * blocksize);                          // offset
303     sourceblock->SetLength(min(blocksize, filesize - (u64)blocknum * blocksize)); // length
304     sourceblock++;
305   }
306 }
307 
UpdateHashes(u32 blocknumber,const void * buffer,size_t length)308 void Par2CreatorSourceFile::UpdateHashes(u32 blocknumber, const void *buffer, size_t length)
309 {
310   // Compute the crc and hash of the data
311   u32 blockcrc = ~0 ^ CRCUpdateBlock(~0, length, buffer);
312   MD5Context blockcontext;
313   blockcontext.Update(buffer, length);
314   MD5Hash blockhash;
315   blockcontext.Final(blockhash);
316 
317   // Store the results in the verification packet
318   verificationpacket->SetBlockHashAndCRC(blocknumber, blockhash, blockcrc);
319 
320 
321   // Update the full file hash, but don't go beyond the end of the file
322   if (length > filesize - blocknumber * length)
323   {
324     length = (size_t)(filesize - blocknumber * (u64)length);
325   }
326 
327   assert(contextfull != 0);
328 
329   contextfull->Update(buffer, length);
330 }
331 
FinishHashes(void)332 void Par2CreatorSourceFile::FinishHashes(void)
333 {
334   assert(contextfull != 0);
335 
336   // Finish computation of the full file hash
337   MD5Hash hash;
338   contextfull->Final(hash);
339 
340   // Store it in the description packet
341   descriptionpacket->HashFull(hash);
342 }
343