/* lzotest.c -- very comprehensive test driver for the LZO library

   This file is part of the LZO real-time data compression library.

   Copyright (C) 2008 Markus Franz Xaver Johannes Oberhumer
   Copyright (C) 2007 Markus Franz Xaver Johannes Oberhumer
   Copyright (C) 2006 Markus Franz Xaver Johannes Oberhumer
   Copyright (C) 2005 Markus Franz Xaver Johannes Oberhumer
   Copyright (C) 2004 Markus Franz Xaver Johannes Oberhumer
   Copyright (C) 2003 Markus Franz Xaver Johannes Oberhumer
   Copyright (C) 2002 Markus Franz Xaver Johannes Oberhumer
   Copyright (C) 2001 Markus Franz Xaver Johannes Oberhumer
   Copyright (C) 2000 Markus Franz Xaver Johannes Oberhumer
   Copyright (C) 1999 Markus Franz Xaver Johannes Oberhumer
   Copyright (C) 1998 Markus Franz Xaver Johannes Oberhumer
   Copyright (C) 1997 Markus Franz Xaver Johannes Oberhumer
   Copyright (C) 1996 Markus Franz Xaver Johannes Oberhumer
   All Rights Reserved.

   The LZO library 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.

   The LZO library 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 the LZO library; see the file COPYING.
   If not, write to the Free Software Foundation, Inc.,
   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

   Markus F.X.J. Oberhumer
   <markus@oberhumer.com>
   http://www.oberhumer.com/opensource/lzo/
 */


#include "lzo/lzoconf.h"


/*************************************************************************
// util
**************************************************************************/

/* portability layer */
#define WANT_LZO_MALLOC 1
#define WANT_LZO_FREAD 1
#define WANT_LZO_WILDARGV 1
#define WANT_LZO_UCLOCK 1
#include "examples/portab.h"

#if defined(HAVE_STRNICMP) && !defined(HAVE_STRNCASECMP)
#  define strncasecmp(a,b,c) strnicmp(a,b,c)
#  define HAVE_STRNCASECMP 1
#endif

#if 0
#  define is_digit(x)   (isdigit((unsigned char)(x)))
#  define is_space(x)   (isspace((unsigned char)(x)))
#else
#  define is_digit(x)   ((unsigned)(x) - '0' <= 9)
#  define is_space(x)   ((x)==' ' || (x)=='\t' || (x)=='\r' || (x)=='\n')
#endif

#include "mygetopt.h"
#include "mygetopt.ch"


/*************************************************************************
// compression include section
**************************************************************************/

#define HAVE_LZO1_H 1
#define HAVE_LZO1A_H 1
#define HAVE_LZO1B_H 1
#define HAVE_LZO1C_H 1
#define HAVE_LZO1F_H 1
#define HAVE_LZO1X_H 1
#define HAVE_LZO1Y_H 1
#define HAVE_LZO1Z_H 1
#define HAVE_LZO2A_H 1

#if defined(NO_ZLIB_H) || (SIZEOF_INT < 4)
#undef HAVE_ZLIB_H
#endif
#if defined(NO_BZLIB_H) || (SIZEOF_INT != 4)
#undef HAVE_BZLIB_H
#endif

#if 0 && defined(LZO_OS_DOS16)
/* don't make this test program too big */
#undef HAVE_LZO1_H
#undef HAVE_LZO1A_H
#undef HAVE_LZO1C_H
#undef HAVE_LZO1Z_H
#undef HAVE_LZO2A_H
#undef HAVE_LZO2B_H
#undef HAVE_ZLIB_H
#endif


/* LZO algorithms */
#if defined(HAVE_LZO1_H)
#  include "lzo/lzo1.h"
#endif
#if defined(HAVE_LZO1A_H)
#  include "lzo/lzo1a.h"
#endif
#if defined(HAVE_LZO1B_H)
#  include "lzo/lzo1b.h"
#endif
#if defined(HAVE_LZO1C_H)
#  include "lzo/lzo1c.h"
#endif
#if defined(HAVE_LZO1F_H)
#  include "lzo/lzo1f.h"
#endif
#if defined(HAVE_LZO1X_H)
#  include "lzo/lzo1x.h"
#endif
#if defined(HAVE_LZO1Y_H)
#  include "lzo/lzo1y.h"
#endif
#if defined(HAVE_LZO1Z_H)
#  include "lzo/lzo1z.h"
#endif
#if defined(HAVE_LZO2A_H)
#  include "lzo/lzo2a.h"
#endif
#if defined(HAVE_LZO2B_H)
#  include "lzo/lzo2b.h"
#endif

/* other compressors */
#if defined(__LZO_PROFESSIONAL__)
#  include "lzopro/t_config.ch"
#endif
#if defined(HAVE_ZLIB_H)
#  include <zlib.h>
#  define ALG_ZLIB
#endif
#if defined(HAVE_BZLIB_H)
#  include <bzlib.h>
#  define ALG_BZIP2
#endif


/*************************************************************************
// enumerate all methods
**************************************************************************/

enum {
/* compression algorithms */
    M_LZO1B_1     =     1,
    M_LZO1B_2, M_LZO1B_3, M_LZO1B_4, M_LZO1B_5,
    M_LZO1B_6, M_LZO1B_7, M_LZO1B_8, M_LZO1B_9,

    M_LZO1C_1     =    11,
    M_LZO1C_2, M_LZO1C_3, M_LZO1C_4, M_LZO1C_5,
    M_LZO1C_6, M_LZO1C_7, M_LZO1C_8, M_LZO1C_9,

    M_LZO1        =    21,
    M_LZO1A       =    31,

    M_LZO1B_99    =   901,
    M_LZO1B_999   =   902,
    M_LZO1C_99    =   911,
    M_LZO1C_999   =   912,
    M_LZO1_99     =   921,
    M_LZO1A_99    =   931,

    M_LZO1F_1     =    61,
    M_LZO1F_999   =   962,
    M_LZO1X_1     =    71,
    M_LZO1X_1_11  =   111,
    M_LZO1X_1_12  =   112,
    M_LZO1X_1_15  =   115,
    M_LZO1X_999   =   972,
    M_LZO1Y_1     =    81,
    M_LZO1Y_999   =   982,
    M_LZO1Z_999   =   992,

    M_LZO2A_999   =   942,
    M_LZO2B_999   =   952,

    M_LAST_LZO_COMPRESSOR = 998,

/* other compressors */
#if defined(ALG_ZLIB)
    M_ZLIB_8_1 =  1101,
    M_ZLIB_8_2, M_ZLIB_8_3, M_ZLIB_8_4, M_ZLIB_8_5,
    M_ZLIB_8_6, M_ZLIB_8_7, M_ZLIB_8_8, M_ZLIB_8_9,
#endif
#if defined(ALG_BZIP2)
    M_BZIP2_1  =  1201,
    M_BZIP2_2, M_BZIP2_3, M_BZIP2_4, M_BZIP2_5,
    M_BZIP2_6, M_BZIP2_7, M_BZIP2_8, M_BZIP2_9,
#endif

/* dummy compressor - for benchmarking */
    M_MEMCPY      =   999,

    M_LAST_COMPRESSOR = 4999,

/* dummy algorithms - for benchmarking */
    M_MEMSET      =  5001,

/* checksum algorithms - for benchmarking */
    M_ADLER32     =  6001,
    M_CRC32       =  6002,
#if defined(ALG_ZLIB)
    M_Z_ADLER32   =  6011,
    M_Z_CRC32     =  6012,
#endif

    M_UNUSED
};


/*************************************************************************
// command line options
**************************************************************************/

struct corpus_entry_t;

int opt_verbose = 2;

long opt_c_loops = 0;
long opt_d_loops = 0;
const struct corpus_entry_t *opt_corpus = NULL;
const char *opt_corpus_path = NULL;
const char *opt_dump_compressed_data = NULL;

lzo_bool opt_use_safe_decompressor = 0;
lzo_bool opt_use_asm_decompressor = 0;
lzo_bool opt_use_asm_fast_decompressor = 0;
lzo_bool opt_optimize_compressed_data = 0;

int opt_dict = 0;
lzo_uint opt_max_dict_len = LZO_UINT_MAX;
const char *opt_dictionary_file = NULL;

lzo_bool opt_read_from_stdin = 0;

