summaryrefslogtreecommitdiffstats
path: root/in_place.c
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--in_place.c220
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;
+ }