lrzip/main.c

500 lines
16 KiB
C
Raw Normal View History

2010-03-29 01:07:08 +02:00
/*
2013-09-02 04:06:08 +02:00
Copyright (C) 2006-2013 Con Kolivas
Copyright (C) 2011 Peter Hyman
Copyright (C) 1998-2003 Andrew Tridgell
2010-03-29 01:07:08 +02:00
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
2010-03-29 01:07:08 +02:00
*/
/* lrzip compression - main program */
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <signal.h>
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
#ifdef HAVE_SYS_TIME_H
# include <sys/time.h>
#endif
#ifdef HAVE_SYS_RESOURCE_H
# include <sys/resource.h>
#endif
#ifdef HAVE_SYS_TYPES_H
# include <sys/types.h>
#endif
#ifdef HAVE_SYS_STAT_H
# include <sys/stat.h>
#endif
2011-08-11 05:57:20 +02:00
2011-03-15 06:32:32 +01:00
#include <termios.h>
#ifdef HAVE_ENDIAN_H
# include <endian.h>
#elif HAVE_SYS_ENDIAN_H
# include <sys/endian.h>
#endif
#ifdef HAVE_ARPA_INET_H
# include <arpa/inet.h>
#endif
2010-03-29 01:07:08 +02:00
#include "rzip.h"
#include "lrzip_core.h"
#include "util.h"
#include "stream.h"
/* needed for CRC routines */
#include "lzma/C/7zCrc.h"
static rzip_control base_control, local_control, *control;
2010-03-29 01:07:08 +02:00
static void usage(void)
{
print_output("lrzip version %s\n", PACKAGE_VERSION);
2013-09-02 04:06:08 +02:00
print_output("Copyright (C) Con Kolivas 2006-2013\n");
print_output("Based on rzip ");
print_output("Copyright (C) Andrew Tridgell 1998-2003\n\n");
print_output("Usage: lrzip [options] <file...>\n");
2011-02-21 02:49:44 +01:00
print_output("General options:\n");
print_output(" -c check integrity of file written on decompression\n");
print_output(" -d decompress\n");
print_output(" -e password protected sha512/aes128 encryption on compression\n");
print_output(" -h|-? show help\n");
2011-02-21 02:49:44 +01:00
print_output(" -H display md5 hash integrity information\n");
print_output(" -i show compressed file information\n");
print_output(" -q don't show compression progress\n");
print_output(" -t test compressed file integrity\n");
print_output(" -v[v] Increase verbosity\n");
print_output(" -V show version\n");
print_output("Options affecting output:\n");
print_output(" -D delete existing files\n");
print_output(" -f force overwrite of any existing files\n");
print_output(" -k keep broken or damaged output files\n");
print_output(" -o filename specify the output file name and/or path\n");
print_output(" -O directory specify the output directory when -o is not used\n");
print_output(" -S suffix specify compressed suffix (default '.lrz')\n");
2011-02-21 02:49:44 +01:00
print_output("Options affecting compression:\n");
print_output(" -b bzip2 compression\n");
print_output(" -g gzip compression using zlib\n");
2011-02-21 02:49:44 +01:00
print_output(" -l lzo compression (ultra fast)\n");
print_output(" -n no backend compression - prepare for other compressor\n");
print_output(" -z zpaq compression (best, extreme compression, extremely slow)\n");
2011-02-21 02:49:44 +01:00
print_output("Low level options:\n");
print_output(" -L level set lzma/bzip2/gzip compression level (1-9, default 7)\n");
print_output(" -N value Set nice value to value (default 19)\n");
print_output(" -p value Set processor count to override number of threads\n");
print_output(" -m size Set maximim available ram in hundreds of MB\n");
print_output(" overrides detected ammount of available ram\n");
print_output(" -T Disable LZO compressibility testing\n");
2011-02-21 02:49:44 +01:00
print_output(" -U Use unlimited window size beyond ramsize (potentially much slower)\n");
print_output(" -w size maximum compression window in hundreds of MB\n");
print_output(" default chosen by heuristic dependent on ram and chosen compression\n");
2011-02-21 02:06:49 +01:00
print_output("\nLRZIP=NOCONFIG environment variable setting can be used to bypass lrzip.conf.\n");
2011-02-22 15:29:17 +01:00
print_output("TMP environment variable will be used for storage of temporary files when needed.\n");
print_output("TMPDIR may also be stored in lrzip.conf file.\n");
2011-02-21 02:49:44 +01:00
print_output("\nIf no filenames or \"-\" is specified, stdin/out will be used.\n");
2010-03-29 01:07:08 +02:00
}
static void sighandler(int sig __UNUSED__)
{
2011-03-15 06:32:32 +01:00
struct termios termios_p;
/* Make sure we haven't died after disabling stdin echo */
tcgetattr(fileno(stdin), &termios_p);
termios_p.c_lflag |= ECHO;
tcsetattr(fileno(stdin), 0, &termios_p);
unlink_files(control);
exit(0);
}
static void show_summary(void)
{
/* OK, if verbosity set, print summary of options selected */
if (!INFO) {
if (!TEST_ONLY)
print_verbose("The following options are in effect for this %s.\n",
DECOMPRESS ? "DECOMPRESSION" : "COMPRESSION");
2011-08-11 06:24:33 +02:00
print_verbose("Threading is %s. Number of CPUs detected: %d\n", control->threads > 1? "ENABLED" : "DISABLED",
control->threads);
print_verbose("Detected %lld bytes ram\n", control->ramsize);
print_verbose("Compression level %d\n", control->compression_level);
print_verbose("Nice Value: %d\n", control->nice_val);
print_verbose("Show Progress\n");
print_maxverbose("Max ");
print_verbose("Verbose\n");
if (FORCE_REPLACE)
print_verbose("Overwrite Files\n");
if (!KEEP_FILES)
print_verbose("Remove input files on completion\n");
2011-08-11 06:24:33 +02:00
if (control->outdir)
print_verbose("Output Directory Specified: %s\n", control->outdir);
else if (control->outname)
print_verbose("Output Filename Specified: %s\n", control->outname);
if (TEST_ONLY)
print_verbose("Test file integrity\n");
2011-08-11 06:24:33 +02:00
if (control->tmpdir)
print_verbose("Temporary Directory set as: %s\n", control->tmpdir);
/* show compression options */
if (!DECOMPRESS && !TEST_ONLY) {
print_verbose("Compression mode is: ");
if (LZMA_COMPRESS)
print_verbose("LZMA. LZO Compressibility testing %s\n", (LZO_TEST? "enabled" : "disabled"));
else if (LZO_COMPRESS)
print_verbose("LZO\n");
else if (BZIP2_COMPRESS)
print_verbose("BZIP2. LZO Compressibility testing %s\n", (LZO_TEST? "enabled" : "disabled"));
else if (ZLIB_COMPRESS)
print_verbose("GZIP\n");
else if (ZPAQ_COMPRESS)
print_verbose("ZPAQ. LZO Compressibility testing %s\n", (LZO_TEST? "enabled" : "disabled"));
else if (NO_COMPRESS)
print_verbose("RZIP pre-processing only\n");
2011-08-11 06:24:33 +02:00
if (control->window)
print_verbose("Compression Window: %lld = %lldMB\n", control->window, control->window * 100ull);
/* show heuristically computed window size */
2011-08-11 06:24:33 +02:00
if (!control->window && !UNLIMITED) {
i64 temp_chunk, temp_window;
if (STDOUT || STDIN)
2011-08-11 06:24:33 +02:00
temp_chunk = control->maxram;
else
2011-08-11 06:24:33 +02:00
temp_chunk = control->ramsize * 2 / 3;
2011-03-08 22:50:46 +01:00
temp_window = temp_chunk / (100 * 1024 * 1024);
print_verbose("Heuristically Computed Compression Window: %lld = %lldMB\n", temp_window, temp_window * 100ull);
}
if (UNLIMITED)
print_verbose("Using Unlimited Window size\n");
}
if (!DECOMPRESS && !TEST_ONLY)
2011-08-11 06:24:33 +02:00
print_maxverbose("Storage time in seconds %lld\n", control->secs);
if (ENCRYPT)
2011-08-11 06:24:33 +02:00
print_maxverbose("Encryption hash loops %lld\n", control->encloops);
}
}
2010-03-29 01:07:08 +02:00
int main(int argc, char *argv[])
{
struct timeval start_time, end_time;
2010-03-29 01:07:08 +02:00
struct sigaction handler;
double seconds,total_time; // for timers
bool lrzcat = false;
Huge rewrite of buffer reading in rzip.c. We use a wrapper instead of accessing the buffer directly, thus allowing us to have window sizes larger than available ram. This is implemented through the use of a "sliding mmap" implementation. Sliding mmap uses two mmapped buffers, one large one as previously, and one page sized smaller one. When an attempt is made to read beyond the end of the large buffer, the small buffer is remapped to the file area that's being accessed. While this implementation is 100x slower than direct mmapping, it allows us to implement unlimited sized compression windows. Implement the -U option with unlimited sized windows. Rework the selection of compression windows. Instead of trying to guess how much ram the machine might be able to access, we try to safely buffer as much ram as we can, and then use that to determine the file buffer size. Do not choose an arbitrary upper window limit unless -w is specified. Rework the -M option to try to buffer the entire file, reducing the buffer size until we succeed. Align buffer sizes to page size. Clean up lots of unneeded variables. Fix lots of minor logic issues to do with window sizes accepted/passed to rzip and the compression backends. More error handling. Change -L to affect rzip compression level directly as well as backend compression level and use 9 by default now. More cleanups of information output. Use 3 point release numbering in case one minor version has many subversions. Numerous minor cleanups and tidying. Updated docs and manpages.
2010-11-04 11:14:55 +01:00
int c, i;
2010-10-31 05:17:04 +01:00
int hours,minutes;
extern int optind;
2010-03-29 01:07:08 +02:00
char *eptr; /* for environment */
control = &base_control;
2010-03-29 01:07:08 +02:00
initialize_control(control);
2010-03-29 01:07:08 +02:00
if (strstr(argv[0], "lrunzip"))
2011-08-11 06:24:33 +02:00
control->flags |= FLAG_DECOMPRESS;
else if (strstr(argv[0], "lrzcat")) {
2011-08-11 06:24:33 +02:00
control->flags |= FLAG_DECOMPRESS | FLAG_STDOUT;
lrzcat = true;
}
2010-03-29 01:07:08 +02:00
/* generate crc table */
CrcGenerateTable();
/* Get Preloaded Defaults from lrzip.conf
* Look in ., $HOME/.lrzip/, /etc/lrzip.
* If LRZIP=NOCONFIG is set, then ignore config
*/
eptr = getenv("LRZIP");
if (eptr == NULL)
2011-08-11 06:24:33 +02:00
read_config(control);
2010-03-29 01:07:08 +02:00
else if (!strstr(eptr,"NOCONFIG"))
2011-08-11 06:24:33 +02:00
read_config(control);
2010-03-29 01:07:08 +02:00
while ((c = getopt(argc, argv, "bcdDefghHiklL:nN:o:O:p:qS:tTUm:vVw:z?")) != -1) {
2010-03-29 01:07:08 +02:00
switch (c) {
case 'b':
2011-08-11 06:24:33 +02:00
if (control->flags & FLAG_NOT_LZMA)
failure("Can only use one of -l, -b, -g, -z or -n\n");
2011-08-11 06:24:33 +02:00
control->flags |= FLAG_BZIP2_COMPRESS;
2010-03-29 01:07:08 +02:00
break;
case 'c':
2011-08-11 06:24:33 +02:00
control->flags |= FLAG_CHECK;
control->flags |= FLAG_HASH;
2010-03-29 01:07:08 +02:00
break;
case 'd':
2011-08-11 06:24:33 +02:00
control->flags |= FLAG_DECOMPRESS;
2010-03-29 01:07:08 +02:00
break;
case 'D':
2011-08-11 06:24:33 +02:00
control->flags &= ~FLAG_KEEP_FILES;
2010-03-29 01:07:08 +02:00
break;
case 'e':
2011-08-11 06:24:33 +02:00
control->flags |= FLAG_ENCRYPT;
break;
2010-03-29 01:07:08 +02:00
case 'f':
2011-08-11 06:24:33 +02:00
control->flags |= FLAG_FORCE_REPLACE;
2010-03-29 01:07:08 +02:00
break;
case 'g':
2011-08-11 06:24:33 +02:00
if (control->flags & FLAG_NOT_LZMA)
failure("Can only use one of -l, -b, -g, -z or -n\n");
2011-08-11 06:24:33 +02:00
control->flags |= FLAG_ZLIB_COMPRESS;
2010-03-29 01:07:08 +02:00
break;
case 'h':
case '?':
usage();
return -1;
case 'H':
2011-08-11 06:24:33 +02:00
control->flags |= FLAG_HASH;
2010-03-29 01:07:08 +02:00
break;
case 'i':
2011-08-11 06:24:33 +02:00
control->flags |= FLAG_INFO;
2010-03-29 01:07:08 +02:00
break;
case 'k':
2011-08-11 06:24:33 +02:00
control->flags |= FLAG_KEEP_BROKEN;
2010-03-29 01:07:08 +02:00
break;
case 'l':
2011-08-11 06:24:33 +02:00
if (control->flags & FLAG_NOT_LZMA)
failure("Can only use one of -l, -b, -g, -z or -n\n");
2011-08-11 06:24:33 +02:00
control->flags |= FLAG_LZO_COMPRESS;
2010-03-29 01:07:08 +02:00
break;
case 'L':
2011-08-11 06:24:33 +02:00
control->compression_level = atoi(optarg);
if (control->compression_level < 1 || control->compression_level > 9)
failure("Invalid compression level (must be 1-9)\n");
break;
2010-03-29 01:07:08 +02:00
case 'n':
2011-08-11 06:24:33 +02:00
if (control->flags & FLAG_NOT_LZMA)
failure("Can only use one of -l, -b, -g, -z or -n\n");
2011-08-11 06:24:33 +02:00
control->flags |= FLAG_NO_COMPRESS;
2010-03-29 01:07:08 +02:00
break;
case 'N':
2011-08-11 06:24:33 +02:00
control->nice_val = atoi(optarg);
if (control->nice_val < -20 || control->nice_val > 19)
failure("Invalid nice value (must be -20..19)\n");
Huge rewrite of buffer reading in rzip.c. We use a wrapper instead of accessing the buffer directly, thus allowing us to have window sizes larger than available ram. This is implemented through the use of a "sliding mmap" implementation. Sliding mmap uses two mmapped buffers, one large one as previously, and one page sized smaller one. When an attempt is made to read beyond the end of the large buffer, the small buffer is remapped to the file area that's being accessed. While this implementation is 100x slower than direct mmapping, it allows us to implement unlimited sized compression windows. Implement the -U option with unlimited sized windows. Rework the selection of compression windows. Instead of trying to guess how much ram the machine might be able to access, we try to safely buffer as much ram as we can, and then use that to determine the file buffer size. Do not choose an arbitrary upper window limit unless -w is specified. Rework the -M option to try to buffer the entire file, reducing the buffer size until we succeed. Align buffer sizes to page size. Clean up lots of unneeded variables. Fix lots of minor logic issues to do with window sizes accepted/passed to rzip and the compression backends. More error handling. Change -L to affect rzip compression level directly as well as backend compression level and use 9 by default now. More cleanups of information output. Use 3 point release numbering in case one minor version has many subversions. Numerous minor cleanups and tidying. Updated docs and manpages.
2010-11-04 11:14:55 +01:00
break;
case 'o':
if (control->outdir)
failure("Cannot have -o and -O together\n");
if (unlikely(STDOUT))
failure("Cannot specify an output filename when outputting to stdout\n");
2011-08-11 06:24:33 +02:00
control->outname = optarg;
control->suffix = "";
2010-03-29 01:07:08 +02:00
break;
case 'O':
2011-08-11 06:24:33 +02:00
if (control->outname) /* can't mix -o and -O */
failure("Cannot have options -o and -O together\n");
if (unlikely(STDOUT))
failure("Cannot specify an output directory when outputting to stdout\n");
2011-08-11 06:24:33 +02:00
control->outdir = malloc(strlen(optarg) + 2);
if (control->outdir == NULL)
fatal("Failed to allocate for outdir\n");
2011-08-11 06:24:33 +02:00
strcpy(control->outdir,optarg);
2010-03-29 01:07:08 +02:00
if (strcmp(optarg+strlen(optarg) - 1, "/")) /* need a trailing slash */
2011-08-11 06:24:33 +02:00
strcat(control->outdir, "/");
2010-03-29 01:07:08 +02:00
break;
case 'p':
2011-08-11 06:24:33 +02:00
control->threads = atoi(optarg);
if (control->threads < 1)
failure("Must have at least one thread\n");
break;
case 'q':
2011-08-11 06:24:33 +02:00
control->flags &= ~FLAG_SHOW_PROGRESS;
break;
case 'S':
2011-08-11 06:24:33 +02:00
if (control->outname)
failure("Specified output filename already, can't specify an extension.\n");
if (unlikely(STDOUT))
failure("Cannot specify a filename suffix when outputting to stdout\n");
2011-08-11 06:24:33 +02:00
control->suffix = optarg;
break;
case 't':
2011-08-11 06:24:33 +02:00
if (control->outname)
failure("Cannot specify an output file name when just testing.\n");
if (!KEEP_FILES)
failure("Doubt that you want to delete a file when just testing.\n");
2011-08-11 06:24:33 +02:00
control->flags |= FLAG_TEST_ONLY;
break;
2010-03-29 01:07:08 +02:00
case 'T':
2011-08-11 06:24:33 +02:00
control->flags &= ~FLAG_THRESHOLD;
2010-03-29 01:07:08 +02:00
break;
case 'U':
2011-08-11 06:24:33 +02:00
control->flags |= FLAG_UNLIMITED;
2010-03-29 01:07:08 +02:00
break;
case 'm':
control->ramsize = atol(optarg) * 1024 * 1024 * 100;
break;
case 'v':
/* set verbosity flag */
2011-08-11 06:24:33 +02:00
if (!(control->flags & FLAG_VERBOSITY) && !(control->flags & FLAG_VERBOSITY_MAX))
control->flags |= FLAG_VERBOSITY;
else if ((control->flags & FLAG_VERBOSITY)) {
control->flags &= ~FLAG_VERBOSITY;
control->flags |= FLAG_VERBOSITY_MAX;
}
2010-03-29 01:07:08 +02:00
break;
case 'V':
print_output("lrzip version %s\n", PACKAGE_VERSION);
exit(0);
break;
case 'w':
2011-08-11 06:24:33 +02:00
control->window = atol(optarg);
2010-03-29 01:07:08 +02:00
break;
case 'z':
2011-08-11 06:24:33 +02:00
if (control->flags & FLAG_NOT_LZMA)
failure("Can only use one of -l, -b, -g, -z or -n\n");
2011-08-11 06:24:33 +02:00
control->flags |= FLAG_ZPAQ_COMPRESS;
2010-03-29 01:07:08 +02:00
break;
}
}
argc -= optind;
argv += optind;
2011-08-11 06:24:33 +02:00
if (control->outname && argc > 1)
failure("Cannot specify output filename with more than 1 file\n");
2010-03-29 01:07:08 +02:00
if (VERBOSE && !SHOW_PROGRESS) {
print_err("Cannot have -v and -q options. -v wins.\n");
2011-08-11 06:24:33 +02:00
control->flags |= FLAG_SHOW_PROGRESS;
2010-03-29 01:07:08 +02:00
}
2011-08-11 06:24:33 +02:00
if (UNLIMITED && control->window) {
print_err("If -U used, cannot specify a window size with -w.\n");
2011-08-11 06:24:33 +02:00
control->window = 0;
}
if (argc < 1)
2011-08-11 06:24:33 +02:00
control->flags |= FLAG_STDIN;
if (UNLIMITED && STDIN) {
print_err("Cannot have -U and stdin, unlimited mode disabled.\n");
2011-08-11 06:24:33 +02:00
control->flags &= ~FLAG_UNLIMITED;
}
setup_overhead(control);
/* Set the main nice value to half that of the backend threads since
* the rzip stage is usually the rate limiting step */
2011-08-11 06:24:33 +02:00
if (control->nice_val > 0 && !NO_COMPRESS) {
if (unlikely(setpriority(PRIO_PROCESS, 0, control->nice_val / 2) == -1))
print_err("Warning, unable to set nice value\n");
} else {
2011-08-11 06:24:33 +02:00
if (unlikely(setpriority(PRIO_PROCESS, 0, control->nice_val) == -1))
print_err("Warning, unable to set nice value\n");
}
2010-03-29 01:07:08 +02:00
/* One extra iteration for the case of no parameters means we will default to stdin/out */
for (i = 0; i <= argc; i++) {
if (i < argc)
2011-08-11 06:24:33 +02:00
control->infile = argv[i];
else if (!(i == 0 && STDIN))
2010-03-29 01:07:08 +02:00
break;
2011-08-11 06:24:33 +02:00
if (control->infile) {
if ((strcmp(control->infile, "-") == 0))
control->flags |= FLAG_STDIN;
else {
struct stat infile_stat;
2011-08-11 06:24:33 +02:00
stat(control->infile, &infile_stat);
if (unlikely(S_ISDIR(infile_stat.st_mode)))
failure("lrzip only works directly on FILES.\n"
"Use lrztar or pipe through tar for compressing directories.\n");
}
}
2010-03-29 01:07:08 +02:00
if (INFO && STDIN)
failure("Will not get file info from STDIN\n");
if ((control->outname && (strcmp(control->outname, "-") == 0)) ||
/* If no output filename is specified, and we're using
* stdin, use stdout */
(!control->outname && STDIN) || lrzcat ) {
control->flags |= FLAG_STDOUT;
control->outFILE = stdout;
control->msgout = stderr;
register_outputfile(control, control->msgout);
}
2010-03-29 01:07:08 +02:00
if (lrzcat) {
2011-08-11 06:24:33 +02:00
control->msgout = stderr;
control->outFILE = stdout;
register_outputfile(control, control->msgout);
}
2010-03-29 01:07:08 +02:00
if (!STDOUT) {
2011-08-11 06:24:33 +02:00
control->msgout = stdout;
register_outputfile(control, control->msgout);
}
if (STDIN)
control->inFILE = stdin;
2010-03-29 01:07:08 +02:00
/* Implement signal handler only once flags are set */
sigemptyset(&handler.sa_mask);
handler.sa_flags = 0;
2010-03-29 01:07:08 +02:00
handler.sa_handler = &sighandler;
sigaction(SIGTERM, &handler, 0);
sigaction(SIGINT, &handler, 0);
if (!FORCE_REPLACE) {
if (STDIN && isatty(fileno((FILE *)stdin))) {
print_err("Will not read stdin from a terminal. Use -f to override.\n");
usage();
exit (1);
}
if (!TEST_ONLY && STDOUT && isatty(fileno((FILE *)stdout))) {
print_err("Will not write stdout to a terminal. Use -f to override.\n");
usage();
exit (1);
}
}
if (CHECK_FILE) {
if (!DECOMPRESS) {
print_err("Can only check file written on decompression.\n");
2011-08-11 06:24:33 +02:00
control->flags &= ~FLAG_CHECK;
} else if (STDOUT) {
print_err("Can't check file written when writing to stdout. Checking disabled.\n");
2011-08-11 06:24:33 +02:00
control->flags &= ~FLAG_CHECK;
}
}
setup_ram(control);
show_summary();
2010-03-29 01:07:08 +02:00
gettimeofday(&start_time, NULL);
2011-03-15 06:32:32 +01:00
if (unlikely(STDIN && ENCRYPT))
failure("Unable to work from STDIN while reading password\n");
2011-03-15 06:32:32 +01:00
memcpy(&local_control, &base_control, sizeof(rzip_control));
2011-03-13 12:22:46 +01:00
if (DECOMPRESS || TEST_ONLY)
decompress_file(&local_control);
else if (INFO)
get_fileinfo(&local_control);
2010-03-29 01:07:08 +02:00
else
compress_file(&local_control);
2010-03-29 01:07:08 +02:00
/* compute total time */
gettimeofday(&end_time, NULL);
total_time = (end_time.tv_sec + (double)end_time.tv_usec / 1000000) -
(start_time.tv_sec + (double)start_time.tv_usec / 1000000);
hours = (int)total_time / 3600;
minutes = (int)(total_time / 60) % 60;
seconds = total_time - hours * 3600 - minutes * 60;
if (!INFO)
print_progress("Total time: %02d:%02d:%05.2f\n", hours, minutes, seconds);
2010-03-29 01:07:08 +02:00
}
return 0;
}