/* set these to 1 to measure the speed impact of a checksum */
lzo_bool opt_compute_adler32 = 0;
lzo_bool opt_compute_crc32 = 0;
static lzo_uint32 adler_in, adler_out;
static lzo_uint32 crc_in, crc_out;

lzo_bool opt_execution_time = 0;
int opt_uclock = -1;
lzo_bool opt_clear_wrkmem = 0;

static const lzo_bool opt_try_to_compress_0_bytes = 1;


/*************************************************************************
// misc globals
**************************************************************************/

static const char *progname = "";
static lzo_uclock_handle_t uch;

/* for statistics and benchmark */
int opt_totals = 0;
static unsigned long total_n = 0;
static unsigned long total_c_len = 0;
static unsigned long total_d_len = 0;
static unsigned long total_blocks = 0;
static double total_perc = 0.0;
static const char *total_method_name = NULL;
static unsigned total_method_names = 0;
/* Note: the average value of a rate (e.g. compression speed) is defined
 * by the Harmonic Mean (and _not_ by the Arithmethic Mean ) */
static unsigned long total_c_mbs_n = 0;
static unsigned long total_d_mbs_n = 0;
static double total_c_mbs_harmonic = 0.0;
static double total_d_mbs_harmonic = 0.0;
static double total_c_mbs_sum = 0.0;
static double total_d_mbs_sum = 0.0;


#if defined(HAVE_LZO1X_H)
int default_method = M_LZO1X_1;
#elif defined(HAVE_LZO1B_H)
int default_method = M_LZO1B_1;
#elif defined(HAVE_LZO1C_H)
int default_method = M_LZO1C_1;
#elif defined(HAVE_LZO1F_H)
int default_method = M_LZO1F_1;
#elif defined(HAVE_LZO1Y_H)
int default_method = M_LZO1Y_1;
#else
int default_method = M_MEMCPY;
#endif


static const int benchmark_methods[] = {
    M_LZO1B_1, M_LZO1B_9,
    M_LZO1C_1, M_LZO1C_9,
    M_LZO1F_1,
    M_LZO1X_1,
    0
};

static const int x1_methods[] = {
    M_LZO1, M_LZO1A, M_LZO1B_1, M_LZO1C_1, M_LZO1F_1, M_LZO1X_1, M_LZO1Y_1,
    0
};

static const int x99_methods[] = {
    M_LZO1_99, M_LZO1A_99, M_LZO1B_99, M_LZO1C_99,
    0
};

static const int x999_methods[] = {
    M_LZO1B_999, M_LZO1C_999, M_LZO1F_999, M_LZO1X_999, M_LZO1Y_999,
    M_LZO1Z_999,
    M_LZO2A_999,
    0
};


/* exit codes of this test program */
#define EXIT_OK         0
#define EXIT_USAGE      1
#define EXIT_FILE       2
#define EXIT_MEM        3
#define EXIT_ADLER      4
#define EXIT_LZO_ERROR  5
#define EXIT_LZO_INIT   6
#define EXIT_INTERNAL   7


/*************************************************************************
// memory setup
**************************************************************************/

static lzo_uint opt_block_size;
static lzo_uint opt_max_data_len;

typedef struct {
    lzo_bytep   ptr;
    lzo_uint    len;
    lzo_uint32  adler;
    lzo_uint32  crc;
    lzo_bytep   alloc_ptr;
    lzo_uint    alloc_len;
    lzo_uint    saved_len;
} mblock_t;

static mblock_t file_data;
static mblock_t block1;
static mblock_t block2;
static mblock_t wrkmem;
static mblock_t dict;


static void mb_alloc_extra(mblock_t *mb, lzo_uint len, lzo_uint extra_bottom, lzo_uint extra_top)
{
    mb->alloc_ptr = mb->ptr = NULL;
    mb->alloc_len = mb->len = 0;

    mb->alloc_len = extra_bottom + len + extra_top;
    if (mb->alloc_len == 0) mb->alloc_len = 1;
    mb->alloc_ptr = (lzo_bytep) lzo_malloc(mb->alloc_len);

    if (mb->alloc_ptr == NULL) {
        fprintf(stderr, "%s: out of memory (wanted %lu bytes)\n", progname, (unsigned long)mb->alloc_len);
        exit(EXIT_MEM);
    }

    mb->ptr = mb->alloc_ptr + extra_bottom;
    mb->len = mb->saved_len = len;
    mb->adler = 1;
    mb->crc = 0;
}


static void mb_alloc(mblock_t *mb, lzo_uint len)
{
    mb_alloc_extra(mb, len, 0, 0);
}


static void mb_free(mblock_t *mb)
{
    if (!mb) return;
    if (mb->alloc_ptr) lzo_free(mb->alloc_ptr);
    mb->alloc_ptr = mb->ptr = NULL;
    mb->alloc_len = mb->len = 0;
}


static lzo_uint get_max_compression_expansion(int m, lzo_uint bl)
{
    if (m == M_MEMCPY || m >= M_LAST_COMPRESSOR)
        return 0;
    if (m == M_LZO2A_999 || m == M_LZO2B_999)
        return bl / 8 + 256;
    if (m > 0  && m < M_LAST_LZO_COMPRESSOR)
        return bl / 16 +  64 + 3;
    return bl / 8 + 256;
}

static lzo_uint get_max_decompression_overrun(int m, lzo_uint bl)
{
    LZO_UNUSED(m);
    LZO_UNUSED(bl);
    /* may overwrite 3 bytes past the end of the decompressed block */
    if (opt_use_asm_fast_decompressor)
        return  (lzo_uint) sizeof(lzo_voidp) - 1;
    return 0;
}


/*************************************************************************
// dictionary support
**************************************************************************/

static void dict_alloc(lzo_uint max_dict_len)
{
    lzo_uint l = 0xbfff;    /* MAX_DICT_LEN */
    if (max_dict_len > 0 && l > max_dict_len)
        l = max_dict_len;
    mb_alloc(&dict, l);
}


/* this default dictionary does not provide good contexts... */
static void dict_set_default(void)
{
    lzo_uint d = 0;
    unsigned i, j;

    dict.len = 16 * 256;
    if (dict.len > dict.alloc_len)
        dict.len = dict.alloc_len;

    lzo_memset(dict.ptr, 0, dict.len);

    for (i = 0; i < 256; i++)
        for (j = 0; j < 16; j++) {
            if (d >= dict.len)
                goto done;
            dict.ptr[d++] = (unsigned char) i;
        }

done:
    dict.adler = lzo_adler32(1, dict.ptr, dict.len);
}


static void dict_load(const char *file_name)
{
    FILE *fp;

    dict.len = 0;
    fp = fopen(file_name,"rb");
    if (fp)
    {
        dict.len = (lzo_uint) lzo_fread(fp, dict.ptr, dict.alloc_len);
        fclose(fp);
        dict.adler = lzo_adler32(1, dict.ptr, dict.len);
    }
}


/*************************************************************************
// compression database
**************************************************************************/

typedef struct
{
    const char *            name;
    int                     id;
    lzo_uint32              mem_compress;
    lzo_uint32              mem_decompress;
    lzo_compress_t          compress;
    lzo_optimize_t          optimize;
    lzo_decompress_t        decompress;
    lzo_decompress_t        decompress_safe;
    lzo_decompress_t        decompress_asm;
    lzo_decompress_t        decompress_asm_safe;
    lzo_decompress_t        decompress_asm_fast;
    lzo_decompress_t        decompress_asm_fast_safe;
    lzo_compress_dict_t     compress_dict;
    lzo_decompress_dict_t   decompress_dict_safe;
}
compress_t;

#include "asm.h"

#include "wrap.h"
#define M_PRIVATE       LZO_PRIVATE
#define m_uint          lzo_uint
#define m_uint32        lzo_uint32
#define m_voidp         lzo_voidp
#define m_bytep         lzo_bytep
#define m_uintp         lzo_uintp
#include "wrapmisc.h"

static const compress_t compress_database[] = {
#include "db.h"
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
};


#if defined(LZOTEST_USE_DYNLOAD)
#  include "dynload/db.ch"
#endif


/*************************************************************************
// method info
**************************************************************************/

