/* Lziprecover - Data recovery tool for the lzip format
Copyright (C) 2009-2024 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
#include
#include "lzip.h"
#include "lzip_index.h"
const char * const pdate_msg = "warning: can't preserve file date";
/* If strip is false, dump to outfd members/gaps/tdata in member_list.
If strip is true, dump to outfd members/gaps/tdata not in member_list. */
int dump_members( const std::vector< std::string > & filenames,
const std::string & default_output_filename,
const Cl_options & cl_opts, const Member_list & member_list,
const bool force, const bool strip, const bool to_stdout )
{
if( to_stdout || default_output_filename.empty() ) outfd = STDOUT_FILENO;
else
{
output_filename = default_output_filename;
set_signal_handler();
if( !open_outstream( force, false, false, false ) ) return 1;
}
if( ( strip || !member_list.tdata || member_list.damaged ||
member_list.empty || member_list.range() ) &&
!check_tty_out() ) return 1; // check tty except for --dump=tdata
unsigned long long copied_size = 0, stripped_size = 0;
unsigned long long copied_tsize = 0, stripped_tsize = 0;
long members = 0, smembers = 0;
int files = 0, tfiles = 0, retval = 0;
bool stdin_used = false;
for( unsigned i = 0; i < filenames.size(); ++i )
{
const bool from_stdin = ( filenames[i] == "-" );
if( from_stdin ) { if( stdin_used ) continue; else stdin_used = true; }
const char * const input_filename =
from_stdin ? "(stdin)" : filenames[i].c_str();
struct stat in_stats; // not used
const int infd = from_stdin ? STDIN_FILENO :
open_instream( input_filename, &in_stats, false, true );
if( infd < 0 ) { set_retval( retval, 1 ); continue; }
const Lzip_index lzip_index( infd, cl_opts, cl_opts.ignore_errors,
cl_opts.ignore_errors );
if( lzip_index.retval() != 0 )
{
show_file_error( input_filename, lzip_index.error().c_str() );
set_retval( retval, lzip_index.retval() );
close( infd );
continue;
}
if( !safe_seek( infd, 0, input_filename ) ) cleanup_and_fail( 1 );
const long blocks = lzip_index.blocks( false ); // not counting tdata
long long stream_pos = 0; // first pos not yet read from file
long gaps = 0;
const long prev_members = members, prev_smembers = smembers;
const unsigned long long prev_stripped_size = stripped_size;
for( long j = 0; j < lzip_index.members(); ++j ) // copy members and gaps
{
const Block & mb = lzip_index.mblock( j );
if( mb.pos() > stream_pos ) // gap
{
const bool in = member_list.damaged ||
member_list.includes( j + gaps, blocks );
if( in == !strip )
{
if( !safe_seek( infd, stream_pos, input_filename ) ||
!copy_file( infd, outfd, mb.pos() - stream_pos ) )
cleanup_and_fail( 1 );
copied_size += mb.pos() - stream_pos; ++members;
}
else { stripped_size += mb.pos() - stream_pos; ++smembers; }
++gaps;
}
bool in = member_list.includes( j + gaps, blocks ); // member
if( !in && member_list.empty && lzip_index.dblock( j ).size() == 0 )
in = true;
if( !in && member_list.damaged )
{
if( !safe_seek( infd, mb.pos(), input_filename ) ) cleanup_and_fail( 1 );
in = ( test_member_from_file( infd, mb.size() ) != 0 ); // damaged
}
if( in == !strip )
{
if( !safe_seek( infd, mb.pos(), input_filename ) ||
!copy_file( infd, outfd, mb.size() ) ) cleanup_and_fail( 1 );
copied_size += mb.size(); ++members;
}
else { stripped_size += mb.size(); ++smembers; }
stream_pos = mb.end();
}
if( strip && members == prev_members ) // all members were stripped
{ if( verbosity >= 1 )
show_file_error( input_filename, "All members stripped, skipping." );
stripped_size = prev_stripped_size; smembers = prev_smembers;
close( infd ); continue; }
if( ( !strip && members > prev_members ) ||
( strip && smembers > prev_smembers ) ) ++files;
// copy trailing data
const unsigned long long cdata_size = lzip_index.cdata_size();
const long long trailing_size = lzip_index.file_size() - cdata_size;
if( member_list.tdata == !strip && trailing_size > 0 &&
( !strip || i + 1 >= filenames.size() ) ) // strip all but last
{
if( !safe_seek( infd, cdata_size, input_filename ) ||
!copy_file( infd, outfd, trailing_size ) ) cleanup_and_fail( 1 );
copied_tsize += trailing_size;
}
else if( trailing_size > 0 ) { stripped_tsize += trailing_size; ++tfiles; }
close( infd );
}
if( !close_outstream( 0 ) ) set_retval( retval, 1 );
if( verbosity >= 1 )
{
if( !strip )
{
if( member_list.damaged || member_list.empty || member_list.range() )
std::fprintf( stderr, "%llu bytes dumped from %ld %s from %d %s.\n",
copied_size,
members, ( members == 1 ) ? "member" : "members",
files, ( files == 1 ) ? "file" : "files" );
if( member_list.tdata )
std::fprintf( stderr, "%llu trailing bytes dumped.\n", copied_tsize );
}
else
{
if( member_list.damaged || member_list.empty || member_list.range() )
std::fprintf( stderr, "%llu bytes stripped from %ld %s from %d %s.\n",
stripped_size,
smembers, ( smembers == 1 ) ? "member" : "members",
files, ( files == 1 ) ? "file" : "files" );
if( member_list.tdata )
std::fprintf( stderr, "%llu trailing bytes stripped from %d %s.\n",
stripped_tsize, tfiles, ( tfiles == 1 ) ? "file" : "files" );
}
}
return retval;
}
/* Remove members, tdata from files in place by opening two descriptors for
each file. */
int remove_members( const std::vector< std::string > & filenames,
const Cl_options & cl_opts, const Member_list & member_list )
{
unsigned long long removed_size = 0, removed_tsize = 0;
long members = 0;
int files = 0, tfiles = 0, retval = 0;
for( unsigned i = 0; i < filenames.size(); ++i )
{
const char * const filename = filenames[i].c_str();
struct stat in_stats, dummy_stats;
const int infd = open_instream( filename, &in_stats, false, true );
if( infd < 0 ) { set_retval( retval, 1 ); continue; }
const Lzip_index lzip_index( infd, cl_opts, cl_opts.ignore_errors,
cl_opts.ignore_errors );
if( lzip_index.retval() != 0 )
{
show_file_error( filename, lzip_index.error().c_str() );
set_retval( retval, lzip_index.retval() );
close( infd );
continue;
}
const int fd = open_truncable_stream( filename, &dummy_stats );
if( fd < 0 ) { close( infd ); set_retval( retval, 1 ); continue; }
if( !safe_seek( infd, 0, filename ) ) return 1;
const long blocks = lzip_index.blocks( false ); // not counting tdata
long long stream_pos = 0; // first pos not yet written to file
long gaps = 0;
bool error = false;
const long prev_members = members;
for( long j = 0; j < lzip_index.members(); ++j ) // copy members and gaps
{
const Block & mb = lzip_index.mblock( j );
const long long prev_end = (j > 0) ? lzip_index.mblock(j - 1).end() : 0;
if( mb.pos() > prev_end ) // gap
{
if( !member_list.damaged && !member_list.includes( j + gaps, blocks ) )
{
if( stream_pos != prev_end &&
( !safe_seek( infd, prev_end, filename ) ||
!safe_seek( fd, stream_pos, filename ) ||
!copy_file( infd, fd, mb.pos() - prev_end ) ) )
{ error = true; set_retval( retval, 1 ); break; }
stream_pos += mb.pos() - prev_end;
}
else ++members;
++gaps;
}
bool in = member_list.includes( j + gaps, blocks ); // member
if( !in && member_list.empty && lzip_index.dblock( j ).size() == 0 )
in = true;
if( !in && member_list.damaged )
{
if( !safe_seek( infd, mb.pos(), filename ) )
{ error = true; set_retval( retval, 1 ); break; }
in = ( test_member_from_file( infd, mb.size() ) != 0 ); // damaged
}
if( !in )
{
if( stream_pos != mb.pos() &&
( !safe_seek( infd, mb.pos(), filename ) ||
!safe_seek( fd, stream_pos, filename ) ||
!copy_file( infd, fd, mb.size() ) ) )
{ error = true; set_retval( retval, 1 ); break; }
stream_pos += mb.size();
}
else ++members;
}
if( error ) { close( fd ); close( infd ); break; }
if( stream_pos == 0 ) // all members were removed
{ show_file_error( filename, "All members would be removed, skipping." );
close( fd ); close( infd ); set_retval( retval, 2 );
members = prev_members; continue; }
const long long cdata_size = lzip_index.cdata_size();
if( cdata_size > stream_pos )
{ removed_size += cdata_size - stream_pos; ++files; }
const long long file_size = lzip_index.file_size();
const long long trailing_size = file_size - cdata_size;
if( trailing_size > 0 )
{
if( !member_list.tdata ) // copy trailing data
{
if( stream_pos != cdata_size &&
( !safe_seek( infd, cdata_size, filename ) ||
!safe_seek( fd, stream_pos, filename ) ||
!copy_file( infd, fd, trailing_size ) ) )
{ close( fd ); close( infd ); set_retval( retval, 1 ); break; }
stream_pos += trailing_size;
}
else { removed_tsize += trailing_size; ++tfiles; }
}
if( stream_pos >= file_size ) // no members were removed
{ close( fd ); close( infd ); continue; }
int result;
do result = ftruncate( fd, stream_pos );
while( result != 0 && errno == EINTR );
if( result != 0 )
{
show_file_error( filename, "Can't truncate file", errno );
close( fd ); close( infd ); set_retval( retval, 1 ); break;
}
if( close( fd ) != 0 || close( infd ) != 0 )
{
show_file_error( filename, "Error closing file", errno );
set_retval( retval, 1 ); break;
}
struct utimbuf t;
t.actime = in_stats.st_atime;
t.modtime = in_stats.st_mtime;
if( utime( filename, &t ) != 0 && verbosity >= 1 )
show_file_error( filename, pdate_msg, errno );
}
if( verbosity >= 1 )
{
if( member_list.damaged || member_list.empty || member_list.range() )
std::fprintf( stderr, "%llu bytes removed from %ld %s from %d %s.\n",
removed_size,
members, ( members == 1 ) ? "member" : "members",
files, ( files == 1 ) ? "file" : "files" );
if( member_list.tdata )
std::fprintf( stderr, "%llu trailing bytes removed from %d %s.\n",
removed_tsize, tfiles, ( tfiles == 1 ) ? "file" : "files" );
}
return retval;
}
/* Set to zero in place the first LZMA byte of each member in each file by
opening one rw descriptor for each file. */
int nonzero_repair( const std::vector< std::string > & filenames,
const Cl_options & cl_opts )
{
long cleared_members = 0;
int files = 0, retval = 0;
for( unsigned i = 0; i < filenames.size(); ++i )
{
const char * const filename = filenames[i].c_str();
struct stat in_stats;
const int fd = open_truncable_stream( filename, &in_stats );
if( fd < 0 ) { set_retval( retval, 1 ); continue; }
const Lzip_index lzip_index( fd, cl_opts, cl_opts.ignore_errors,
cl_opts.ignore_errors );
if( lzip_index.retval() != 0 )
{
show_file_error( filename, lzip_index.error().c_str() );
set_retval( retval, lzip_index.retval() );
close( fd );
continue;
}
enum { bufsize = Lzip_header::size + 1 };
uint8_t header_buf[bufsize];
const uint8_t * const p = header_buf; // keep gcc 6.1.0 quiet
const Lzip_header & header = *(const Lzip_header *)p;
uint8_t * const mark = header_buf + header.size;
bool write_attempted = false;
for( long j = 0; j < lzip_index.members(); ++j ) // clear the members
{
const Block & mb = lzip_index.mblock( j );
if( seek_read( fd, header_buf, bufsize, mb.pos() ) != bufsize )
{ show_file_error( filename, "Error reading member header", errno );
set_retval( retval, 1 ); break; }
if( !header.check( cl_opts.ignore_errors ) )
{ show_file_error( filename, "Member header became corrupt as we read it." );
set_retval( retval, 2 ); break; }
if( *mark == 0 ) continue;
*mark = 0; write_attempted = true;
if( seek_write( fd, mark, 1, mb.pos() + header.size ) != 1 )
{ show_file_error( filename, "Error writing to file", errno );
set_retval( retval, 1 ); break; }
++cleared_members;
}
if( close( fd ) != 0 )
{
show_file_error( filename, "Error closing file", errno );
set_retval( retval, 1 ); break;
}
if( write_attempted )
{
struct utimbuf t;
t.actime = in_stats.st_atime;
t.modtime = in_stats.st_mtime;
if( utime( filename, &t ) != 0 && verbosity >= 1 )
show_file_error( filename, pdate_msg, errno );
++files;
}
}
if( verbosity >= 1 )
std::fprintf( stderr, "%lu %s cleared in %d %s.\n", cleared_members,
( cleared_members == 1 ) ? "member" : "members",
files, ( files == 1 ) ? "file" : "files" );
return retval;
}