/***************************************************************************
 *
 *   BSD LICENSE
 *
 *   Copyright(c) 2007-2026 Intel Corporation. All rights reserved.
 *   All rights reserved.
 *
 *   Redistribution and use in source and binary forms, with or without
 *   modification, are permitted provided that the following conditions
 *   are met:
 *
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in
 *       the documentation and/or other materials provided with the
 *       distribution.
 *     * Neither the name of Intel Corporation nor the names of its
 *       contributors may be used to endorse or promote products derived
 *       from this software without specific prior written permission.
 *
 *   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 *   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 *   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 *   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 *   OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 *   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 *   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 *   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 *   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 *   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 ***************************************************************************/
#include "qzip.h"

int main(int argc, char **argv)
{
    int ret = QZ_OK;
    int arg_count; /* number of files or directories to process */
    g_program_name = qzipBaseName(argv[0]);
    char *out_name = NULL;
    FILE *stream_out = NULL;
    int option_f = 0;
    Qz7zItemList_T *the_list;
    int is_good_7z = 0;
    int is_dir = 0;
    int recursive_mode = 0;
    errno = 0;
    int is_format_set = 0;
    char resolved_path[PATH_MAX];

    while (true) {
        int optc;
        int long_idx = -1;
        char *stop = NULL;

        optc = getopt_long(argc, argv, g_short_opts, g_long_opts, &long_idx);
        if (optc < 0) {
            break;
        }
        switch (optc) {
        case 'd':
            g_decompress = 1;
            break;
        case 'h':
            help();
            exit(OK);
            break;
        case 'k':
            g_keep = 1;
            break;
        case 'V':
            version();
            exit(OK);
            break;
        case 'R':
            recursive_mode = 1;
            break;
        case 's':
            g_params_th.is_sensitive_mode = true;
            break;
        case 'A':
            if (strcmp(optarg, "deflate") == 0) {
                g_params_th.comp_algorithm = QZ_DEFLATE;
            } else if (strcmp(optarg, "lz4") == 0) {
                g_params_th.comp_algorithm = QZ_LZ4;
            } else if (strcmp(optarg, "lz4s") == 0) {
                g_params_th.comp_algorithm = QZ_LZ4s;
            } else if (strcmp(optarg, "zstd") == 0) {
                g_params_th.comp_algorithm = QZ_ZSTD;
            } else {
                printf("Error service arg: %s\n", optarg);
                return -1;
            }
            break;
        case 'H':
            if (strcmp(optarg, "static") == 0) {
                g_params_th.huffman_hdr = QZ_STATIC_HDR;
            } else if (strcmp(optarg, "dynamic") == 0) {
                g_params_th.huffman_hdr = QZ_DYNAMIC_HDR;
            } else {
                printf("Error huffman arg: %s\n", optarg);
                return -1;
            }
            break;
        case 'O':
            if (strcmp(optarg, "gzip") == 0) {
                g_params_th.data_fmt = QZIP_DEFLATE_GZIP;
            } else if (strcmp(optarg, "gzipext") == 0) {
                g_params_th.data_fmt = QZIP_DEFLATE_GZIP_EXT;
            } else if (strcmp(optarg, "7z") == 0) {
                g_params_th.data_fmt = QZIP_DEFLATE_RAW;
            } else if (strcmp(optarg, "deflate_4B") == 0) {
                g_params_th.data_fmt = QZIP_DEFLATE_4B;
            } else if (strcmp(optarg, "lz4") == 0) {
                g_params_th.data_fmt = QZIP_LZ4_FH;
            } else if (strcmp(optarg, "lz4s") == 0) {
                g_params_th.data_fmt = QZIP_LZ4S_BK;
            } else {
                printf("Error gzip header format arg: %s\n", optarg);
                return -1;
            }
            is_format_set = 1;
            break;
        case 'o':
            out_name = optarg;
            break;
        case 'L':
            g_params_th.comp_lvl = QZIP_GET_LOWER_32BITS(strtoul(optarg, &stop, 0));
            if (*stop != '\0' || ERANGE == errno ||
                g_params_th.comp_lvl > QZ_DEFLATE_COMP_LVL_MAXIMUM ||
                g_params_th.comp_lvl <= 0) {
                printf("Error compLevel arg: %s\n", optarg);
                return -1;
            }
            break;
        case 'C':
            g_params_th.hw_buff_sz =
                QZIP_GET_LOWER_32BITS(strtoul(optarg, &stop, 0));
            if (*stop != '\0' || ERANGE == errno ||
                g_params_th.hw_buff_sz > USDM_ALLOC_MAX_SZ / 2) {
                printf("Error chunk size arg: %s\n", optarg);
                return -1;
            }
            break;
        case 'r':
            g_params_th.req_cnt_thrshold =
                QZIP_GET_LOWER_32BITS(strtoul(optarg, &stop, 0));
            if (*stop != '\0' || errno) {
                printf("Error request count threshold: %s\n", optarg);
                return -1;
            }
            break;
        case 'f':
            option_f = 1;
            break;
        case 'P':
            if (strcmp(optarg, "busy") == 0) {
                g_params_th.polling_mode = QZ_BUSY_POLLING;
            } else {
                printf("Error set polling mode: %s\n", optarg);
                return -1;
            }
            break;
        case 'g':
            if (strcmp(optarg, "none") == 0) {
                qzSetLogLevel(LOG_NONE);
            } else if (strcmp(optarg, "error") == 0) {
                qzSetLogLevel(LOG_ERROR);
            } else if (strcmp(optarg, "warn") == 0) {
                qzSetLogLevel(LOG_WARNING);
            } else if (strcmp(optarg, "info") == 0) {
                qzSetLogLevel(LOG_INFO);
            } else if (strcmp(optarg, "debug") == 0) {
                qzSetLogLevel(LOG_DEBUG1);
            } else {
                printf("Error log level: %s\n", optarg);
                return -1;
            }
            break;
        case 'b':
            block_buff_len =
                QZIP_GET_LOWER_32BITS(strtoul(optarg, &stop, 0));
            if (*stop != '\0' || ERANGE == errno ||
                block_buff_len > 512 * 1024 * 1024) {
                printf("Error block size arg: %s\n", optarg);
                return -1;
            }
            break;
        default:
            tryHelp();
        }
    }

    arg_count = argc - optind;
    if (0 == arg_count && isatty(fileno((FILE *)stdin))) {
        help();
        exit(OK);
    }

    if (g_decompress) {
        if (g_params_th.data_fmt == QZIP_LZ4S_BK) {
            QZ_ERROR("Don't support lz4s decompression.\n");
            exit(ERROR);
        }
        g_params_th.direction = QZ_DIR_DECOMPRESS;
    } else {
        g_params_th.direction = QZ_DIR_COMPRESS;
    }

    if (0 == arg_count) {
        if (isatty(fileno((FILE *)stdout)) && 0 == option_f &&
            0 == g_decompress) {
            printf("qzip: compressed data not written to a terminal. "
                   "Use -f to force compression.\n");
            printf("For help, type: qzip -h\n");
        } else {
            if (qatzipSetup(&g_sess, &g_params_th)) {
                fprintf(stderr, "qatzipSetup session failed\n");
                exit(ERROR);
            }
            stream_out = stdout;
            stdout = freopen(NULL, "w", stdout);
            processStream(&g_sess, stdin, stream_out, g_decompress == 0);
        }
    } else if (g_params_th.data_fmt == QZIP_DEFLATE_RAW &&
               !g_decompress) { //compress into 7z
        QZ_DEBUG("going to compress files into 7z archive ...\n");

        if (!out_name) {
            QZ_ERROR("Should use '-o' to specify an output name\n");
            help();
            exit(ERROR);
        }

        if (qatzipSetup(&g_sess, &g_params_th)) {
            fprintf(stderr, "qatzipSetup session failed\n");
            exit(ERROR);
        }

        for (int i = 0, j = optind; i < arg_count; ++i, ++j) {
            if (access(argv[j], F_OK)) {
                QZ_ERROR("%s: No such file or directory\n", argv[j]);
                exit(ERROR);
            }
        }

        the_list = itemListCreate(arg_count, argv);
        if (!the_list) {
            exit(ERROR);
        }
        ret = qz7zCompress(&g_sess, the_list, out_name);
        itemListDestroy(the_list);
    } else {  // decompress from 7z; compress into gz; decompress from gz
        while (optind < argc) {

            /* To avoid CWE-22: Improper Limitation of a Pathname to a Restricted Directory
             * ('Path Traversal') attacks.
             * http://cwe.mitre.org/data/definitions/22.html
             */
            if (!realpath(argv[optind], resolved_path)) {
                QZ_ERROR("%s: No such file or directory\n", argv[optind]);
                exit(ERROR);
            }

            QzSuffix_T  suffix = getSuffix(argv[optind]);

            is_dir = checkDirectory(argv[optind]);
            if (g_decompress && !is_dir) {
                QzSuffixCheckStatus_T check_res = checkSuffix(suffix, is_format_set);
                if (E_CHECK_SUFFIX_UNSUPPORT == check_res) {
                    QZ_ERROR("Error: %s: Wrong suffix. Supported suffix: 7z/gz/lz4.\n",
                             argv[optind]);
                    exit(ERROR);
                }
                if (E_CHECK_SUFFIX_FORMAT_UNMATCH == check_res) {
                    QZ_ERROR("Error: %s: Suffix is not matched with format\n",
                             argv[optind]);
                    exit(ERROR);
                }
            }

            if (qatzipSetup(&g_sess, &g_params_th)) {
                fprintf(stderr, "qatzipSetup session failed\n");
                exit(ERROR);
            }

            if (g_decompress) {
                if (!recursive_mode)  {
                    if (suffix == E_SUFFIX_7Z) {
                        is_good_7z = check7zArchive(argv[optind]);
                        if (is_good_7z < 0) {
                            exit(ERROR);
                        }

                        if (arg_count > 1) {
                            fprintf(stderr, "only support decompressing ONE 7z "
                                    "archive for ONE command!\n");
                            exit(ERROR);
                        }

                        QZ_DEBUG(" this is a 7z archive, "
                                 "going to decompress ...\n");
                        g_params_th.data_fmt = QZIP_DEFLATE_RAW;
                        if (qatzipSetup(&g_sess, &g_params_th)) {
                            fprintf(stderr, "qatzipSetup session  failed\n");
                            exit(ERROR);
                        }
                        ret = qz7zDecompress(&g_sess, argv[optind++]);
                    } else if (suffix == E_SUFFIX_GZ) {
                        processFile(&g_sess, argv[optind++], out_name,
                                    g_decompress == 0);
                    } else if (suffix == E_SUFFIX_LZ4) {
                        if (g_params_th.data_fmt != QZIP_LZ4_FH) {
                            QZ_ERROR("Error: Suffix(.lz4) doesn't match the data format,"
                                     "please confirm the data format\n");
                            exit(ERROR);
                        }
                        processFile(&g_sess, argv[optind++], out_name,
                                    g_decompress == 0);
                    } else {
                        QZ_ERROR("Error: %s: Wrong suffix. Supported suffix: "
                                 "7z/gz/lz4.\n", argv[optind]);
                        exit(ERROR);
                    }
                }  else {
                    processFile(&g_sess, argv[optind++], out_name,
                                g_decompress == 0);
                }

            } else { // compress
                processFile(&g_sess, argv[optind++], out_name,
                            g_decompress == 0);
            }
        }
    }

    if (qatzipClose(&g_sess)) {
        exit(ERROR);
    }

    return ret;
}