static
lzo_decompress_t get_decomp_info ( const compress_t *c, const char **nn )
{
    lzo_decompress_t d = 0;
    const char *n = NULL;

    /* safe has priority over asm/fast */
    if (!d && opt_use_safe_decompressor && opt_use_asm_fast_decompressor)
    {
        d = c->decompress_asm_fast_safe;
        n = " [fs]";
    }
    if (!d && opt_use_safe_decompressor && opt_use_asm_decompressor)
    {
        d = c->decompress_asm_safe;
        n = " [as]";
    }
    if (!d && opt_use_safe_decompressor)
    {
        d = c->decompress_safe;
        n = " [s]";
    }
    if (!d && opt_use_asm_fast_decompressor)
    {
        d = c->decompress_asm_fast;
        n = " [f]";
    }
    if (!d && opt_use_asm_decompressor)
    {
        d = c->decompress_asm;
        n = " [a]";
    }
    if (!d)
    {
        d = c->decompress;
        n = "";
    }
    if (!d)
        n = "(null)";

    if (opt_dict && c->decompress_dict_safe)
        n = "";

    if (nn)
        *nn = n;
    return d;
}


static
const compress_t *find_method_by_id ( int method )
{
    const compress_t *db;
    size_t size = sizeof(compress_database) / sizeof(*(compress_database));
    size_t i;

#if defined(LZOTEST_USE_DYNLOAD)
#  include "dynload/find_id.ch"
#endif

    db = compress_database;
    for (i = 0; i < size && db->name != NULL; i++, db++)
    {
        if (method == db->id)
            return db;
    }
    return NULL;
}


static
const compress_t *find_method_by_name ( const char *name )
{
    const compress_t *db;
    size_t size = sizeof(compress_database) / sizeof(*(compress_database));
    size_t i;

#if defined(LZOTEST_USE_DYNLOAD)
#  include "dynload/find_name.ch"
#endif

    db = compress_database;
    for (i = 0; i < size && db->name != NULL; i++, db++)
    {
        size_t n = strlen(db->name);

#if defined(HAVE_STRNCASECMP)
        if (strncasecmp(name,db->name,n) == 0 && (!name[n] || name[n] == ','))
            return db;
#else
        if (strncmp(name,db->name,n) == 0 && (!name[n] || name[n] == ','))
            return db;
#endif
    }
    return NULL;
}


static
lzo_bool is_compressor ( const compress_t *c )
{
    return (c->id <= M_LAST_COMPRESSOR || c->id >= 9721);
}


/*************************************************************************
// check that memory gets accessed within bounds
**************************************************************************/

void memchecker_init ( mblock_t *mb, lzo_xint l, unsigned char random_byte )
{
    lzo_uint i;
    lzo_uint len = (lzo_uint) l;
    lzo_bytep p;

    assert(len <= mb->len);

    /* bottom */
    p = mb->ptr;
    for (i = 0; i < 16 && p > mb->alloc_ptr; i++)
        *--p = random_byte++;
    /* top */
    p = mb->ptr + len;
    for (i = 0; i < 16 && p < mb->alloc_ptr + mb->alloc_len; i++)
        *p++ = random_byte++;
#if 0 || defined(LZO_DEBUG)
    /* fill in garbage */
    p = mb->ptr;
    random_byte |= 1;
    for (i = 0; i < len; i++, random_byte += 2)
        *p++ = random_byte;
#endif
}


int memchecker_check ( mblock_t *mb, lzo_xint l, unsigned char random_byte )
{
    lzo_uint i;
    lzo_uint len = (lzo_uint) l;
    lzo_bytep p;

    assert(len <= mb->len);

    /* bottom */
    p = mb->ptr;
    for (i = 0; i < 16 && p > mb->alloc_ptr; i++)
        if (*--p != random_byte++)
            return -1;
    /* top */
    p = mb->ptr + len;
    for (i = 0; i < 16 && p < mb->alloc_ptr + mb->alloc_len; i++)
        if (*p++ != random_byte++)
            return -1;
    return 0;
}


/*************************************************************************
// compress a block
**************************************************************************/

static
int call_compressor   ( const compress_t *c,
                        const lzo_bytep src, lzo_uint  src_len,
                              lzo_bytep dst, lzo_uintp dst_len )
{
    int r = -100;

    if (c && c->compress && wrkmem.len >= c->mem_compress)
    {
        unsigned char random_byte = (unsigned char) src_len;
        memchecker_init(&wrkmem, c->mem_compress, random_byte);
        if (opt_clear_wrkmem)
            lzo_memset(wrkmem.ptr, 0, c->mem_compress);

        if (opt_dict && c->compress_dict)
            r = c->compress_dict(src,src_len,dst,dst_len,wrkmem.ptr,dict.ptr,dict.len);
        else
            r = c->compress(src,src_len,dst,dst_len,wrkmem.ptr);

        if (memchecker_check(&wrkmem, c->mem_compress, random_byte) != 0)
            printf("WARNING: wrkmem overwrite error (compress) !!!\n");
    }

    if (r == 0 && opt_compute_adler32)
    {
        lzo_uint32 adler;
        adler = lzo_adler32(0, NULL, 0);
        adler = lzo_adler32(adler, src, src_len);
        adler_in = adler;
    }
    if (r == 0 && opt_compute_crc32)
    {
        lzo_uint32 crc;
        crc = lzo_crc32(0, NULL, 0);
        crc = lzo_crc32(crc, src, src_len);
        crc_in = crc;
    }

    return r;
}


/*************************************************************************
// decompress a block
**************************************************************************/

static
int call_decompressor ( const compress_t *c, lzo_decompress_t d,
                        const lzo_bytep src, lzo_uint  src_len,
                              lzo_bytep dst, lzo_uintp dst_len )
{
    int r = -100;

    if (c && d && wrkmem.len >= c->mem_decompress)
    {
        unsigned char random_byte = (unsigned char) src_len;
        memchecker_init(&wrkmem, c->mem_decompress, random_byte);
        if (opt_clear_wrkmem)
            lzo_memset(wrkmem.ptr, 0, c->mem_decompress);

        if (opt_dict && c->decompress_dict_safe)
            r = c->decompress_dict_safe(src,src_len,dst,dst_len,wrkmem.ptr,dict.ptr,dict.len);
        else
            r = d(src,src_len,dst,dst_len,wrkmem.ptr);

        if (memchecker_check(&wrkmem, c->mem_decompress, random_byte) != 0)
            printf("WARNING: wrkmem overwrite error (decompress) !!!\n");
    }

    if (r == 0 && opt_compute_adler32)
        adler_out = lzo_adler32(1, dst, *dst_len);
    if (r == 0 && opt_compute_crc32)
        crc_out = lzo_crc32(0, dst, *dst_len);

    return r;
}


/*************************************************************************
// optimize a block
**************************************************************************/

static
int call_optimizer   ( const compress_t *c,
                             lzo_bytep src, lzo_uint  src_len,
                             lzo_bytep dst, lzo_uintp dst_len )
{
    if (c && c->optimize && wrkmem.len >= c->mem_decompress)
        return c->optimize(src,src_len,dst,dst_len,wrkmem.ptr);
    return 0;
}


/***********************************************************************
// read a file
************************************************************************/

static int load_file(const char *file_name, lzo_uint max_len)
{
    FILE *fp;
    long ll = -1;
    lzo_uint l;
    int r;
    mblock_t *mb = &file_data;

    mb_free(mb);

    fp = fopen(file_name,"rb");
    if (fp == NULL)
    {
        fprintf(stderr,"%s: ",file_name);
        perror("fopen");
        fflush(stderr);
        return EXIT_FILE;
    }
    r = fseek(fp,(long)max_len,SEEK_SET);
    if (r != 0)
        r = fseek(fp,0,SEEK_END);
    if (r == 0)
    {
        ll = ftell(fp);
        r = fseek(fp,0,SEEK_SET);
    }
    if (r != 0 || ll < 0)
    {
        fprintf(stderr,"%s: ",file_name);
        perror("fseek");
        fflush(stderr);
        return EXIT_FILE;
    }

    l = (lzo_uint) ll;
    if (max_len > 0 && l > max_len)
        l = max_len;
    mb_alloc(mb, l);
    mb->len = (lzo_uint) lzo_fread(fp, mb->ptr, mb->len);
    if (fclose(fp) != 0)
    {
        mb_free(mb);
        fprintf(stderr,"%s: ",file_name);
        perror("fclose");
        fflush(stderr);
        return EXIT_FILE;
    }

    return EXIT_OK;
}


