/* Xlunzip - Test tool for the lzip_decompress linux module Copyright (C) 2016-2018 Antonio Diaz Diaz. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #define _FILE_OFFSET_BITS 64 #include #include #include #include #include #include #include #include #include "linux_lunzip.h" #include "lzip.h" /* Returns the number of bytes really read. If (returned value < size) and (errno == 0), means EOF was reached. */ long readblock( const int fd, uint8_t * const buf, const long size ) { long sz = 0; errno = 0; while( sz < size ) { const int n = read( fd, buf + sz, min( 1L << 20, size - sz ) ); if( n > 0 ) sz += n; else if( n == 0 ) break; /* EOF */ else if( errno != EINTR ) break; errno = 0; } return sz; } /* Returns the address of a malloc'd buffer containing the file data and the buffer and file sizes in '*buffer_sizep' and '*file_sizep'. In case of error, returns 0 and does not modify '*size'. */ uint8_t * read_file( const int infd, long * const buffer_sizep, long * const file_sizep, struct Pretty_print * const pp ) { long buffer_size = 1 << 20; uint8_t * buffer = (uint8_t *)malloc( buffer_size ); if( !buffer ) { show_file_error( pp->name, "Not enough memory.", 0 ); return 0; } long file_size = readblock( infd, buffer, buffer_size ); while( file_size >= buffer_size && !errno ) { if( buffer_size >= LONG_MAX ) { show_file_error( pp->name, "File is too large.", 0 ); free( buffer ); return 0; } buffer_size = ( buffer_size <= LONG_MAX / 2 ) ? 2 * buffer_size : LONG_MAX; uint8_t * const tmp = (uint8_t *)realloc( buffer, buffer_size ); if( !tmp ) { show_file_error( pp->name, "Not enough memory.", 0 ); free( buffer ); return 0; } buffer = tmp; file_size += readblock( infd, buffer + file_size, buffer_size - file_size ); } if( errno ) { show_file_error( pp->name, "Error reading file", errno ); free( buffer ); return 0; } *buffer_sizep = buffer_size; *file_sizep = file_size; return buffer; } struct File_sizes { unsigned long long csize; unsigned long long dsize; long trailing; }; const char * set_file_sizes( struct File_sizes * const file_sizes, const uint8_t * const buffer, const long file_size ) { if( file_size < min_member_size ) return "Input file is too short."; const Lzip_header * header = (const Lzip_header *)buffer; if( !Lh_verify_magic( *header ) ) return "Bad magic number (file not in lzip format)."; if( !Lh_verify_version( *header ) ) return "Version of lzip member format not supported."; file_sizes->csize = file_sizes->dsize = file_sizes->trailing = 0; unsigned long pos = file_size; /* always points to a header or to EOF */ while( pos >= min_member_size ) { const Lzip_trailer * const trailer = (const Lzip_trailer *)( buffer + pos - Lt_size ); const unsigned long long member_size = Lt_get_member_size( *trailer ); if( member_size < min_member_size || member_size > pos ) { if( file_sizes->csize == 0 ) /* maybe trailing data */ { if( member_size == 0 ) /* skip trailing zeros */ while( pos > Lt_size && buffer[pos-8] == 0 ) --pos; else --pos; continue; } return "Member size in trailer is corrupt."; } header = (const Lzip_header *)( buffer + pos - member_size ); if( !Lh_verify_magic( *header ) || !Lh_verify_version( *header ) ) { if( file_sizes->csize == 0 ) { --pos; continue; } /* maybe trailing data */ return "Bad member header inside file."; } if( file_sizes->csize == 0 && file_size - pos > 0 ) { file_sizes->trailing = file_size - pos; header = (const Lzip_header *)( buffer + pos ); if( file_size - pos > Lh_size && Lh_verify_magic( *header ) && Lh_verify_version( *header ) ) return "Last member in input file is truncated or corrupt."; } pos -= member_size; file_sizes->csize += member_size; file_sizes->dsize += Lt_get_data_size( *trailer ); } if( pos != 0 || file_sizes->csize == 0 ) return "Can't get file sizes."; if( file_sizes->csize + file_sizes->trailing != (unsigned long)file_size ) return "Error getting file sizes."; if( file_sizes->csize > LONG_MAX ) return "File is larger than LONG_MAX."; if( file_sizes->dsize > LONG_MAX ) return "Data is larger than LONG_MAX."; return 0; } const char * global_name; static void error(char *x) { show_file_error( global_name, x, 0 ); } /* * Load the compressed file at the end of the buffer used to hold the * decompressed data. Verify that the in-place decompression does not * overwrite the compressed data. * * |----- compressed data ------| * V V * |---------------|-------------------|--------| * ^ ^ * |------- decompressed data ---------| */ int decompress_in_place( const int infd, struct Pretty_print * const pp, const bool testing ) { long buffer_size = 0, file_size = 0; uint8_t * buffer = read_file( infd, &buffer_size, &file_size, pp ); if( !buffer ) return 1; struct File_sizes file_sizes; const char * emsg = set_file_sizes( &file_sizes, buffer, file_size ); if( emsg ) { show_file_error( pp->name, emsg, 0 ); return 2; } const long csize = file_sizes.csize; const long dsize = file_sizes.dsize; /* const long trailing = file_sizes.trailing; */ /* ( (csize-36+63) >> 6 ) + 36 never failed with single member */ const long rextra = ( csize >> 5 ) + 72; if( buffer_size < dsize + rextra ) /* avoid realloc if big enough */ { buffer_size = dsize + rextra; buffer = (uint8_t *)realloc( buffer, buffer_size ); if( !buffer ) { show_file_error( pp->name, "Not enough memory.", 0 ); return 1; } } else buffer_size = max( dsize + rextra, csize ); const long cbegin = buffer_size - csize; if( cbegin > 0 ) memmove( buffer + cbegin, buffer, csize ); long in_pos, out_pos; int retval; global_name = pp->name; retval = convert_retval( __lunzip( buffer + cbegin, csize, 0, 0, buffer, buffer_size, &in_pos, &out_pos, error ) ); if( retval == 0 && !testing ) { const long len = flush( buffer, out_pos ); if( len < out_pos ) { show_file_error( pp->name, "Write error", errno ); return 1; } } free( buffer ); if( retval ) return retval; if( verbosity >= 1 ) Pp_show_msg( pp, 0 ); if( verbosity >= 2 ) { if( out_pos <= 0 || in_pos <= 0 ) fputs( "no data compressed. ", stderr ); else fprintf( stderr, "%6.3f:1, %5.2f%% ratio, %5.2f%% saved. ", (double)out_pos / in_pos, ( 100.0 * in_pos ) / out_pos, 100.0 - ( ( 100.0 * in_pos ) / out_pos ) ); if( verbosity >= 3 ) fprintf( stderr, "decompressed %9lu, compressed %8lu. ", out_pos, in_pos ); } if( verbosity >= 1 ) fputs( testing ? "ok\n" : "done\n", stderr ); return 0; }