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