1 | /* xdelta 3 - delta compression tools and library
|
---|
2 | * Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006, 2007,
|
---|
3 | * Joshua P. MacDonald
|
---|
4 | *
|
---|
5 | * This program is free software; you can redistribute it and/or modify
|
---|
6 | * it under the terms of the GNU General Public License as published by
|
---|
7 | * the Free Software Foundation; either version 2 of the License, or
|
---|
8 | * (at your option) any later version.
|
---|
9 | *
|
---|
10 | * This program is distributed in the hope that it will be useful,
|
---|
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
---|
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
---|
13 | * GNU General Public License for more details.
|
---|
14 | *
|
---|
15 | * You should have received a copy of the GNU General Public License
|
---|
16 | * along with this program; if not, write to the Free Software
|
---|
17 | * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
---|
18 | */
|
---|
19 |
|
---|
20 | /* This is all the extra stuff you need for convenience to users in a command line
|
---|
21 | * application. It contains these major components:
|
---|
22 | *
|
---|
23 | * 1. VCDIFF tools
|
---|
24 | * 2. external compression support (this is POSIX-specific).
|
---|
25 | * 3. a general read/write loop that handles all of the Xdelta decode/encode/VCDIFF-print
|
---|
26 | * functions
|
---|
27 | * 4. command-line interpreter
|
---|
28 | * 5. an Xdelta application header which stores default filename, external compression settings
|
---|
29 | * 6. output/error printing
|
---|
30 | * 7. basic file support and OS interface
|
---|
31 | */
|
---|
32 |
|
---|
33 | /* TODO list:
|
---|
34 | * 1. do exact gzip-like filename, stdout handling. make a .vcdiff extension, refuse
|
---|
35 | * to encode to stdout without -cf, etc.
|
---|
36 | * 2. Allow the user to add a comment string to the app header without disturbing the default
|
---|
37 | * behavior.
|
---|
38 | * 3. "Source file must be seekable" is not actually true for encoding, given current
|
---|
39 | * behavior. Allow non-seekable sources? It would in theory let you use a fifo for
|
---|
40 | * the source.
|
---|
41 | */
|
---|
42 |
|
---|
43 | /* On error handling and printing:
|
---|
44 | *
|
---|
45 | * The xdelta library sets stream->msg to indicate what condition caused an internal
|
---|
46 | * failure, but many failures originate here and are printed here. The return convention
|
---|
47 | * is 0 for success, as throughout Xdelta code, but special attention is required here for
|
---|
48 | * the operating system calls with different error handling. See the main_file_* routines.
|
---|
49 | * All errors in this file have a message printed at the time of occurance. Since some of
|
---|
50 | * these calls occur within calls to the library, the error may end up being printed again
|
---|
51 | * with a more general error message.
|
---|
52 | */
|
---|
53 |
|
---|
54 | /******************************************************************************************/
|
---|
55 |
|
---|
56 | #ifndef XD3_POSIX
|
---|
57 | #define XD3_POSIX 0
|
---|
58 | #endif
|
---|
59 | #ifndef XD3_STDIO
|
---|
60 | #define XD3_STDIO 0
|
---|
61 | #endif
|
---|
62 | #ifndef XD3_WIN32
|
---|
63 | #define XD3_WIN32 0
|
---|
64 | #endif
|
---|
65 |
|
---|
66 | /* Combines xd3_strerror() and strerror() */
|
---|
67 | const char* xd3_mainerror(int err_num);
|
---|
68 |
|
---|
69 | /* XPRINTX (used by main) prefixes an "xdelta3: " to the output. */
|
---|
70 | #define XPR fprintf
|
---|
71 | #define NT stderr, "xdelta3: "
|
---|
72 |
|
---|
73 | /* If none are set, default to posix. */
|
---|
74 | #if (XD3_POSIX + XD3_STDIO + XD3_WIN32) == 0
|
---|
75 | #undef XD3_POSIX
|
---|
76 | #define XD3_POSIX 1
|
---|
77 | #endif
|
---|
78 |
|
---|
79 | /* Handle externally-compressed inputs. */
|
---|
80 | #ifndef EXTERNAL_COMPRESSION
|
---|
81 | #define EXTERNAL_COMPRESSION 1
|
---|
82 | #endif
|
---|
83 |
|
---|
84 | #define PRINTHDR_SPECIAL -4378291
|
---|
85 |
|
---|
86 | /* The number of soft-config variables. */
|
---|
87 | #define XD3_SOFTCFG_VARCNT 7
|
---|
88 |
|
---|
89 | /* this is used as in XPR(NT XD3_LIB_ERRMSG (stream, ret)) to print an error message
|
---|
90 | * from the library. */
|
---|
91 | #define XD3_LIB_ERRMSG(stream, ret) "%s: %s\n", xd3_errstring (stream), xd3_mainerror (ret)
|
---|
92 |
|
---|
93 | #include <stdio.h> /* fprintf */
|
---|
94 |
|
---|
95 | #if XD3_POSIX
|
---|
96 | #include <unistd.h> /* close, read, write... */
|
---|
97 | #include <sys/types.h>
|
---|
98 | #include <fcntl.h>
|
---|
99 | #endif
|
---|
100 |
|
---|
101 | #ifndef _WIN32
|
---|
102 | #include <unistd.h> /* lots */
|
---|
103 | #include <sys/time.h> /* gettimeofday() */
|
---|
104 | #include <sys/stat.h> /* stat() and fstat() */
|
---|
105 | #else
|
---|
106 | #define strtoll _strtoi64
|
---|
107 | #include <sys/types.h>
|
---|
108 | #include <sys/stat.h>
|
---|
109 | #ifndef WIFEXITED
|
---|
110 | # define WIFEXITED(stat) (((*((int *) &(stat))) & 0xff) == 0)
|
---|
111 | #endif
|
---|
112 | #ifndef WEXITSTATUS
|
---|
113 | # define WEXITSTATUS(stat) (((*((int *) &(stat))) >> 8) & 0xff)
|
---|
114 | #endif
|
---|
115 | #ifndef S_ISREG
|
---|
116 | //# ifdef S_IFREG
|
---|
117 | //# define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
|
---|
118 | //# else
|
---|
119 | # define S_ISREG(m) 1
|
---|
120 | //# endif
|
---|
121 | #endif /* !S_ISREG */
|
---|
122 |
|
---|
123 | // For standard input/output handles
|
---|
124 | static STARTUPINFO winStartupInfo;
|
---|
125 | #endif
|
---|
126 |
|
---|
127 | /******************************************************************************************
|
---|
128 | ENUMS and TYPES
|
---|
129 | ******************************************************************************************/
|
---|
130 |
|
---|
131 | /* These flags (mainly pertaining to main_read() operations) are set in the
|
---|
132 | * main_file->flags variable. All are related to with external decompression support.
|
---|
133 | *
|
---|
134 | * RD_FIRST causes the external decompression check when the input is first read.
|
---|
135 | *
|
---|
136 | * RD_NONEXTERNAL disables external decompression for reading a compressed input, in the
|
---|
137 | * case of Xdelta inputs. Note: Xdelta is supported as an external compression type,
|
---|
138 | * which makes is the reason for this flag. An example to justify this is: to create a
|
---|
139 | * delta between two files that are VCDIFF-compressed. Two external Xdelta decoders are
|
---|
140 | * run to supply decompressed source and target inputs to the Xdelta encoder. */
|
---|
141 | typedef enum
|
---|
142 | {
|
---|
143 | RD_FIRST = (1 << 0),
|
---|
144 | RD_NONEXTERNAL = (1 << 1),
|
---|
145 | RD_EXTERNAL_V1 = (1 << 2),
|
---|
146 | } xd3_read_flags;
|
---|
147 |
|
---|
148 | /* main_file->mode values */
|
---|
149 | typedef enum
|
---|
150 | {
|
---|
151 | XO_READ = 0,
|
---|
152 | XO_WRITE = 1,
|
---|
153 | } main_file_modes;
|
---|
154 |
|
---|
155 | /* Main commands. For example, CMD_PRINTHDR is the "xdelta printhdr" command. */
|
---|
156 | typedef enum
|
---|
157 | {
|
---|
158 | CMD_NONE = 0,
|
---|
159 | CMD_PRINTHDR,
|
---|
160 | CMD_PRINTHDRS,
|
---|
161 | CMD_PRINTDELTA,
|
---|
162 | #if XD3_ENCODER
|
---|
163 | CMD_ENCODE,
|
---|
164 | #endif
|
---|
165 | CMD_DECODE,
|
---|
166 | CMD_TEST,
|
---|
167 | CMD_CONFIG,
|
---|
168 | } xd3_cmd;
|
---|
169 |
|
---|
170 | #if XD3_ENCODER
|
---|
171 | #define CMD_DEFAULT CMD_ENCODE
|
---|
172 | #define IS_ENCODE(cmd) (cmd == CMD_ENCODE)
|
---|
173 | #else
|
---|
174 | #define CMD_DEFAULT CMD_DECODE
|
---|
175 | #define IS_ENCODE(cmd) (0)
|
---|
176 | #endif
|
---|
177 |
|
---|
178 | typedef struct _main_file main_file;
|
---|
179 | typedef struct _main_extcomp main_extcomp;
|
---|
180 | typedef struct _main_blklru main_blklru;
|
---|
181 | typedef struct _main_blklru_list main_blklru_list;
|
---|
182 |
|
---|
183 | /* The main_file object supports abstract system calls like open, close, read, write, seek,
|
---|
184 | * stat. The program uses these to represent both seekable files and non-seekable files.
|
---|
185 | * Source files must be seekable, but the target input and any output file do not require
|
---|
186 | * seekability.
|
---|
187 | */
|
---|
188 | struct _main_file
|
---|
189 | {
|
---|
190 | #if XD3_STDIO
|
---|
191 | FILE *file;
|
---|
192 | #elif XD3_POSIX
|
---|
193 | int file;
|
---|
194 | #elif XD3_WIN32
|
---|
195 | HANDLE file;
|
---|
196 | #endif
|
---|
197 |
|
---|
198 | int mode; /* XO_READ and XO_WRITE */
|
---|
199 | const char *filename; /* File name or /dev/stdin, /dev/stdout, /dev/stderr. */
|
---|
200 | char *filename_copy; /* File name or /dev/stdin, /dev/stdout, /dev/stderr. */
|
---|
201 | const char *realname; /* File name or /dev/stdin, /dev/stdout, /dev/stderr. */
|
---|
202 | const main_extcomp *compressor; /* External compression struct. */
|
---|
203 | int flags; /* RD_FIRST, RD_NONEXTERNAL, ... */
|
---|
204 | xoff_t nread; /* for input position */
|
---|
205 | xoff_t nwrite; /* for output position */
|
---|
206 | uint8_t *snprintf_buf; /* internal snprintf() use */
|
---|
207 | };
|
---|
208 |
|
---|
209 | /* Various strings and magic values used to detect and call external compression. See
|
---|
210 | * below for examples. */
|
---|
211 | struct _main_extcomp
|
---|
212 | {
|
---|
213 | const char *recomp_cmdname;
|
---|
214 | const char *recomp_options;
|
---|
215 |
|
---|
216 | const char *decomp_cmdname;
|
---|
217 | const char *decomp_options;
|
---|
218 |
|
---|
219 | const char *ident;
|
---|
220 | const char *magic;
|
---|
221 | int magic_size;
|
---|
222 | int flags;
|
---|
223 | };
|
---|
224 |
|
---|
225 | /* This file implements a small LRU of source blocks. For encoding purposes,
|
---|
226 | * we prevent paging in blocks we've already scanned in the source (return
|
---|
227 | * XD3_NOTAVAIL). */
|
---|
228 | struct _main_blklru_list
|
---|
229 | {
|
---|
230 | main_blklru_list *next;
|
---|
231 | main_blklru_list *prev;
|
---|
232 | };
|
---|
233 |
|
---|
234 | struct _main_blklru
|
---|
235 | {
|
---|
236 | uint8_t *blk;
|
---|
237 | xoff_t blkno;
|
---|
238 | main_blklru_list link;
|
---|
239 | };
|
---|
240 |
|
---|
241 | #define LRU_SIZE 32U
|
---|
242 | #define XD3_MINSRCWINSZ XD3_ALLOCSIZE
|
---|
243 |
|
---|
244 | /* ... represented as a list (no cache index). */
|
---|
245 | XD3_MAKELIST(main_blklru_list,main_blklru,link);
|
---|
246 |
|
---|
247 | // TODO:
|
---|
248 | // struct _main_state
|
---|
249 | // {
|
---|
250 |
|
---|
251 | /* Program options: various command line flags and options. */
|
---|
252 | static int option_stdout = 0;
|
---|
253 | static int option_force = 0;
|
---|
254 | static int option_verbose = 0;
|
---|
255 | static int option_quiet = 0;
|
---|
256 | static int option_use_appheader = 1;
|
---|
257 | static uint8_t* option_appheader = NULL;
|
---|
258 | static int option_use_secondary = 0;
|
---|
259 | static char* option_secondary = NULL;
|
---|
260 | static int option_use_checksum = 1;
|
---|
261 | static int option_use_altcodetable = 0;
|
---|
262 | static char* option_smatch_config = NULL;
|
---|
263 | static int option_no_compress = 0;
|
---|
264 | static int option_no_output = 0; /* do not open or write output */
|
---|
265 | static const char *option_source_filename = NULL;
|
---|
266 |
|
---|
267 | static int option_level = XD3_DEFAULT_LEVEL;
|
---|
268 | static usize_t option_iopt_size = XD3_DEFAULT_IOPT_SIZE;
|
---|
269 | static usize_t option_winsize = XD3_DEFAULT_WINSIZE;
|
---|
270 | static usize_t option_srcwinsz = XD3_DEFAULT_SRCWINSZ;
|
---|
271 | static usize_t option_sprevsz = XD3_DEFAULT_SPREVSZ;
|
---|
272 |
|
---|
273 | /* These variables are supressed to avoid their use w/o support. main() warns
|
---|
274 | * appropriately. */
|
---|
275 | #if EXTERNAL_COMPRESSION
|
---|
276 | static int option_decompress_inputs = 1;
|
---|
277 | static int option_recompress_outputs = 1;
|
---|
278 | #endif
|
---|
279 |
|
---|
280 | /* This is for comparing "printdelta" output without attention to
|
---|
281 | * copy-instruction modes. */
|
---|
282 | #if VCDIFF_TOOLS
|
---|
283 | static int option_print_cpymode = 1;
|
---|
284 | #endif
|
---|
285 |
|
---|
286 | /* Static variables */
|
---|
287 | IF_DEBUG(static int main_mallocs = 0;)
|
---|
288 |
|
---|
289 | static char* program_name = NULL;
|
---|
290 | static uint8_t* appheader_used = NULL;
|
---|
291 | static uint8_t* main_bdata = NULL;
|
---|
292 |
|
---|
293 | /* The LRU: obviously this is shared by all callers. */
|
---|
294 | static int lru_size = 0;
|
---|
295 | static main_blklru *lru = NULL; /* array of lru_size elts */
|
---|
296 | static main_blklru_list lru_list;
|
---|
297 | static main_blklru_list lru_free;
|
---|
298 | static int do_not_lru = 0; /* set to avoid lru, instead discard oldest */
|
---|
299 |
|
---|
300 | static int lru_hits = 0;
|
---|
301 | static int lru_misses = 0;
|
---|
302 | static int lru_filled = 0;
|
---|
303 |
|
---|
304 | /* Hacks for VCDIFF tools */
|
---|
305 | static int allow_fake_source = 0;
|
---|
306 |
|
---|
307 | /* This array of compressor types is compiled even if EXTERNAL_COMPRESSION is false just so
|
---|
308 | * the program knows the mapping of IDENT->NAME. */
|
---|
309 | static main_extcomp extcomp_types[] =
|
---|
310 | {
|
---|
311 | /* The entry for xdelta3 must be 0 because the program_name is set there. */
|
---|
312 | { "xdelta3", "-cfq", "xdelta3", "-dcfq", "X", "\xd6\xc3\xc4", 3, RD_NONEXTERNAL },
|
---|
313 | { "bzip2", "-cf", "bzip2", "-dcf", "B", "BZh", 3, 0 },
|
---|
314 | { "gzip", "-cf", "gzip", "-dcf", "G", "\037\213", 2, 0 },
|
---|
315 | { "compress", "-cf", "uncompress", "-cf", "Z", "\037\235", 2, 0 },
|
---|
316 |
|
---|
317 | /* TODO: add commandline support for magic-less formats */
|
---|
318 | /*{ "lzma", "-cf", "lzma", "-dcf", "M", "]\000", 2, 0 },*/
|
---|
319 | };
|
---|
320 |
|
---|
321 | // };
|
---|
322 |
|
---|
323 | static void main_get_appheader (xd3_stream *stream, main_file *ifile,
|
---|
324 | main_file *output, main_file *sfile);
|
---|
325 |
|
---|
326 | static int main_help (void);
|
---|
327 |
|
---|
328 | static int
|
---|
329 | main_version (void)
|
---|
330 | {
|
---|
331 | /* $Format: " DP(RINT \"VERSION=3.$Xdelta3Version$\\n\");" $ */
|
---|
332 | DP(RINT "VERSION=3.0q\n");
|
---|
333 | return EXIT_SUCCESS;
|
---|
334 | }
|
---|
335 |
|
---|
336 | static int
|
---|
337 | main_config (void)
|
---|
338 | {
|
---|
339 | main_version ();
|
---|
340 |
|
---|
341 | DP(RINT "EXTERNAL_COMPRESSION=%d\n", EXTERNAL_COMPRESSION);
|
---|
342 | DP(RINT "GENERIC_ENCODE_TABLES=%d\n", GENERIC_ENCODE_TABLES);
|
---|
343 | DP(RINT "GENERIC_ENCODE_TABLES_COMPUTE=%d\n", GENERIC_ENCODE_TABLES_COMPUTE);
|
---|
344 | DP(RINT "REGRESSION_TEST=%d\n", REGRESSION_TEST);
|
---|
345 | DP(RINT "SECONDARY_DJW=%d\n", SECONDARY_DJW);
|
---|
346 | DP(RINT "SECONDARY_FGK=%d\n", SECONDARY_FGK);
|
---|
347 | DP(RINT "VCDIFF_TOOLS=%d\n", VCDIFF_TOOLS);
|
---|
348 | DP(RINT "XD3_ALLOCSIZE=%d\n", XD3_ALLOCSIZE);
|
---|
349 | DP(RINT "XD3_DEBUG=%d\n", XD3_DEBUG);
|
---|
350 | DP(RINT "XD3_ENCODER=%d\n", XD3_ENCODER);
|
---|
351 | DP(RINT "XD3_POSIX=%d\n", XD3_POSIX);
|
---|
352 | DP(RINT "XD3_STDIO=%d\n", XD3_STDIO);
|
---|
353 | DP(RINT "XD3_WIN32=%d\n", XD3_WIN32);
|
---|
354 | DP(RINT "XD3_USE_LARGEFILE64=%d\n", XD3_USE_LARGEFILE64);
|
---|
355 | DP(RINT "XD3_DEFAULT_LEVEL=%d\n", XD3_DEFAULT_LEVEL);
|
---|
356 | DP(RINT "XD3_DEFAULT_IOPT_SIZE=%d\n", XD3_DEFAULT_IOPT_SIZE);
|
---|
357 | DP(RINT "XD3_DEFAULT_SPREVSZ=%d\n", XD3_DEFAULT_SPREVSZ);
|
---|
358 | DP(RINT "XD3_DEFAULT_SRCWINSZ=%d\n", XD3_DEFAULT_SRCWINSZ);
|
---|
359 | DP(RINT "XD3_DEFAULT_WINSIZE=%d\n", XD3_DEFAULT_WINSIZE);
|
---|
360 | DP(RINT "XD3_HARDMAXWINSIZE=%d\n", XD3_HARDMAXWINSIZE);
|
---|
361 | DP(RINT "XD3_NODECOMPRESSSIZE=%d\n", XD3_NODECOMPRESSSIZE);
|
---|
362 |
|
---|
363 | return EXIT_SUCCESS;
|
---|
364 | }
|
---|
365 |
|
---|
366 | static void
|
---|
367 | reset_defaults(void)
|
---|
368 | {
|
---|
369 | option_stdout = 0;
|
---|
370 | option_force = 0;
|
---|
371 | option_verbose = 0;
|
---|
372 | option_quiet = 0;
|
---|
373 | option_appheader = NULL;
|
---|
374 | option_use_secondary = 0;
|
---|
375 | option_secondary = NULL;
|
---|
376 | option_use_altcodetable = 0;
|
---|
377 | option_smatch_config = NULL;
|
---|
378 | option_no_compress = 0;
|
---|
379 | option_no_output = 0;
|
---|
380 | option_source_filename = NULL;
|
---|
381 | program_name = NULL;
|
---|
382 | appheader_used = NULL;
|
---|
383 | main_bdata = NULL;
|
---|
384 | lru_size = 0;
|
---|
385 | lru = NULL;
|
---|
386 | do_not_lru = 0;
|
---|
387 | lru_hits = 0;
|
---|
388 | lru_misses = 0;
|
---|
389 | lru_filled = 0;
|
---|
390 | allow_fake_source = 0;
|
---|
391 | option_smatch_config = NULL;
|
---|
392 |
|
---|
393 | option_use_appheader = 1;
|
---|
394 | option_use_checksum = 1;
|
---|
395 | #if EXTERNAL_COMPRESSION
|
---|
396 | option_decompress_inputs = 1;
|
---|
397 | option_recompress_outputs = 1;
|
---|
398 | #endif
|
---|
399 | #if VCDIFF_TOOLS
|
---|
400 | option_print_cpymode = 1;
|
---|
401 | #endif
|
---|
402 | option_level = XD3_DEFAULT_LEVEL;
|
---|
403 | option_iopt_size = XD3_DEFAULT_IOPT_SIZE;
|
---|
404 | option_winsize = XD3_DEFAULT_WINSIZE;
|
---|
405 | option_srcwinsz = XD3_DEFAULT_SRCWINSZ;
|
---|
406 | option_sprevsz = XD3_DEFAULT_SPREVSZ;
|
---|
407 | }
|
---|
408 |
|
---|
409 | static void*
|
---|
410 | main_malloc1 (usize_t size)
|
---|
411 | {
|
---|
412 | void* r = malloc (size);
|
---|
413 | if (r == NULL) { XPR(NT "malloc: %s\n", xd3_mainerror (ENOMEM)); }
|
---|
414 | else if (option_verbose > 3) { XPR(NT "malloc: %u: %p\n", size, r); }
|
---|
415 | return r;
|
---|
416 | }
|
---|
417 |
|
---|
418 | static void*
|
---|
419 | main_malloc (usize_t size)
|
---|
420 | {
|
---|
421 | void *r = main_malloc1 (size);
|
---|
422 | if (r) { IF_DEBUG (main_mallocs += 1); }
|
---|
423 | return r;
|
---|
424 | }
|
---|
425 |
|
---|
426 | static void*
|
---|
427 | main_alloc (void *opaque,
|
---|
428 | usize_t items,
|
---|
429 | usize_t size)
|
---|
430 | {
|
---|
431 | return main_malloc1 (items * size);
|
---|
432 | }
|
---|
433 |
|
---|
434 | static void
|
---|
435 | main_free1 (void *opaque, void *ptr)
|
---|
436 | {
|
---|
437 | if (option_verbose > 3) { XPR(NT "free: %p\n", ptr); }
|
---|
438 | free (ptr);
|
---|
439 | }
|
---|
440 |
|
---|
441 | static void
|
---|
442 | main_free (void *ptr)
|
---|
443 | {
|
---|
444 | if (ptr)
|
---|
445 | {
|
---|
446 | IF_DEBUG (main_mallocs -= 1);
|
---|
447 | main_free1 (NULL, ptr);
|
---|
448 | IF_DEBUG (XD3_ASSERT(main_mallocs >= 0));
|
---|
449 | }
|
---|
450 | }
|
---|
451 |
|
---|
452 | /* This ensures that (ret = errno) always indicates failure, in case errno was
|
---|
453 | * accidentally not set. If this prints there's a bug somewhere. */
|
---|
454 | static int
|
---|
455 | get_errno (void)
|
---|
456 | {
|
---|
457 | #ifndef _WIN32
|
---|
458 | if (errno == 0)
|
---|
459 | {
|
---|
460 | XPR(NT "you found a bug: expected errno != 0\n");
|
---|
461 | errno = XD3_INTERNAL;
|
---|
462 | }
|
---|
463 | return errno;
|
---|
464 | #else
|
---|
465 | DWORD errNum = GetLastError();
|
---|
466 | if (errNum == NO_ERROR) {
|
---|
467 | errNum = XD3_INTERNAL;
|
---|
468 | }
|
---|
469 | return errNum;
|
---|
470 | #endif
|
---|
471 | }
|
---|
472 |
|
---|
473 | const char*
|
---|
474 | xd3_mainerror(int err_num) {
|
---|
475 | #ifndef _WIN32
|
---|
476 | const char* x = xd3_strerror (err_num);
|
---|
477 | if (x != NULL) {
|
---|
478 | return x;
|
---|
479 | }
|
---|
480 | return strerror(err_num);
|
---|
481 | #else
|
---|
482 | static char err_buf[256];
|
---|
483 | const char* x = xd3_strerror (err_num);
|
---|
484 | if (x != NULL) {
|
---|
485 | return x;
|
---|
486 | }
|
---|
487 | memset (err_buf, 0, 256);
|
---|
488 | FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
|
---|
489 | NULL, err_num,
|
---|
490 | MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT),
|
---|
491 | err_buf, 256, NULL);
|
---|
492 | return err_buf;
|
---|
493 | #endif
|
---|
494 | }
|
---|
495 |
|
---|
496 | static long
|
---|
497 | get_millisecs_now (void)
|
---|
498 | {
|
---|
499 | #ifndef _WIN32
|
---|
500 | struct timeval tv;
|
---|
501 |
|
---|
502 | gettimeofday (& tv, NULL);
|
---|
503 |
|
---|
504 | return (tv.tv_sec) * 1000L + (tv.tv_usec) / 1000;
|
---|
505 | #else
|
---|
506 | // Found this in an example on www.codeproject.com
|
---|
507 | // It doesn't matter that the offset is Jan 1, 1601
|
---|
508 | // Result is the numbre of 100 nanosecond units
|
---|
509 | // 100ns * 10,000 = 1ms
|
---|
510 | SYSTEMTIME st;
|
---|
511 | FILETIME ft;
|
---|
512 | __int64 *pi = (__int64*)&ft;
|
---|
513 | GetLocalTime(&st);
|
---|
514 | SystemTimeToFileTime(&st, &ft);
|
---|
515 | return (long)((*pi) / 10000);
|
---|
516 | #endif
|
---|
517 | }
|
---|
518 |
|
---|
519 | /* Always >= 1 millisec, right? */
|
---|
520 | static long
|
---|
521 | get_millisecs_since (void)
|
---|
522 | {
|
---|
523 | static long last = 0;
|
---|
524 | long now = get_millisecs_now();
|
---|
525 | long diff = now - last;
|
---|
526 | last = now;
|
---|
527 | return diff;
|
---|
528 | }
|
---|
529 |
|
---|
530 | static char*
|
---|
531 | main_format_bcnt (xoff_t r, char *buf)
|
---|
532 | {
|
---|
533 | static const char* fmts[] = { "B", "KB", "MB", "GB" };
|
---|
534 | int i;
|
---|
535 |
|
---|
536 | for (i = 0; i < SIZEOF_ARRAY(fmts); i += 1)
|
---|
537 | {
|
---|
538 | if (r <= (10 * 1024) || i == (-1 + (int)SIZEOF_ARRAY(fmts)))
|
---|
539 | {
|
---|
540 | sprintf (buf, "%"Q"u %s", r, fmts[i]);
|
---|
541 | break;
|
---|
542 | }
|
---|
543 | r /= 1024;
|
---|
544 | }
|
---|
545 | return buf;
|
---|
546 | }
|
---|
547 |
|
---|
548 | static char*
|
---|
549 | main_format_rate (xoff_t bytes, long millis, char *buf)
|
---|
550 | {
|
---|
551 | xoff_t r = (xoff_t)(1.0 * bytes / (1.0 * millis / 1000.0));
|
---|
552 | static char lbuf[32];
|
---|
553 |
|
---|
554 | main_format_bcnt (r, lbuf);
|
---|
555 | sprintf (buf, "%s/sec", lbuf);
|
---|
556 | return buf;
|
---|
557 | }
|
---|
558 |
|
---|
559 | static char*
|
---|
560 | main_format_millis (long millis, char *buf)
|
---|
561 | {
|
---|
562 | if (millis < 1000) { sprintf (buf, "%lu ms", millis); }
|
---|
563 | else if (millis < 10000) { sprintf (buf, "%.1f sec", millis / 1000.0); }
|
---|
564 | else { sprintf (buf, "%lu sec", millis / 1000L); }
|
---|
565 | return buf;
|
---|
566 | }
|
---|
567 |
|
---|
568 | /* A safe version of strtol for xoff_t. */
|
---|
569 | static int
|
---|
570 | main_strtoxoff (const char* s, xoff_t *xo, char which)
|
---|
571 | {
|
---|
572 | char *e;
|
---|
573 | xoff_t x;
|
---|
574 |
|
---|
575 | XD3_ASSERT(s && *s != 0);
|
---|
576 |
|
---|
577 | {
|
---|
578 | /* Should check LONG_MIN, LONG_MAX, LLONG_MIN, LLONG_MAX? */
|
---|
579 | #if SIZEOF_XOFF_T == 4
|
---|
580 | long xx = strtol (s, &e, 0);
|
---|
581 | #else
|
---|
582 | long long xx = strtoll (s, &e, 0);
|
---|
583 | #endif
|
---|
584 |
|
---|
585 | if (xx < 0)
|
---|
586 | {
|
---|
587 | XPR(NT "-%c: negative integer: %s\n", which, s);
|
---|
588 | return EXIT_FAILURE;
|
---|
589 | }
|
---|
590 |
|
---|
591 | x = xx;
|
---|
592 | }
|
---|
593 |
|
---|
594 | if (*e != 0)
|
---|
595 | {
|
---|
596 | XPR(NT "-%c: invalid integer: %s\n", which, s);
|
---|
597 | return EXIT_FAILURE;
|
---|
598 | }
|
---|
599 |
|
---|
600 | (*xo) = x;
|
---|
601 | return 0;
|
---|
602 | }
|
---|
603 |
|
---|
604 | static int
|
---|
605 | main_atou (const char* arg, usize_t *xo, usize_t low, usize_t high, char which)
|
---|
606 | {
|
---|
607 | xoff_t x;
|
---|
608 | int ret;
|
---|
609 |
|
---|
610 | if ((ret = main_strtoxoff (arg, & x, which))) { return ret; }
|
---|
611 |
|
---|
612 | if (x < low)
|
---|
613 | {
|
---|
614 | XPR(NT "-%c: minimum value: %u\n", which, low);
|
---|
615 | return EXIT_FAILURE;
|
---|
616 | }
|
---|
617 | if (high == 0)
|
---|
618 | {
|
---|
619 | high = USIZE_T_MAX;
|
---|
620 | }
|
---|
621 | if (x > high)
|
---|
622 | {
|
---|
623 | XPR(NT "-%c: maximum value: %u\n", which, high);
|
---|
624 | return EXIT_FAILURE;
|
---|
625 | }
|
---|
626 | (*xo) = (usize_t)x;
|
---|
627 | return 0;
|
---|
628 | }
|
---|
629 |
|
---|
630 | /******************************************************************************************
|
---|
631 | FILE BASICS
|
---|
632 | ******************************************************************************************/
|
---|
633 |
|
---|
634 | /* With all the variation in file system-call semantics, arguments, return values and
|
---|
635 | * error-handling for the POSIX and STDIO file APIs, the insides of these functions make
|
---|
636 | * me sick, which is why these wrappers exist. */
|
---|
637 |
|
---|
638 | #define XOPEN_OPNAME (xfile->mode == XO_READ ? "read" : "write")
|
---|
639 | #define XOPEN_STDIO (xfile->mode == XO_READ ? "rb" : "wb")
|
---|
640 | #define XOPEN_POSIX (xfile->mode == XO_READ ? O_RDONLY : O_WRONLY | O_CREAT | O_TRUNC)
|
---|
641 | #define XOPEN_MODE (xfile->mode == XO_READ ? 0 : 0666)
|
---|
642 |
|
---|
643 | #define XF_ERROR(op, name, ret) \
|
---|
644 | XPR(NT "file %s failed: %s: %s: %s\n", (op), XOPEN_OPNAME, (name), xd3_mainerror (ret))
|
---|
645 |
|
---|
646 | #if XD3_STDIO
|
---|
647 | #define XFNO(f) fileno(f->file)
|
---|
648 | #define XSTDOUT_XF(f) { (f)->file = stdout; (f)->filename = "/dev/stdout"; }
|
---|
649 | #define XSTDIN_XF(f) { (f)->file = stdin; (f)->filename = "/dev/stdin"; }
|
---|
650 |
|
---|
651 | #elif XD3_POSIX
|
---|
652 | #define XFNO(f) f->file
|
---|
653 | #define XSTDOUT_XF(f) { (f)->file = STDOUT_FILENO; (f)->filename = "/dev/stdout"; }
|
---|
654 | #define XSTDIN_XF(f) { (f)->file = STDIN_FILENO; (f)->filename = "/dev/stdin"; }
|
---|
655 |
|
---|
656 | #elif XD3_WIN32
|
---|
657 | #define XFNO(f) -1
|
---|
658 | #define XSTDOUT_XF(f) { (f)->file = winStartupInfo.hStdOutput; (f)->filename = "(stdout)"; }
|
---|
659 | #define XSTDIN_XF(f) { (f)->file = winStartupInfo.hStdInput; (f)->filename = "(stdin)"; }
|
---|
660 | #endif
|
---|
661 |
|
---|
662 | static void
|
---|
663 | main_file_init (main_file *xfile)
|
---|
664 | {
|
---|
665 | memset (xfile, 0, sizeof (*xfile));
|
---|
666 |
|
---|
667 | #if XD3_POSIX
|
---|
668 | xfile->file = -1;
|
---|
669 | #endif
|
---|
670 | #if XD3_WIN32
|
---|
671 | xfile->file = INVALID_HANDLE_VALUE;
|
---|
672 | #endif
|
---|
673 | }
|
---|
674 |
|
---|
675 | static int
|
---|
676 | main_file_isopen (main_file *xfile)
|
---|
677 | {
|
---|
678 | #if XD3_STDIO
|
---|
679 | return xfile->file != NULL;
|
---|
680 |
|
---|
681 | #elif XD3_POSIX
|
---|
682 | return xfile->file != -1;
|
---|
683 |
|
---|
684 | #elif XD3_WIN32
|
---|
685 | return xfile->file != INVALID_HANDLE_VALUE;
|
---|
686 | #endif
|
---|
687 | }
|
---|
688 |
|
---|
689 | static int
|
---|
690 | main_file_close (main_file *xfile)
|
---|
691 | {
|
---|
692 | int ret = 0;
|
---|
693 |
|
---|
694 | if (! main_file_isopen (xfile))
|
---|
695 | {
|
---|
696 | return 0;
|
---|
697 | }
|
---|
698 |
|
---|
699 | #if XD3_STDIO
|
---|
700 | ret = fclose (xfile->file);
|
---|
701 | xfile->file = NULL;
|
---|
702 |
|
---|
703 | #elif XD3_POSIX
|
---|
704 | ret = close (xfile->file);
|
---|
705 | xfile->file = -1;
|
---|
706 |
|
---|
707 | #elif XD3_WIN32
|
---|
708 | if (!CloseHandle(xfile->file)) {
|
---|
709 | ret = get_errno ();
|
---|
710 | }
|
---|
711 | xfile->file = INVALID_HANDLE_VALUE;
|
---|
712 | #endif
|
---|
713 |
|
---|
714 | if (ret != 0) { XF_ERROR ("close", xfile->filename, ret = get_errno ()); }
|
---|
715 | return ret;
|
---|
716 | }
|
---|
717 |
|
---|
718 | static void
|
---|
719 | main_file_cleanup (main_file *xfile)
|
---|
720 | {
|
---|
721 | if (main_file_isopen (xfile))
|
---|
722 | {
|
---|
723 | main_file_close (xfile);
|
---|
724 | }
|
---|
725 |
|
---|
726 | if (xfile->snprintf_buf != NULL)
|
---|
727 | {
|
---|
728 | main_free(xfile->snprintf_buf);
|
---|
729 | xfile->snprintf_buf = NULL;
|
---|
730 | }
|
---|
731 |
|
---|
732 | if (xfile->filename_copy != NULL)
|
---|
733 | {
|
---|
734 | main_free(xfile->filename_copy);
|
---|
735 | xfile->filename_copy = NULL;
|
---|
736 | }
|
---|
737 | }
|
---|
738 |
|
---|
739 | static int
|
---|
740 | main_file_open (main_file *xfile, const char* name, int mode)
|
---|
741 | {
|
---|
742 | int ret = 0;
|
---|
743 |
|
---|
744 | xfile->mode = mode;
|
---|
745 |
|
---|
746 | XD3_ASSERT (name != NULL);
|
---|
747 | XD3_ASSERT (! main_file_isopen (xfile));
|
---|
748 | if (name[0] == 0)
|
---|
749 | {
|
---|
750 | XPR(NT "invalid file name: empty string\n");
|
---|
751 | return XD3_INVALID;
|
---|
752 | }
|
---|
753 |
|
---|
754 | #if XD3_STDIO
|
---|
755 | xfile->file = fopen (name, XOPEN_STDIO);
|
---|
756 |
|
---|
757 | ret = (xfile->file == NULL) ? get_errno () : 0;
|
---|
758 |
|
---|
759 | #elif XD3_POSIX
|
---|
760 | if ((ret = open (name, XOPEN_POSIX, XOPEN_MODE)) < 0)
|
---|
761 | {
|
---|
762 | ret = get_errno ();
|
---|
763 | }
|
---|
764 | else
|
---|
765 | {
|
---|
766 | xfile->file = ret;
|
---|
767 | ret = 0;
|
---|
768 | }
|
---|
769 |
|
---|
770 | #elif XD3_WIN32
|
---|
771 | xfile->file = CreateFile(name,
|
---|
772 | (mode == XO_READ) ? GENERIC_READ : GENERIC_WRITE,
|
---|
773 | FILE_SHARE_READ,
|
---|
774 | NULL,
|
---|
775 | (mode == XO_READ) ? OPEN_EXISTING : (option_force ? CREATE_ALWAYS : CREATE_NEW),
|
---|
776 | FILE_ATTRIBUTE_NORMAL,
|
---|
777 | NULL);
|
---|
778 | if (xfile->file == INVALID_HANDLE_VALUE) {
|
---|
779 | ret = get_errno ();
|
---|
780 | }
|
---|
781 | #endif
|
---|
782 | if (ret) { XF_ERROR ("open", name, ret); }
|
---|
783 | else { xfile->realname = name; xfile->nread = 0; }
|
---|
784 | return ret;
|
---|
785 | }
|
---|
786 |
|
---|
787 | static int
|
---|
788 | main_file_stat (main_file *xfile, xoff_t *size, int err_ifnoseek)
|
---|
789 | {
|
---|
790 | int ret = 0;
|
---|
791 | #if XD3_WIN32
|
---|
792 | LARGE_INTEGER li;
|
---|
793 | if (GetFileSizeEx(xfile->file, &li) == 0) {
|
---|
794 | ret = get_errno ();
|
---|
795 | } else {
|
---|
796 | *size = li.QuadPart;
|
---|
797 | }
|
---|
798 | #else
|
---|
799 | struct stat sbuf;
|
---|
800 | if (fstat (XFNO (xfile), & sbuf) < 0)
|
---|
801 | {
|
---|
802 | ret = get_errno ();
|
---|
803 | if (err_ifnoseek) { XF_ERROR ("stat", xfile->filename, ret); }
|
---|
804 | return ret;
|
---|
805 | }
|
---|
806 |
|
---|
807 | if (! S_ISREG (sbuf.st_mode))
|
---|
808 | {
|
---|
809 | if (err_ifnoseek) { XPR(NT "source file must be seekable: %s\n", xfile->filename); }
|
---|
810 | return ESPIPE;
|
---|
811 | }
|
---|
812 | (*size) = sbuf.st_size;
|
---|
813 | #endif
|
---|
814 | return ret;
|
---|
815 | }
|
---|
816 |
|
---|
817 | static int
|
---|
818 | main_file_exists (main_file *xfile)
|
---|
819 | {
|
---|
820 | struct stat sbuf;
|
---|
821 | return stat (xfile->filename, & sbuf) == 0 && S_ISREG (sbuf.st_mode);
|
---|
822 | }
|
---|
823 |
|
---|
824 | #if (XD3_POSIX || EXTERNAL_COMPRESSION)
|
---|
825 | /* POSIX-generic code takes a function pointer to read() or write(). This calls the
|
---|
826 | * function repeatedly until the buffer is full or EOF. The NREAD parameter is not
|
---|
827 | * set for write, NULL is passed. Return is signed, < 0 indicate errors, otherwise
|
---|
828 | * byte count. */
|
---|
829 | typedef int (xd3_posix_func) (int fd, uint8_t *buf, usize_t size);
|
---|
830 |
|
---|
831 | static int
|
---|
832 | xd3_posix_io (int fd, uint8_t *buf, usize_t size, xd3_posix_func *func, usize_t *nread)
|
---|
833 | {
|
---|
834 | int ret;
|
---|
835 | usize_t nproc = 0;
|
---|
836 |
|
---|
837 | while (nproc < size)
|
---|
838 | {
|
---|
839 | int result = (*func) (fd, buf + nproc, size - nproc);
|
---|
840 |
|
---|
841 | if (result < 0)
|
---|
842 | {
|
---|
843 | ret = get_errno ();
|
---|
844 | if (ret != EAGAIN && ret != EINTR)
|
---|
845 | {
|
---|
846 | return ret;
|
---|
847 | }
|
---|
848 | result = 0;
|
---|
849 | }
|
---|
850 |
|
---|
851 | if (nread != NULL && result == 0) { break; }
|
---|
852 |
|
---|
853 | nproc += result;
|
---|
854 | }
|
---|
855 | if (nread != NULL) { (*nread) = nproc; }
|
---|
856 | return 0;
|
---|
857 | }
|
---|
858 | #endif
|
---|
859 |
|
---|
860 | /* POSIX is unbuffered, while STDIO is buffered. main_file_read() should always be called
|
---|
861 | * on blocks. */
|
---|
862 | static int
|
---|
863 | main_file_read (main_file *ifile,
|
---|
864 | uint8_t *buf,
|
---|
865 | usize_t size,
|
---|
866 | usize_t *nread,
|
---|
867 | const char *msg)
|
---|
868 | {
|
---|
869 | int ret = 0;
|
---|
870 |
|
---|
871 | #if XD3_STDIO
|
---|
872 | usize_t result;
|
---|
873 |
|
---|
874 | result = fread (buf, 1, size, ifile->file);
|
---|
875 |
|
---|
876 | if (result < size && ferror (ifile->file))
|
---|
877 | {
|
---|
878 | ret = get_errno ();
|
---|
879 | }
|
---|
880 | else
|
---|
881 | {
|
---|
882 | *nread = result;
|
---|
883 | }
|
---|
884 |
|
---|
885 | #elif XD3_POSIX
|
---|
886 | ret = xd3_posix_io (ifile->file, buf, size, (xd3_posix_func*) &read, nread);
|
---|
887 |
|
---|
888 | #elif XD3_WIN32
|
---|
889 | DWORD nread2;
|
---|
890 | if (ReadFile (ifile->file, buf, size, &nread2, NULL) == 0) {
|
---|
891 | ret = get_errno();
|
---|
892 | } else {
|
---|
893 | *nread = (usize_t)nread2;
|
---|
894 | }
|
---|
895 | #endif
|
---|
896 |
|
---|
897 | if (ret)
|
---|
898 | {
|
---|
899 | XPR(NT "%s: %s: %s\n", msg, ifile->filename, xd3_mainerror (ret));
|
---|
900 | }
|
---|
901 | else
|
---|
902 | {
|
---|
903 | if (option_verbose > 3) { XPR(NT "main read: %s: %u\n", ifile->filename, (*nread)); }
|
---|
904 | ifile->nread += (*nread);
|
---|
905 | }
|
---|
906 |
|
---|
907 | return ret;
|
---|
908 | }
|
---|
909 |
|
---|
910 | static int
|
---|
911 | main_file_write (main_file *ofile, uint8_t *buf, usize_t size, const char *msg)
|
---|
912 | {
|
---|
913 | int ret = 0;
|
---|
914 |
|
---|
915 | #if XD3_STDIO
|
---|
916 | usize_t result;
|
---|
917 |
|
---|
918 | result = fwrite (buf, 1, size, ofile->file);
|
---|
919 |
|
---|
920 | if (result != size) { ret = get_errno (); }
|
---|
921 |
|
---|
922 | #elif XD3_POSIX
|
---|
923 | ret = xd3_posix_io (ofile->file, buf, size, (xd3_posix_func*) &write, NULL);
|
---|
924 |
|
---|
925 | #elif XD3_WIN32
|
---|
926 | DWORD nwrite;
|
---|
927 | if (WriteFile(ofile->file, buf, size, &nwrite, NULL) == 0) {
|
---|
928 | ret = get_errno ();
|
---|
929 | } else {
|
---|
930 | if (size != nwrite) {
|
---|
931 | XPR(NT "Incorrect write count");
|
---|
932 | ret = XD3_INTERNAL;
|
---|
933 | }
|
---|
934 | }
|
---|
935 | #endif
|
---|
936 |
|
---|
937 | if (ret)
|
---|
938 | {
|
---|
939 | XPR(NT "%s: %s: %s\n", msg, ofile->filename, xd3_mainerror (ret));
|
---|
940 | }
|
---|
941 | else
|
---|
942 | {
|
---|
943 | if (option_verbose > 3) { XPR(NT "main write: %s: %u\n", ofile->filename, size); }
|
---|
944 | ofile->nwrite += size;
|
---|
945 | }
|
---|
946 |
|
---|
947 | return ret;
|
---|
948 | }
|
---|
949 |
|
---|
950 | static int
|
---|
951 | main_file_seek (main_file *xfile, xoff_t pos)
|
---|
952 | {
|
---|
953 | int ret = 0;
|
---|
954 |
|
---|
955 | #if XD3_STDIO
|
---|
956 | if (fseek (xfile->file, pos, SEEK_SET) != 0) { ret = get_errno (); }
|
---|
957 |
|
---|
958 | #elif XD3_POSIX
|
---|
959 | if (lseek (xfile->file, pos, SEEK_SET) != pos) { ret = get_errno (); }
|
---|
960 |
|
---|
961 | #elif XD3_WIN32
|
---|
962 | LARGE_INTEGER move, out;
|
---|
963 | move.QuadPart = pos;
|
---|
964 | if (SetFilePointerEx(xfile->file, move, &out, FILE_BEGIN) == 0) {
|
---|
965 | ret = get_errno ();
|
---|
966 | }
|
---|
967 | #endif
|
---|
968 |
|
---|
969 | if (ret)
|
---|
970 | {
|
---|
971 | XPR(NT "seek failed: %s: %s\n", xfile->filename, xd3_mainerror (ret));
|
---|
972 | }
|
---|
973 |
|
---|
974 | return ret;
|
---|
975 | }
|
---|
976 |
|
---|
977 | /******************************************************************************************
|
---|
978 | VCDIFF TOOLS
|
---|
979 | ******************************************************************************************/
|
---|
980 |
|
---|
981 | #if VCDIFF_TOOLS
|
---|
982 |
|
---|
983 | /* The following macros let VCDIFF printing something printf-like with
|
---|
984 | * main_file_write(), e.g.,:
|
---|
985 | *
|
---|
986 | * VC(UT "trying to be portable: %d\n", x)VE;
|
---|
987 | */
|
---|
988 | #define SNPRINTF_BUFSIZE 1024
|
---|
989 | #define VC do { if (((ret = snprintf
|
---|
990 | #define UT (char*)xfile->snprintf_buf, SNPRINTF_BUFSIZE,
|
---|
991 | #define VE ) >= SNPRINTF_BUFSIZE \
|
---|
992 | && (ret = main_print_overflow(ret)) != 0) \
|
---|
993 | || (ret = main_file_write(xfile, xfile->snprintf_buf, \
|
---|
994 | ret, "print")) != 0) \
|
---|
995 | { return ret; } } while (0)
|
---|
996 |
|
---|
997 | #ifdef WIN32
|
---|
998 | /* According to the internet, Windows vsnprintf() differs from most Unix
|
---|
999 | * implementations regarding the terminating 0 when the boundary condition
|
---|
1000 | * is met. It doesn't matter here, we don't rely on the trailing 0. */
|
---|
1001 | #include <stdarg.h>
|
---|
1002 | int
|
---|
1003 | snprintf (char *str, int n, char *fmt, ...)
|
---|
1004 | {
|
---|
1005 | va_list a;
|
---|
1006 | int ret;
|
---|
1007 | va_start (a, fmt);
|
---|
1008 | ret = vsnprintf (str, n, fmt, a);
|
---|
1009 | va_end (a);
|
---|
1010 | return ret;
|
---|
1011 | }
|
---|
1012 | #endif
|
---|
1013 |
|
---|
1014 | static int
|
---|
1015 | main_print_overflow (int x)
|
---|
1016 | {
|
---|
1017 | XPR(NT "internal print buffer overflow: %d bytes\n", x);
|
---|
1018 | return XD3_INTERNAL;
|
---|
1019 | }
|
---|
1020 |
|
---|
1021 | /* This function prints a single VCDIFF window. */
|
---|
1022 | static int
|
---|
1023 | main_print_window (xd3_stream* stream, main_file *xfile)
|
---|
1024 | {
|
---|
1025 | int ret;
|
---|
1026 | usize_t size = 0;
|
---|
1027 |
|
---|
1028 | VC(UT " Offset Code Type1 Size1 @Addr1 + Type2 Size2 @Addr2\n")VE;
|
---|
1029 |
|
---|
1030 | while (stream->inst_sect.buf < stream->inst_sect.buf_max)
|
---|
1031 | {
|
---|
1032 | uint code = stream->inst_sect.buf[0];
|
---|
1033 |
|
---|
1034 | if ((ret = xd3_decode_instruction (stream))) { return ret; }
|
---|
1035 |
|
---|
1036 | VC(UT " %06"Q"u %03u %s %3u", stream->dec_winstart + size, code,
|
---|
1037 | xd3_rtype_to_string (stream->dec_current1.type, option_print_cpymode),
|
---|
1038 | (usize_t) stream->dec_current1.size)VE;
|
---|
1039 |
|
---|
1040 | if (stream->dec_current1.type != XD3_NOOP)
|
---|
1041 | {
|
---|
1042 | size += stream->dec_current1.size;
|
---|
1043 | if (stream->dec_current1.type >= XD3_CPY)
|
---|
1044 | {
|
---|
1045 | VC(UT " @%-6u", (usize_t)stream->dec_current1.addr)VE;
|
---|
1046 | }
|
---|
1047 | else
|
---|
1048 | {
|
---|
1049 | VC(UT " ")VE;
|
---|
1050 | }
|
---|
1051 | }
|
---|
1052 |
|
---|
1053 | if (stream->dec_current2.type != XD3_NOOP)
|
---|
1054 | {
|
---|
1055 | size += stream->dec_current2.size;
|
---|
1056 | VC(UT " %s %3u",
|
---|
1057 | xd3_rtype_to_string (stream->dec_current2.type, option_print_cpymode),
|
---|
1058 | (usize_t)stream->dec_current2.size)VE;
|
---|
1059 |
|
---|
1060 | if (stream->dec_current2.type >= XD3_CPY)
|
---|
1061 | {
|
---|
1062 | VC(UT " @%-6u", (usize_t)stream->dec_current2.addr)VE;
|
---|
1063 | }
|
---|
1064 | }
|
---|
1065 |
|
---|
1066 | VC(UT "\n")VE;
|
---|
1067 | }
|
---|
1068 |
|
---|
1069 | if (stream->dec_tgtlen != size && (stream->flags & XD3_SKIP_WINDOW) == 0)
|
---|
1070 | {
|
---|
1071 | XPR(NT "target window size inconsistency");
|
---|
1072 | return XD3_INTERNAL;
|
---|
1073 | }
|
---|
1074 |
|
---|
1075 | if (stream->dec_position != stream->dec_maxpos)
|
---|
1076 | {
|
---|
1077 | XPR(NT "target window position inconsistency");
|
---|
1078 | return XD3_INTERNAL;
|
---|
1079 | }
|
---|
1080 |
|
---|
1081 | if (stream->addr_sect.buf != stream->addr_sect.buf_max)
|
---|
1082 | {
|
---|
1083 | XPR(NT "address section inconsistency");
|
---|
1084 | return XD3_INTERNAL;
|
---|
1085 | }
|
---|
1086 |
|
---|
1087 | return 0;
|
---|
1088 | }
|
---|
1089 |
|
---|
1090 | static int
|
---|
1091 | main_print_vcdiff_file (main_file *xfile, main_file *file, const char *type)
|
---|
1092 | {
|
---|
1093 | int ret; /* Used by above macros */
|
---|
1094 | if (file->filename)
|
---|
1095 | {
|
---|
1096 | VC(UT "XDELTA filename (%s): %s\n", type, file->filename)VE;
|
---|
1097 | }
|
---|
1098 | if (file->compressor)
|
---|
1099 | {
|
---|
1100 | VC(UT "XDELTA ext comp (%s): %s\n", type, file->compressor->recomp_cmdname)VE;
|
---|
1101 | }
|
---|
1102 | return 0;
|
---|
1103 | }
|
---|
1104 |
|
---|
1105 | /* This function prints a VCDIFF input, mainly for debugging purposes. */
|
---|
1106 | static int
|
---|
1107 | main_print_func (xd3_stream* stream, main_file *xfile)
|
---|
1108 | {
|
---|
1109 | int ret;
|
---|
1110 |
|
---|
1111 | if (xfile->snprintf_buf == NULL)
|
---|
1112 | {
|
---|
1113 | if ((xfile->snprintf_buf = main_malloc(SNPRINTF_BUFSIZE)) == NULL)
|
---|
1114 | {
|
---|
1115 | return ENOMEM;
|
---|
1116 | }
|
---|
1117 | }
|
---|
1118 |
|
---|
1119 | if (stream->dec_winstart == 0)
|
---|
1120 | {
|
---|
1121 | VC(UT "VCDIFF version: 0\n")VE;
|
---|
1122 |
|
---|
1123 | VC(UT "VCDIFF header size: %d\n", stream->dec_hdrsize)VE;
|
---|
1124 | VC(UT "VCDIFF header indicator: ")VE;
|
---|
1125 | if ((stream->dec_hdr_ind & VCD_SECONDARY) != 0) VC(UT "VCD_SECONDARY ")VE;
|
---|
1126 | if ((stream->dec_hdr_ind & VCD_CODETABLE) != 0) VC(UT "VCD_CODETABLE ")VE;
|
---|
1127 | if ((stream->dec_hdr_ind & VCD_APPHEADER) != 0) VC(UT "VCD_APPHEADER ")VE;
|
---|
1128 | if (stream->dec_hdr_ind == 0) VC(UT "none")VE;
|
---|
1129 | VC(UT "\n")VE;
|
---|
1130 |
|
---|
1131 | IF_SEC(VC(UT "VCDIFF secondary compressor: %s\n",
|
---|
1132 | stream->sec_type ? stream->sec_type->name : "none")VE);
|
---|
1133 | IF_NSEC(VC(UT "VCDIFF secondary compressor: unsupported\n")VE);
|
---|
1134 |
|
---|
1135 | if (stream->dec_hdr_ind & VCD_APPHEADER)
|
---|
1136 | {
|
---|
1137 | uint8_t *apphead;
|
---|
1138 | usize_t appheadsz;
|
---|
1139 | ret = xd3_get_appheader (stream, & apphead, & appheadsz);
|
---|
1140 |
|
---|
1141 | if (ret == 0 && appheadsz > 0)
|
---|
1142 | {
|
---|
1143 | int sq = option_quiet;
|
---|
1144 | main_file i, o, s;
|
---|
1145 | XD3_ASSERT (apphead != NULL);
|
---|
1146 | VC(UT "VCDIFF application header: ")VE;
|
---|
1147 | if ((ret = main_file_write (xfile, apphead, appheadsz, "print")) != 0) { return ret; }
|
---|
1148 | VC(UT "\n")VE;
|
---|
1149 |
|
---|
1150 | main_file_init (& i);
|
---|
1151 | main_file_init (& o);
|
---|
1152 | main_file_init (& s);
|
---|
1153 | option_quiet = 1;
|
---|
1154 | main_get_appheader (stream, &i, & o, & s);
|
---|
1155 | option_quiet = sq;
|
---|
1156 | if ((ret = main_print_vcdiff_file (xfile, & o, "output"))) { return ret; }
|
---|
1157 | if ((ret = main_print_vcdiff_file (xfile, & s, "source"))) { return ret; }
|
---|
1158 | main_file_cleanup (& i);
|
---|
1159 | main_file_cleanup (& o);
|
---|
1160 | main_file_cleanup (& s);
|
---|
1161 | }
|
---|
1162 | }
|
---|
1163 | }
|
---|
1164 | else
|
---|
1165 | {
|
---|
1166 | VC(UT "\n")VE;
|
---|
1167 | }
|
---|
1168 |
|
---|
1169 | VC(UT "VCDIFF window number: %"Q"u\n", stream->current_window)VE;
|
---|
1170 | VC(UT "VCDIFF window indicator: ")VE;
|
---|
1171 | if ((stream->dec_win_ind & VCD_SOURCE) != 0) VC(UT "VCD_SOURCE ")VE;
|
---|
1172 | if ((stream->dec_win_ind & VCD_TARGET) != 0) VC(UT "VCD_TARGET ")VE;
|
---|
1173 | if ((stream->dec_win_ind & VCD_ADLER32) != 0) VC(UT "VCD_ADLER32 ")VE;
|
---|
1174 | if (stream->dec_win_ind == 0) VC(UT "none")VE;
|
---|
1175 | VC(UT "\n")VE;
|
---|
1176 |
|
---|
1177 | if ((stream->dec_win_ind & VCD_ADLER32) != 0)
|
---|
1178 | {
|
---|
1179 | VC(UT "VCDIFF adler32 checksum: %08X\n", (usize_t)stream->dec_adler32)VE;
|
---|
1180 | }
|
---|
1181 |
|
---|
1182 | if (stream->dec_del_ind != 0)
|
---|
1183 | {
|
---|
1184 | VC(UT "VCDIFF delta indicator: ")VE;
|
---|
1185 | if ((stream->dec_del_ind & VCD_DATACOMP) != 0) VC(UT "VCD_DATACOMP ")VE;
|
---|
1186 | if ((stream->dec_del_ind & VCD_INSTCOMP) != 0) VC(UT "VCD_INSTCOMP ")VE;
|
---|
1187 | if ((stream->dec_del_ind & VCD_ADDRCOMP) != 0) VC(UT "VCD_ADDRCOMP ")VE;
|
---|
1188 | if (stream->dec_del_ind == 0) VC(UT "none")VE;
|
---|
1189 | VC(UT "\n")VE;
|
---|
1190 | }
|
---|
1191 |
|
---|
1192 | if (stream->dec_winstart != 0)
|
---|
1193 | {
|
---|
1194 | VC(UT "VCDIFF window at offset: %"Q"u\n", stream->dec_winstart)VE;
|
---|
1195 | }
|
---|
1196 |
|
---|
1197 | if (SRCORTGT (stream->dec_win_ind))
|
---|
1198 | {
|
---|
1199 | VC(UT "VCDIFF copy window length: %u\n", (usize_t)stream->dec_cpylen)VE;
|
---|
1200 | VC(UT "VCDIFF copy window offset: %"Q"u\n", stream->dec_cpyoff)VE;
|
---|
1201 | }
|
---|
1202 |
|
---|
1203 | VC(UT "VCDIFF delta encoding length: %u\n", (usize_t)stream->dec_enclen)VE;
|
---|
1204 | VC(UT "VCDIFF target window length: %u\n", (usize_t)stream->dec_tgtlen)VE;
|
---|
1205 |
|
---|
1206 | VC(UT "VCDIFF data section length: %u\n", (usize_t)stream->data_sect.size)VE;
|
---|
1207 | VC(UT "VCDIFF inst section length: %u\n", (usize_t)stream->inst_sect.size)VE;
|
---|
1208 | VC(UT "VCDIFF addr section length: %u\n", (usize_t)stream->addr_sect.size)VE;
|
---|
1209 |
|
---|
1210 | ret = 0;
|
---|
1211 | if ((stream->flags & XD3_JUST_HDR) != 0)
|
---|
1212 | {
|
---|
1213 | /* Print a header -- finished! */
|
---|
1214 | ret = PRINTHDR_SPECIAL;
|
---|
1215 | }
|
---|
1216 | else if ((stream->flags & XD3_SKIP_WINDOW) == 0)
|
---|
1217 | {
|
---|
1218 | ret = main_print_window (stream, xfile);
|
---|
1219 | }
|
---|
1220 |
|
---|
1221 | return ret;
|
---|
1222 | }
|
---|
1223 | #endif /* VCDIFF_TOOLS */
|
---|
1224 |
|
---|
1225 | /******************************************************************************************
|
---|
1226 | Input decompression, output recompression
|
---|
1227 | ******************************************************************************************/
|
---|
1228 |
|
---|
1229 | #if EXTERNAL_COMPRESSION
|
---|
1230 | /* This is tricky POSIX-specific code with lots of fork(), pipe(), dup(), waitpid(), and
|
---|
1231 | * exec() business. Most of this code originated in PRCS1, which did automatic
|
---|
1232 | * package-file decompression. It works with both XD3_POSIX and XD3_STDIO file
|
---|
1233 | * disciplines.
|
---|
1234 | *
|
---|
1235 | * To automatically detect compressed inputs requires a child process to reconstruct the
|
---|
1236 | * input stream, which was advanced in order to detect compression, because it may not be
|
---|
1237 | * seekable. In other words, the main program reads part of the input stream, and if it
|
---|
1238 | * detects a compressed input it then forks a pipe copier process, which copies the
|
---|
1239 | * first-read block out of the main-program's memory, then streams the remaining
|
---|
1240 | * compressed input into the input-decompression pipe.
|
---|
1241 | */
|
---|
1242 |
|
---|
1243 | #include <unistd.h>
|
---|
1244 | #include <sys/stat.h>
|
---|
1245 | #include <sys/wait.h>
|
---|
1246 |
|
---|
1247 | /* Remember which pipe FD is which. */
|
---|
1248 | #define PIPE_READ_FD 0
|
---|
1249 | #define PIPE_WRITE_FD 1
|
---|
1250 |
|
---|
1251 | static pid_t ext_subprocs[2];
|
---|
1252 | static char* ext_tmpfile = NULL;
|
---|
1253 |
|
---|
1254 | /* Like write(), but makes repeated calls to empty the buffer. */
|
---|
1255 | static int
|
---|
1256 | main_pipe_write (int outfd, const uint8_t *exist_buf, usize_t remain)
|
---|
1257 | {
|
---|
1258 | int ret;
|
---|
1259 |
|
---|
1260 | if ((ret = xd3_posix_io (outfd, (uint8_t*) exist_buf, remain, (xd3_posix_func*) &write, NULL)))
|
---|
1261 | {
|
---|
1262 | XPR(NT "pipe write failed: %s", xd3_mainerror (ret));
|
---|
1263 | return ret;
|
---|
1264 | }
|
---|
1265 |
|
---|
1266 | return 0;
|
---|
1267 | }
|
---|
1268 |
|
---|
1269 | /* A simple error-reporting waitpid interface. */
|
---|
1270 | static int
|
---|
1271 | main_waitpid_check(pid_t pid)
|
---|
1272 | {
|
---|
1273 | int status;
|
---|
1274 | int ret = 0;
|
---|
1275 |
|
---|
1276 | if (waitpid (pid, & status, 0) < 0)
|
---|
1277 | {
|
---|
1278 | ret = get_errno ();
|
---|
1279 | XPR(NT "compression subprocess: wait: %s\n", xd3_mainerror (ret));
|
---|
1280 | }
|
---|
1281 | else if (! WIFEXITED (status))
|
---|
1282 | {
|
---|
1283 | ret = ECHILD;
|
---|
1284 | XPR(NT "compression subprocess: signal %d\n",
|
---|
1285 | WIFSIGNALED (status) ? WTERMSIG (status) : WSTOPSIG (status));
|
---|
1286 | }
|
---|
1287 | else if (WEXITSTATUS (status) != 0)
|
---|
1288 | {
|
---|
1289 | ret = ECHILD;
|
---|
1290 | XPR(NT "compression subprocess: exit %d\n", WEXITSTATUS (status));
|
---|
1291 | }
|
---|
1292 |
|
---|
1293 | return ret;
|
---|
1294 | }
|
---|
1295 |
|
---|
1296 | /* Wait for any existing child processes to check for abnormal exit. */
|
---|
1297 | static int
|
---|
1298 | main_external_compression_finish (void)
|
---|
1299 | {
|
---|
1300 | int i;
|
---|
1301 | int ret;
|
---|
1302 |
|
---|
1303 | for (i = 0; i < 2; i += 1)
|
---|
1304 | {
|
---|
1305 | if (! ext_subprocs[i]) { continue; }
|
---|
1306 |
|
---|
1307 | if ((ret = main_waitpid_check (ext_subprocs[i])))
|
---|
1308 | {
|
---|
1309 | return ret;
|
---|
1310 | }
|
---|
1311 | }
|
---|
1312 |
|
---|
1313 | return 0;
|
---|
1314 | }
|
---|
1315 |
|
---|
1316 | /* This runs as a forked process of main_input_decompress_setup() to copy input to the
|
---|
1317 | * decompression process. First, the available input is copied out of the existing
|
---|
1318 | * buffer, then the buffer is reused to continue reading from the compressed input
|
---|
1319 | * file. */
|
---|
1320 | static int
|
---|
1321 | main_pipe_copier (uint8_t *pipe_buf,
|
---|
1322 | usize_t pipe_bufsize,
|
---|
1323 | usize_t nread,
|
---|
1324 | main_file *ifile,
|
---|
1325 | int outfd)
|
---|
1326 | {
|
---|
1327 | int ret;
|
---|
1328 |
|
---|
1329 | for (;;)
|
---|
1330 | {
|
---|
1331 | if (nread > 0 && (ret = main_pipe_write (outfd, pipe_buf, nread)))
|
---|
1332 | {
|
---|
1333 | return ret;
|
---|
1334 | }
|
---|
1335 |
|
---|
1336 | if (nread < pipe_bufsize)
|
---|
1337 | {
|
---|
1338 | break;
|
---|
1339 | }
|
---|
1340 |
|
---|
1341 | if ((ret = main_file_read (ifile, pipe_buf, pipe_bufsize, & nread, "pipe read failed")) < 0)
|
---|
1342 | {
|
---|
1343 | return ret;
|
---|
1344 | }
|
---|
1345 | }
|
---|
1346 |
|
---|
1347 | return 0;
|
---|
1348 | }
|
---|
1349 |
|
---|
1350 | /* This function is called after we have read some amount of data from the input file and
|
---|
1351 | * detected a compressed input. Here we start a decompression subprocess by forking
|
---|
1352 | * twice. The first process runs the decompression command, the second process copies
|
---|
1353 | * data to the input of the first. */
|
---|
1354 | static int
|
---|
1355 | main_input_decompress_setup (const main_extcomp *decomp,
|
---|
1356 | main_file *ifile,
|
---|
1357 | uint8_t *input_buf,
|
---|
1358 | usize_t input_bufsize,
|
---|
1359 | uint8_t *pipe_buf,
|
---|
1360 | usize_t pipe_bufsize,
|
---|
1361 | usize_t pipe_avail,
|
---|
1362 | usize_t *nread)
|
---|
1363 | {
|
---|
1364 | int outpipefd[2], inpipefd[2]; /* The two pipes: input and output file descriptors. */
|
---|
1365 | int input_fd = -1; /* The resulting input_fd (output of decompression). */
|
---|
1366 | pid_t decomp_id, copier_id; /* The two subprocs. */
|
---|
1367 | int ret;
|
---|
1368 |
|
---|
1369 | outpipefd[0] = outpipefd[1] = -1;
|
---|
1370 | inpipefd[0] = inpipefd[1] = -1;
|
---|
1371 |
|
---|
1372 | if (pipe (outpipefd) || pipe (inpipefd))
|
---|
1373 | {
|
---|
1374 | XPR(NT "pipe failed: %s\n", xd3_mainerror (ret = get_errno ()));
|
---|
1375 | goto pipe_cleanup;
|
---|
1376 | }
|
---|
1377 |
|
---|
1378 | if ((decomp_id = fork ()) < 0)
|
---|
1379 | {
|
---|
1380 | XPR(NT "fork failed: %s\n", xd3_mainerror (ret = get_errno ()));
|
---|
1381 | goto pipe_cleanup;
|
---|
1382 | }
|
---|
1383 |
|
---|
1384 | /* The first child runs the decompression process: */
|
---|
1385 | if (decomp_id == 0)
|
---|
1386 | {
|
---|
1387 | /* Setup pipes: write to the outpipe, read from the inpipe. */
|
---|
1388 | if (dup2 (outpipefd[PIPE_WRITE_FD], STDOUT_FILENO) < 0 ||
|
---|
1389 | dup2 (inpipefd[PIPE_READ_FD], STDIN_FILENO) < 0 ||
|
---|
1390 | close (outpipefd[PIPE_READ_FD]) ||
|
---|
1391 | close (outpipefd[PIPE_WRITE_FD]) ||
|
---|
1392 | close (inpipefd[PIPE_READ_FD]) ||
|
---|
1393 | close (inpipefd[PIPE_WRITE_FD]) ||
|
---|
1394 | execlp (decomp->decomp_cmdname, decomp->decomp_cmdname, decomp->decomp_options, NULL))
|
---|
1395 | {
|
---|
1396 | XPR(NT "child process %s failed to execute: %s\n", decomp->decomp_cmdname, xd3_mainerror (get_errno ()));
|
---|
1397 | }
|
---|
1398 |
|
---|
1399 | _exit (127);
|
---|
1400 | }
|
---|
1401 |
|
---|
1402 | ext_subprocs[0] = decomp_id;
|
---|
1403 |
|
---|
1404 | if ((copier_id = fork ()) < 0)
|
---|
1405 | {
|
---|
1406 | XPR(NT "fork failed: %s\n", xd3_mainerror (ret = get_errno ()));
|
---|
1407 | goto pipe_cleanup;
|
---|
1408 | }
|
---|
1409 |
|
---|
1410 | /* The second child runs the copier process: */
|
---|
1411 | if (copier_id == 0)
|
---|
1412 | {
|
---|
1413 | int exitval = 0;
|
---|
1414 |
|
---|
1415 | if (close (inpipefd[PIPE_READ_FD]) ||
|
---|
1416 | main_pipe_copier (pipe_buf, pipe_bufsize, pipe_avail, ifile, inpipefd[PIPE_WRITE_FD]) ||
|
---|
1417 | close (inpipefd[PIPE_WRITE_FD]))
|
---|
1418 | {
|
---|
1419 | XPR(NT "child copier process failed: %s\n", xd3_mainerror (get_errno ()));
|
---|
1420 | exitval = 1;
|
---|
1421 | }
|
---|
1422 |
|
---|
1423 | _exit (exitval);
|
---|
1424 | }
|
---|
1425 |
|
---|
1426 | ext_subprocs[1] = copier_id;
|
---|
1427 |
|
---|
1428 | /* The parent closes both pipes after duplicating the output of compression. */
|
---|
1429 | input_fd = dup (outpipefd[PIPE_READ_FD]);
|
---|
1430 |
|
---|
1431 | if (input_fd < 0 ||
|
---|
1432 | main_file_close (ifile) ||
|
---|
1433 | close (outpipefd[PIPE_READ_FD]) ||
|
---|
1434 | close (outpipefd[PIPE_WRITE_FD]) ||
|
---|
1435 | close (inpipefd[PIPE_READ_FD]) ||
|
---|
1436 | close (inpipefd[PIPE_WRITE_FD]))
|
---|
1437 | {
|
---|
1438 | XPR(NT "dup/close failed: %s\n", xd3_mainerror (ret = get_errno ()));
|
---|
1439 | goto pipe_cleanup;
|
---|
1440 | }
|
---|
1441 |
|
---|
1442 | #if XD3_STDIO
|
---|
1443 | /* Note: fdopen() acquires the fd, closes it when finished. */
|
---|
1444 | if ((ifile->file = fdopen (input_fd, "r")) == NULL)
|
---|
1445 | {
|
---|
1446 | XPR(NT "fdopen failed: %s\n", xd3_mainerror (ret = get_errno ()));
|
---|
1447 | goto pipe_cleanup;
|
---|
1448 | }
|
---|
1449 |
|
---|
1450 | #elif XD3_POSIX
|
---|
1451 | ifile->file = input_fd;
|
---|
1452 | #endif
|
---|
1453 |
|
---|
1454 | ifile->compressor = decomp;
|
---|
1455 |
|
---|
1456 | /* Now the input file is decompressed. */
|
---|
1457 | return main_file_read (ifile, input_buf, input_bufsize, nread, "input decompression failed");
|
---|
1458 |
|
---|
1459 | pipe_cleanup:
|
---|
1460 | close (input_fd);
|
---|
1461 | close (outpipefd[PIPE_READ_FD]);
|
---|
1462 | close (outpipefd[PIPE_WRITE_FD]);
|
---|
1463 | close (inpipefd[PIPE_READ_FD]);
|
---|
1464 | close (inpipefd[PIPE_WRITE_FD]);
|
---|
1465 | return ret;
|
---|
1466 | }
|
---|
1467 |
|
---|
1468 |
|
---|
1469 | /* This routine is called when the first buffer of input data is read by the main program
|
---|
1470 | * (unless input decompression is disabled by command-line option). If it recognizes the
|
---|
1471 | * magic number of a known input type it invokes decompression.
|
---|
1472 | *
|
---|
1473 | * Skips decompression if the decompression type or the file type is RD_NONEXTERNAL.
|
---|
1474 | *
|
---|
1475 | * Behaves exactly like main_file_read, otherwise.
|
---|
1476 | *
|
---|
1477 | * This function uses a separate buffer to read the first small block of input. If a
|
---|
1478 | * compressed input is detected, the separate buffer is passed to the pipe copier. This
|
---|
1479 | * avoids using the same size buffer in both cases. */
|
---|
1480 | static int
|
---|
1481 | main_decompress_input_check (main_file *ifile,
|
---|
1482 | uint8_t *input_buf,
|
---|
1483 | usize_t input_size,
|
---|
1484 | usize_t *nread)
|
---|
1485 | {
|
---|
1486 | int i;
|
---|
1487 | int ret;
|
---|
1488 | uint8_t check_buf[XD3_ALLOCSIZE];
|
---|
1489 | usize_t check_nread;
|
---|
1490 |
|
---|
1491 | if ((ret = main_file_read (ifile, check_buf, min (input_size, XD3_ALLOCSIZE), & check_nread, "input read failed")))
|
---|
1492 | {
|
---|
1493 | return ret;
|
---|
1494 | }
|
---|
1495 |
|
---|
1496 | for (i = 0; i < SIZEOF_ARRAY (extcomp_types); i += 1)
|
---|
1497 | {
|
---|
1498 | const main_extcomp *decomp = & extcomp_types[i];
|
---|
1499 |
|
---|
1500 | if ((check_nread > decomp->magic_size) &&
|
---|
1501 | /* The following expr skips decompression if we are trying to read a VCDIFF
|
---|
1502 | * input and that is the magic number. */
|
---|
1503 | !((decomp->flags & RD_NONEXTERNAL) && (ifile->flags & RD_NONEXTERNAL)) &&
|
---|
1504 | memcmp (check_buf, decomp->magic, decomp->magic_size) == 0)
|
---|
1505 | {
|
---|
1506 | if (! option_quiet)
|
---|
1507 | {
|
---|
1508 | XPR(NT "%s | %s %s\n",
|
---|
1509 | ifile->filename,
|
---|
1510 | decomp->decomp_cmdname,
|
---|
1511 | decomp->decomp_options);
|
---|
1512 | }
|
---|
1513 |
|
---|
1514 | return main_input_decompress_setup (decomp, ifile,
|
---|
1515 | input_buf, input_size,
|
---|
1516 | check_buf, XD3_ALLOCSIZE,
|
---|
1517 | check_nread, nread);
|
---|
1518 | }
|
---|
1519 | }
|
---|
1520 |
|
---|
1521 | /* Now read the rest of the input block. */
|
---|
1522 | (*nread) = 0;
|
---|
1523 |
|
---|
1524 | if (check_nread == XD3_ALLOCSIZE)
|
---|
1525 | {
|
---|
1526 | ret = main_file_read (ifile, input_buf + XD3_ALLOCSIZE,
|
---|
1527 | input_size - XD3_ALLOCSIZE, nread,
|
---|
1528 | "input read failed");
|
---|
1529 | }
|
---|
1530 |
|
---|
1531 | memcpy (input_buf, check_buf, check_nread);
|
---|
1532 |
|
---|
1533 | (*nread) += check_nread;
|
---|
1534 |
|
---|
1535 | return 0;
|
---|
1536 | }
|
---|
1537 |
|
---|
1538 | /* This is called when the source file needs to be decompressed. We fork/exec a
|
---|
1539 | * decompression command with the proper input and output to a temporary file. */
|
---|
1540 | static int
|
---|
1541 | main_decompress_source (main_file *sfile, xd3_source *source)
|
---|
1542 | {
|
---|
1543 | const main_extcomp *decomp = sfile->compressor;
|
---|
1544 | pid_t decomp_id; /* One subproc. */
|
---|
1545 | int input_fd = -1;
|
---|
1546 | int output_fd = -1;
|
---|
1547 | int ret;
|
---|
1548 | char *tmpname = NULL;
|
---|
1549 | char *tmpdir = getenv ("TMPDIR");
|
---|
1550 | static const char tmpl[] = "/xd3src.XXXXXX";
|
---|
1551 |
|
---|
1552 | /* Make a template for mkstmp() */
|
---|
1553 | if (tmpdir == NULL) { tmpdir = "/tmp"; }
|
---|
1554 | if ((tmpname = main_malloc (strlen (tmpdir) + sizeof (tmpl) + 1)) == NULL) { return ENOMEM; }
|
---|
1555 | sprintf (tmpname, "%s%s", tmpdir, tmpl);
|
---|
1556 |
|
---|
1557 | XD3_ASSERT (ext_tmpfile == NULL);
|
---|
1558 | ext_tmpfile = tmpname;
|
---|
1559 |
|
---|
1560 | /* Open the output FD. */
|
---|
1561 | if ((output_fd = mkstemp (tmpname)) < 0)
|
---|
1562 | {
|
---|
1563 | XPR(NT "mkstemp failed: %s: %s", tmpname, xd3_mainerror (ret = get_errno ()));
|
---|
1564 | goto cleanup;
|
---|
1565 | }
|
---|
1566 |
|
---|
1567 | /* Copy the input FD, reset file position. */
|
---|
1568 | XD3_ASSERT (main_file_isopen (sfile));
|
---|
1569 | #if XD3_STDIO
|
---|
1570 | if ((input_fd = dup (fileno (sfile->file))) < 0)
|
---|
1571 | {
|
---|
1572 | XPR(NT "dup failed: %s", xd3_mainerror (ret = get_errno ()));
|
---|
1573 | goto cleanup;
|
---|
1574 | }
|
---|
1575 | main_file_close (sfile);
|
---|
1576 | sfile->file = NULL;
|
---|
1577 | #elif XD3_POSIX
|
---|
1578 | input_fd = sfile->file;
|
---|
1579 | sfile->file = -1;
|
---|
1580 | #endif
|
---|
1581 |
|
---|
1582 | if ((ret = lseek (input_fd, SEEK_SET, 0)) != 0)
|
---|
1583 | {
|
---|
1584 | XPR(NT "lseek failed: : %s", xd3_mainerror (ret = get_errno ()));
|
---|
1585 | goto cleanup;
|
---|
1586 | }
|
---|
1587 |
|
---|
1588 | if ((decomp_id = fork ()) < 0)
|
---|
1589 | {
|
---|
1590 | XPR(NT "fork failed: %s", xd3_mainerror (ret = get_errno ()));
|
---|
1591 | goto cleanup;
|
---|
1592 | }
|
---|
1593 |
|
---|
1594 | /* The child runs the decompression process: */
|
---|
1595 | if (decomp_id == 0)
|
---|
1596 | {
|
---|
1597 | /* Setup pipes: write to the output file, read from the pipe. */
|
---|
1598 | if (dup2 (input_fd, STDIN_FILENO) < 0 ||
|
---|
1599 | dup2 (output_fd, STDOUT_FILENO) < 0 ||
|
---|
1600 | execlp (decomp->decomp_cmdname, decomp->decomp_cmdname, decomp->decomp_options, NULL))
|
---|
1601 | {
|
---|
1602 | XPR(NT "child process %s failed to execute: %s\n",
|
---|
1603 | decomp->decomp_cmdname, xd3_mainerror (get_errno ()));
|
---|
1604 | }
|
---|
1605 |
|
---|
1606 | _exit (127);
|
---|
1607 | }
|
---|
1608 |
|
---|
1609 | close (input_fd);
|
---|
1610 | close (output_fd);
|
---|
1611 | input_fd = -1;
|
---|
1612 | output_fd = -1;
|
---|
1613 |
|
---|
1614 | /* Then wait for completion. */
|
---|
1615 | if ((ret = main_waitpid_check (decomp_id)))
|
---|
1616 | {
|
---|
1617 | goto cleanup;
|
---|
1618 | }
|
---|
1619 |
|
---|
1620 | /* Open/stat the decompressed source file. */
|
---|
1621 | if ((ret = main_file_open (sfile, tmpname, XO_READ))) { goto cleanup; }
|
---|
1622 | if ((ret = main_file_stat (sfile, & source->size, 1))) { goto cleanup; }
|
---|
1623 | return 0;
|
---|
1624 |
|
---|
1625 | cleanup:
|
---|
1626 | close (input_fd);
|
---|
1627 | close (output_fd);
|
---|
1628 | if (tmpname) { free (tmpname); }
|
---|
1629 | ext_tmpfile = NULL;
|
---|
1630 | return ret;
|
---|
1631 | }
|
---|
1632 |
|
---|
1633 | /* Initiate re-compression of the output stream. This is easier than input decompression
|
---|
1634 | * because we know beforehand that the stream will be compressed, whereas the input has
|
---|
1635 | * already been read when we decide it should be decompressed. Thus, it only requires one
|
---|
1636 | * subprocess and one pipe. */
|
---|
1637 | static int
|
---|
1638 | main_recompress_output (main_file *ofile)
|
---|
1639 | {
|
---|
1640 | pid_t recomp_id; /* One subproc. */
|
---|
1641 | int pipefd[2]; /* One pipe. */
|
---|
1642 | int output_fd = -1;
|
---|
1643 | int ret;
|
---|
1644 | const main_extcomp *recomp = ofile->compressor;
|
---|
1645 |
|
---|
1646 | pipefd[0] = pipefd[1] = -1;
|
---|
1647 |
|
---|
1648 | if (pipe (pipefd))
|
---|
1649 | {
|
---|
1650 | XPR(NT "pipe failed: %s\n", xd3_mainerror (ret = get_errno ()));
|
---|
1651 | goto pipe_cleanup;
|
---|
1652 | }
|
---|
1653 |
|
---|
1654 | if ((recomp_id = fork ()) < 0)
|
---|
1655 | {
|
---|
1656 | XPR(NT "fork failed: %s\n", xd3_mainerror (ret = get_errno ()));
|
---|
1657 | goto pipe_cleanup;
|
---|
1658 | }
|
---|
1659 |
|
---|
1660 | /* The child runs the recompression process: */
|
---|
1661 | if (recomp_id == 0)
|
---|
1662 | {
|
---|
1663 | /* Setup pipes: write to the output file, read from the pipe. */
|
---|
1664 | if (dup2 (XFNO (ofile), STDOUT_FILENO) < 0 ||
|
---|
1665 | dup2 (pipefd[PIPE_READ_FD], STDIN_FILENO) < 0 ||
|
---|
1666 | close (pipefd[PIPE_READ_FD]) ||
|
---|
1667 | close (pipefd[PIPE_WRITE_FD]) ||
|
---|
1668 | execlp (recomp->recomp_cmdname, recomp->recomp_cmdname, recomp->recomp_options, NULL))
|
---|
1669 | {
|
---|
1670 | XPR(NT "child process %s failed to execute: %s\n", recomp->recomp_cmdname, xd3_mainerror (get_errno ()));
|
---|
1671 | }
|
---|
1672 |
|
---|
1673 | _exit (127);
|
---|
1674 | }
|
---|
1675 |
|
---|
1676 | ext_subprocs[0] = recomp_id;
|
---|
1677 |
|
---|
1678 | /* The parent closes both pipes after duplicating the output-fd for writing to the
|
---|
1679 | * compression pipe. */
|
---|
1680 | output_fd = dup (pipefd[PIPE_WRITE_FD]);
|
---|
1681 |
|
---|
1682 | if (output_fd < 0 ||
|
---|
1683 | main_file_close (ofile) ||
|
---|
1684 | close (pipefd[PIPE_READ_FD]) ||
|
---|
1685 | close (pipefd[PIPE_WRITE_FD]))
|
---|
1686 | {
|
---|
1687 | XPR(NT "close failed: %s\n", xd3_mainerror (ret = get_errno ()));
|
---|
1688 | goto pipe_cleanup;
|
---|
1689 | }
|
---|
1690 |
|
---|
1691 | #if XD3_STDIO
|
---|
1692 | /* Note: fdopen() acquires the fd, closes it when finished. */
|
---|
1693 | if ((ofile->file = fdopen (output_fd, "w")) == NULL)
|
---|
1694 | {
|
---|
1695 | XPR(NT "fdopen failed: %s\n", xd3_mainerror (ret = get_errno ()));
|
---|
1696 | goto pipe_cleanup;
|
---|
1697 | }
|
---|
1698 |
|
---|
1699 | #elif XD3_POSIX
|
---|
1700 | ofile->file = output_fd;
|
---|
1701 | #endif
|
---|
1702 |
|
---|
1703 | /* Now the output file will be compressed. */
|
---|
1704 | return 0;
|
---|
1705 |
|
---|
1706 | pipe_cleanup:
|
---|
1707 | close (output_fd);
|
---|
1708 | close (pipefd[PIPE_READ_FD]);
|
---|
1709 | close (pipefd[PIPE_WRITE_FD]);
|
---|
1710 | return ret;
|
---|
1711 | }
|
---|
1712 | #endif /* EXTERNAL_COMPRESSION */
|
---|
1713 |
|
---|
1714 | /* Identify the compressor that was used based on its ident string, which is passed in the
|
---|
1715 | * application header. */
|
---|
1716 | static const main_extcomp*
|
---|
1717 | main_ident_compressor (const char *ident)
|
---|
1718 | {
|
---|
1719 | int i;
|
---|
1720 |
|
---|
1721 | for (i = 0; i < SIZEOF_ARRAY (extcomp_types); i += 1)
|
---|
1722 | {
|
---|
1723 | if (strcmp (extcomp_types[i].ident, ident) == 0)
|
---|
1724 | {
|
---|
1725 | return & extcomp_types[i];
|
---|
1726 | }
|
---|
1727 | }
|
---|
1728 |
|
---|
1729 | return NULL;
|
---|
1730 | }
|
---|
1731 |
|
---|
1732 | /* Return the main_extcomp record to use for this identifier, if possible. */
|
---|
1733 | static const main_extcomp*
|
---|
1734 | main_get_compressor (const char *ident)
|
---|
1735 | {
|
---|
1736 | const main_extcomp *ext = main_ident_compressor (ident);
|
---|
1737 |
|
---|
1738 | if (ext == NULL)
|
---|
1739 | {
|
---|
1740 | if (! option_quiet)
|
---|
1741 | {
|
---|
1742 | XPR(NT "warning: cannot recompress output: "
|
---|
1743 | "unrecognized external compression ID: %s\n", ident);
|
---|
1744 | }
|
---|
1745 | return NULL;
|
---|
1746 | }
|
---|
1747 | else if (! EXTERNAL_COMPRESSION)
|
---|
1748 | {
|
---|
1749 | if (! option_quiet)
|
---|
1750 | {
|
---|
1751 | XPR(NT "warning: external support not compiled: "
|
---|
1752 | "original input was compressed: %s\n", ext->recomp_cmdname);
|
---|
1753 | }
|
---|
1754 | return NULL;
|
---|
1755 | }
|
---|
1756 | else
|
---|
1757 | {
|
---|
1758 | return ext;
|
---|
1759 | }
|
---|
1760 | }
|
---|
1761 |
|
---|
1762 | /******************************************************************************************
|
---|
1763 | APPLICATION HEADER
|
---|
1764 | ******************************************************************************************/
|
---|
1765 |
|
---|
1766 | #if XD3_ENCODER
|
---|
1767 | static const char*
|
---|
1768 | main_apphead_string (const char* x)
|
---|
1769 | {
|
---|
1770 | const char *y;
|
---|
1771 |
|
---|
1772 | if (x == NULL) { return ""; }
|
---|
1773 |
|
---|
1774 | if (strcmp (x, "/dev/stdin") == 0 ||
|
---|
1775 | strcmp (x, "/dev/stdout") == 0 ||
|
---|
1776 | strcmp (x, "/dev/stderr") == 0) { return "-"; }
|
---|
1777 |
|
---|
1778 | // TODO: this is not portable
|
---|
1779 | return (y = strrchr (x, '/')) == NULL ? x : y + 1;
|
---|
1780 | }
|
---|
1781 |
|
---|
1782 | static int
|
---|
1783 | main_set_appheader (xd3_stream *stream, main_file *input, main_file *sfile)
|
---|
1784 | {
|
---|
1785 | /* The user may disable the application header. Once the appheader is set, this
|
---|
1786 | * disables setting it again. */
|
---|
1787 | if (appheader_used || ! option_use_appheader) { return 0; }
|
---|
1788 |
|
---|
1789 | /* The user may specify the application header, otherwise format the default header. */
|
---|
1790 | if (option_appheader)
|
---|
1791 | {
|
---|
1792 | appheader_used = option_appheader;
|
---|
1793 | }
|
---|
1794 | else
|
---|
1795 | {
|
---|
1796 | const char *iname;
|
---|
1797 | const char *icomp;
|
---|
1798 | const char *sname;
|
---|
1799 | const char *scomp;
|
---|
1800 | int len;
|
---|
1801 |
|
---|
1802 | iname = main_apphead_string (input->filename);
|
---|
1803 | icomp = (input->compressor == NULL) ? "" : input->compressor->ident;
|
---|
1804 | len = strlen (iname) + strlen (icomp) + 2;
|
---|
1805 |
|
---|
1806 | if (sfile->filename != NULL)
|
---|
1807 | {
|
---|
1808 | sname = main_apphead_string (sfile->filename);
|
---|
1809 | scomp = (sfile->compressor == NULL) ? "" : sfile->compressor->ident;
|
---|
1810 | len += strlen (sname) + strlen (scomp) + 2;
|
---|
1811 | }
|
---|
1812 | else
|
---|
1813 | {
|
---|
1814 | sname = scomp = "";
|
---|
1815 | }
|
---|
1816 |
|
---|
1817 | if ((appheader_used = main_malloc (len)) == NULL)
|
---|
1818 | {
|
---|
1819 | return ENOMEM;
|
---|
1820 | }
|
---|
1821 |
|
---|
1822 | if (sfile->filename == NULL)
|
---|
1823 | {
|
---|
1824 | sprintf ((char*)appheader_used, "%s/%s", iname, icomp);
|
---|
1825 | }
|
---|
1826 | else
|
---|
1827 | {
|
---|
1828 | sprintf ((char*)appheader_used, "%s/%s/%s/%s", iname, icomp, sname, scomp);
|
---|
1829 | }
|
---|
1830 | }
|
---|
1831 |
|
---|
1832 | xd3_set_appheader (stream, appheader_used, strlen ((char*)appheader_used));
|
---|
1833 |
|
---|
1834 | return 0;
|
---|
1835 | }
|
---|
1836 | #endif
|
---|
1837 |
|
---|
1838 | static void
|
---|
1839 | main_get_appheader_params (main_file *file, char **parsed, int output, const char *type,
|
---|
1840 | main_file *other)
|
---|
1841 | {
|
---|
1842 | /* Set the filename if it was not specified. If output, option_stdout (-c) overrides. */
|
---|
1843 | if (file->filename == NULL && ! (output && option_stdout) && strcmp (parsed[0], "-") != 0)
|
---|
1844 | {
|
---|
1845 | file->filename = parsed[0];
|
---|
1846 |
|
---|
1847 | if (other->filename != NULL) {
|
---|
1848 | /* Take directory from the other file, if it has one. */
|
---|
1849 | /* TODO: This results in nonsense names like /dev/foo.tar.gz
|
---|
1850 | * and probably the filename-default logic interferes with
|
---|
1851 | * multi-file operation and the standard file extension?
|
---|
1852 | * Possibly the name header is bad, should be off by default.
|
---|
1853 | * Possibly we just want to remember external/compression
|
---|
1854 | * settings. */
|
---|
1855 | char *last_slash = strrchr(other->filename, '/');
|
---|
1856 |
|
---|
1857 | if (last_slash != NULL) {
|
---|
1858 | int dlen = last_slash - other->filename;
|
---|
1859 |
|
---|
1860 | XD3_ASSERT(file->filename_copy == NULL);
|
---|
1861 | file->filename_copy = main_malloc(dlen + 2 + strlen(file->filename));
|
---|
1862 |
|
---|
1863 | strncpy(file->filename_copy, other->filename, dlen);
|
---|
1864 | file->filename_copy[dlen] = '/';
|
---|
1865 | strcpy(file->filename_copy + dlen + 1, parsed[0]);
|
---|
1866 |
|
---|
1867 | file->filename = file->filename_copy;
|
---|
1868 | }
|
---|
1869 | }
|
---|
1870 |
|
---|
1871 | if (! option_quiet)
|
---|
1872 | {
|
---|
1873 | XPR(NT "using default %s filename: %s\n", type, file->filename);
|
---|
1874 | }
|
---|
1875 | }
|
---|
1876 |
|
---|
1877 | /* Set the compressor, initiate de/recompression later. */
|
---|
1878 | if (file->compressor == NULL && *parsed[1] != 0)
|
---|
1879 | {
|
---|
1880 | file->compressor = main_get_compressor (parsed[1]);
|
---|
1881 | }
|
---|
1882 | }
|
---|
1883 |
|
---|
1884 | static void
|
---|
1885 | main_get_appheader (xd3_stream *stream, main_file *ifile, main_file *output, main_file *sfile)
|
---|
1886 | {
|
---|
1887 | uint8_t *apphead;
|
---|
1888 | usize_t appheadsz;
|
---|
1889 | int ret;
|
---|
1890 |
|
---|
1891 | /* The user may disable the application header. Once the appheader is set, this
|
---|
1892 | * disables setting it again. */
|
---|
1893 | if (! option_use_appheader) { return; }
|
---|
1894 |
|
---|
1895 | ret = xd3_get_appheader (stream, & apphead, & appheadsz);
|
---|
1896 |
|
---|
1897 | /* Ignore failure, it only means we haven't received a header yet. */
|
---|
1898 | if (ret != 0) { return; }
|
---|
1899 |
|
---|
1900 | if (appheadsz > 0)
|
---|
1901 | {
|
---|
1902 | char *start = (char*)apphead;
|
---|
1903 | char *slash;
|
---|
1904 | int place = 0;
|
---|
1905 | char *parsed[4];
|
---|
1906 |
|
---|
1907 | memset (parsed, 0, sizeof (parsed));
|
---|
1908 |
|
---|
1909 | while ((slash = strchr (start, '/')) != NULL)
|
---|
1910 | {
|
---|
1911 | *slash = 0;
|
---|
1912 | parsed[place++] = start;
|
---|
1913 | start = slash + 1;
|
---|
1914 | }
|
---|
1915 |
|
---|
1916 | parsed[place++] = start;
|
---|
1917 |
|
---|
1918 | /* First take the output parameters. */
|
---|
1919 | if (place == 2 || place == 4)
|
---|
1920 | {
|
---|
1921 | main_get_appheader_params (output, parsed, 1, "output", ifile);
|
---|
1922 | }
|
---|
1923 |
|
---|
1924 | /* Then take the source parameters. */
|
---|
1925 | if (place == 4)
|
---|
1926 | {
|
---|
1927 | main_get_appheader_params (sfile, parsed+2, 0, "source", ifile);
|
---|
1928 | }
|
---|
1929 | }
|
---|
1930 |
|
---|
1931 | option_use_appheader = 0;
|
---|
1932 | return;
|
---|
1933 | }
|
---|
1934 |
|
---|
1935 | /******************************************************************************************
|
---|
1936 | Main I/O routines
|
---|
1937 | ******************************************************************************************/
|
---|
1938 |
|
---|
1939 | /* This function acts like the above except it may also try to recognize a compressed
|
---|
1940 | * input when the first buffer of data is read. The EXTERNAL_COMPRESSION code is called
|
---|
1941 | * to search for magic numbers. */
|
---|
1942 | static int
|
---|
1943 | main_read_primary_input (main_file *ifile,
|
---|
1944 | uint8_t *buf,
|
---|
1945 | usize_t size,
|
---|
1946 | usize_t *nread)
|
---|
1947 | {
|
---|
1948 | #if EXTERNAL_COMPRESSION
|
---|
1949 | if (option_decompress_inputs && ifile->flags & RD_FIRST)
|
---|
1950 | {
|
---|
1951 | ifile->flags &= ~RD_FIRST;
|
---|
1952 |
|
---|
1953 | return main_decompress_input_check (ifile, buf, size, nread);
|
---|
1954 | }
|
---|
1955 | #endif
|
---|
1956 |
|
---|
1957 | return main_file_read (ifile, buf, size, nread, "input read failed");
|
---|
1958 | }
|
---|
1959 |
|
---|
1960 | /* This function simply writes the stream output buffer, if there is any. This is used
|
---|
1961 | * for both encode and decode commands. (The VCDIFF tools use main_print_func()). */
|
---|
1962 | static int
|
---|
1963 | main_write_output (xd3_stream* stream, main_file *ofile)
|
---|
1964 | {
|
---|
1965 | int ret;
|
---|
1966 |
|
---|
1967 | if (stream->avail_out > 0 && (ret = main_file_write (ofile, stream->next_out, stream->avail_out, "write failed")))
|
---|
1968 | {
|
---|
1969 | return ret;
|
---|
1970 | }
|
---|
1971 |
|
---|
1972 | return 0;
|
---|
1973 | }
|
---|
1974 |
|
---|
1975 | /* Open the main output file, sets a default file name, initiate recompression. This
|
---|
1976 | * function is expected to fprint any error messages. */
|
---|
1977 | static int
|
---|
1978 | main_open_output (xd3_stream *stream, main_file *ofile)
|
---|
1979 | {
|
---|
1980 | int ret;
|
---|
1981 |
|
---|
1982 | if (ofile->filename == NULL)
|
---|
1983 | {
|
---|
1984 | XSTDOUT_XF (ofile);
|
---|
1985 |
|
---|
1986 | if (option_verbose > 1) { XPR(NT "using standard output: %s\n", ofile->filename); }
|
---|
1987 | }
|
---|
1988 | else
|
---|
1989 | {
|
---|
1990 | /* Stat the file to check for overwrite. */
|
---|
1991 | if (option_force == 0 && main_file_exists (ofile))
|
---|
1992 | {
|
---|
1993 | XPR(NT "to overwrite output file specify -f: %s\n", ofile->filename);
|
---|
1994 | return EEXIST;
|
---|
1995 | }
|
---|
1996 |
|
---|
1997 | if ((ret = main_file_open (ofile, ofile->filename, XO_WRITE)))
|
---|
1998 | {
|
---|
1999 | return ret;
|
---|
2000 | }
|
---|
2001 |
|
---|
2002 | if (option_verbose > 1) { XPR(NT "output file: %s\n", ofile->filename); }
|
---|
2003 | }
|
---|
2004 |
|
---|
2005 | #if EXTERNAL_COMPRESSION
|
---|
2006 | /* Do output recompression. */
|
---|
2007 | if (ofile->compressor != NULL && option_recompress_outputs == 1)
|
---|
2008 | {
|
---|
2009 | if (! option_quiet)
|
---|
2010 | {
|
---|
2011 | XPR(NT "%s %s | %s\n",
|
---|
2012 | ofile->compressor->recomp_cmdname,
|
---|
2013 | ofile->compressor->recomp_options,
|
---|
2014 | ofile->filename);
|
---|
2015 | }
|
---|
2016 |
|
---|
2017 | if ((ret = main_recompress_output (ofile)))
|
---|
2018 | {
|
---|
2019 | return ret;
|
---|
2020 | }
|
---|
2021 | }
|
---|
2022 | #endif
|
---|
2023 |
|
---|
2024 | return 0;
|
---|
2025 | }
|
---|
2026 |
|
---|
2027 | /* This is called at different times for encoding and decoding. The encoder calls it
|
---|
2028 | * immediately, the decoder delays until the application header is received.
|
---|
2029 | * Stream may be NULL, in which case xd3_set_source is not called. */
|
---|
2030 | static int
|
---|
2031 | main_set_source (xd3_stream *stream, int cmd, main_file *sfile, xd3_source *source)
|
---|
2032 | {
|
---|
2033 | int ret = 0, i;
|
---|
2034 | uint8_t *tmp_buf = NULL;
|
---|
2035 |
|
---|
2036 | /* Open it, check for seekability, set required xd3_source fields. */
|
---|
2037 | if (allow_fake_source)
|
---|
2038 | {
|
---|
2039 | sfile->mode = XO_READ;
|
---|
2040 | sfile->realname = sfile->filename;
|
---|
2041 | sfile->nread = 0;
|
---|
2042 | source->size = UINT64_MAX;
|
---|
2043 | }
|
---|
2044 | else
|
---|
2045 | {
|
---|
2046 | if ((ret = main_file_open (sfile, sfile->filename, XO_READ)) ||
|
---|
2047 | (ret = main_file_stat (sfile, & source->size, 1)))
|
---|
2048 | {
|
---|
2049 | goto error;
|
---|
2050 | }
|
---|
2051 | }
|
---|
2052 |
|
---|
2053 | source->name = sfile->filename;
|
---|
2054 | source->ioh = sfile;
|
---|
2055 | source->curblkno = (xoff_t) -1;
|
---|
2056 | source->curblk = NULL;
|
---|
2057 |
|
---|
2058 | #if EXTERNAL_COMPRESSION
|
---|
2059 | if (option_decompress_inputs)
|
---|
2060 | {
|
---|
2061 | /* If encoding, read the header to check for decompression. */
|
---|
2062 | if (IS_ENCODE (cmd))
|
---|
2063 | {
|
---|
2064 | usize_t nread;
|
---|
2065 | tmp_buf = main_malloc(XD3_ALLOCSIZE);
|
---|
2066 |
|
---|
2067 | if ((ret = main_file_read (sfile, tmp_buf, XD3_ALLOCSIZE,
|
---|
2068 | & nread, "source read failed")))
|
---|
2069 | {
|
---|
2070 | goto error;
|
---|
2071 | }
|
---|
2072 |
|
---|
2073 | /* Check known magic numbers. */
|
---|
2074 | for (i = 0; i < SIZEOF_ARRAY (extcomp_types); i += 1)
|
---|
2075 | {
|
---|
2076 | const main_extcomp *decomp = & extcomp_types[i];
|
---|
2077 |
|
---|
2078 | if ((nread > decomp->magic_size) &&
|
---|
2079 | memcmp (tmp_buf, decomp->magic, decomp->magic_size) == 0)
|
---|
2080 | {
|
---|
2081 | sfile->compressor = decomp;
|
---|
2082 | break;
|
---|
2083 | }
|
---|
2084 | }
|
---|
2085 |
|
---|
2086 | if (sfile->compressor == NULL)
|
---|
2087 | {
|
---|
2088 | if (option_verbose > 2)
|
---|
2089 | {
|
---|
2090 | XPR(NT "source block 0 read (not compressed)\n");
|
---|
2091 | }
|
---|
2092 | }
|
---|
2093 | }
|
---|
2094 |
|
---|
2095 | /* In either the encoder or decoder, start decompression. */
|
---|
2096 | if (sfile->compressor)
|
---|
2097 | {
|
---|
2098 | xoff_t osize = source->size;
|
---|
2099 |
|
---|
2100 | if (osize > XD3_NODECOMPRESSSIZE)
|
---|
2101 | {
|
---|
2102 | XPR(NT "source file too large for external decompression: %s: %"Q"u\n",
|
---|
2103 | sfile->filename, osize);
|
---|
2104 | ret = XD3_INTERNAL;
|
---|
2105 | goto error;
|
---|
2106 | }
|
---|
2107 |
|
---|
2108 | if ((ret = main_decompress_source (sfile, source)))
|
---|
2109 | {
|
---|
2110 | goto error;
|
---|
2111 | }
|
---|
2112 |
|
---|
2113 | if (! option_quiet)
|
---|
2114 | {
|
---|
2115 | char s1[32], s2[32];
|
---|
2116 | XPR(NT "%s | %s %s => %s %.1f%% [ %s , %s ]\n",
|
---|
2117 | sfile->filename,
|
---|
2118 | sfile->compressor->decomp_cmdname,
|
---|
2119 | sfile->compressor->decomp_options,
|
---|
2120 | sfile->realname,
|
---|
2121 | 100.0 * source->size / osize,
|
---|
2122 | main_format_bcnt (osize, s1),
|
---|
2123 | main_format_bcnt (source->size, s2));
|
---|
2124 | }
|
---|
2125 | }
|
---|
2126 | }
|
---|
2127 | #endif
|
---|
2128 |
|
---|
2129 | /* At this point we know source->size.
|
---|
2130 | * Source buffer, blksize, LRU init. */
|
---|
2131 | if (source->size < option_srcwinsz)
|
---|
2132 | {
|
---|
2133 | /* Reduce sizes to actual source size, read whole file */
|
---|
2134 | option_srcwinsz = source->size;
|
---|
2135 | source->blksize = source->size;
|
---|
2136 | lru_size = 1;
|
---|
2137 | }
|
---|
2138 | else
|
---|
2139 | {
|
---|
2140 | option_srcwinsz = max(option_srcwinsz, XD3_MINSRCWINSZ);
|
---|
2141 |
|
---|
2142 | source->blksize = (option_srcwinsz / LRU_SIZE);
|
---|
2143 | lru_size = LRU_SIZE;
|
---|
2144 | }
|
---|
2145 |
|
---|
2146 | main_blklru_list_init (& lru_list);
|
---|
2147 | main_blklru_list_init (& lru_free);
|
---|
2148 |
|
---|
2149 | if (option_verbose)
|
---|
2150 | {
|
---|
2151 | static char buf[32];
|
---|
2152 |
|
---|
2153 | XPR(NT "source %s winsize %s size %"Q"u\n",
|
---|
2154 | sfile->filename, main_format_bcnt(option_srcwinsz, buf), source->size);
|
---|
2155 | }
|
---|
2156 |
|
---|
2157 | if (option_verbose > 1)
|
---|
2158 | {
|
---|
2159 | XPR(NT "source block size: %u\n", source->blksize);
|
---|
2160 | }
|
---|
2161 |
|
---|
2162 | if ((lru = main_malloc (sizeof (main_blklru) * lru_size)) == NULL)
|
---|
2163 | {
|
---|
2164 | ret = ENOMEM;
|
---|
2165 | goto error;
|
---|
2166 | }
|
---|
2167 |
|
---|
2168 | for (i = 0; i < lru_size; i += 1)
|
---|
2169 | {
|
---|
2170 | lru[i].blkno = (xoff_t) -1;
|
---|
2171 |
|
---|
2172 | if ((lru[i].blk = main_malloc (source->blksize)) == NULL)
|
---|
2173 | {
|
---|
2174 | ret = ENOMEM;
|
---|
2175 | goto error;
|
---|
2176 | }
|
---|
2177 |
|
---|
2178 | main_blklru_list_push_back (& lru_free, & lru[i]);
|
---|
2179 | }
|
---|
2180 |
|
---|
2181 | if (stream && (ret = xd3_set_source (stream, source)))
|
---|
2182 | {
|
---|
2183 | XPR(NT XD3_LIB_ERRMSG (stream, ret));
|
---|
2184 | goto error;
|
---|
2185 | }
|
---|
2186 |
|
---|
2187 | error:
|
---|
2188 | if (tmp_buf != NULL)
|
---|
2189 | {
|
---|
2190 | main_free (tmp_buf);
|
---|
2191 | }
|
---|
2192 |
|
---|
2193 | return ret;
|
---|
2194 | }
|
---|
2195 |
|
---|
2196 | static void
|
---|
2197 | main_set_winsize (main_file *ifile) {
|
---|
2198 | xoff_t file_size;
|
---|
2199 |
|
---|
2200 | if (main_file_stat (ifile, &file_size, 0) == 0)
|
---|
2201 | {
|
---|
2202 | option_winsize = (usize_t) min(file_size, (xoff_t) option_winsize);
|
---|
2203 | }
|
---|
2204 |
|
---|
2205 | option_winsize = max(option_winsize, XD3_ALLOCSIZE);
|
---|
2206 |
|
---|
2207 | if (option_verbose > 1)
|
---|
2208 | {
|
---|
2209 | XPR(NT "input window size: %u\n", option_winsize);
|
---|
2210 | }
|
---|
2211 | }
|
---|
2212 |
|
---|
2213 | /******************************************************************************************
|
---|
2214 | Source routines
|
---|
2215 | ******************************************************************************************/
|
---|
2216 |
|
---|
2217 | /* This is the callback for reading a block of source. This function is blocking and it
|
---|
2218 | * implements a small LRU.
|
---|
2219 | *
|
---|
2220 | * Note that it is possible for main_input() to handle getblk requests in a non-blocking
|
---|
2221 | * manner. If the callback is NULL then the caller of xd3_*_input() must handle the
|
---|
2222 | * XD3_GETSRCBLK return value and fill the source in the same way. See xd3_getblk for
|
---|
2223 | * details. To see an example of non-blocking getblk, see xdelta-test.h. */
|
---|
2224 | static int
|
---|
2225 | main_getblk_func (xd3_stream *stream,
|
---|
2226 | xd3_source *source,
|
---|
2227 | xoff_t blkno)
|
---|
2228 | {
|
---|
2229 | xoff_t pos = blkno * source->blksize;
|
---|
2230 | main_file *sfile = (main_file*) source->ioh;
|
---|
2231 | main_blklru *blru = NULL;
|
---|
2232 | usize_t onblk = xd3_bytes_on_srcblk (source, blkno);
|
---|
2233 | usize_t nread;
|
---|
2234 | int ret;
|
---|
2235 | int i;
|
---|
2236 |
|
---|
2237 | if (allow_fake_source)
|
---|
2238 | {
|
---|
2239 | source->curblkno = blkno;
|
---|
2240 | source->onblk = onblk;
|
---|
2241 | source->curblk = lru[0].blk;
|
---|
2242 | return 0;
|
---|
2243 | }
|
---|
2244 |
|
---|
2245 | if (do_not_lru)
|
---|
2246 | {
|
---|
2247 | /* Direct lookup assumes sequential scan w/o skipping blocks. */
|
---|
2248 | int idx = blkno % lru_size;
|
---|
2249 | if (lru[idx].blkno == blkno)
|
---|
2250 | {
|
---|
2251 | source->curblkno = blkno;
|
---|
2252 | source->onblk = onblk;
|
---|
2253 | source->curblk = lru[idx].blk;
|
---|
2254 | lru_hits += 1;
|
---|
2255 | return 0;
|
---|
2256 | }
|
---|
2257 |
|
---|
2258 | if (lru[idx].blkno != -1LL &&
|
---|
2259 | lru[idx].blkno != blkno - lru_size)
|
---|
2260 | {
|
---|
2261 | return XD3_TOOFARBACK;
|
---|
2262 | }
|
---|
2263 | }
|
---|
2264 | else
|
---|
2265 | {
|
---|
2266 | /* Sequential search through LRU. */
|
---|
2267 | for (i = 0; i < lru_size; i += 1)
|
---|
2268 | {
|
---|
2269 | if (lru[i].blkno == blkno)
|
---|
2270 | {
|
---|
2271 | main_blklru_list_remove (& lru[i]);
|
---|
2272 | main_blklru_list_push_back (& lru_list, & lru[i]);
|
---|
2273 |
|
---|
2274 | source->curblkno = blkno;
|
---|
2275 | source->onblk = onblk;
|
---|
2276 | source->curblk = lru[i].blk;
|
---|
2277 | lru_hits += 1;
|
---|
2278 | return 0;
|
---|
2279 | }
|
---|
2280 | }
|
---|
2281 | }
|
---|
2282 |
|
---|
2283 | if (! main_blklru_list_empty (& lru_free))
|
---|
2284 | {
|
---|
2285 | blru = main_blklru_list_pop_front (& lru_free);
|
---|
2286 | }
|
---|
2287 | else if (! main_blklru_list_empty (& lru_list))
|
---|
2288 | {
|
---|
2289 | if (do_not_lru) {
|
---|
2290 | blru = & lru[blkno % lru_size];
|
---|
2291 | main_blklru_list_remove(blru);
|
---|
2292 | } else {
|
---|
2293 | blru = main_blklru_list_pop_front (& lru_list);
|
---|
2294 | }
|
---|
2295 | lru_misses += 1;
|
---|
2296 | }
|
---|
2297 |
|
---|
2298 | lru_filled += 1;
|
---|
2299 |
|
---|
2300 | if ((ret = main_file_seek (sfile, pos)))
|
---|
2301 | {
|
---|
2302 | return ret;
|
---|
2303 | }
|
---|
2304 |
|
---|
2305 | if ((ret = main_file_read (sfile, (uint8_t*) blru->blk, source->blksize,
|
---|
2306 | & nread, "source read failed")))
|
---|
2307 | {
|
---|
2308 | return ret;
|
---|
2309 | }
|
---|
2310 |
|
---|
2311 | if (nread != onblk)
|
---|
2312 | {
|
---|
2313 | XPR(NT "source file size change: %s\n", sfile->filename);
|
---|
2314 | return XD3_INTERNAL;
|
---|
2315 | }
|
---|
2316 |
|
---|
2317 | main_blklru_list_push_back (& lru_list, blru);
|
---|
2318 |
|
---|
2319 | if (option_verbose > 3)
|
---|
2320 | {
|
---|
2321 | if (blru->blkno != -1LL)
|
---|
2322 | {
|
---|
2323 | XPR(NT "source block %"Q"u ejects %"Q"u (lru_hits=%u, lru_misses=%u, lru_filled=%u)\n",
|
---|
2324 | blkno, blru->blkno, lru_hits, lru_misses, lru_filled);
|
---|
2325 | }
|
---|
2326 | else
|
---|
2327 | {
|
---|
2328 | XPR(NT "source block %"Q"u read (lru_hits=%u, lru_misses=%u, lru_filled=%u)\n",
|
---|
2329 | blkno, lru_hits, lru_misses, lru_filled);
|
---|
2330 | }
|
---|
2331 | }
|
---|
2332 |
|
---|
2333 | blru->blkno = blkno;
|
---|
2334 | source->curblk = blru->blk;
|
---|
2335 | source->curblkno = blkno;
|
---|
2336 | source->onblk = onblk;
|
---|
2337 |
|
---|
2338 | return 0;
|
---|
2339 | }
|
---|
2340 |
|
---|
2341 | /******************************************************************************************
|
---|
2342 | Main routines
|
---|
2343 | ******************************************************************************************/
|
---|
2344 |
|
---|
2345 | /* This is a generic input function. It calls the xd3_encode_input or xd3_decode_input
|
---|
2346 | * functions and makes calls to the various input handling routines above, which
|
---|
2347 | * coordinate external decompression.
|
---|
2348 | */
|
---|
2349 | static int
|
---|
2350 | main_input (xd3_cmd cmd,
|
---|
2351 | main_file *ifile,
|
---|
2352 | main_file *ofile,
|
---|
2353 | main_file *sfile)
|
---|
2354 | {
|
---|
2355 | int ret;
|
---|
2356 | xd3_stream stream;
|
---|
2357 | usize_t nread;
|
---|
2358 | int stream_flags = 0;
|
---|
2359 | xd3_config config;
|
---|
2360 | xd3_source source;
|
---|
2361 | xoff_t last_total_in = 0;
|
---|
2362 | xoff_t last_total_out = 0;
|
---|
2363 | long start_time;
|
---|
2364 | int stdout_only = 0;
|
---|
2365 |
|
---|
2366 | int (*input_func) (xd3_stream*);
|
---|
2367 | int (*output_func) (xd3_stream*, main_file *);
|
---|
2368 |
|
---|
2369 | memset (& source, 0, sizeof (source));
|
---|
2370 | memset (& config, 0, sizeof (config));
|
---|
2371 |
|
---|
2372 | config.alloc = main_alloc;
|
---|
2373 | config.freef = main_free1;
|
---|
2374 | config.sec_data.ngroups = 1;
|
---|
2375 | config.sec_addr.ngroups = 1;
|
---|
2376 | config.sec_inst.ngroups = 1;
|
---|
2377 | config.iopt_size = option_iopt_size;
|
---|
2378 | config.sprevsz = option_sprevsz;
|
---|
2379 |
|
---|
2380 | do_not_lru = 0;
|
---|
2381 |
|
---|
2382 | start_time = get_millisecs_now ();
|
---|
2383 |
|
---|
2384 | /* main_input setup. */
|
---|
2385 | switch ((int) cmd)
|
---|
2386 | {
|
---|
2387 | #if VCDIFF_TOOLS
|
---|
2388 | if (1) { case CMD_PRINTHDR: stream_flags = XD3_JUST_HDR; }
|
---|
2389 | else if (1) { case CMD_PRINTHDRS: stream_flags = XD3_SKIP_WINDOW; }
|
---|
2390 | else { case CMD_PRINTDELTA: stream_flags = XD3_SKIP_EMIT; }
|
---|
2391 | ifile->flags |= RD_NONEXTERNAL;
|
---|
2392 | input_func = xd3_decode_input;
|
---|
2393 | output_func = main_print_func;
|
---|
2394 | stream_flags |= XD3_ADLER32_NOVER;
|
---|
2395 | stdout_only = 1;
|
---|
2396 | break;
|
---|
2397 | #endif
|
---|
2398 | #if XD3_ENCODER
|
---|
2399 | case CMD_ENCODE:
|
---|
2400 | do_not_lru = 1;
|
---|
2401 | input_func = xd3_encode_input;
|
---|
2402 | output_func = main_write_output;
|
---|
2403 |
|
---|
2404 | if (option_use_checksum) { stream_flags |= XD3_ADLER32; }
|
---|
2405 | if (option_use_secondary)
|
---|
2406 | {
|
---|
2407 | /* The default secondary compressor is DJW, if it's compiled, being used, etc. */
|
---|
2408 | if (option_secondary == NULL)
|
---|
2409 | {
|
---|
2410 | if (SECONDARY_DJW) { stream_flags |= XD3_SEC_DJW; }
|
---|
2411 | }
|
---|
2412 | else
|
---|
2413 | {
|
---|
2414 | if (strcmp (option_secondary, "fgk") == 0 && SECONDARY_FGK)
|
---|
2415 | {
|
---|
2416 | stream_flags |= XD3_SEC_FGK;
|
---|
2417 | }
|
---|
2418 | else if (strcmp (option_secondary, "djw") == 0 && SECONDARY_DJW)
|
---|
2419 | {
|
---|
2420 | stream_flags |= XD3_SEC_DJW;
|
---|
2421 | }
|
---|
2422 | else
|
---|
2423 | {
|
---|
2424 | XPR(NT "unrecognized secondary compressor type: %s\n", option_secondary);
|
---|
2425 | return EXIT_FAILURE;
|
---|
2426 | }
|
---|
2427 | }
|
---|
2428 | }
|
---|
2429 | if (option_no_compress) { stream_flags |= XD3_NOCOMPRESS; }
|
---|
2430 | if (option_use_altcodetable) { stream_flags |= XD3_ALT_CODE_TABLE; }
|
---|
2431 | if (option_smatch_config)
|
---|
2432 | {
|
---|
2433 | char *s = option_smatch_config, *e;
|
---|
2434 | int values[XD3_SOFTCFG_VARCNT];
|
---|
2435 | int got;
|
---|
2436 |
|
---|
2437 | config.smatch_cfg = XD3_SMATCH_SOFT;
|
---|
2438 |
|
---|
2439 | for (got = 0; got < XD3_SOFTCFG_VARCNT; got += 1, s = e + 1)
|
---|
2440 | {
|
---|
2441 | values[got] = strtol (s, &e, 10);
|
---|
2442 |
|
---|
2443 | if ((values[got] < 0) ||
|
---|
2444 | (e == s) ||
|
---|
2445 | (got < XD3_SOFTCFG_VARCNT-1 && *e == 0) ||
|
---|
2446 | (got == XD3_SOFTCFG_VARCNT-1 && *e != 0))
|
---|
2447 | {
|
---|
2448 | XPR(NT "invalid string match specifier (-C) %d: %s\n",
|
---|
2449 | got, s);
|
---|
2450 | return EXIT_FAILURE;
|
---|
2451 | }
|
---|
2452 | }
|
---|
2453 |
|
---|
2454 | config.smatcher_soft.large_look = values[0];
|
---|
2455 | config.smatcher_soft.large_step = values[1];
|
---|
2456 | config.smatcher_soft.small_look = values[2];
|
---|
2457 | config.smatcher_soft.small_chain = values[3];
|
---|
2458 | config.smatcher_soft.small_lchain = values[4];
|
---|
2459 | config.smatcher_soft.max_lazy = values[5];
|
---|
2460 | config.smatcher_soft.long_enough = values[6];
|
---|
2461 | }
|
---|
2462 | else
|
---|
2463 | {
|
---|
2464 | if (option_verbose > 1)
|
---|
2465 | {
|
---|
2466 | XPR(NT "compression level: %d\n", option_level);
|
---|
2467 | }
|
---|
2468 | if (option_level == 0)
|
---|
2469 | {
|
---|
2470 | stream_flags |= XD3_NOCOMPRESS;
|
---|
2471 | config.smatch_cfg = XD3_SMATCH_FASTEST;
|
---|
2472 | }
|
---|
2473 | else if (option_level == 1) { config.smatch_cfg = XD3_SMATCH_FASTEST; }
|
---|
2474 | else if (option_level <= 5) { config.smatch_cfg = XD3_SMATCH_FAST; }
|
---|
2475 | else if (option_level == 6) { config.smatch_cfg = XD3_SMATCH_DEFAULT; }
|
---|
2476 | else { config.smatch_cfg = XD3_SMATCH_SLOW; }
|
---|
2477 | }
|
---|
2478 | break;
|
---|
2479 | #endif
|
---|
2480 | case CMD_DECODE:
|
---|
2481 | if (option_use_checksum == 0) { stream_flags |= XD3_ADLER32_NOVER; }
|
---|
2482 | stream_flags = 0;
|
---|
2483 | ifile->flags |= RD_NONEXTERNAL;
|
---|
2484 | input_func = xd3_decode_input;
|
---|
2485 | output_func = main_write_output;
|
---|
2486 | break;
|
---|
2487 | default:
|
---|
2488 | XPR(NT "internal error\n");
|
---|
2489 | return EXIT_FAILURE;
|
---|
2490 | }
|
---|
2491 |
|
---|
2492 | main_set_winsize (ifile);
|
---|
2493 |
|
---|
2494 | if ((main_bdata = main_malloc (option_winsize)) == NULL)
|
---|
2495 | {
|
---|
2496 | return EXIT_FAILURE;
|
---|
2497 | }
|
---|
2498 |
|
---|
2499 | if (IS_ENCODE (cmd))
|
---|
2500 | {
|
---|
2501 | /* When encoding, open the source file, possibly decompress it. The decoder delays
|
---|
2502 | * this step until XD3_GOTHEADER. */
|
---|
2503 | if (sfile->filename != NULL && (ret = main_set_source (NULL, cmd, sfile, & source)))
|
---|
2504 | {
|
---|
2505 | return EXIT_FAILURE;
|
---|
2506 | }
|
---|
2507 | }
|
---|
2508 |
|
---|
2509 | config.winsize = option_winsize;
|
---|
2510 | config.srcwin_maxsz = option_srcwinsz;
|
---|
2511 | config.getblk = main_getblk_func;
|
---|
2512 | config.flags = stream_flags;
|
---|
2513 |
|
---|
2514 | if ((ret = xd3_config_stream (& stream, & config)))
|
---|
2515 | {
|
---|
2516 | XPR(NT XD3_LIB_ERRMSG (& stream, ret));
|
---|
2517 | return EXIT_FAILURE;
|
---|
2518 | }
|
---|
2519 |
|
---|
2520 | if (IS_ENCODE (cmd) && sfile->filename != NULL &&
|
---|
2521 | (ret = xd3_set_source (& stream, & source)))
|
---|
2522 | {
|
---|
2523 | XPR(NT XD3_LIB_ERRMSG (& stream, ret));
|
---|
2524 | return EXIT_FAILURE;
|
---|
2525 | }
|
---|
2526 |
|
---|
2527 | /* This times each window. */
|
---|
2528 | get_millisecs_since ();
|
---|
2529 |
|
---|
2530 | /* Main input loop. */
|
---|
2531 | do
|
---|
2532 | {
|
---|
2533 | xoff_t input_offset;
|
---|
2534 | xoff_t input_remain;
|
---|
2535 | usize_t try_read;
|
---|
2536 |
|
---|
2537 | input_offset = ifile->nread;
|
---|
2538 |
|
---|
2539 | input_remain = XOFF_T_MAX - input_offset;
|
---|
2540 |
|
---|
2541 | try_read = (usize_t) min ((xoff_t) config.winsize, input_remain);
|
---|
2542 |
|
---|
2543 | if ((ret = main_read_primary_input (ifile, main_bdata, try_read, & nread)))
|
---|
2544 | {
|
---|
2545 | return EXIT_FAILURE;
|
---|
2546 | }
|
---|
2547 |
|
---|
2548 | /* If we've reached EOF tell the stream to flush. */
|
---|
2549 | if (nread < try_read)
|
---|
2550 | {
|
---|
2551 | stream_flags |= XD3_FLUSH;
|
---|
2552 | xd3_set_flags (& stream, stream_flags);
|
---|
2553 | }
|
---|
2554 |
|
---|
2555 | #if XD3_ENCODER
|
---|
2556 | /* After the first main_read_primary_input completes, we know all the information
|
---|
2557 | * needed to encode the application header. */
|
---|
2558 | if (cmd == CMD_ENCODE && (ret = main_set_appheader (& stream, ifile, sfile)))
|
---|
2559 | {
|
---|
2560 | return EXIT_FAILURE;
|
---|
2561 | }
|
---|
2562 | #endif
|
---|
2563 | xd3_avail_input (& stream, main_bdata, nread);
|
---|
2564 |
|
---|
2565 | /* If we read zero bytes after encoding at least one window... */
|
---|
2566 | if (nread == 0 && stream.current_window > 0) {
|
---|
2567 | break;
|
---|
2568 | }
|
---|
2569 |
|
---|
2570 | again:
|
---|
2571 | ret = input_func (& stream);
|
---|
2572 |
|
---|
2573 | switch (ret)
|
---|
2574 | {
|
---|
2575 | case XD3_INPUT:
|
---|
2576 | continue;
|
---|
2577 |
|
---|
2578 | case XD3_GOTHEADER:
|
---|
2579 | {
|
---|
2580 | XD3_ASSERT (stream.current_window == 0);
|
---|
2581 |
|
---|
2582 | /* Need to process the appheader as soon as possible. It may contain a
|
---|
2583 | * suggested default filename/decompression routine for the ofile, and it may
|
---|
2584 | * contain default/decompression routine for the sources. */
|
---|
2585 | if (cmd == CMD_DECODE)
|
---|
2586 | {
|
---|
2587 | int have_src = sfile->filename != NULL;
|
---|
2588 | int need_src = xd3_decoder_needs_source (& stream);
|
---|
2589 | int recv_src;
|
---|
2590 |
|
---|
2591 | /* May need to set the sfile->filename if none was given. */
|
---|
2592 | main_get_appheader (& stream, ifile, ofile, sfile);
|
---|
2593 |
|
---|
2594 | recv_src = sfile->filename != NULL;
|
---|
2595 |
|
---|
2596 | /* Check if the user expected a source to be required although it was not. */
|
---|
2597 | if (have_src && ! need_src && ! option_quiet)
|
---|
2598 | {
|
---|
2599 | XPR(NT "warning: output window %"Q"u does not copy source\n", stream.current_window);
|
---|
2600 | }
|
---|
2601 |
|
---|
2602 | /* Check if we have no source name and need one. */
|
---|
2603 | /* TODO: this doesn't fire due to cpyblocks_ calculation check? */
|
---|
2604 | if (need_src && ! recv_src)
|
---|
2605 | {
|
---|
2606 | XPR(NT "input requires a source file, use -s\n");
|
---|
2607 | return EXIT_FAILURE;
|
---|
2608 | }
|
---|
2609 |
|
---|
2610 | /* Now open the source file. */
|
---|
2611 | if (need_src && (ret = main_set_source (& stream, cmd, sfile, & source)))
|
---|
2612 | {
|
---|
2613 | return EXIT_FAILURE;
|
---|
2614 | }
|
---|
2615 | }
|
---|
2616 | else if (cmd == CMD_PRINTHDR ||
|
---|
2617 | cmd == CMD_PRINTHDRS ||
|
---|
2618 | cmd == CMD_PRINTDELTA)
|
---|
2619 | {
|
---|
2620 | if (xd3_decoder_needs_source (& stream) && sfile->filename == NULL)
|
---|
2621 | {
|
---|
2622 | allow_fake_source = 1;
|
---|
2623 | sfile->filename = "<placeholder>";
|
---|
2624 | main_set_source (& stream, cmd, sfile, & source);
|
---|
2625 | }
|
---|
2626 | }
|
---|
2627 | }
|
---|
2628 | /* FALLTHROUGH */
|
---|
2629 | case XD3_WINSTART:
|
---|
2630 | {
|
---|
2631 | /* e.g., set or unset XD3_SKIP_WINDOW. */
|
---|
2632 | /* xd3_set_flags (& stream, stream_flags); */
|
---|
2633 | goto again;
|
---|
2634 | }
|
---|
2635 |
|
---|
2636 | case XD3_OUTPUT:
|
---|
2637 | {
|
---|
2638 | if (option_no_output == 0)
|
---|
2639 | {
|
---|
2640 | /* Defer opening the output file until the stream produces its first
|
---|
2641 | * output for both encoder and decoder, this way we delay long enough for
|
---|
2642 | * the decoder to receive the application header. (Or longer if there are
|
---|
2643 | * skipped windows, but I can't think of any reason not to delay open.) */
|
---|
2644 |
|
---|
2645 | if (! main_file_isopen (ofile) && (ret = main_open_output (& stream, ofile)) != 0)
|
---|
2646 | {
|
---|
2647 | return EXIT_FAILURE;
|
---|
2648 | }
|
---|
2649 | if ((ret = output_func (& stream, ofile)) && (ret != PRINTHDR_SPECIAL))
|
---|
2650 | {
|
---|
2651 | return EXIT_FAILURE;
|
---|
2652 | }
|
---|
2653 | if (ret == PRINTHDR_SPECIAL)
|
---|
2654 | {
|
---|
2655 | xd3_abort_stream (& stream);
|
---|
2656 | ret = EXIT_SUCCESS;
|
---|
2657 | goto done;
|
---|
2658 | }
|
---|
2659 | ret = 0;
|
---|
2660 | }
|
---|
2661 |
|
---|
2662 | xd3_consume_output (& stream);
|
---|
2663 | goto again;
|
---|
2664 | }
|
---|
2665 |
|
---|
2666 | case XD3_WINFINISH:
|
---|
2667 | {
|
---|
2668 | if (IS_ENCODE (cmd) || cmd == CMD_DECODE)
|
---|
2669 | {
|
---|
2670 | if (! option_quiet && IS_ENCODE (cmd) && main_file_isopen (sfile))
|
---|
2671 | {
|
---|
2672 | /* Warn when no source copies are found */
|
---|
2673 | if (! xd3_encoder_used_source (& stream))
|
---|
2674 | {
|
---|
2675 | XPR(NT "warning: input window %"Q"u..%"Q"u has no source copies\n",
|
---|
2676 | stream.current_window * option_winsize,
|
---|
2677 | (stream.current_window+1) * option_winsize);
|
---|
2678 | }
|
---|
2679 |
|
---|
2680 | /* Limited instruction buffer size affects source copies */
|
---|
2681 | if (option_verbose > 1 && stream.i_slots_used > stream.iopt_size)
|
---|
2682 | {
|
---|
2683 | XPR(NT "warning: input position %"Q"u overflowed instruction buffer, "
|
---|
2684 | "needed %u (vs. %u), consider raising -I\n",
|
---|
2685 | stream.current_window * option_winsize,
|
---|
2686 | stream.i_slots_used, stream.iopt_size);
|
---|
2687 | }
|
---|
2688 | }
|
---|
2689 |
|
---|
2690 | if (option_verbose)
|
---|
2691 | {
|
---|
2692 | char rrateavg[32], wrateavg[32], tm[32];
|
---|
2693 | char rdb[32], wdb[32];
|
---|
2694 | char trdb[32], twdb[32];
|
---|
2695 | long millis = get_millisecs_since ();
|
---|
2696 | usize_t this_read = (usize_t)(stream.total_in - last_total_in);
|
---|
2697 | usize_t this_write = (usize_t)(stream.total_out - last_total_out);
|
---|
2698 | last_total_in = stream.total_in;
|
---|
2699 | last_total_out = stream.total_out;
|
---|
2700 |
|
---|
2701 | if (option_verbose > 1)
|
---|
2702 | {
|
---|
2703 | XPR(NT "%"Q"u: in %s (%s): out %s (%s): total in %s: out %s: %s\n",
|
---|
2704 | stream.current_window,
|
---|
2705 | main_format_bcnt (this_read, rdb),
|
---|
2706 | main_format_rate (this_read, millis, rrateavg),
|
---|
2707 | main_format_bcnt (this_write, wdb),
|
---|
2708 | main_format_rate (this_write, millis, wrateavg),
|
---|
2709 | main_format_bcnt (stream.total_in, trdb),
|
---|
2710 | main_format_bcnt (stream.total_out, twdb),
|
---|
2711 | main_format_millis (millis, tm));
|
---|
2712 | }
|
---|
2713 | else
|
---|
2714 | {
|
---|
2715 | XPR(NT "%"Q"u: in %s: out %s: total in %s: out %s: %s\n",
|
---|
2716 | stream.current_window,
|
---|
2717 | main_format_bcnt (this_read, rdb),
|
---|
2718 | main_format_bcnt (this_write, wdb),
|
---|
2719 | main_format_bcnt (stream.total_in, trdb),
|
---|
2720 | main_format_bcnt (stream.total_out, twdb),
|
---|
2721 | main_format_millis (millis, tm));
|
---|
2722 | }
|
---|
2723 | }
|
---|
2724 | }
|
---|
2725 | goto again;
|
---|
2726 | }
|
---|
2727 |
|
---|
2728 | default:
|
---|
2729 | /* input_func() error */
|
---|
2730 | XPR(NT XD3_LIB_ERRMSG (& stream, ret));
|
---|
2731 | return EXIT_FAILURE;
|
---|
2732 | }
|
---|
2733 | }
|
---|
2734 | while (nread == config.winsize);
|
---|
2735 | done:
|
---|
2736 | /* Close the inputs. (ifile must be open, sfile may be open) */
|
---|
2737 | main_file_close (ifile);
|
---|
2738 | main_file_close (sfile);
|
---|
2739 |
|
---|
2740 | /* If output file is not open yet because of delayed-open, it means we never encountered
|
---|
2741 | * a window in the delta, but it could have had a VCDIFF header? TODO: solve this
|
---|
2742 | * elsewhere. For now, it prints "nothing to output" below, but the check doesn't
|
---|
2743 | * happen in case of option_no_output. */
|
---|
2744 | if (! option_no_output)
|
---|
2745 | {
|
---|
2746 | if (!stdout_only && ! main_file_isopen (ofile))
|
---|
2747 | {
|
---|
2748 | XPR(NT "nothing to output: %s\n", ifile->filename);
|
---|
2749 | return EXIT_FAILURE;
|
---|
2750 | }
|
---|
2751 |
|
---|
2752 | /* Have to close the output before calling main_external_compression_finish, or else it hangs. */
|
---|
2753 | if (main_file_close (ofile) != 0)
|
---|
2754 | {
|
---|
2755 | return EXIT_FAILURE;
|
---|
2756 | }
|
---|
2757 | }
|
---|
2758 |
|
---|
2759 | #if EXTERNAL_COMPRESSION
|
---|
2760 | if ((ret = main_external_compression_finish ()))
|
---|
2761 | {
|
---|
2762 | XPR(NT "external compression commands failed\n");
|
---|
2763 | return EXIT_FAILURE;
|
---|
2764 | }
|
---|
2765 | #endif
|
---|
2766 |
|
---|
2767 | if ((ret = xd3_close_stream (& stream)))
|
---|
2768 | {
|
---|
2769 | XPR(NT XD3_LIB_ERRMSG (& stream, ret));
|
---|
2770 | return EXIT_FAILURE;
|
---|
2771 | }
|
---|
2772 |
|
---|
2773 | if (option_verbose > 1)
|
---|
2774 | {
|
---|
2775 | XPR(NT "scanner configuration: %s\n", stream.smatcher.name);
|
---|
2776 | XPR(NT "target hash table size: %u\n", stream.small_hash.size);
|
---|
2777 | if (sfile->filename != NULL)
|
---|
2778 | {
|
---|
2779 | XPR(NT "source hash table size: %u\n", stream.large_hash.size);
|
---|
2780 | }
|
---|
2781 | }
|
---|
2782 |
|
---|
2783 | if (option_verbose > 2)
|
---|
2784 | {
|
---|
2785 | XPR(NT "source copies: %"Q"u (%"Q"u bytes)\n", stream.n_scpy, stream.l_scpy);
|
---|
2786 | XPR(NT "target copies: %"Q"u (%"Q"u bytes)\n", stream.n_tcpy, stream.l_tcpy);
|
---|
2787 | XPR(NT "adds: %"Q"u (%"Q"u bytes)\n", stream.n_add, stream.l_add);
|
---|
2788 | XPR(NT "runs: %"Q"u (%"Q"u bytes)\n", stream.n_run, stream.l_run);
|
---|
2789 | }
|
---|
2790 |
|
---|
2791 | xd3_free_stream (& stream);
|
---|
2792 |
|
---|
2793 | if (option_verbose)
|
---|
2794 | {
|
---|
2795 | char tm[32];
|
---|
2796 | long end_time = get_millisecs_now ();
|
---|
2797 | XPR(NT "finished in %s; input %"Q"u output %"Q"u bytes (%0.2f%%)\n",
|
---|
2798 | main_format_millis (end_time - start_time, tm),
|
---|
2799 | ifile->nread, ofile->nwrite,
|
---|
2800 | 100.0 * ofile->nwrite / ifile->nread);
|
---|
2801 | }
|
---|
2802 |
|
---|
2803 | return EXIT_SUCCESS;
|
---|
2804 | }
|
---|
2805 |
|
---|
2806 | /* free memory before exit, reset single-use variables. */
|
---|
2807 | static void
|
---|
2808 | main_cleanup (void)
|
---|
2809 | {
|
---|
2810 | int i;
|
---|
2811 |
|
---|
2812 | if (appheader_used != NULL &&
|
---|
2813 | appheader_used != option_appheader)
|
---|
2814 | {
|
---|
2815 | main_free (appheader_used);
|
---|
2816 | appheader_used = NULL;
|
---|
2817 | }
|
---|
2818 |
|
---|
2819 | main_free (main_bdata);
|
---|
2820 | main_bdata = NULL;
|
---|
2821 |
|
---|
2822 | #if EXTERNAL_COMPRESSION
|
---|
2823 | main_free (ext_tmpfile);
|
---|
2824 | ext_tmpfile = NULL;
|
---|
2825 | #endif
|
---|
2826 |
|
---|
2827 | for (i = 0; lru && i < lru_size; i += 1)
|
---|
2828 | {
|
---|
2829 | main_free (lru[i].blk);
|
---|
2830 | }
|
---|
2831 |
|
---|
2832 | main_free (lru);
|
---|
2833 | lru = NULL;
|
---|
2834 |
|
---|
2835 | lru_hits = 0;
|
---|
2836 | lru_misses = 0;
|
---|
2837 | lru_filled = 0;
|
---|
2838 |
|
---|
2839 | XD3_ASSERT (main_mallocs == 0);
|
---|
2840 | }
|
---|
2841 |
|
---|
2842 | static void
|
---|
2843 | setup_environment (int argc,
|
---|
2844 | char **argv,
|
---|
2845 | int *argc_out,
|
---|
2846 | char ***argv_out,
|
---|
2847 | char ***argv_free,
|
---|
2848 | char **env_free)
|
---|
2849 | {
|
---|
2850 | int n, i, i0;
|
---|
2851 | char *p, *v = getenv("XDELTA");
|
---|
2852 | if (v == NULL) {
|
---|
2853 | (*argc_out) = argc;
|
---|
2854 | (*argv_out) = argv;
|
---|
2855 | (*argv_free) = NULL;
|
---|
2856 | (*env_free) = NULL;
|
---|
2857 | return;
|
---|
2858 | }
|
---|
2859 |
|
---|
2860 | (*env_free) = main_malloc(strlen(v) + 1);
|
---|
2861 | strcpy(*env_free, v);
|
---|
2862 |
|
---|
2863 | /* Space needed for extra args, at least # of spaces */
|
---|
2864 | n = argc + 1;
|
---|
2865 | for (p = *env_free; *p != 0; ) {
|
---|
2866 | if (*p++ == ' ') {
|
---|
2867 | n++;
|
---|
2868 | }
|
---|
2869 | }
|
---|
2870 |
|
---|
2871 | (*argv_free) = main_malloc(sizeof(char*) * (n + 1));
|
---|
2872 | (*argv_out) = (*argv_free);
|
---|
2873 | (*argv_out)[0] = argv[0];
|
---|
2874 | (*argv_out)[n] = NULL;
|
---|
2875 |
|
---|
2876 | i = 1;
|
---|
2877 | for (p = *env_free; *p != 0; ) {
|
---|
2878 | (*argv_out)[i++] = p;
|
---|
2879 | while (*p != ' ' && *p != 0) {
|
---|
2880 | p++;
|
---|
2881 | }
|
---|
2882 | while (*p == ' ') {
|
---|
2883 | *p++ = 0;
|
---|
2884 | }
|
---|
2885 | }
|
---|
2886 |
|
---|
2887 | for (i0 = 1; i0 < argc; i0++) {
|
---|
2888 | (*argv_out)[i++] = argv[i0];
|
---|
2889 | }
|
---|
2890 |
|
---|
2891 | /* Counting spaces is an upper bound, argv stays NULL terminated. */
|
---|
2892 | (*argc_out) = i;
|
---|
2893 | while (i <= n) {
|
---|
2894 | (*argv_out)[i++] = NULL;
|
---|
2895 | }
|
---|
2896 | }
|
---|
2897 |
|
---|
2898 | int
|
---|
2899 | #if PYTHON_MODULE || SWIG_MODULE
|
---|
2900 | xd3_main_cmdline (int argc, char **argv)
|
---|
2901 | #else
|
---|
2902 | main (int argc, char **argv)
|
---|
2903 | #endif
|
---|
2904 | {
|
---|
2905 | xd3_cmd cmd;
|
---|
2906 | main_file ifile;
|
---|
2907 | main_file ofile;
|
---|
2908 | main_file sfile;
|
---|
2909 | static const char *flags = "0123456789cdefhnqvDJNORTVs:B:C:E:F:I:L:O:M:P:W:A::S::";
|
---|
2910 | int my_optind;
|
---|
2911 | char *my_optarg;
|
---|
2912 | char *my_optstr;
|
---|
2913 | char *sfilename;
|
---|
2914 | int env_argc;
|
---|
2915 | char **env_argv;
|
---|
2916 | char **free_argv; /* malloc() in setup_environment() */
|
---|
2917 | char *free_value; /* malloc() in setup_environment() */
|
---|
2918 | int ret;
|
---|
2919 |
|
---|
2920 | #ifdef _WIN32
|
---|
2921 | GetStartupInfo(&winStartupInfo);
|
---|
2922 | setvbuf(stderr, NULL, _IONBF, 0); /* Do not buffer stderr */
|
---|
2923 | #endif
|
---|
2924 |
|
---|
2925 | main_file_init (& ifile);
|
---|
2926 | main_file_init (& ofile);
|
---|
2927 | main_file_init (& sfile);
|
---|
2928 |
|
---|
2929 | reset_defaults();
|
---|
2930 |
|
---|
2931 | free_argv = NULL;
|
---|
2932 | free_value = NULL;
|
---|
2933 | setup_environment(argc, argv, &env_argc, &env_argv, &free_argv, &free_value);
|
---|
2934 | cmd = CMD_NONE;
|
---|
2935 | sfilename = NULL;
|
---|
2936 | my_optind = 1;
|
---|
2937 | argv = env_argv;
|
---|
2938 | argc = env_argc;
|
---|
2939 | program_name = env_argv[0];
|
---|
2940 | extcomp_types[0].recomp_cmdname = program_name;
|
---|
2941 | extcomp_types[0].decomp_cmdname = program_name;
|
---|
2942 | takearg:
|
---|
2943 | my_optarg = NULL;
|
---|
2944 | my_optstr = argv[my_optind];
|
---|
2945 | /* This doesn't use getopt() because it makes trouble for -P & python which reenter
|
---|
2946 | * main() and thus care about freeing all memory. I never had much trust for getopt
|
---|
2947 | * anyway, it's too opaque. This implements a fairly standard non-long-option getopt
|
---|
2948 | * with support for named operations (e.g., "xdelta3 [encode|decode|printhdr...] < in >
|
---|
2949 | * out"). */
|
---|
2950 | if (my_optstr)
|
---|
2951 | {
|
---|
2952 | if (*my_optstr == '-') { my_optstr += 1; }
|
---|
2953 | else if (cmd == CMD_NONE) { goto nonflag; }
|
---|
2954 | else { my_optstr = NULL; }
|
---|
2955 | }
|
---|
2956 | while (my_optstr)
|
---|
2957 | {
|
---|
2958 | char *s;
|
---|
2959 | my_optarg = NULL;
|
---|
2960 | if ((ret = *my_optstr++) == 0) { my_optind += 1; goto takearg; }
|
---|
2961 |
|
---|
2962 | /* Option handling: first check for one ':' following the option in flags, then
|
---|
2963 | * check for two. The syntax allows:
|
---|
2964 | *
|
---|
2965 | * 1. -Afoo defines optarg="foo"
|
---|
2966 | * 2. -A foo defines optarg="foo"
|
---|
2967 | * 3. -A "" defines optarg="" (allows optional empty-string)
|
---|
2968 | * 4. -A [EOA or -moreargs] error (mandatory case)
|
---|
2969 | * 5. -A [EOA -moreargs] defines optarg=NULL (optional case)
|
---|
2970 | * 6. -A=foo defines optarg="foo"
|
---|
2971 | * 7. -A= defines optarg="" (mandatory case)
|
---|
2972 | * 8. -A= defines optarg=NULL (optional case)
|
---|
2973 | *
|
---|
2974 | * See tests in test_command_line_arguments().
|
---|
2975 | */
|
---|
2976 | s = strchr (flags, ret);
|
---|
2977 | if (s && s[1] && s[1] == ':')
|
---|
2978 | {
|
---|
2979 | int eqcase = 0;
|
---|
2980 | int option = s[2] && s[2] == ':';
|
---|
2981 |
|
---|
2982 | /* Case 1, set optarg to the remaining characters. */
|
---|
2983 | my_optarg = my_optstr;
|
---|
2984 | my_optstr = "";
|
---|
2985 |
|
---|
2986 | /* Case 2-5 */
|
---|
2987 | if (*my_optarg == 0)
|
---|
2988 | {
|
---|
2989 | /* Condition 4-5 */
|
---|
2990 | int have_arg = my_optind < (argc - 1) && *argv[my_optind+1] != '-';
|
---|
2991 |
|
---|
2992 | if (! have_arg)
|
---|
2993 | {
|
---|
2994 | if (! option)
|
---|
2995 | {
|
---|
2996 | /* Case 4 */
|
---|
2997 | XPR(NT "-%c: requires an argument\n", ret);
|
---|
2998 | ret = EXIT_FAILURE;
|
---|
2999 | goto cleanup;
|
---|
3000 | }
|
---|
3001 | /* Case 5. */
|
---|
3002 | my_optarg = NULL;
|
---|
3003 | }
|
---|
3004 | else
|
---|
3005 | {
|
---|
3006 | /* Case 2-3. */
|
---|
3007 | my_optarg = argv[++my_optind];
|
---|
3008 | }
|
---|
3009 | }
|
---|
3010 | /* Case 6-8. */
|
---|
3011 | else if (*my_optarg == '=')
|
---|
3012 | {
|
---|
3013 | /* Remove the = in all cases. */
|
---|
3014 | my_optarg += 1;
|
---|
3015 | eqcase = 1;
|
---|
3016 |
|
---|
3017 | if (option && *my_optarg == 0)
|
---|
3018 | {
|
---|
3019 | /* Case 8. */
|
---|
3020 | my_optarg = NULL;
|
---|
3021 | }
|
---|
3022 | }
|
---|
3023 | }
|
---|
3024 |
|
---|
3025 | switch (ret)
|
---|
3026 | {
|
---|
3027 | /* case: if no '-' was found, maybe check for a command name. */
|
---|
3028 | nonflag:
|
---|
3029 | if (strcmp (my_optstr, "decode") == 0) { cmd = CMD_DECODE; }
|
---|
3030 | else if (strcmp (my_optstr, "encode") == 0)
|
---|
3031 | {
|
---|
3032 | #if XD3_ENCODER
|
---|
3033 | cmd = CMD_ENCODE;
|
---|
3034 | #else
|
---|
3035 | XPR(NT "encoder support not compiled\n");
|
---|
3036 | return EXIT_FAILURE;
|
---|
3037 | #endif
|
---|
3038 | }
|
---|
3039 | else if (strcmp (my_optstr, "config") == 0) { cmd = CMD_CONFIG; }
|
---|
3040 | #if REGRESSION_TEST
|
---|
3041 | else if (strcmp (my_optstr, "test") == 0) { cmd = CMD_TEST; }
|
---|
3042 | #endif
|
---|
3043 | #if VCDIFF_TOOLS
|
---|
3044 | else if (strcmp (my_optstr, "printhdr") == 0) { cmd = CMD_PRINTHDR; }
|
---|
3045 | else if (strcmp (my_optstr, "printhdrs") == 0) { cmd = CMD_PRINTHDRS; }
|
---|
3046 | else if (strcmp (my_optstr, "printdelta") == 0) { cmd = CMD_PRINTDELTA; }
|
---|
3047 | #endif
|
---|
3048 |
|
---|
3049 | /* If no option was found and still no command, let the default command be
|
---|
3050 | * encode. The remaining args are treated as filenames. */
|
---|
3051 | if (cmd == CMD_NONE)
|
---|
3052 | {
|
---|
3053 | cmd = CMD_DEFAULT;
|
---|
3054 | my_optstr = NULL;
|
---|
3055 | break;
|
---|
3056 | }
|
---|
3057 | else
|
---|
3058 | {
|
---|
3059 | /* But if we find a command name, continue the getopt loop. */
|
---|
3060 | my_optind += 1;
|
---|
3061 | goto takearg;
|
---|
3062 | }
|
---|
3063 |
|
---|
3064 | /* gzip-like options */
|
---|
3065 | case '0': case '1': case '2': case '3': case '4':
|
---|
3066 | case '5': case '6': case '7': case '8': case '9':
|
---|
3067 | option_level = ret - '0';
|
---|
3068 | break;
|
---|
3069 | case 'f': option_force = 1; break;
|
---|
3070 | case 'v': option_verbose += 1; option_quiet = 0; break;
|
---|
3071 | case 'q': option_quiet = 1; option_verbose = 0; break;
|
---|
3072 | case 'c': option_stdout = 1; break;
|
---|
3073 | case 'd':
|
---|
3074 | if (cmd == CMD_NONE) { cmd = CMD_DECODE; }
|
---|
3075 | else { ret = main_help (); goto exit; }
|
---|
3076 | break;
|
---|
3077 | case 'e':
|
---|
3078 | #if XD3_ENCODER
|
---|
3079 | if (cmd == CMD_NONE) { cmd = CMD_ENCODE; }
|
---|
3080 | else { ret = main_help (); goto exit; }
|
---|
3081 | break;
|
---|
3082 | #else
|
---|
3083 | XPR(NT "encoder support not compiled\n");
|
---|
3084 | return EXIT_FAILURE;
|
---|
3085 | #endif
|
---|
3086 |
|
---|
3087 | case 'n': option_use_checksum = 0; break;
|
---|
3088 | case 'N': option_no_compress = 1; break;
|
---|
3089 | case 'T': option_use_altcodetable = 1; break;
|
---|
3090 | case 'C': option_smatch_config = my_optarg; break;
|
---|
3091 | case 'J': option_no_output = 1; break;
|
---|
3092 | case 'S': if (my_optarg == NULL) { option_use_secondary = 0; }
|
---|
3093 | else { option_use_secondary = 1; option_secondary = my_optarg; } break;
|
---|
3094 | case 'A': if (my_optarg == NULL) { option_use_appheader = 0; }
|
---|
3095 | else { option_appheader = (uint8_t*) my_optarg; } break;
|
---|
3096 | case 'B':
|
---|
3097 | if ((ret = main_atou (my_optarg, & option_srcwinsz, XD3_MINSRCWINSZ,
|
---|
3098 | 0, 'B')))
|
---|
3099 | {
|
---|
3100 | goto exit;
|
---|
3101 | }
|
---|
3102 | break;
|
---|
3103 | case 'I':
|
---|
3104 | if ((ret = main_atou (my_optarg, & option_iopt_size, 0,
|
---|
3105 | 0, 'I')))
|
---|
3106 | {
|
---|
3107 | goto exit;
|
---|
3108 | }
|
---|
3109 | break;
|
---|
3110 | case 'P':
|
---|
3111 | if ((ret = main_atou (my_optarg, & option_sprevsz, 0,
|
---|
3112 | 0, 'P')))
|
---|
3113 | {
|
---|
3114 | goto exit;
|
---|
3115 | }
|
---|
3116 | break;
|
---|
3117 | case 'W':
|
---|
3118 | if ((ret = main_atou (my_optarg, & option_winsize, XD3_ALLOCSIZE,
|
---|
3119 | XD3_HARDMAXWINSIZE, 'W')))
|
---|
3120 | {
|
---|
3121 | goto exit;
|
---|
3122 | }
|
---|
3123 | break;
|
---|
3124 | case 'D':
|
---|
3125 | #if EXTERNAL_COMPRESSION == 0
|
---|
3126 | if (option_verbose > 0)
|
---|
3127 | {
|
---|
3128 | XPR(NT "warning: -D option ignored, "
|
---|
3129 | "external compression support was not compiled\n");
|
---|
3130 | }
|
---|
3131 | #else
|
---|
3132 | option_decompress_inputs = 0;
|
---|
3133 | #endif
|
---|
3134 | break;
|
---|
3135 | case 'R':
|
---|
3136 | #if EXTERNAL_COMPRESSION == 0
|
---|
3137 | if (option_verbose > 0)
|
---|
3138 | {
|
---|
3139 | XPR(NT "warning: -R option ignored, "
|
---|
3140 | "external compression support was not compiled\n");
|
---|
3141 | }
|
---|
3142 | #else
|
---|
3143 | option_recompress_outputs = 0;
|
---|
3144 | #endif
|
---|
3145 | break;
|
---|
3146 | case 's':
|
---|
3147 | if (sfilename != NULL)
|
---|
3148 | {
|
---|
3149 | XPR(NT "specify only one source file\n");
|
---|
3150 | goto cleanup;
|
---|
3151 | }
|
---|
3152 |
|
---|
3153 | sfilename = my_optarg;
|
---|
3154 | break;
|
---|
3155 |
|
---|
3156 | case 'V':
|
---|
3157 | ret = main_version (); goto exit;
|
---|
3158 | default:
|
---|
3159 | ret = main_help (); goto exit;
|
---|
3160 | }
|
---|
3161 | }
|
---|
3162 |
|
---|
3163 | option_source_filename = sfilename;
|
---|
3164 |
|
---|
3165 | /* In case there were no arguments, set the default command. */
|
---|
3166 | if (cmd == CMD_NONE) { cmd = CMD_DEFAULT; }
|
---|
3167 |
|
---|
3168 | argc -= my_optind;
|
---|
3169 | argv += my_optind;
|
---|
3170 |
|
---|
3171 | /* There may be up to two more arguments. */
|
---|
3172 | if (argc > 2)
|
---|
3173 | {
|
---|
3174 | XPR(NT "too many filenames: %s ...\n", argv[2]);
|
---|
3175 | ret = EXIT_FAILURE;
|
---|
3176 | goto cleanup;
|
---|
3177 | }
|
---|
3178 |
|
---|
3179 | ifile.flags = RD_FIRST;
|
---|
3180 | sfile.flags = RD_FIRST;
|
---|
3181 | sfile.filename = option_source_filename;
|
---|
3182 |
|
---|
3183 | /* The infile takes the next argument, if there is one. But if not, infile is set to
|
---|
3184 | * stdin. */
|
---|
3185 | if (argc > 0)
|
---|
3186 | {
|
---|
3187 | ifile.filename = argv[0];
|
---|
3188 |
|
---|
3189 | if ((ret = main_file_open (& ifile, ifile.filename, XO_READ)))
|
---|
3190 | {
|
---|
3191 | goto cleanup;
|
---|
3192 | }
|
---|
3193 | }
|
---|
3194 | else
|
---|
3195 | {
|
---|
3196 | XSTDIN_XF (& ifile);
|
---|
3197 | }
|
---|
3198 |
|
---|
3199 | /* The ofile takes the following argument, if there is one. But if not, it is left NULL
|
---|
3200 | * until the application header is processed. It will be set in main_open_output. */
|
---|
3201 | if (argc > 1)
|
---|
3202 | {
|
---|
3203 | /* Check for conflicting arguments. */
|
---|
3204 | if (option_stdout && ! option_quiet)
|
---|
3205 | {
|
---|
3206 | XPR(NT "warning: -c option overrides output filename: %s\n", argv[1]);
|
---|
3207 | }
|
---|
3208 |
|
---|
3209 | if (! option_stdout) { ofile.filename = argv[1]; }
|
---|
3210 | }
|
---|
3211 |
|
---|
3212 | switch (cmd)
|
---|
3213 | {
|
---|
3214 | case CMD_PRINTHDR:
|
---|
3215 | case CMD_PRINTHDRS:
|
---|
3216 | case CMD_PRINTDELTA:
|
---|
3217 | #if XD3_ENCODER
|
---|
3218 | case CMD_ENCODE:
|
---|
3219 | #endif
|
---|
3220 | case CMD_DECODE:
|
---|
3221 | ret = main_input (cmd, & ifile, & ofile, & sfile);
|
---|
3222 | break;
|
---|
3223 |
|
---|
3224 | #if REGRESSION_TEST
|
---|
3225 | case CMD_TEST:
|
---|
3226 | ret = xd3_selftest ();
|
---|
3227 | break;
|
---|
3228 | #endif
|
---|
3229 |
|
---|
3230 | case CMD_CONFIG:
|
---|
3231 | ret = main_config ();
|
---|
3232 | break;
|
---|
3233 |
|
---|
3234 | default:
|
---|
3235 | ret = main_help ();
|
---|
3236 | break;
|
---|
3237 | }
|
---|
3238 |
|
---|
3239 | #if EXTERNAL_COMPRESSION
|
---|
3240 | if (ext_tmpfile != NULL)
|
---|
3241 | {
|
---|
3242 | unlink (ext_tmpfile);
|
---|
3243 | }
|
---|
3244 | #endif
|
---|
3245 |
|
---|
3246 | if (0)
|
---|
3247 | {
|
---|
3248 | cleanup:
|
---|
3249 | ret = EXIT_FAILURE;
|
---|
3250 | exit:
|
---|
3251 | (void)0;
|
---|
3252 | }
|
---|
3253 |
|
---|
3254 | main_file_cleanup (& ifile);
|
---|
3255 | main_file_cleanup (& ofile);
|
---|
3256 | main_file_cleanup (& sfile);
|
---|
3257 |
|
---|
3258 | main_free (free_argv);
|
---|
3259 | main_free (free_value);
|
---|
3260 |
|
---|
3261 | main_cleanup ();
|
---|
3262 |
|
---|
3263 | fflush (stdout);
|
---|
3264 | fflush (stderr);
|
---|
3265 | return ret;
|
---|
3266 | }
|
---|
3267 |
|
---|
3268 | static int
|
---|
3269 | main_help (void)
|
---|
3270 | {
|
---|
3271 | /* Note: update wiki when command-line features change */
|
---|
3272 | main_version ();
|
---|
3273 | DP(RINT "usage: xdelta3 [command/options] [input [output]]\n");
|
---|
3274 | DP(RINT "special command names:\n");
|
---|
3275 | DP(RINT " config prints xdelta3 configuration\n");
|
---|
3276 | DP(RINT " decode decompress the input\n");
|
---|
3277 | DP(RINT " encode compress the input%s\n", XD3_ENCODER ? "" : " [Not compiled]");
|
---|
3278 | #if REGRESSION_TEST
|
---|
3279 | DP(RINT " test run the builtin tests\n");
|
---|
3280 | #endif
|
---|
3281 | #if VCDIFF_TOOLS
|
---|
3282 | DP(RINT "special commands for VCDIFF inputs:\n");
|
---|
3283 | DP(RINT " printdelta print information about the entire delta\n");
|
---|
3284 | DP(RINT " printhdr print information about the first window\n");
|
---|
3285 | DP(RINT " printhdrs print information about all windows\n");
|
---|
3286 | #endif
|
---|
3287 | DP(RINT "standard options:\n");
|
---|
3288 | DP(RINT " -0 .. -9 compression level\n");
|
---|
3289 | DP(RINT " -c use stdout\n");
|
---|
3290 | DP(RINT " -d decompress\n");
|
---|
3291 | DP(RINT " -e compress%s\n", XD3_ENCODER ? "" : " [Not compiled]");
|
---|
3292 | DP(RINT " -f force overwrite\n");
|
---|
3293 | DP(RINT " -h show help\n");
|
---|
3294 | DP(RINT " -q be quiet\n");
|
---|
3295 | DP(RINT " -v be verbose (max 2)\n");
|
---|
3296 | DP(RINT " -V show version\n");
|
---|
3297 |
|
---|
3298 | DP(RINT "memory options:\n");
|
---|
3299 | DP(RINT " -B bytes source window size\n");
|
---|
3300 | DP(RINT " -W bytes input window size\n");
|
---|
3301 | DP(RINT " -P size compression duplicates window\n");
|
---|
3302 | DP(RINT " -I size instruction buffer size (0 = unlimited)\n");
|
---|
3303 |
|
---|
3304 | DP(RINT "compression options:\n");
|
---|
3305 | DP(RINT " -s source source file to copy from (if any)\n");
|
---|
3306 | DP(RINT " -S [djw|fgk] enable/disable secondary compression\n");
|
---|
3307 | DP(RINT " -N disable small string-matching compression\n");
|
---|
3308 | DP(RINT " -D disable external decompression (encode/decode)\n");
|
---|
3309 | DP(RINT " -R disable external recompression (decode)\n");
|
---|
3310 | DP(RINT " -n disable checksum (encode/decode)\n");
|
---|
3311 | DP(RINT " -C soft config (encode, undocumented)\n");
|
---|
3312 | DP(RINT " -A [apphead] disable/provide application header (encode)\n");
|
---|
3313 |
|
---|
3314 | #if XD3_DEBUG > 0
|
---|
3315 | DP(RINT "developer options:\n");
|
---|
3316 | DP(RINT " -J disable output (check/compute only)\n");
|
---|
3317 | DP(RINT " -P repeat count (for profiling)\n");
|
---|
3318 | DP(RINT " -T use alternate code table\n");
|
---|
3319 | #endif
|
---|
3320 |
|
---|
3321 | DP(RINT "the XDELTA environment variable may contain extra args:\n");
|
---|
3322 | DP(RINT " XDELTA=\"-s source-x.y.tar.gz\" \\\n");
|
---|
3323 | DP(RINT " tar --use-compress-program=xdelta3 \\\n");
|
---|
3324 | DP(RINT " -cf target-x.z.tar.gz.vcdiff target-x.y/\n");
|
---|
3325 | return EXIT_FAILURE;
|
---|
3326 | }
|
---|
3327 |
|
---|