/***********************************************************************
// print some compression statistics
************************************************************************/

static double t_div(double a, double b)
{
    return b > 0.00001 ? a / b : 0;
}

static double set_perc_d(double perc, char *s)
{
    if (perc <= 0) {
        strcpy(s, "0.0");
        return 0;
    }
    if (perc <= 100 - 1.0 / 16) {
        sprintf(s, "%4.1f", perc);
    }
    else {
        long p = (long) (perc + 0.5);
        if (p < 100)
            strcpy(s, "???");
        else if (p >= 9999)
            strcpy(s, "9999");
        else
            sprintf(s, "%ld", p);
    }
    return perc;
}

static double set_perc(unsigned long c_len, unsigned long d_len, char *s)
{
    double perc = 0.0;
    if (d_len > 0)
        perc = c_len * 100.0 / d_len;
    return set_perc_d(perc, s);
}


static
void print_stats ( const char *method_name, const char *file_name,
                   long t_loops, long c_loops, long d_loops,
                   double t_secs, double c_secs, double d_secs,
                   unsigned long c_len, unsigned long d_len,
                   unsigned long blocks )
{
    unsigned long x_len = d_len;
    unsigned long t_bytes, c_bytes, d_bytes;
    double c_mbs, d_mbs, t_mbs;
    double perc;
    char perc_str[4+1];

    perc = set_perc(c_len, d_len, perc_str);

    c_bytes = x_len * c_loops * t_loops;
    d_bytes = x_len * d_loops * t_loops;
    t_bytes = c_bytes + d_bytes;

    if (opt_uclock == 0)
        c_secs = d_secs = t_secs = 0.0;

    /* speed in uncompressed megabytes per second (1 megabyte = 1.000.000 bytes) */
    c_mbs = (c_secs > 0.001) ? (c_bytes / c_secs) / 1000000.0 : 0;
    d_mbs = (d_secs > 0.001) ? (d_bytes / d_secs) / 1000000.0 : 0;
    t_mbs = (t_secs > 0.001) ? (t_bytes / t_secs) / 1000000.0 : 0;

    total_n++;
    total_c_len += c_len;
    total_d_len += d_len;
    total_blocks += blocks;
    total_perc += perc;
    if (c_mbs > 0) {
        total_c_mbs_n += 1;
        total_c_mbs_harmonic += 1.0 / c_mbs;
        total_c_mbs_sum += c_mbs;
    }
    if (d_mbs > 0) {
        total_d_mbs_n += 1;
        total_d_mbs_harmonic += 1.0 / d_mbs;
        total_d_mbs_sum += d_mbs;
    }

    if (opt_verbose >= 2)
    {
        printf("  compressed into %lu bytes,  %s%%  (%s%.3f bits/byte)\n",
               c_len, perc_str, "", perc * 0.08);

#if 0
        printf("%-15s %5ld: ","overall", t_loops);
        printf("%10lu bytes, %8.2f secs, %8.3f MB/sec\n",
               t_bytes, t_secs, t_mbs);
#else
        LZO_UNUSED(t_mbs);
#endif
        printf("%-15s %5ld: ","compress", c_loops);
        printf("%10lu bytes, %8.2f secs, %8.3f MB/sec\n",
               c_bytes, c_secs, c_mbs);
        printf("%-15s %5ld: ","decompress", d_loops);
        printf("%10lu bytes, %8.2f secs, %8.3f MB/sec\n",
               d_bytes, d_secs, d_mbs);
        printf("\n");
    }

    /* create a line for util/table.pl */
    if (opt_verbose >= 1)
    {
        /* get basename */
        const char *n, *nn, *b;
        for (nn = n = b = file_name; *nn; nn++)
            if (*nn == '/' || *nn == '\\' || *nn == ':')
                b = nn + 1;
            else
                n = b;

        printf("%-13s| %-14s %8lu %4lu %9lu %4s %s%8.3f %8.3f |\n",
               method_name, n, d_len, blocks, c_len, perc_str, "", c_mbs, d_mbs);
    }

    if (opt_verbose >= 2)
        printf("\n");
}


static
void print_totals ( void )
{
    char perc_str[4+1];

    if ((opt_verbose >= 1 && total_n > 1) || (opt_totals >= 2))
    {
        unsigned long n = total_n > 0 ? total_n : 1;
        const char *t1 = "-------";
        const char *t2 = total_method_names == 1 ? total_method_name : "";
#if 1 && defined(__ACCLIB_PCLOCK_CH_INCLUDED)
        char uclock_mode[32+1];
        sprintf(uclock_mode, "[clock=%d]", uch.mode);
        t1 = uclock_mode;
        if (opt_uclock == 0) t1 = t2;
#endif

#if defined(LZOTEST_USE_DYNLOAD)
#  include "dynload/print_totals.ch"
#endif

#if 1
        set_perc_d(total_perc / n, perc_str);
        printf("%-13s  %-12s %10lu %4.1f %9lu %4s %8.3f %8.3f\n",
            t1, "***AVG***",
            total_d_len / n, total_blocks * 1.0 / n, total_c_len / n, perc_str,
            t_div(total_c_mbs_n, total_c_mbs_harmonic),
            t_div(total_d_mbs_n, total_d_mbs_harmonic));
#endif
        set_perc(total_c_len, total_d_len, perc_str);
        printf("%-13s  %-12s %10lu %4lu %9lu %4s %s%8.3f %8.3f\n",
            t2, "***TOTALS***",
            total_d_len, total_blocks, total_c_len, perc_str, "",
            t_div(total_c_mbs_n, total_c_mbs_harmonic),
            t_div(total_d_mbs_n, total_d_mbs_harmonic));
    }
}


/*************************************************************************
// compress and decompress a file
**************************************************************************/

