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