diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2018-08-26 07:57:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2018-08-26 07:57:14 +0000 |
commit | b9c3804bd95fa4adf48dd177743ccedf6f35b321 (patch) | |
tree | 742dfd2b9b12f5a22f8deb808697300440230305 /in_place.c | |
parent | Initial commit. (diff) | |
download | xlunzip-upstream/0.3.tar.xz xlunzip-upstream/0.3.zip |
Adding upstream version 0.3.upstream/0.3
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | in_place.c | 220 |
1 files changed, 220 insertions, 0 deletions
diff --git a/in_place.c b/in_place.c new file mode 100644 index 0000000..65a10dc --- /dev/null +++ b/in_place.c @@ -0,0 +1,220 @@ +/* Xlunzip - Test tool for the lunzip 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 <http://www.gnu.org/licenses/>. +*/ + +#define _FILE_OFFSET_BITS 64 + +#include <errno.h> +#include <limits.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#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; } + close( infd ); + *buffer_sizep = buffer_size; + *file_sizep = file_size; + return buffer; + } + + +struct File_sizes + { + long long csize; + 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 = (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; + long long pos = file_size; /* always points to a header or to EOF */ + while( pos >= min_member_size ) + { + const Lzip_trailer * const trailer = + (Lzip_trailer *)( buffer + pos - Lt_size ); + const long long member_size = Lt_get_member_size( *trailer ); + if( member_size < min_member_size || member_size > pos ) + { + if( file_sizes->csize == 0 ) { --pos; continue; } /* maybe trailing data */ + return "Member size in trailer is corrupt."; + } + header = (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 = (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 != file_size ) + return "Error getting file sizes."; + 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 long csize = file_sizes.csize; + const long long dsize = file_sizes.dsize; + /* const long trailing = file_sizes.trailing; */ + if( csize <= 0 || csize > LONG_MAX ) + { show_file_error( pp->name, "File is larger than LONG_MAX.", 0 ); + return 2; } + if( dsize < 0 || dsize > LONG_MAX ) + { show_file_error( pp->name, "Data is larger than LONG_MAX.", 0 ); + return 2; } + /* ( (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; + } |