static
int process_file ( const compress_t *c, lzo_decompress_t decompress,
                   const char *method_name,
                   const char *file_name,
                   long t_loops, long c_loops, long d_loops )
{
    long t_i;
    unsigned long blocks = 0;
    unsigned long compressed_len = 0;
    double t_time = 0, c_time = 0, d_time = 0;
    lzo_uclock_t t_start, t_stop, x_start, x_stop;
    FILE *fp_dump = NULL;

    if (opt_dump_compressed_data)
        fp_dump = fopen(opt_dump_compressed_data,"wb");

/* process the file */

    lzo_uclock_flush_cpu_cache(&uch, 0);
    lzo_uclock_read(&uch, &t_start);
    for (t_i = 0; t_i < t_loops; t_i++)
    {
        lzo_uint len, c_len, c_len_max, d_len = 0;
        const lzo_bytep d = file_data.ptr;

        len = file_data.len;
        c_len = 0;
        blocks = 0;

        /* process blocks */
        if (len > 0 || opt_try_to_compress_0_bytes) do
        {
            lzo_uint bl;
            long c_i;
            int r;
            unsigned char random_byte = (unsigned char) file_data.len;
#if 1 && defined(CLOCKS_PER_SEC)
            random_byte ^= (unsigned char) clock();
#endif
            blocks++;

            bl = len > opt_block_size ? opt_block_size : len;
            /* update lengths for memchecker_xxx() */
            block1.len = bl + get_max_compression_expansion(c->id, bl);
            block2.len = bl + get_max_decompression_overrun(c->id, bl);
#if defined(__LZO_CHECKER)
            /* malloc a block of the exact size to detect any overrun */
            assert(block1.alloc_ptr == NULL);
            assert(block2.alloc_ptr == NULL);
            mb_alloc(&block1, block1.len);
            mb_alloc(&block2, block2.len);
#endif
            assert(block1.len <= block1.saved_len);
            assert(block2.len <= block2.saved_len);

            memchecker_init(&block1, block1.len, random_byte);
            memchecker_init(&block2, block2.len, random_byte);

        /* compress the block */
            c_len = c_len_max = 0;
            lzo_uclock_flush_cpu_cache(&uch, 0);
            lzo_uclock_read(&uch, &x_start);
            for (r = 0, c_i = 0; r == 0 && c_i < c_loops; c_i++)
            {
                c_len = block1.len;
                r = call_compressor(c, d, bl, block1.ptr, &c_len);
                if (r == 0 && c_len > c_len_max)
                    c_len_max = c_len;
                if (r == 0 && c_len > block1.len)
                    goto compress_overrun;
            }
            lzo_uclock_read(&uch, &x_stop);
            c_time += lzo_uclock_get_elapsed(&uch, &x_start, &x_stop);
            if (r != 0)
            {
                printf("  compression failed in block %ld (%d) (%lu %lu)\n",
                    blocks, r, (long)c_len, (long)bl);
                return EXIT_LZO_ERROR;
            }
            if (memchecker_check(&block1, block1.len, random_byte) != 0)
            {
compress_overrun:
                printf("  compression overwrite error in block %lu "
                    "(%lu %lu %lu %lu)\n",
                    blocks, (long)c_len, (long)d_len, (long)bl, (long)block1.len);
                return EXIT_LZO_ERROR;
            }

        /* optimize the compressed block */
            if (c_len < bl && opt_optimize_compressed_data)
            {
                d_len = bl;
                r = call_optimizer(c, block1.ptr, c_len, block2.ptr, &d_len);
                if (r != 0 || d_len != bl)
                {
                    printf("  optimization failed in block %lu (%d) "
                        "(%lu %lu %lu)\n", blocks, r,
                        (long)c_len, (long)d_len, (long)bl);
                    return EXIT_LZO_ERROR;
                }
                if (memchecker_check(&block1, block1.len, random_byte) != 0 ||
                    memchecker_check(&block2, block2.len, random_byte) != 0)
                {
                    printf("  optimize overwrite error in block %lu "
                        "(%lu %lu %lu %lu)\n",
                        blocks, (long)c_len, (long)d_len, (long)bl, (long)block1.len);
                    return EXIT_LZO_ERROR;
                }
            }

            /* dump compressed data to disk */
            if (fp_dump)
            {
                (void) lzo_fwrite(fp_dump, block1.ptr, c_len);
                fflush(fp_dump);
            }

        /* decompress the block and verify */
            lzo_uclock_flush_cpu_cache(&uch, 0);
            lzo_uclock_read(&uch, &x_start);
            for (r = 0, c_i = 0; r == 0 && c_i < d_loops; c_i++)
            {
                d_len = bl;
                r = call_decompressor(c, decompress, block1.ptr, c_len, block2.ptr, &d_len);
                if (d_len != bl)
                    break;
            }
            lzo_uclock_read(&uch, &x_stop);
            d_time += lzo_uclock_get_elapsed(&uch, &x_start, &x_stop);
            if (r != 0)
            {
                printf("  decompression failed in block %lu (%d) "
                    "(%lu %lu %lu)\n", blocks, r,
                    (long)c_len, (long)d_len, (long)bl);
                return EXIT_LZO_ERROR;
            }
            if (d_len != bl)
            {
                printf("  decompression size error in block %lu (%lu %lu %lu)\n",
                    blocks, (long)c_len, (long)d_len, (long)bl);
                return EXIT_LZO_ERROR;
            }
            if (is_compressor(c))
            {
                if (lzo_memcmp(d, block2.ptr, bl) != 0)
                {
                    lzo_uint x = 0;
                    while (x < bl && block2.ptr[x] == d[x])
                        x++;
                    printf("  decompression data error in block %lu at offset "
                        "%lu (%lu %lu)\n", blocks, (long)x,
                        (long)c_len, (long)d_len);
                    if (opt_compute_adler32)
                        printf("      checksum: 0x%08lx 0x%08lx\n",
                            (long)adler_in, (long)adler_out);
#if 0
                    printf("Orig:  ");
                    r = (x >= 10) ? -10 : 0 - (int) x;
                    for (j = r; j <= 10 && x + j < bl; j++)
                        printf(" %02x", (int)d[x+j]);
                    printf("\nDecomp:");
                    for (j = r; j <= 10 && x + j < bl; j++)
                        printf(" %02x", (int)block2.ptr[x+j]);
                    printf("\n");
#endif
                    return EXIT_LZO_ERROR;
                }
                if ((opt_compute_adler32 && adler_in != adler_out) ||
                    (opt_compute_crc32 && crc_in != crc_out))
                {
                    printf("  checksum error in block %lu (%lu %lu)\n",
                        blocks, (long)c_len, (long)d_len);
                    printf("      adler32: 0x%08lx 0x%08lx\n",
                        (long)adler_in, (long)adler_out);
                    printf("      crc32: 0x%08lx 0x%08lx\n",
                        (long)crc_in, (long)crc_out);
                    return EXIT_LZO_ERROR;
                }
            }

            if (memchecker_check(&block2, block2.len, random_byte) != 0)
            {
                printf("  decompression overwrite error in block %lu "
                    "(%lu %lu %lu %lu)\n",
                    blocks, (long)c_len, (long)d_len, (long)bl, (long)block2.len);
                return EXIT_LZO_ERROR;
            }

#if defined(__LZO_CHECKER)
            /* free in reverse order of allocations */
            mb_free(&block2);
            mb_free(&block1);
#endif

            d += bl;
            len -= bl;
            compressed_len += (unsigned long) c_len_max;
        }
        while (len > 0);
    }
    lzo_uclock_read(&uch, &t_stop);
    t_time += lzo_uclock_get_elapsed(&uch, &t_start, &t_stop);

    if (fp_dump)
        fclose(fp_dump);
    opt_dump_compressed_data = NULL;    /* only dump the first file */

    print_stats(method_name, file_name,
                t_loops, c_loops, d_loops,
                t_time, c_time, d_time,
                compressed_len, (unsigned long) file_data.len, blocks);
    if (total_method_name != c->name) {
        total_method_name = c->name;
        total_method_names += 1;
    }

    return EXIT_OK;
}



static
int do_file ( int method, const char *file_name,
              long c_loops, long d_loops,
              lzo_uint32p p_adler, lzo_uint32p p_crc )
{
    int r;
    const compress_t *c;
    lzo_decompress_t decompress;
    lzo_uint32 adler, crc;
    char method_name[256+1];
    const char *n;
    const long t_loops = 1;

    adler_in = adler_out = 0;
    crc_in = crc_out = 0;
    if (p_adler)
        *p_adler = 0;
    if (p_crc)
        *p_crc = 0;

    c = find_method_by_id(method);
    if (c == NULL || c->name == NULL || c->compress == NULL)
        return EXIT_INTERNAL;
    decompress = get_decomp_info(c,&n);
    if (!decompress || n == NULL || wrkmem.len < c->mem_decompress)
        return EXIT_INTERNAL;
    strcpy(method_name,c->name);
    strcat(method_name,n);

    if (c_loops < 1)  c_loops = 1;
    if (d_loops < 1)  d_loops = 1;

    fflush(stdout); fflush(stderr);

    /* read the whole file */
    r = load_file(file_name, opt_max_data_len);
    if (r != 0)
        return r;

    /* compute some checksums */
    adler = lzo_adler32(0, NULL, 0);
    adler = lzo_adler32(adler, file_data.ptr, file_data.len);
    if (p_adler)
        *p_adler = adler;
    crc = lzo_crc32(0, NULL, 0);
    crc = lzo_crc32(crc, file_data.ptr, file_data.len);
    if (p_crc)
        *p_crc = crc;

    if (opt_verbose >= 2)
    {
        printf("File %s: %lu bytes   (0x%08lx, 0x%08lx)\n",
                file_name, (long) file_data.len, (long) adler, (long) crc);
        printf("  compressing %lu bytes (%ld/%ld/%ld loops, %lu block-size)\n",
                (long) file_data.len, t_loops, c_loops, d_loops, (long) opt_block_size);
        printf("  %s\n", method_name);
    }

    r = process_file(c, decompress, method_name, file_name,
                     t_loops, c_loops, d_loops);

    return r;
}


/*************************************************************************
// Calgary Corpus and Silesia Corpus test suite driver
**************************************************************************/

struct corpus_entry_t
{
    const char *name;
    long loops;
    lzo_uint32 adler;
    lzo_uint32 crc;
};

