diff options
Diffstat (limited to 'unzcrash.cc')
-rw-r--r-- | unzcrash.cc | 213 |
1 files changed, 72 insertions, 141 deletions
diff --git a/unzcrash.cc b/unzcrash.cc index 9a32b82..a118b9d 100644 --- a/unzcrash.cc +++ b/unzcrash.cc @@ -1,6 +1,6 @@ /* Unzcrash - Tests robustness of decompressors to corrupted data. Inspired by unzcrash.c from Julian Seward's bzip2. - Copyright (C) 2008-2017 Antonio Diaz Diaz. + Copyright (C) 2008-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 @@ -44,12 +44,13 @@ #define INT64_MAX 0x7FFFFFFFFFFFFFFFLL #endif +void show_error( const char * const msg, const int errcode = 0, + const bool help = false ); namespace { const char * const Program_name = "Unzcrash"; const char * const program_name = "unzcrash"; -const char * const program_year = "2017"; const char * invocation_name = 0; int verbosity = 0; @@ -58,10 +59,12 @@ int verbosity = 0; void show_help() { std::printf( "%s - Tests robustness of decompressors to corrupted data.\n", Program_name ); - std::printf( "\nUsage: %s [options] \"lzip -tv\" filename.lz\n", invocation_name ); + std::printf( "\nUsage: %s [options] 'lzip -t' file.lz\n", invocation_name ); std::printf( "\nBy default, unzcrash reads the specified file and then repeatedly\n" "decompresses it, increasing 256 times each byte of the compressed data,\n" - "so as to test all possible one-byte errors.\n" + "so as to test all possible one-byte errors. Note that it may take years\n" + "or even centuries to test all possible one-byte errors in a large file\n" + "(tens of MB).\n" "\nIf the '--block' option is given, unzcrash reads the specified file\n" "and then repeatedly decompresses it, setting all bytes in each\n" "successive block to the value given, so as to test all possible full\n" @@ -88,8 +91,9 @@ void show_help() " -V, --version output version information and exit\n" " -b, --bits=<range> test N-bit errors instead of full byte\n" " -B, --block[=<size>][,<val>] test blocks of given size [512,0]\n" - " -d, --delta=<n> test one of every n bytes/blocks/truncations\n" + " -d, --delta=<n> test one byte/block/truncation every n bytes\n" " -e, --set-byte=<pos>,<val> set byte at position <pos> to value <val>\n" + " -n, --no-verify skip initial verification of file.lz\n" " -p, --position=<bytes> first byte position to test [default 0]\n" " -q, --quiet suppress all messages\n" " -s, --size=<bytes> number of byte positions to test [all]\n" @@ -107,106 +111,22 @@ void show_help() "Lziprecover home page: http://www.nongnu.org/lzip/lziprecover.html\n" ); } +} // end namespace -void show_version() - { - std::printf( "%s %s\n", program_name, PROGVERSION ); - std::printf( "Copyright (C) %s Antonio Diaz Diaz.\n", program_year ); - std::printf( "License GPLv2+: GNU GPL version 2 or later <http://gnu.org/licenses/gpl.html>\n" - "This is free software: you are free to change and redistribute it.\n" - "There is NO WARRANTY, to the extent permitted by law.\n" ); - } - - -void show_error( const char * const msg, const int errcode = 0, - const bool help = false ) - { - if( verbosity < 0 ) return; - if( msg && msg[0] ) - { - std::fprintf( stderr, "%s: %s", program_name, msg ); - if( errcode > 0 ) std::fprintf( stderr, ": %s", std::strerror( errcode ) ); - std::fputc( '\n', stderr ); - } - if( help ) - std::fprintf( stderr, "Try '%s --help' for more information.\n", - invocation_name ); - } - - -void internal_error( const char * const msg ) - { - if( verbosity >= 0 ) - std::fprintf( stderr, "%s: internal error: %s\n", program_name, msg ); - std::exit( 3 ); - } +#include "main_common.cc" -long long getnum( const char * const ptr, const long long llimit = -LLONG_MAX, - const long long ulimit = LLONG_MAX, - const char ** const tailp = 0 ) - { - char * tail; - errno = 0; - long long result = strtoll( ptr, &tail, 0 ); - if( tail == ptr ) - { - show_error( "Bad or missing numerical argument.", 0, true ); - std::exit( 1 ); - } - - if( !errno && tail[0] ) - { - char * const p = tail++; - int factor; - bool bsuf; // 'B' suffix is present - if( tail[0] == 'i' ) { ++tail; factor = 1024; } else factor = 1000; - if( tail[0] == 'B' ) { ++tail; bsuf = true; } else bsuf = false; - int exponent = -1; // -1 = bad multiplier - switch( *p ) - { - case 'Y': exponent = 8; break; - case 'Z': exponent = 7; break; - case 'E': exponent = 6; break; - case 'P': exponent = 5; break; - case 'T': exponent = 4; break; - case 'G': exponent = 3; break; - case 'M': exponent = 2; break; - case 'K': if( factor == 1024 ) exponent = 1; break; - case 'k': if( factor == 1000 ) exponent = 1; break; - case 'B': if( factor == 1000 && !bsuf ) exponent = 0; break; - default : if( tailp ) { tail = p; exponent = 0; } break; - } - if( exponent < 0 ) - { - show_error( "Bad multiplier in numerical argument.", 0, true ); - std::exit( 1 ); - } - for( int i = 0; i < exponent; ++i ) - { - if( LLONG_MAX / factor >= std::labs( result ) ) result *= factor; - else { errno = ERANGE; break; } - } - } - if( !errno && ( result < llimit || result > ulimit ) ) errno = ERANGE; - if( errno ) - { - show_error( "Numerical argument out of limits." ); - std::exit( 1 ); - } - if( tailp ) *tailp = tail; - return result; - } +namespace { void parse_block( const char * const ptr, long & size, uint8_t & value ) { const char * tail = ptr; if( tail[0] != ',' ) - size = getnum( ptr, 1, INT_MAX, &tail ); + size = getnum( ptr, 0, 1, INT_MAX, &tail ); if( tail[0] == ',' ) - value = getnum( tail + 1, 0, 255 ); + value = getnum( tail + 1, 0, 0, 255 ); else if( tail[0] ) { show_error( "Bad separator in argument of '--block'", 0, true ); @@ -237,7 +157,7 @@ struct Bad_byte void parse_pos_value( const char * const ptr, Bad_byte & bad_byte ) { const char * tail; - bad_byte.pos = getnum( ptr, 0, INT64_MAX, &tail ); + bad_byte.pos = getnum( ptr, 0, 0, INT64_MAX, &tail ); if( tail[0] != ',' ) { show_error( "Bad separator between <pos> and <val>.", 0, true ); @@ -246,7 +166,7 @@ void parse_pos_value( const char * const ptr, Bad_byte & bad_byte ) if( tail[1] == '+' ) { ++tail; bad_byte.mode = Bad_byte::delta; } else if( tail[1] == 'f' ) { ++tail; bad_byte.mode = Bad_byte::flip; } else bad_byte.mode = Bad_byte::literal; - bad_byte.value = getnum( tail + 1, 0, 255 ); + bad_byte.value = getnum( tail + 1, 0, 0, 255 ); } @@ -348,7 +268,7 @@ public: if( data[i] ) { std::printf( "%d", i + 1 ); - if( --c ) std::fputs( ",", stdout ); + if( --c ) std::fputc( ',', stdout ); } std::fputs( " bit errors.\n", stdout ); } @@ -378,27 +298,29 @@ int main( const int argc, const char * const argv[] ) const char * zcmp_program = "zcmp"; long pos = 0; long max_size = LONG_MAX; - long delta = 1; + long delta = 0; // to be set later long block_size = 512; Mode program_mode = m_byte; uint8_t block_value = 0; + bool verify = true; invocation_name = argv[0]; const Arg_parser::Option options[] = { - { 'h', "help", Arg_parser::no }, - { 'b', "bits", Arg_parser::yes }, - { 'B', "block", Arg_parser::maybe }, - { 'd', "delta", Arg_parser::yes }, - { 'e', "set-byte", Arg_parser::yes }, - { 'p', "position", Arg_parser::yes }, - { 'q', "quiet", Arg_parser::no }, - { 's', "size", Arg_parser::yes }, - { 't', "truncate", Arg_parser::no }, - { 'v', "verbose", Arg_parser::no }, - { 'V', "version", Arg_parser::no }, - { 'z', "zcmp", Arg_parser::yes }, - { 0 , 0, Arg_parser::no } }; + { 'h', "help", Arg_parser::no }, + { 'b', "bits", Arg_parser::yes }, + { 'B', "block", Arg_parser::maybe }, + { 'd', "delta", Arg_parser::yes }, + { 'e', "set-byte", Arg_parser::yes }, + { 'n', "no-verify", Arg_parser::no }, + { 'p', "position", Arg_parser::yes }, + { 'q', "quiet", Arg_parser::no }, + { 's', "size", Arg_parser::yes }, + { 't', "truncate", Arg_parser::no }, + { 'v', "verbose", Arg_parser::no }, + { 'V', "version", Arg_parser::no }, + { 'z', "zcmp", Arg_parser::yes }, + { 0 , 0, Arg_parser::no } }; const Arg_parser parser( argc, argv, options ); if( parser.error().size() ) // bad option @@ -416,11 +338,12 @@ int main( const int argc, const char * const argv[] ) case 'b': if( !bits.parse( arg ) ) return 1; program_mode = m_byte; break; case 'B': if( arg[0] ) parse_block( arg, block_size, block_value ); program_mode = m_block; break; - case 'd': delta = getnum( arg, 1, INT_MAX ); break; + case 'd': delta = getnum( arg, block_size, 1, INT_MAX ); break; case 'e': parse_pos_value( arg, bad_byte ); break; - case 'p': pos = getnum( arg, -LONG_MAX, LONG_MAX ); break; + case 'n': verify = false; break; + case 'p': pos = getnum( arg, block_size, -LONG_MAX, LONG_MAX ); break; case 'q': verbosity = -1; break; - case 's': max_size = getnum( arg, -LONG_MAX, LONG_MAX ); break; + case 's': max_size = getnum( arg, block_size, -LONG_MAX, LONG_MAX ); break; case 't': program_mode = m_truncate; break; case 'v': if( verbosity < 4 ) ++verbosity; break; case 'V': show_version(); return 0; @@ -432,11 +355,12 @@ int main( const int argc, const char * const argv[] ) if( argind + 2 != parser.arguments() ) { if( verbosity >= 0 ) - std::fprintf( stderr, "Usage: %s \"lzip -tv\" filename.lz\n", - invocation_name ); + std::fprintf( stderr, "Usage: %s 'lzip -t' file.lz\n", invocation_name ); return 1; } + if( delta <= 0 ) delta = ( program_mode == m_block ) ? block_size : 1; + const char * const filename = parser.argument( argind + 1 ).c_str(); long file_size = 0; uint8_t * const buffer = read_file( filename, &file_size ); @@ -449,28 +373,31 @@ int main( const int argc, const char * const argv[] ) // verify original file if( verbosity >= 1 ) fprintf( stderr, "Testing file '%s'\n", filename ); - FILE * f = popen( command, "w" ); - if( !f ) - { show_error( "Can't open pipe to decompressor", errno ); return 1; } - if( (long)std::fwrite( buffer, 1, file_size, f ) != file_size ) - { show_error( "Can't write to decompressor", errno ); return 1; } - if( pclose( f ) != 0 ) - { - if( verbosity >= 0 ) - std::fprintf( stderr, "%s: Can't run '%s'.\n", program_name, command ); - return 1; - } - if( zcmp_command[0] ) + if( verify ) { - f = popen( zcmp_command, "w" ); + FILE * f = popen( command, "w" ); if( !f ) - { show_error( "Can't open pipe to zcmp command", errno ); return 1; } + { show_error( "Can't open pipe to decompressor", errno ); return 1; } if( (long)std::fwrite( buffer, 1, file_size, f ) != file_size ) - { show_error( "Can't write to zcmp command", errno ); return 1; } + { show_error( "Can't write to decompressor", errno ); return 1; } if( pclose( f ) != 0 ) { - show_error( "zcmp command failed. Skipping comparison" ); - zcmp_command[0] = 0; + if( verbosity >= 0 ) + std::fprintf( stderr, "%s: Can't run '%s'.\n", program_name, command ); + return 1; + } + if( zcmp_command[0] ) + { + f = popen( zcmp_command, "w" ); + if( !f ) + { show_error( "Can't open pipe to zcmp command", errno ); return 1; } + if( (long)std::fwrite( buffer, 1, file_size, f ) != file_size ) + { show_error( "Can't write to zcmp command", errno ); return 1; } + if( pclose( f ) != 0 ) + { + show_error( "zcmp command failed. Disabling comparisons" ); + zcmp_command[0] = 0; + } } } @@ -494,7 +421,7 @@ int main( const int argc, const char * const argv[] ) if( verbosity >= 0 ) std::fprintf( stderr, "length %ld\n", i ); ++positions; ++decompressions; - f = popen( command, "w" ); + FILE * f = popen( command, "w" ); if( !f ) { show_error( "Can't open pipe", errno ); return 1; } std::fwrite( buffer, 1, i, f ); if( pclose( f ) == 0 ) @@ -520,13 +447,13 @@ int main( const int argc, const char * const argv[] ) { uint8_t * block = (uint8_t *)std::malloc( block_size ); if( !block ) { show_error( "Not enough memory." ); return 1; } - for( long i = pos; i < end; i += std::min( block_size * delta, end - i ) ) + for( long i = pos; i < end; i += std::min( delta, end - i ) ) { const long size = std::min( block_size, file_size - i ); if( verbosity >= 0 ) std::fprintf( stderr, "block %ld,%ld\n", i, size ); ++positions; ++decompressions; - f = popen( command, "w" ); + FILE * f = popen( command, "w" ); if( !f ) { show_error( "Can't open pipe", errno ); return 1; } std::memcpy( block , buffer + i, size ); std::memset( buffer + i, block_value, size ); @@ -571,15 +498,17 @@ int main( const int argc, const char * const argv[] ) if( verbosity >= 2 ) std::fprintf( stderr, "0x%02X (0x%02X+0x%02X) ", buffer[i], byte, j ); - f = popen( command, "w" ); + FILE * f = popen( command, "w" ); if( !f ) { show_error( "Can't open pipe", errno ); return 1; } std::fwrite( buffer, 1, file_size, f ); if( pclose( f ) == 0 ) { ++successes; if( verbosity >= 0 ) - std::fprintf( stderr, "0x%02X (0x%02X+0x%02X) passed the test\n", - buffer[i], byte, j ); + { if( verbosity < 2 ) + std::fprintf( stderr, "0x%02X (0x%02X+0x%02X) ", + buffer[i], byte, j ); + std::fputs( "passed the test\n", stderr ); } if( zcmp_command[0] ) { f = popen( zcmp_command, "w" ); @@ -606,10 +535,12 @@ int main( const int argc, const char * const argv[] ) positions, mode_str[program_mode], decompressions, successes ); if( successes > 0 ) { - if( zcmp_command[0] ) + if( zcmp_command[0] == 0 ) + std::fputs( "\n comparisons disabled\n", stderr ); + else if( failed_comparisons > 0 ) std::fprintf( stderr, ", of which\n%8ld comparisons failed\n", failed_comparisons ); - else std::fprintf( stderr, "\n comparisons disabled\n" ); + else std::fputs( "\n all comparisons passed\n", stderr ); } else std::fputc( '\n', stderr ); } |