1 /* p_unix.cpp --
2
3 This file is part of the UPX executable compressor.
4
5 Copyright (C) 1996-2020 Markus Franz Xaver Johannes Oberhumer
6 Copyright (C) 1996-2020 Laszlo Molnar
7 Copyright (C) 2000-2020 John F. Reiser
8 All Rights Reserved.
9
10 UPX and the UCL library are free software; you can redistribute them
11 and/or modify them under the terms of the GNU General Public License as
12 published by the Free Software Foundation; either version 2 of
13 the License, or (at your option) any later version.
14
15 This program is distributed in the hope that it will be useful,
16 but WITHOUT ANY WARRANTY; without even the implied warranty of
17 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 GNU General Public License for more details.
19
20 You should have received a copy of the GNU General Public License
21 along with this program; see the file COPYING.
22 If not, write to the Free Software Foundation, Inc.,
23 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
24
25 Markus F.X.J. Oberhumer Laszlo Molnar
26 <markus@oberhumer.com> <ezerotven+github@gmail.com>
27
28 John F. Reiser
29 <jreiser@users.sourceforge.net>
30 */
31
32
33 #include "conf.h"
34
35 #include "file.h"
36 #include "filter.h"
37 #include "packer.h"
38 #include "p_unix.h"
39 #include "p_elf.h"
40
41 // do not change
42 #define BLOCKSIZE (512*1024)
43
44
45 /*************************************************************************
46 //
47 **************************************************************************/
48
PackUnix(InputFile * f)49 PackUnix::PackUnix(InputFile *f) :
50 super(f), exetype(0), blocksize(0), overlay_offset(0), lsize(0)
51 {
52 COMPILE_TIME_ASSERT(sizeof(Elf32_Ehdr) == 52);
53 COMPILE_TIME_ASSERT(sizeof(Elf32_Phdr) == 32);
54 COMPILE_TIME_ASSERT(sizeof(b_info) == 12);
55 COMPILE_TIME_ASSERT(sizeof(l_info) == 12);
56 COMPILE_TIME_ASSERT(sizeof(p_info) == 12);
57 }
58
59
60 // common part of canPack(), enhanced by subclasses
canPack()61 bool PackUnix::canPack()
62 {
63 if (exetype == 0)
64 return false;
65
66 #if defined(__unix__) && !defined(__MSYS2__)
67 // must be executable by owner
68 if ((fi->st.st_mode & S_IXUSR) == 0)
69 throwCantPack("file not executable; try 'chmod +x'");
70 #endif
71 if (file_size < 4096)
72 throwCantPack("file is too small");
73
74 // info: currently the header is 36 (32+4) bytes before EOF
75 unsigned char buf[256];
76 fi->seek(-(off_t)sizeof(buf), SEEK_END);
77 fi->readx(buf, sizeof(buf));
78 checkAlreadyPacked(buf, sizeof(buf));
79
80 return true;
81 }
82
83
writePackHeader(OutputFile * fo)84 void PackUnix::writePackHeader(OutputFile *fo)
85 {
86 unsigned char buf[32];
87 memset(buf, 0, sizeof(buf));
88
89 const int hsize = ph.getPackHeaderSize();
90 assert((unsigned)hsize <= sizeof(buf));
91
92 // note: magic constants are always le32
93 set_le32(buf+0, UPX_MAGIC_LE32);
94 set_le32(buf+4, UPX_MAGIC2_LE32);
95
96 checkPatch(NULL, 0, 0, 0); // reset
97 patchPackHeader(buf, hsize);
98 checkPatch(NULL, 0, 0, 0); // reset
99
100 fo->write(buf, hsize);
101 }
102
103
104 /*************************************************************************
105 // Generic Unix pack(). Subclasses must provide patchLoader().
106 //
107 // A typical compressed Unix executable looks like this:
108 // - loader stub
109 // - 12 bytes header info
110 // - the compressed blocks, each with a 8 byte header for block sizes
111 // - 4 bytes block end marker (uncompressed size 0)
112 // - 32 bytes UPX packheader
113 // - 4 bytes overlay offset (needed for decompression)
114 **************************************************************************/
115
116 // see note below and Packer::compress()
checkCompressionRatio(unsigned,unsigned) const117 bool PackUnix::checkCompressionRatio(unsigned, unsigned) const
118 {
119 return true;
120 }
121
pack1(OutputFile *,Filter &)122 void PackUnix::pack1(OutputFile * /*fo*/, Filter & /*ft*/)
123 {
124 // derived class usually provides this
125 }
126
getStrategy(Filter &)127 int PackUnix::getStrategy(Filter &/*ft*/)
128 {
129 // Called just before reading and compressing each block.
130 // Might want to adjust blocksize, etc.
131
132 // If user specified the filter, then use it (-2==filter_strategy).
133 // Else try the first two filters, and pick the better (2==filter_strategy).
134 return (opt->no_filter ? -3 : ((opt->filter > 0) ? -2 : 2));
135 }
136
pack2(OutputFile * fo,Filter & ft)137 int PackUnix::pack2(OutputFile *fo, Filter &ft)
138 {
139 // compress blocks
140 unsigned total_in = 0;
141 unsigned total_out = 0;
142
143 // FIXME: ui_total_passes is not correct with multiple blocks...
144 // ui_total_passes = (file_size + blocksize - 1) / blocksize;
145 // if (ui_total_passes == 1)
146 // ui_total_passes = 0;
147
148 unsigned remaining = file_size;
149 unsigned n_block = 0;
150 while (remaining > 0)
151 {
152 // FIXME: disable filters if we have more than one block.
153 // FIXME: There is only 1 un-filter in the stub [as of 2002-11-10].
154 // So the next block really has no choice!
155 // This merely prevents an assert() in compressWithFilters(),
156 // which assumes it has free choice on each call [block].
157 // And if the choices aren't the same on each block,
158 // then un-filtering will give incorrect results.
159 int filter_strategy = getStrategy(ft);
160 if (file_size > (off_t)blocksize)
161 filter_strategy = -3; // no filters
162
163 int l = fi->readx(ibuf, UPX_MIN(blocksize, remaining));
164 remaining -= l;
165
166 // Note: compression for a block can fail if the
167 // file is e.g. blocksize + 1 bytes long
168
169 // compress
170 ph.overlap_overhead = 0;
171 ph.c_len = ph.u_len = l;
172 ft.buf_len = l;
173
174 // compressWithFilters() updates u_adler _inside_ compress();
175 // that is, AFTER filtering. We want BEFORE filtering,
176 // so that decompression checks the end-to-end checksum.
177 unsigned const end_u_adler = upx_adler32(ibuf, ph.u_len, ph.u_adler);
178 compressWithFilters(&ft, OVERHEAD, NULL_cconf, filter_strategy,
179 !!n_block++); // check compression ratio only on first block
180
181 if (ph.c_len < ph.u_len) {
182 const upx_bytep tbuf = NULL;
183 if (ft.id == 0) tbuf = ibuf;
184 ph.overlap_overhead = OVERHEAD;
185 if (!testOverlappingDecompression(obuf, tbuf, ph.overlap_overhead)) {
186 // not in-place compressible
187 ph.c_len = ph.u_len;
188 }
189 }
190 if (ph.c_len >= ph.u_len) {
191 // block is not compressible
192 ph.c_len = ph.u_len;
193 // must manually update checksum of compressed data
194 ph.c_adler = upx_adler32(ibuf, ph.u_len, ph.saved_c_adler);
195 }
196
197 // write block header
198 b_info blk_info;
199 memset(&blk_info, 0, sizeof(blk_info));
200 set_te32(&blk_info.sz_unc, ph.u_len);
201 set_te32(&blk_info.sz_cpr, ph.c_len);
202 if (ph.c_len < ph.u_len) {
203 blk_info.b_method = (unsigned char) ph.method;
204 blk_info.b_ftid = (unsigned char) ph.filter;
205 blk_info.b_cto8 = (unsigned char) ph.filter_cto;
206 }
207 fo->write(&blk_info, sizeof(blk_info));
208 b_len += sizeof(b_info);
209
210 // write compressed data
211 if (ph.c_len < ph.u_len) {
212 fo->write(obuf, ph.c_len);
213 verifyOverlappingDecompression(); // uses ph.u_adler
214 }
215 else {
216 fo->write(ibuf, ph.u_len);
217 }
218 ph.u_adler = end_u_adler;
219
220 total_in += ph.u_len;
221 total_out += ph.c_len;
222 }
223
224 // update header with totals
225 ph.u_len = total_in;
226 ph.c_len = total_out;
227
228 if ((off_t)total_in != file_size) {
229 throwEOFException();
230 }
231
232 return 1; // default: write end-of-compression bhdr next
233 }
234
235 void
patchLoaderChecksum()236 PackUnix::patchLoaderChecksum()
237 {
238 unsigned char *const ptr = getLoader();
239 l_info *const lp = &linfo;
240 // checksum for loader; also some PackHeader info
241 lp->l_magic = UPX_MAGIC_LE32; // LE32 always
242 set_te16(&lp->l_lsize, (upx_uint16_t) lsize);
243 lp->l_version = (unsigned char) ph.version;
244 lp->l_format = (unsigned char) ph.format;
245 // INFO: lp->l_checksum is currently unused
246 set_te32(&lp->l_checksum, upx_adler32(ptr, lsize));
247 }
248
pack3(OutputFile * fo,Filter & ft)249 off_t PackUnix::pack3(OutputFile *fo, Filter &ft)
250 {
251 if (0==linker) {
252 // If no filter, then linker is not constructed by side effect
253 // of packExtent calling compressWithFilters.
254 // This is typical after "/usr/bin/patchelf --set-rpath".
255 buildLoader(&ft);
256 }
257 upx_byte *p = getLoader();
258 lsize = getLoaderSize();
259 updateLoader(fo);
260 patchLoaderChecksum();
261 fo->write(p, lsize);
262 return fo->getBytesWritten();
263 }
264
pack4(OutputFile * fo,Filter &)265 void PackUnix::pack4(OutputFile *fo, Filter &)
266 {
267 writePackHeader(fo);
268
269 unsigned tmp;
270 set_te32(&tmp, overlay_offset);
271 fo->write(&tmp, sizeof(tmp));
272 }
273
pack(OutputFile * fo)274 void PackUnix::pack(OutputFile *fo)
275 {
276 Filter ft(ph.level);
277 ft.addvalue = 0;
278 b_len = 0;
279 progid = 0;
280
281 // set options
282 blocksize = opt->o_unix.blocksize;
283 if (blocksize <= 0)
284 blocksize = BLOCKSIZE;
285 if ((off_t)blocksize > file_size)
286 blocksize = file_size;
287
288 // init compression buffers
289 ibuf.alloc(blocksize);
290 obuf.allocForCompression(blocksize);
291
292 fi->seek(0, SEEK_SET);
293 pack1(fo, ft); // generate Elf header, etc.
294
295 p_info hbuf;
296 set_te32(&hbuf.p_progid, progid);
297 set_te32(&hbuf.p_filesize, file_size);
298 set_te32(&hbuf.p_blocksize, blocksize);
299 fo->write(&hbuf, sizeof(hbuf));
300
301 // append the compressed body
302 if (pack2(fo, ft)) {
303 // write block end marker (uncompressed size 0)
304 b_info hdr; memset(&hdr, 0, sizeof(hdr));
305 set_le32(&hdr.sz_cpr, UPX_MAGIC_LE32);
306 fo->write(&hdr, sizeof(hdr));
307 }
308
309 pack3(fo, ft); // append loader
310
311 pack4(fo, ft); // append PackHeader and overlay_offset; update Elf header
312
313 // finally check the compression ratio
314 if (!checkFinalCompressionRatio(fo))
315 throwNotCompressible();
316 }
317
318
packExtent(const Extent & x,unsigned & total_in,unsigned & total_out,Filter * ft,OutputFile * fo,unsigned hdr_u_len)319 void PackUnix::packExtent(
320 const Extent &x,
321 unsigned &total_in,
322 unsigned &total_out,
323 Filter *ft,
324 OutputFile *fo,
325 unsigned hdr_u_len
326 )
327 {
328 unsigned const init_u_adler = ph.u_adler;
329 unsigned const init_c_adler = ph.c_adler;
330 MemBuffer hdr_ibuf;
331 if (hdr_u_len) {
332 hdr_ibuf.alloc(hdr_u_len);
333 fi->seek(0, SEEK_SET);
334 int l = fi->readx(hdr_ibuf, hdr_u_len);
335 (void)l;
336 }
337 fi->seek(x.offset, SEEK_SET);
338 for (off_t rest = x.size; 0 != rest; ) {
339 int const filter_strategy = ft ? getStrategy(*ft) : 0;
340 int l = fi->readx(ibuf, UPX_MIN(rest, (off_t)blocksize));
341 if (l == 0) {
342 break;
343 }
344 rest -= l;
345
346 // Note: compression for a block can fail if the
347 // file is e.g. blocksize + 1 bytes long
348
349 // compress
350 ph.c_len = ph.u_len = l;
351 ph.overlap_overhead = 0;
352 unsigned end_u_adler = 0;
353 if (ft) {
354 // compressWithFilters() updates u_adler _inside_ compress();
355 // that is, AFTER filtering. We want BEFORE filtering,
356 // so that decompression checks the end-to-end checksum.
357 end_u_adler = upx_adler32(ibuf, ph.u_len, ph.u_adler);
358 ft->buf_len = l;
359
360 // compressWithFilters() requirements?
361 ph.filter = 0;
362 ph.filter_cto = 0;
363 ft->id = 0;
364 ft->cto = 0;
365
366 compressWithFilters(ft, OVERHEAD, NULL_cconf, filter_strategy,
367 0, 0, 0, hdr_ibuf, hdr_u_len);
368 }
369 else {
370 (void) compress(ibuf, ph.u_len, obuf); // ignore return value
371 }
372
373 if (ph.c_len < ph.u_len) {
374 const upx_bytep tbuf = NULL;
375 if (ft == NULL || ft->id == 0) tbuf = ibuf;
376 ph.overlap_overhead = OVERHEAD;
377 if (!testOverlappingDecompression(obuf, tbuf, ph.overlap_overhead)) {
378 // not in-place compressible
379 ph.c_len = ph.u_len;
380 }
381 }
382 if (ph.c_len >= ph.u_len) {
383 // block is not compressible
384 ph.c_len = ph.u_len;
385 memcpy(obuf, ibuf, ph.c_len);
386 // must update checksum of compressed data
387 ph.c_adler = upx_adler32(ibuf, ph.u_len, ph.saved_c_adler);
388 }
389
390 // write block sizes
391 b_info tmp;
392 if (hdr_u_len) {
393 unsigned hdr_c_len = 0;
394 MemBuffer hdr_obuf;
395 hdr_obuf.allocForCompression(hdr_u_len);
396 int r = upx_compress(hdr_ibuf, hdr_u_len, hdr_obuf, &hdr_c_len, 0,
397 ph.method, 10, NULL, NULL);
398 if (r != UPX_E_OK)
399 throwInternalError("header compression failed");
400 if (hdr_c_len >= hdr_u_len)
401 throwInternalError("header compression size increase");
402 ph.saved_u_adler = upx_adler32(hdr_ibuf, hdr_u_len, init_u_adler);
403 ph.saved_c_adler = upx_adler32(hdr_obuf, hdr_c_len, init_c_adler);
404 ph.u_adler = upx_adler32(ibuf, ph.u_len, ph.saved_u_adler);
405 ph.c_adler = upx_adler32(obuf, ph.c_len, ph.saved_c_adler);
406 end_u_adler = ph.u_adler;
407 memset(&tmp, 0, sizeof(tmp));
408 set_te32(&tmp.sz_unc, hdr_u_len);
409 set_te32(&tmp.sz_cpr, hdr_c_len);
410 tmp.b_method = (unsigned char) ph.method;
411 fo->write(&tmp, sizeof(tmp));
412 b_len += sizeof(b_info);
413 fo->write(hdr_obuf, hdr_c_len);
414 total_out += hdr_c_len;
415 total_in += hdr_u_len;
416 hdr_u_len = 0; // compress hdr one time only
417 }
418 memset(&tmp, 0, sizeof(tmp));
419 set_te32(&tmp.sz_unc, ph.u_len);
420 set_te32(&tmp.sz_cpr, ph.c_len);
421 if (ph.c_len < ph.u_len) {
422 tmp.b_method = (unsigned char) ph.method;
423 if (ft) {
424 tmp.b_ftid = (unsigned char) ft->id;
425 tmp.b_cto8 = ft->cto;
426 }
427 }
428 fo->write(&tmp, sizeof(tmp));
429 b_len += sizeof(b_info);
430
431 if (ft) {
432 ph.u_adler = end_u_adler;
433 }
434 // write compressed data
435 if (ph.c_len < ph.u_len) {
436 fo->write(obuf, ph.c_len);
437 // Checks ph.u_adler after decompression, after unfiltering
438 verifyOverlappingDecompression(ft);
439 }
440 else {
441 fo->write(ibuf, ph.u_len);
442 }
443
444 total_in += ph.u_len;
445 total_out += ph.c_len;
446 }
447 }
448
unpackExtent(unsigned wanted,OutputFile * fo,unsigned & total_in,unsigned & total_out,unsigned & c_adler,unsigned & u_adler,bool first_PF_X,unsigned szb_info,bool is_rewrite)449 void PackUnix::unpackExtent(unsigned wanted, OutputFile *fo,
450 unsigned &total_in, unsigned &total_out,
451 unsigned &c_adler, unsigned &u_adler,
452 bool first_PF_X, unsigned szb_info, bool is_rewrite
453 )
454 {
455 b_info hdr; memset(&hdr, 0, sizeof(hdr));
456 while (wanted) {
457 fi->readx(&hdr, szb_info);
458 int const sz_unc = ph.u_len = get_te32(&hdr.sz_unc);
459 int const sz_cpr = ph.c_len = get_te32(&hdr.sz_cpr);
460 ph.filter_cto = hdr.b_cto8;
461
462 if (sz_unc == 0) { // must never happen while 0!=wanted
463 throwCantUnpack("corrupt b_info");
464 break;
465 }
466 if (sz_unc <= 0 || sz_cpr <= 0)
467 throwCantUnpack("corrupt b_info");
468 if (sz_cpr > sz_unc || sz_unc > (int)blocksize)
469 throwCantUnpack("corrupt b_info");
470
471 int j = blocksize + OVERHEAD - sz_cpr;
472 fi->readx(ibuf+j, sz_cpr);
473 // update checksum of compressed data
474 c_adler = upx_adler32(ibuf + j, sz_cpr, c_adler);
475 // decompress
476 if (sz_cpr < sz_unc)
477 {
478 decompress(ibuf+j, ibuf, false);
479 if (12==szb_info) { // modern per-block filter
480 if (hdr.b_ftid) {
481 Filter ft(ph.level); // FIXME: ph.level for b_info?
482 ft.init(hdr.b_ftid, 0);
483 ft.cto = hdr.b_cto8;
484 ft.unfilter(ibuf, sz_unc);
485 }
486 }
487 else { // ancient per-file filter
488 if (first_PF_X) { // Elf32_Ehdr is never filtered
489 first_PF_X = false; // but everything else might be
490 }
491 else if (ph.filter) {
492 Filter ft(ph.level);
493 ft.init(ph.filter, 0);
494 ft.cto = (unsigned char) ph.filter_cto;
495 ft.unfilter(ibuf, sz_unc);
496 }
497 }
498 j = 0;
499 }
500 // update checksum of uncompressed data
501 u_adler = upx_adler32(ibuf + j, sz_unc, u_adler);
502 total_in += sz_cpr;
503 total_out += sz_unc;
504 // write block
505 if (fo) {
506 if (is_rewrite) {
507 fo->rewrite(ibuf + j, sz_unc);
508 }
509 else {
510 fo->write(ibuf + j, sz_unc);
511 }
512 }
513 if (wanted < (unsigned)sz_unc)
514 throwCantUnpack("corrupt b_info");
515 wanted -= sz_unc;
516 }
517 }
518
519 /*************************************************************************
520 // Generic Unix canUnpack().
521 **************************************************************************/
522
canUnpack()523 int PackUnix::canUnpack()
524 {
525 int const small = 32 + sizeof(overlay_offset);
526 // Allow zero-filled last page, for Mac OS X code signing.
527 int bufsize = 2*4096 + 2*small +1;
528 if (bufsize > fi->st_size())
529 bufsize = fi->st_size();
530 MemBuffer buf(bufsize);
531
532 fi->seek(-(off_t)bufsize, SEEK_END);
533 fi->readx(buf, bufsize);
534 int i = bufsize;
535 while (i > small && 0 == buf[--i]) { }
536 i -= small;
537 // allow incompressible extents
538 if (i < 0 || !getPackHeader(buf + i, bufsize - i, true))
539 return false;
540
541 int l = ph.buf_offset + ph.getPackHeaderSize();
542 if (l < 0 || l + 4 > bufsize)
543 throwCantUnpack("file corrupted");
544 overlay_offset = get_te32(buf + i + l);
545 if ((off_t)overlay_offset >= file_size)
546 throwCantUnpack("file corrupted");
547
548 return true;
549 }
550
551
552 /*************************************************************************
553 // Generic Unix unpack().
554 //
555 // This code looks much like the one in stub/l_linux.c
556 // See notes there.
557 **************************************************************************/
558
unpack(OutputFile * fo)559 void PackUnix::unpack(OutputFile *fo)
560 {
561 b_info bhdr;
562 unsigned const szb_info = (ph.version <= 11)
563 ? sizeof(bhdr.sz_unc) + sizeof(bhdr.sz_cpr) // old style
564 : sizeof(bhdr);
565
566 unsigned c_adler = upx_adler32(NULL, 0);
567 unsigned u_adler = upx_adler32(NULL, 0);
568
569 // defaults for ph.version == 8
570 unsigned orig_file_size = 0;
571 blocksize = 512 * 1024;
572
573 fi->seek(overlay_offset, SEEK_SET);
574 if (ph.version > 8)
575 {
576 p_info hbuf;
577 fi->readx(&hbuf, sizeof(hbuf));
578 orig_file_size = get_te32(&hbuf.p_filesize);
579 blocksize = get_te32(&hbuf.p_blocksize);
580
581 if (file_size > (off_t)orig_file_size || blocksize > orig_file_size)
582 throwCantUnpack("file header corrupted");
583 }
584 else
585 {
586 // skip 4 bytes (program id)
587 fi->seek(4, SEEK_CUR);
588 }
589
590 if ((int)(blocksize + OVERHEAD) < 0)
591 throwCantUnpack("blocksize corrupted");
592 ibuf.alloc(blocksize + OVERHEAD);
593
594 // decompress blocks
595 unsigned total_in = 0;
596 unsigned total_out = 0;
597 memset(&bhdr, 0, sizeof(bhdr));
598 for (;;)
599 {
600 #define buf ibuf
601 int i;
602 unsigned sz_unc, sz_cpr;
603
604 fi->readx(&bhdr, szb_info);
605 ph.u_len = sz_unc = get_te32(&bhdr.sz_unc);
606 ph.c_len = sz_cpr = get_te32(&bhdr.sz_cpr);
607
608 if (sz_unc == 0) // uncompressed size 0 -> EOF
609 {
610 // note: must reload sz_cpr as magic is always stored le32
611 sz_cpr = get_le32(&bhdr.sz_cpr);
612 if (sz_cpr != UPX_MAGIC_LE32) // sz_cpr must be h->magic
613 throwCompressedDataViolation();
614 break;
615 }
616 if (sz_unc <= 0 || sz_cpr <= 0)
617 throwCompressedDataViolation();
618 if (sz_cpr > sz_unc || sz_unc > blocksize)
619 throwCompressedDataViolation();
620
621 i = blocksize + OVERHEAD - sz_cpr;
622 if (i < 0)
623 throwCantUnpack("corrupt b_info");
624 fi->readx(buf+i, sz_cpr);
625 // update checksum of compressed data
626 c_adler = upx_adler32(buf + i, sz_cpr, c_adler);
627 // decompress
628 if (sz_cpr < sz_unc) {
629 decompress(buf+i, buf, false);
630 if (0!=bhdr.b_ftid) {
631 Filter ft(ph.level);
632 ft.init(bhdr.b_ftid);
633 ft.cto = bhdr.b_cto8;
634 ft.unfilter(buf, sz_unc);
635 }
636 i = 0;
637 }
638 // update checksum of uncompressed data
639 u_adler = upx_adler32(buf + i, sz_unc, u_adler);
640 total_in += sz_cpr;
641 total_out += sz_unc;
642 // write block
643 if (fo)
644 fo->write(buf + i, sz_unc);
645 #undef buf
646 }
647
648 // update header with totals
649 ph.c_len = total_in;
650 ph.u_len = total_out;
651
652 // all bytes must be written
653 if (ph.version > 8 && total_out != orig_file_size)
654 throwEOFException();
655
656 // finally test the checksums
657 if (ph.c_adler != c_adler || ph.u_adler != u_adler)
658 throwChecksumError();
659 }
660
661 /* vim:set ts=4 sw=4 et: */
662