static const struct corpus_entry_t calgary_corpus[] =
{
    { "bib",       8,  0x4bd09e98L, 0xb856ebe8L },
    { "book1",     1,  0xd4d3613eL, 0x24e19972L },
    { "book2",     1,  0x6fe14cc3L, 0xba0f3f26L },
    { "geo",       6,  0xf3cc5be0L, 0x4d3a6ed0L },
    { "news",      2,  0x2ed405b8L, 0xcafac853L },
    { "obj1",     35,  0x3887dd2cL, 0xc7b0cd26L },
    { "obj2",      4,  0xf89407c4L, 0x3ae33007L },
    { "paper1",   17,  0xfe65ce62L, 0x2b6baca0L },
    { "paper2",   11,  0x1238b7c2L, 0xf76cba72L },
    { "pic",       4,  0xf61a5702L, 0x4b17e59cL },
    { "progc",    25,  0x4c00ba45L, 0x6fb16094L },
    { "progl",    20,  0x4cba738eL, 0xddbf6baaL },
    { "progp",    28,  0x7495b92bL, 0x493a1809L },
    { "trans",    15,  0x52a2cec8L, 0xcdec06a6L },
    { NULL,        0,  0x00000000L, 0x00000000L }
};

static const struct corpus_entry_t silesia_corpus[] =
{
    { "dickens",   1,  0x170f606fL, 0xaf3a6b76L },
    { "mozilla",   1,  0x1188dd4eL, 0x7fb0ab7dL },
    { "mr",        1,  0xaea14b97L, 0xa341883fL },
    { "nci",       1,  0x0af16f1fL, 0x60ff63d3L },
    { "ooffice",   1,  0x83c8f689L, 0xa023e1faL },
    { "osdb",      1,  0xb825b790L, 0xa0ca388cL },
    { "reymont",   1,  0xce5c82caL, 0x50d35f03L },
    { "samba",     1,  0x19dbb9f5L, 0x2beac5f3L },
    { "sao",       1,  0x7edfc4a9L, 0xfda125bfL },
    { "webster",   1,  0xf2962fc6L, 0x01f5a2e9L },
    { "xml",       1,  0xeccd03d6L, 0xff8f3051L },
    { "x-ray",     1,  0xc95435a0L, 0xc86a35c6L },
    { NULL,        0,  0x00000000L, 0x00000000L }
};


static
int do_corpus ( const struct corpus_entry_t *corpus, int method, const char *path,
                long c_loops, long d_loops )
{
    size_t i, n;
    char name[256];

    if (path == NULL || strlen(path) >= sizeof(name) - 12)
        return EXIT_USAGE;

    strcpy(name,path);
    n = strlen(name);
    if (n > 0 && name[n-1] != '/' && name[n-1] != '\\' && name[n-1] != ':')
    {
        strcat(name,"/");
        n++;
    }

    for (i = 0; corpus[i].name != NULL; i++)
    {
        lzo_uint32 adler, crc;
        long c = c_loops * corpus[i].loops;
        long d = d_loops * corpus[i].loops;
        int r;

        strcpy(name+n,corpus[i].name);
        r = do_file(method, name, c, d, &adler, &crc);
        if (r != 0)
            return r;
        if (adler != corpus[i].adler)
        {
            printf("  invalid test suite\n");
            return EXIT_ADLER;
        }
        if (corpus[i].crc && crc != corpus[i].crc)
        {
            printf("  internal checksum error !!  (0x%08lx 0x%08lx)\n",
                    (long) crc, (long) corpus[i].crc);
            return EXIT_INTERNAL;
        }
    }
    return EXIT_OK;
}


/*************************************************************************
// usage
**************************************************************************/

static
void usage ( const char *name, int exit_code, lzo_bool show_methods )
{
    FILE *f;
    int i;

    f = stdout;

    fflush(stdout); fflush(stderr);

    fprintf(f,"Usage: %s [option..] file...\n", name);
    fprintf(f,"\n");
    fprintf(f,"Options:\n");
    fprintf(f,"  -m#     compression method\n");
    fprintf(f,"  -b#     set input block size (default %ld, max %ld)\n",
        (long) opt_block_size, (long) opt_max_data_len);
    fprintf(f,"  -n#     number of compression/decompression runs\n");
    fprintf(f,"  -c#     number of compression runs\n");
    fprintf(f,"  -d#     number of decompression runs\n");
    fprintf(f,"  -S      use safe decompressor (if available)\n");
    fprintf(f,"  -A      use assembler decompressor (if available)\n");
    fprintf(f,"  -F      use fast assembler decompressor (if available)\n");
    fprintf(f,"  -O      optimize compressed data (if available)\n");
    fprintf(f,"  -s DIR  process Calgary Corpus test suite in directory `DIR'\n");
    fprintf(f,"  -@      read list of files to compress from stdin\n");
    fprintf(f,"  -q      be quiet\n");
    fprintf(f,"  -Q      be very quiet\n");
    fprintf(f,"  -v      be verbose\n");
    fprintf(f,"  -L      display software license\n");

    if (show_methods)
    {
#if defined(__ACCLIB_PCLOCK_CH_INCLUDED)
        lzo_uclock_t t_dummy;
        lzo_uclock_read(&uch, &t_dummy);
        (void) lzo_uclock_get_elapsed(&uch, &t_dummy, &t_dummy);
        fprintf(f,"\nAll timings are recorded using uclock mode %d %s.\n", uch.mode, uch.name);
#endif
        fprintf(f,"\n\n");
        fprintf(f,"The following compression methods are available:\n");
        fprintf(f,"\n");
        fprintf(f,"  usage   name           memory          available extras\n");
        fprintf(f,"  -----   ----           ------          ----------------\n");

        for (i = 0; i <= M_LAST_COMPRESSOR; i++)
        {
            const compress_t *c;
            c = find_method_by_id(i);
            if (c)
            {
                char n[16];
                static const char * const s[3] = {"          ", ", ", ""};
                int j = 0;
                unsigned long m = c->mem_compress;

                sprintf(n,"-m%d",i);
                fprintf(f,"  %-6s  %-13s",n,c->name);
#if 1
                fprintf(f,"%9ld", m);
#else
                m = (m + 1023) / 1024;
                fprintf(f,"%6ld kB", m);
#endif

                if (c->decompress_safe)
                    fprintf(f, "%s%s", j++ == 0 ? s[0] : s[1], "safe");
                if (c->decompress_asm)
                    fprintf(f, "%s%s", j++ == 0 ? s[0] : s[1], "asm");
                if (c->decompress_asm_safe)
                    fprintf(f, "%s%s", j++ == 0 ? s[0] : s[1], "asm+safe");
                if (c->decompress_asm_fast)
                    fprintf(f, "%s%s", j++ == 0 ? s[0] : s[1], "fastasm");
                if (c->decompress_asm_fast_safe)
                    fprintf(f, "%s%s", j++ == 0 ? s[0] : s[1], "fastasm+safe");
                if (c->optimize)
                    fprintf(f, "%s%s", j++ == 0 ? s[0] : s[1], "optimize");
                if (j > 0)
                    fprintf(f, s[2]);
                fprintf(f,"\n");
            }
        }
    }
    else
    {
        fprintf(f,"\n");
        fprintf(f,"Type '%s -m' to list all available methods.\n", name);
    }

    fflush(f);
    if (exit_code < 0)
        exit_code = EXIT_USAGE;
    exit(exit_code);
}


static
void license(void)
{
    FILE *f;

    f = stdout;

    fflush(stdout); fflush(stderr);

#if defined(__LZO_PROFESSIONAL__)
#  include "lzopro/license.ch"
#else
fprintf(f,
"   The LZO library is free software; you can redistribute it and/or\n"
"   modify it under the terms of the GNU General Public License as\n"
"   published by the Free Software Foundation; either version 2 of\n"
"   the License, or (at your option) any later version.\n"
"\n"
"   The LZO library is distributed in the hope that it will be useful,\n"
"   but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
"   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n"
"   GNU General Public License for more details.\n"
    );
fprintf(f,
"\n"
"   You should have received a copy of the GNU General Public License\n"
"   along with the LZO library; see the file COPYING.\n"
"   If not, write to the Free Software Foundation, Inc.,\n"
"   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n"
"\n"
"   Markus F.X.J. Oberhumer\n"
"   <markus@oberhumer.com>\n"
"   http://www.oberhumer.com/opensource/lzo/\n"
"\n"
    );
#endif

    fflush(f);
    exit(EXIT_OK);
}


/*************************************************************************
// parse method option '-m'
**************************************************************************/

static int methods[256+1];
static int methods_n = 0;

