1 /* Lziprecover - Data recovery tool for the lzip format
2    Copyright (C) 2009-2021 Antonio Diaz Diaz.
3 
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 2 of the License, or
7    (at your option) any later version.
8 
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13 
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 */
17 
18 #define _FILE_OFFSET_BITS 64
19 
20 #include <algorithm>
21 #include <cerrno>
22 #include <cstdio>
23 #include <cstring>
24 #include <string>
25 #include <vector>
26 #include <stdint.h>
27 #include <unistd.h>
28 #include <utime.h>
29 #include <sys/stat.h>
30 
31 #include "lzip.h"
32 #include "lzip_index.h"
33 
34 
35 // If strip is false, dump to outfd members/gaps/tdata in member_list.
36 // If strip is true, dump to outfd members/gaps/tdata not in member_list.
dump_members(const std::vector<std::string> & filenames,const std::string & default_output_filename,const Member_list & member_list,const bool force,bool ignore_errors,bool ignore_trailing,const bool loose_trailing,const bool strip,const bool to_stdout)37 int dump_members( const std::vector< std::string > & filenames,
38                   const std::string & default_output_filename,
39                   const Member_list & member_list, const bool force,
40                   bool ignore_errors, bool ignore_trailing,
41                   const bool loose_trailing, const bool strip,
42                   const bool to_stdout )
43   {
44   if( to_stdout || default_output_filename.empty() ) outfd = STDOUT_FILENO;
45   else
46     {
47     output_filename = default_output_filename;
48     set_signal_handler();
49     if( !open_outstream( force, false, false, false ) ) return 1;
50     }
51   unsigned long long copied_size = 0, stripped_size = 0;
52   unsigned long long copied_tsize = 0, stripped_tsize = 0;
53   long members = 0, smembers = 0;
54   int files = 0, tfiles = 0, retval = 0;
55   if( member_list.damaged ) ignore_errors = true;
56   if( member_list.tdata ) ignore_trailing = true;
57   bool stdin_used = false;
58   for( unsigned i = 0; i < filenames.size(); ++i )
59     {
60     const bool from_stdin = ( filenames[i] == "-" );
61     if( from_stdin ) { if( stdin_used ) continue; else stdin_used = true; }
62     const char * const input_filename =
63       from_stdin ? "(stdin)" : filenames[i].c_str();
64     struct stat in_stats;				// not used
65     const int infd = from_stdin ? STDIN_FILENO :
66       open_instream( input_filename, &in_stats, false, true );
67     if( infd < 0 ) { set_retval( retval, 1 ); continue; }
68 
69     const Lzip_index lzip_index( infd, ignore_trailing, loose_trailing,
70                                  ignore_errors, ignore_errors );
71     if( lzip_index.retval() != 0 )
72       {
73       show_file_error( input_filename, lzip_index.error().c_str() );
74       set_retval( retval, lzip_index.retval() );
75       close( infd );
76       continue;
77       }
78     if( !safe_seek( infd, 0 ) ) cleanup_and_fail( 1 );
79     const long blocks = lzip_index.blocks( false );	// not counting tdata
80     long long stream_pos = 0;		// first pos not yet read from file
81     long gaps = 0;
82     const long prev_members = members, prev_smembers = smembers;
83     const unsigned long long prev_stripped_size = stripped_size;
84     for( long j = 0; j < lzip_index.members(); ++j )	// copy members and gaps
85       {
86       const Block & mb = lzip_index.mblock( j );
87       if( mb.pos() > stream_pos )				// gap
88         {
89         const bool in = member_list.damaged ||
90                         member_list.includes( j + gaps, blocks );
91         if( in == !strip )
92           {
93           if( !safe_seek( infd, stream_pos ) ||
94               !copy_file( infd, outfd, mb.pos() - stream_pos ) )
95             cleanup_and_fail( 1 );
96           copied_size += mb.pos() - stream_pos; ++members;
97           }
98         else { stripped_size += mb.pos() - stream_pos; ++smembers; }
99         ++gaps;
100         }
101       bool in = member_list.includes( j + gaps, blocks );	// member
102       if( !in && member_list.damaged )
103         {
104         if( !safe_seek( infd, mb.pos() ) ) cleanup_and_fail( 1 );
105         in = ( test_member_from_file( infd, mb.size() ) != 0 );	// damaged
106         }
107       if( in == !strip )
108         {
109         if( !safe_seek( infd, mb.pos() ) ||
110             !copy_file( infd, outfd, mb.size() ) ) cleanup_and_fail( 1 );
111         copied_size += mb.size(); ++members;
112         }
113       else { stripped_size += mb.size(); ++smembers; }
114       stream_pos = mb.end();
115       }
116     if( strip && members == prev_members )	// all members were stripped
117       { if( verbosity >= 1 )
118           show_file_error( input_filename, "All members stripped, skipping." );
119         stripped_size = prev_stripped_size; smembers = prev_smembers;
120         close( infd ); continue; }
121     if( ( !strip && members > prev_members ) ||
122         ( strip && smembers > prev_smembers ) ) ++files;
123     // copy trailing data
124     const unsigned long long cdata_size = lzip_index.cdata_size();
125     const long long trailing_size = lzip_index.file_size() - cdata_size;
126     if( member_list.tdata == !strip && trailing_size > 0 &&
127         ( !strip || i + 1 >= filenames.size() ) )	// strip all but last
128       {
129       if( !safe_seek( infd, cdata_size ) ||
130           !copy_file( infd, outfd, trailing_size ) ) cleanup_and_fail( 1 );
131       copied_tsize += trailing_size;
132       }
133     else if( trailing_size > 0 ) { stripped_tsize += trailing_size; ++tfiles; }
134     close( infd );
135     }
136   if( close_outstream( 0 ) != 0 ) set_retval( retval, 1 );
137   if( verbosity >= 1 )
138     {
139     if( !strip )
140       {
141       if( member_list.damaged || member_list.range() )
142         std::fprintf( stderr, "%llu bytes dumped from %ld %s from %d %s.\n",
143                       copied_size,
144                       members, ( members == 1 ) ? "member" : "members",
145                       files, ( files == 1 ) ? "file" : "files" );
146       if( member_list.tdata )
147         std::fprintf( stderr, "%llu trailing bytes dumped.\n", copied_tsize );
148       }
149     else
150       {
151       if( member_list.damaged || member_list.range() )
152         std::fprintf( stderr, "%llu bytes stripped from %ld %s from %d %s.\n",
153                       stripped_size,
154                       smembers, ( smembers == 1 ) ? "member" : "members",
155                       files, ( files == 1 ) ? "file" : "files" );
156       if( member_list.tdata )
157         std::fprintf( stderr, "%llu trailing bytes stripped from %d %s.\n",
158                       stripped_tsize, tfiles, ( tfiles == 1 ) ? "file" : "files" );
159       }
160     }
161   return retval;
162   }
163 
164 
remove_members(const std::vector<std::string> & filenames,const Member_list & member_list,bool ignore_errors,bool ignore_trailing,const bool loose_trailing)165 int remove_members( const std::vector< std::string > & filenames,
166                     const Member_list & member_list, bool ignore_errors,
167                     bool ignore_trailing, const bool loose_trailing )
168   {
169   unsigned long long removed_size = 0, removed_tsize = 0;
170   long members = 0;
171   int files = 0, tfiles = 0, retval = 0;
172   if( member_list.damaged ) ignore_errors = true;
173   if( member_list.tdata ) ignore_trailing = true;
174   for( unsigned i = 0; i < filenames.size(); ++i )
175     {
176     const char * const filename = filenames[i].c_str();
177     struct stat in_stats, dummy_stats;
178     const int infd = open_instream( filename, &in_stats, false, true );
179     if( infd < 0 ) { set_retval( retval, 1 ); continue; }
180 
181     const Lzip_index lzip_index( infd, ignore_trailing, loose_trailing,
182                                  ignore_errors, ignore_errors );
183     if( lzip_index.retval() != 0 )
184       {
185       show_file_error( filename, lzip_index.error().c_str() );
186       set_retval( retval, lzip_index.retval() );
187       close( infd );
188       continue;
189       }
190     const int fd = open_truncable_stream( filename, &dummy_stats );
191     if( fd < 0 ) { close( infd ); set_retval( retval, 1 ); continue; }
192 
193     if( !safe_seek( infd, 0 ) ) return 1;
194     const long blocks = lzip_index.blocks( false );	// not counting tdata
195     long long stream_pos = 0;		// first pos not yet written to file
196     long gaps = 0;
197     bool error = false;
198     const long prev_members = members;
199     for( long j = 0; j < lzip_index.members(); ++j )	// copy members and gaps
200       {
201       const Block & mb = lzip_index.mblock( j );
202       const long long prev_end = (j > 0) ? lzip_index.mblock(j - 1).end() : 0;
203       if( mb.pos() > prev_end )					// gap
204         {
205         if( !member_list.damaged && !member_list.includes( j + gaps, blocks ) )
206           {
207           if( stream_pos != prev_end &&
208               ( !safe_seek( infd, prev_end ) ||
209                 !safe_seek( fd, stream_pos ) ||
210                 !copy_file( infd, fd, mb.pos() - prev_end ) ) )
211             { error = true; set_retval( retval, 1 ); break; }
212           stream_pos += mb.pos() - prev_end;
213           }
214         else ++members;
215         ++gaps;
216         }
217       bool in = member_list.includes( j + gaps, blocks );	// member
218       if( !in && member_list.damaged )
219         {
220         if( !safe_seek( infd, mb.pos() ) )
221           { error = true; set_retval( retval, 1 ); break; }
222         in = ( test_member_from_file( infd, mb.size() ) != 0 );	// damaged
223         }
224       if( !in )
225         {
226         if( stream_pos != mb.pos() &&
227             ( !safe_seek( infd, mb.pos() ) ||
228               !safe_seek( fd, stream_pos ) ||
229               !copy_file( infd, fd, mb.size() ) ) )
230           { error = true; set_retval( retval, 1 ); break; }
231         stream_pos += mb.size();
232         }
233       else ++members;
234       }
235     if( error ) { close( fd ); close( infd ); break; }
236     if( stream_pos == 0 )			// all members were removed
237       { show_file_error( filename, "All members would be removed, skipping." );
238         close( fd ); close( infd ); set_retval( retval, 2 );
239         members = prev_members; continue; }
240     const long long cdata_size = lzip_index.cdata_size();
241     if( cdata_size > stream_pos )
242       { removed_size += cdata_size - stream_pos; ++files; }
243     const long long file_size = lzip_index.file_size();
244     const long long trailing_size = file_size - cdata_size;
245     if( trailing_size > 0 )
246       {
247       if( !member_list.tdata )	// copy trailing data
248         {
249         if( stream_pos != cdata_size &&
250             ( !safe_seek( infd, cdata_size ) ||
251               !safe_seek( fd, stream_pos ) ||
252               !copy_file( infd, fd, trailing_size ) ) )
253           { close( fd ); close( infd ); set_retval( retval, 1 ); break; }
254         stream_pos += trailing_size;
255         }
256       else { removed_tsize += trailing_size; ++tfiles; }
257       }
258     if( stream_pos >= file_size )		// no members were removed
259       { close( fd ); close( infd ); continue; }
260     int result;
261     do result = ftruncate( fd, stream_pos );
262       while( result != 0 && errno == EINTR );
263     if( result != 0 )
264       {
265       show_file_error( filename, "Can't truncate file", errno );
266       close( fd ); close( infd ); set_retval( retval, 1 ); break;
267       }
268     if( close( fd ) != 0 || close( infd ) != 0 )
269       {
270       show_file_error( filename, "Error closing file", errno );
271       set_retval( retval, 1 ); break;
272       }
273     struct utimbuf t;
274     t.actime = in_stats.st_atime;
275     t.modtime = in_stats.st_mtime;
276     utime( filename, &t );
277     }
278   if( verbosity >= 1 )
279     {
280     if( member_list.damaged || member_list.range() )
281       std::fprintf( stderr, "%llu bytes removed from %ld %s from %d %s.\n",
282                     removed_size,
283                     members, ( members == 1 ) ? "member" : "members",
284                     files, ( files == 1 ) ? "file" : "files" );
285     if( member_list.tdata )
286       std::fprintf( stderr, "%llu trailing bytes removed from %d %s.\n",
287                     removed_tsize, tfiles, ( tfiles == 1 ) ? "file" : "files" );
288     }
289   return retval;
290   }
291