static void add_method(int m)
{
    int i;

    if (m > 0)
    {
        if (!find_method_by_id(m)) {
            fprintf(stdout,"%s: invalid method %d\n",progname,m);
            exit(EXIT_USAGE);
        }

        for (i = 0; i < methods_n; i++)
            if (methods[i] == m)
                return;

        if (methods_n >= 256)
        {
            fprintf(stderr,"%s: too many methods\n",progname);
            exit(EXIT_USAGE);
        }

        methods[methods_n++] = m;
        methods[methods_n] = 0;
    }
}


static void add_methods(const int *ml)
{
    while (*ml != 0)
        add_method(*ml++);
}


static void add_all_methods(int first, int last)
{
    int m;

    for (m = first; m <= last; m++)
        if (find_method_by_id(m) != NULL)
            add_method(m);
}


static int m_strcmp(const char *a, const char *b)
{
    size_t n;

    if (a[0] == 0 || b[0] == 0)
        return 1;
    n = strlen(b);
    if (strncmp(a,b,n) == 0 && (a[n] == 0 || a[n] == ','))
        return 0;
    return 1;
}


static lzo_bool m_strisdigit(const char *s)
{
    for (;;)
    {
        if (!is_digit(*s))
            return 0;
        s++;
        if (*s == 0 || *s == ',')
            return 1;
    }
}


static void parse_methods(const char *p)
{
    const compress_t *c;

    for (;;)
    {
        if (p == NULL || p[0] == 0)
            usage(progname,-1,1);
        else if ((c = find_method_by_name(p)) != NULL)
            add_method(c->id);
        else if (m_strcmp(p,"all") == 0 || m_strcmp(p,"avail") == 0)
            add_all_methods(1,M_LAST_COMPRESSOR);
        else if (m_strcmp(p,"ALL") == 0)
        {
            add_all_methods(1,M_LAST_COMPRESSOR);
            add_all_methods(9721,9729);
            add_all_methods(9781,9789);
        }
        else if (m_strcmp(p,"lzo") == 0)
            add_all_methods(1,M_MEMCPY);
        else if (m_strcmp(p,"bench") == 0)
            add_methods(benchmark_methods);
        else if (m_strcmp(p,"m1") == 0)
            add_methods(x1_methods);
        else if (m_strcmp(p,"m99") == 0)
            add_methods(x99_methods);
        else if (m_strcmp(p,"m999") == 0)
            add_methods(x999_methods);
        else if (m_strcmp(p,"1x999") == 0)
            add_all_methods(9721,9729);
        else if (m_strcmp(p,"1y999") == 0)
            add_all_methods(9821,9829);
#if defined(ALG_ZLIB)
        else if (m_strcmp(p,"zlib") == 0)
            add_all_methods(M_ZLIB_8_1,M_ZLIB_8_9);
#endif
#if defined(ALG_BZIP2)
        else if (m_strcmp(p,"bzip2") == 0)
            add_all_methods(M_BZIP2_1,M_BZIP2_9);
#endif
#if defined(__LZO_PROFESSIONAL__)
#  include "lzopro/t_opt_m.ch"
#endif
        else if (m_strisdigit(p))
            add_method(atoi(p));
        else
        {
            printf("%s: invalid method '%s'\n\n",progname,p);
            exit(EXIT_USAGE);
        }

        while (*p && *p != ',')
            p++;
        while (*p == ',')
            p++;
        if (*p == 0)
            return;
    }
}


/*************************************************************************
// options
**************************************************************************/

enum {
    OPT_LONGOPT_ONLY = 512,
    OPT_ADLER32,
    OPT_CALGARY_CORPUS,
    OPT_CLEAR_WRKMEM,
    OPT_CRC32,
    OPT_DICT,
    OPT_DUMP,
    OPT_EXECUTION_TIME,
    OPT_MAX_DATA_LEN,
    OPT_MAX_DICT_LEN,
    OPT_SILESIA_CORPUS,
    OPT_UCLOCK,
#if defined(LZOTEST_USE_DYNLOAD)
#  include "dynload/o_enum.ch"
#endif
    OPT_UNUSED
};

static const struct mfx_option longopts[] =
{
 /* { name  has_arg  *flag  val } */
    {"help",             0, 0, 'h'+256}, /* give help */
    {"license",          0, 0, 'L'},     /* display software license */
    {"quiet",            0, 0, 'q'},     /* quiet mode */
    {"verbose",          0, 0, 'v'},     /* verbose mode */
    {"version",          0, 0, 'V'+256}, /* display version number */

    {"adler32",          0, 0, OPT_ADLER32},
    {"calgary-corpus",   1, 0, OPT_CALGARY_CORPUS},
    {"clear-wrkmem",     0, 0, OPT_CLEAR_WRKMEM},
    {"clock",            1, 0, OPT_UCLOCK},
    {"corpus",           1, 0, OPT_CALGARY_CORPUS},
    {"crc32",            0, 0, OPT_CRC32},
    {"dict",             1, 0, OPT_DICT},
    {"dump-compressed",  1, 0, OPT_DUMP},
    {"execution-time",   0, 0, OPT_EXECUTION_TIME},
    {"max-data-length",  1, 0, OPT_MAX_DATA_LEN},
    {"max-dict-length",  1, 0, OPT_MAX_DICT_LEN},
    {"silesia-corpus",   1, 0, OPT_SILESIA_CORPUS},
    {"uclock",           1, 0, OPT_UCLOCK},
    {"methods",          1, 0, 'm'},
    {"totals",           0, 0, 'T'},
#if defined(LZOTEST_USE_DYNLOAD)
#  include "dynload/o_longopts.ch"
#endif

    { 0, 0, 0, 0 }
};


static int do_option(int optc)
{
    switch (optc)
    {
    case 'A':
        opt_use_asm_decompressor = 1;
        break;
    case 'b':
        opt_block_size = 0; /* set to opt_max_data_len later */
        if (mfx_optarg)
        {
            if (!mfx_optarg || !is_digit(mfx_optarg[0]))
                return optc;
            opt_block_size = atol(mfx_optarg);
        }
        break;
    case 'c':
    case 'C':
        if (!mfx_optarg || !is_digit(mfx_optarg[0]))
            return optc;
        opt_c_loops = atol(mfx_optarg);
        break;
    case 'd':
    case 'D':
        if (!mfx_optarg || !is_digit(mfx_optarg[0]))
            return optc;
        opt_d_loops = atol(mfx_optarg);
        break;
    case 'F':
        opt_use_asm_fast_decompressor = 1;
        break;
    case 'h':
    case 'H':
    case '?':
    case 'h'+256:
        usage(progname,EXIT_OK,0);
        break;
    case 'L':
        license();
        break;
    case 'm':
        parse_methods(mfx_optarg);
        break;
    case 'n':
        if (!mfx_optarg || !is_digit(mfx_optarg[0]))
            return optc;
        opt_c_loops = opt_d_loops = atol(mfx_optarg);
        break;
    case 'O':
        opt_optimize_compressed_data = 1;
        break;
    case 'q':
        opt_verbose -= 1;
        break;
    case 'Q':
        opt_verbose = 0;
        break;
    case 's':
    case OPT_CALGARY_CORPUS:
        if (!mfx_optarg || !mfx_optarg[0])
            return optc;
        opt_corpus_path = mfx_optarg;
        opt_corpus = calgary_corpus;
        break;
    case OPT_SILESIA_CORPUS:
        if (!mfx_optarg || !mfx_optarg[0])
            return optc;
        opt_corpus_path = mfx_optarg;
        opt_corpus = silesia_corpus;
        break;
    case 'S':
        opt_use_safe_decompressor = 1;
        break;
    case 'T':
        opt_totals += 1;
        break;
    case 'v':
        opt_verbose += 1;
        break;
    case 'V':
    case 'V'+256:
        exit(EXIT_OK);
        break;
    case '@':
        opt_read_from_stdin = 1;
        break;

    case '1': case '2': case '3': case '4': case '5':
    case '6': case '7': case '8': case '9':
        /* this is a dirty hack... */
        parse_methods(nextchar-1);
        if (nextchar[0])
        {
            nextchar = NULL;
            mfx_optind++;
        }
        break;

    case OPT_ADLER32:
        opt_compute_adler32 = 1;
        break;
    case OPT_CLEAR_WRKMEM:
        opt_clear_wrkmem = 1;
        break;
    case OPT_CRC32:
        opt_compute_crc32 = 1;
        break;
    case OPT_DICT:
        opt_dict = 1;
        opt_dictionary_file = mfx_optarg;
        break;
    case OPT_EXECUTION_TIME:
        opt_execution_time = 1;
        break;
    case OPT_DUMP:
        opt_dump_compressed_data = mfx_optarg;
        break;
    case OPT_MAX_DATA_LEN:
        if (!mfx_optarg || !is_digit(mfx_optarg[0]))
            return optc;
        opt_max_data_len = atol(mfx_optarg);
        break;
    case OPT_MAX_DICT_LEN:
        if (!mfx_optarg || !is_digit(mfx_optarg[0]))
            return optc;
        opt_max_dict_len = atol(mfx_optarg);
        break;
    case OPT_UCLOCK:
        if (!mfx_optarg || !is_digit(mfx_optarg[0]))
            return optc;
        opt_uclock = atoi(mfx_optarg);
#if defined(__ACCLIB_PCLOCK_CH_INCLUDED)
        if (opt_uclock > 0)
            uch.mode = opt_uclock;
#endif
        break;

#if defined(LZOTEST_USE_DYNLOAD)
#  include "dynload/o_do_option.ch"
#endif

    case '\0':
        return -1;
    case ':':
        return -2;
    default:
        fprintf(stderr,"%s: internal error in getopt (%d)\n",progname,optc);
        return -3;
    }
    return 0;
}


static int get_options(int argc, char **argv)
{
    int optc;

    mfx_optind = 0;
    mfx_opterr = 1;
    while ((optc = mfx_getopt_long (argc, argv,
                      "Ab::c:C:d:D:FhHLm::n:OqQs:STvV@123456789"
#if defined(LZOTEST_USE_DYNLOAD)
#  include "dynload/o_shortopts.ch"
#endif
                      , longopts, (int *)0)) >= 0)
    {
        if (do_option(optc) != 0)
            exit(EXIT_USAGE);
    }

    return mfx_optind;
}


/*************************************************************************
// main
**************************************************************************/

int __lzo_cdecl_main main(int argc, char *argv[])
{
    int r = EXIT_OK;
    int i, ii;
    int m;
    time_t t_total;
    const char *s;

    lzo_wildargv(&argc, &argv);
    lzo_uclock_open(&uch);

    progname = argv[0];
    for (s = progname; *s; s++)
        if ((*s == '/' || *s == '\\') && s[1])
            progname = s + 1;

#if defined(__LZO_PROFESSIONAL__)
    printf("\nLZO Professional real-time data compression library (v%s, %s).\n",
           lzo_version_string(), lzo_version_date());
    printf("Copyright (C) 1996-2008 Markus Franz Xaver Johannes Oberhumer\nAll Rights Reserved.\n\n");
#elif defined(LZOTEST_USE_DYNLOAD)
#  include "dynload/init.ch"
#else
    printf("\nLZO real-time data compression library (v%s, %s).\n",
           lzo_version_string(), lzo_version_date());
    printf("Copyright (C) 1996-2008 Markus Franz Xaver Johannes Oberhumer\nAll Rights Reserved.\n\n");
#endif


/*
 * Step 1: initialize the LZO library
 */

    if (lzo_init() != LZO_E_OK)
    {
        printf("internal error - lzo_init() failed !!!\n");
        printf("(this usually indicates a compiler bug - try recompiling\nwithout optimizations, and enable `-DLZO_DEBUG' for diagnostics)\n");
        exit(1);
    }


/*
 * Step 2: setup default options
 */

    opt_max_data_len = 64 * 1024L * 1024L;
    opt_block_size = 256 * 1024L;

#if defined(LZO_ARCH_I086) && defined(ACC_MM_AHSHIFT)
#  if 1 && defined(LZO_ARCH_I086PM) && defined(BLX286)
    opt_max_data_len = 32 * 1024L * 1024L;
#  else
    opt_max_data_len = 14 * 1024L * 1024L;
#  endif
    /* reduce memory requirements for ancient 16-bit DOS 640kB real-mode */
    if (ACC_MM_AHSHIFT != 3) {
        opt_max_data_len = 16 * 1024L;
    }
#elif defined(LZO_OS_TOS)
    /* reduce memory requirements for 14 MB machines */
    opt_max_data_len = 8 * 1024L * 1024L;
#endif



/*
 * Step 3: parse options
 */

    if (argc < 2)
        usage(progname,-1,0);
    i = get_options(argc,argv);

    if (methods_n == 0)
        add_method(default_method);
    if (methods_n > 1 && opt_read_from_stdin)
    {
        printf("%s: cannot use multiple methods and '-@'\n", progname);
        exit(EXIT_USAGE);
    }

    if (opt_block_size == 0)
        opt_block_size = opt_max_data_len;
    if (opt_block_size > opt_max_data_len)
        opt_block_size = opt_max_data_len;

    if (opt_c_loops < 1)
        opt_c_loops = 1;
    if (opt_d_loops < 1)
        opt_d_loops = 1;


/*
 * Step 4: start work
 */

    wrkmem.len = 0;
    for (ii = 0; ii < methods_n; ii++) {
        const compress_t *c = find_method_by_id(methods[ii]);
        assert(c != NULL);
        if (c->mem_compress > wrkmem.len)
            wrkmem.len = c->mem_compress;
        if (c->mem_decompress > wrkmem.len)
            wrkmem.len = c->mem_decompress;
    }

    mb_alloc(&wrkmem, wrkmem.len);

#if !defined(__LZO_CHECKER)
    mb_alloc_extra(&block1, opt_block_size + get_max_compression_expansion(-1, opt_block_size), 16, 16);
    mb_alloc_extra(&block2, opt_block_size + get_max_decompression_overrun(-1, opt_block_size), 16, 16);
#endif

    if (opt_dict)
    {
        opt_optimize_compressed_data = 0;
        dict_alloc(opt_max_dict_len);
        if (opt_dictionary_file)
        {
            dict_load(opt_dictionary_file);
            if (dict.len > 0)
                printf("Using dictionary '%s', %ld bytes, ID 0x%08lx.\n",
                        opt_dictionary_file,
                        (long) dict.len, (long) dict.adler);
        }
        if (dict.len == 0)
        {
            dict_set_default();
            printf("Using default dictionary, %ld bytes, ID 0x%08lx.\n",
                    (long) dict.len, (long) dict.adler);
        }
    }

    t_total = time(NULL);
    ii = i;
    for (m = 0; m < methods_n && r == EXIT_OK; m++)
    {
        int method = methods[m];

        i = ii;
        if (i >= argc && opt_corpus_path == NULL && !opt_read_from_stdin)
            usage(progname,-1,0);
        if (m == 0 && opt_verbose >= 1)
            printf("%lu block-size\n\n", (long) opt_block_size);

        assert(find_method_by_id(method) != NULL);

        if (opt_corpus_path != NULL)
            r = do_corpus(opt_corpus, method, opt_corpus_path,
                          opt_c_loops, opt_d_loops);
        else
        {
            for ( ; i < argc && r == EXIT_OK; i++)
            {
                r = do_file(method,argv[i],opt_c_loops,opt_d_loops,NULL,NULL);
                if (r == EXIT_FILE)     /* ignore file errors */
                    r = EXIT_OK;
            }
            if (opt_read_from_stdin)
            {
                char buf[512], *p;

                while (r == EXIT_OK && fgets(buf,sizeof(buf)-1,stdin) != NULL)
                {
                    buf[sizeof(buf)-1] = 0;
                    p = buf + strlen(buf);
                    while (p > buf && is_space(p[-1]))
                            *--p = 0;
                    p = buf;
                    while (*p && is_space(*p))
                        p++;
                    if (*p)
                        r = do_file(method,p,opt_c_loops,opt_d_loops,NULL,NULL);
                    if (r == EXIT_FILE)     /* ignore file errors */
                        r = EXIT_OK;
                }
                opt_read_from_stdin = 0;
            }
        }
    }

#if defined(LZOTEST_USE_DYNLOAD)
#  include "dynload/exit.ch"
#endif
    t_total = time(NULL) - t_total;

    if (opt_totals)
        print_totals();
    if (opt_execution_time || (methods_n > 1 && opt_verbose >= 1))
        printf("\n%s: execution time: %lu seconds\n", progname, (long) t_total);
    if (r != EXIT_OK)
        printf("\n%s: exit code: %d\n", progname, r);

    lzo_uclock_close(&uch);
    return r;
}


/*
vi:ts=4:et
*/

