/*
 *  options.c:  Command line processing routines for nwipe.
 *
 *  Copyright Darik Horn <dajhorn-dban@vanadac.com>.
 *
 *  Modifications to original dwipe Copyright Andy Beverley <andy@andybev.com>
 *
 *  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, version 2.
 *
 *  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, write to the Free Software Foundation, Inc.,
 *  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 */

#include "nwipe.h"
#include "context.h"
#include "method.h"
#include "prng.h"
#include "options.h"
#include "logging.h"
#include "version.h"
#include "conf.h"
#include "cpu_features.h"
#include "libconfig.h"

/* The global options struct. */
nwipe_options_t nwipe_options;

int nwipe_options_parse( int argc, char** argv )
{
    extern char* optarg;  // The working getopt option argument.
    extern int optind;  // The working getopt index into argv.
    extern int optopt;  // The last unhandled getopt option.
    extern int opterr;  // The last getopt error number.

    extern nwipe_prng_t nwipe_twister;
    extern nwipe_prng_t nwipe_isaac;
    extern nwipe_prng_t nwipe_isaac64;
    extern nwipe_prng_t nwipe_add_lagg_fibonacci_prng;
    extern nwipe_prng_t nwipe_xoroshiro256_prng;
    extern nwipe_prng_t nwipe_aes_ctr_prng;

    extern config_t nwipe_cfg;
    config_setting_t* setting;
    const char* user_defined_tag;

    /* The getopt() result holder. */
    int nwipe_opt;

    /* Excluded drive indexes */
    int idx_drive_chr;
    int idx_optarg;
    int idx_drive;

    /* Array index variable. */
    int i;

    /* The list of acceptable short options. */
    char nwipe_options_short[] = "Vvhl:P:m:p:qr:e:";

    /* Used when reading value fron nwipe.conf */
    const char* read_value = NULL;

    int ret;

    /* The list of acceptable long options. */
    static struct option nwipe_options_long[] = {
        /* Set when the user wants to wipe without a confirmation prompt. */
        { "autonuke", no_argument, 0, 0 },

        /* Set when the user wants to have the system powerdown on completion of wipe. */
        { "autopoweroff", no_argument, 0, 0 },

        /* A GNU standard option. Corresponds to the 'h' short option. */
        { "help", no_argument, 0, 0 },

        /* The wipe method. Corresponds to the 'm' short option. */
        { "method", required_argument, 0, 'm' },

        /* Log file. Corresponds to the 'l' short option. */
        { "logfile", required_argument, 0, 'l' },

        /* PDFreport path. Corresponds to the 'P' short option. */
        { "PDFreportpath", required_argument, 0, 'P' },

        /* Exclude devices, comma separated list */
        { "exclude", required_argument, 0, 'e' },

        /* The Pseudo Random Number Generator. */
        { "prng", required_argument, 0, 'p' },
        { "prng-benchmark", no_argument, 0, 0 },
        { "prng-bench-seconds", required_argument, 0, 0 },

        /* The number of times to run the method. */
        { "rounds", required_argument, 0, 'r' },

        /* Whether to blank the disk after wiping. */
        { "noblank", no_argument, 0, 0 },

        /* Whether to ignore all USB devices. */
        { "nousb", no_argument, 0, 0 },

        /* Whether to exit after wiping or wait for a keypress. */
        { "nowait", no_argument, 0, 0 },

        /* Whether to allow signals to interrupt a wipe. */
        { "nosignals", no_argument, 0, 0 },

        /* Whether to display the gui. */
        { "nogui", no_argument, 0, 0 },

        /* Whether to anonymize the serial numbers. */
        { "quiet", no_argument, 0, 0 },

        /* A flag to indicate whether the devices would be opened in sync mode. */
        { "sync", required_argument, 0, 0 },

        /* Verify that wipe patterns are being written to the device. */
        { "verify", required_argument, 0, 0 },

        /* I/O mode selection: auto/direct/cached. */
        { "directio", no_argument, 0, 0 },
        { "cachedio", no_argument, 0, 0 },

        /* Enables a field on the PDF that holds a tag that identifies the host computer */
        { "pdftag", no_argument, 0, 0 },

        /* Display program version. */
        { "verbose", no_argument, 0, 0 },

        /* Display program version. */
        { "version", no_argument, 0, 0 },

        /* Requisite padding for getopt(). */
        { 0, 0, 0, 0 } };

    /* Set default options. */
    nwipe_options.autonuke = 0;
    nwipe_options.autopoweroff = 0;
    nwipe_options.method = &nwipe_random;
    nwipe_options.prng_auto = 1; /* by default the PRNG is selected through the benchmark selection */
    nwipe_options.prng_benchmark_only = 0;
    nwipe_options.prng_bench_seconds = 1.0; /* default for interactive / manual */

    /*
     * Determines and sets the default PRNG based on AES-NI support and system architecture.
     * It selects AES-CTR PRNG if AES-NI is supported, xoroshiro256 for 64-bit systems without AES-NI,
     * and add lagged Fibonacci for 32-bit systems.
     */

    if( has_aes_ni() )
    {
        nwipe_options.prng = &nwipe_aes_ctr_prng;
    }
    else if( sizeof( unsigned long int ) >= 8 )
    {
        nwipe_options.prng = &nwipe_xoroshiro256_prng;
        nwipe_log( NWIPE_LOG_WARNING, "CPU doesn't support AES New Instructions, opting for XORoshiro-256 instead." );
    }
    else
    {
        nwipe_options.prng = &nwipe_add_lagg_fibonacci_prng;
    }

    nwipe_options.rounds = 1;
    nwipe_options.noblank = 0;
    nwipe_options.nousb = 0;
    nwipe_options.nowait = 0;
    nwipe_options.nosignals = 0;
    nwipe_options.nogui = 0;
    nwipe_options.quiet = 0;
    nwipe_options.sync = DEFAULT_SYNC_RATE;
    nwipe_options.verbose = 0;
    nwipe_options.verify = NWIPE_VERIFY_LAST;
    nwipe_options.io_mode = NWIPE_IO_MODE_AUTO; /* Default: auto-select I/O mode. */
    nwipe_options.PDF_toggle_host_info = 0; /* Default: host visibility on PDF disabled */
    nwipe_options.PDFtag = 0;
    memset( nwipe_options.logfile, '\0', sizeof( nwipe_options.logfile ) );
    memset( nwipe_options.PDFreportpath, '\0', sizeof( nwipe_options.PDFreportpath ) );
    strncpy( nwipe_options.PDFreportpath, ".", 2 );

    /*
     * Read PDF Enable/Disable settings from nwipe.conf if available
     */
    if( ( ret = nwipe_conf_read_setting( "PDF_Certificate.PDF_Enable", &read_value ) ) )
    {
        /* error occurred */
        nwipe_log( NWIPE_LOG_ERROR,
                   "nwipe_conf_read_setting():Error reading PDF_Certificate.PDF_Enable from nwipe.conf, ret code %i",
                   ret );

        /* Use default values */
        nwipe_options.PDF_enable = 1;
    }
    else
    {
        if( !strcmp( read_value, "ENABLED" ) )
        {
            nwipe_options.PDF_enable = 1;
        }
        else
        {
            if( !strcmp( read_value, "DISABLED" ) )
            {
                nwipe_options.PDF_enable = 0;
            }
            else
            {
                // error occurred
                nwipe_log(
                    NWIPE_LOG_ERROR,
                    "PDF_Certificate.PDF_Enable in nwipe.conf returned a value that was neither ENABLED or DISABLED" );
                nwipe_options.PDF_enable = 1;  // Default to Enabled
            }
        }
    }

    /*
     * Read PDF host visibility settings from nwipe.conf if available
     */
    if( ( ret = nwipe_conf_read_setting( "PDF_Certificate.PDF_Host_Visibility", &read_value ) ) )
    {
        /* error occurred */
        nwipe_log(
            NWIPE_LOG_ERROR,
            "nwipe_conf_read_setting():Error reading PDF_Certificate.PDF_toggle_host_info from nwipe.conf, ret code %i",
            ret );

        nwipe_options.PDF_toggle_host_info = 0; /* Disable host visibility on PDF */
    }
    else
    {
        if( !strcmp( read_value, "ENABLED" ) )
        {
            nwipe_options.PDF_toggle_host_info = 1;
        }
        else
        {
            if( !strcmp( read_value, "DISABLED" ) )
            {
                nwipe_options.PDF_toggle_host_info = 0;
            }
            else
            {
                // error occurred
                nwipe_log( NWIPE_LOG_ERROR,
                           "PDF_Certificate.PDF_toggle_host_info in nwipe.conf returned a value that was neither "
                           "ENABLED or DISABLED" );
                nwipe_options.PDF_toggle_host_info = 0;  // Default to disabled
            }
        }
    }

    /*
     * Read PDF tag Enable/Disable settings from nwipe.conf if available
     */

    setting = config_lookup( &nwipe_cfg, "PDF_Certificate" );

    if( config_setting_lookup_string( setting, "User_Defined_Tag", &user_defined_tag ) )
    {
        if( user_defined_tag[0] != 0 )
        {
            nwipe_options.PDFtag = 1;
        }
        else
        {
            nwipe_options.PDFtag = 0;
        }
    }

    /*
     * PDF Preview enable/disable
     */
    if( ( ret = nwipe_conf_read_setting( "PDF_Certificate.PDF_Preview", &read_value ) ) )
    {
        /* error occurred */
        nwipe_log( NWIPE_LOG_ERROR,
                   "nwipe_conf_read_setting():Error reading PDF_Certificate.PDF_Preview from nwipe.conf, ret code %i",
                   ret );

        /* Use default values */
        nwipe_options.PDF_enable = 1;
    }
    else
    {
        if( !strcmp( read_value, "ENABLED" ) )
        {
            nwipe_options.PDF_preview_details = 1;
        }
        else
        {
            if( !strcmp( read_value, "DISABLED" ) )
            {
                nwipe_options.PDF_preview_details = 0;
            }
            else
            {
                /* error occurred */
                nwipe_log(
                    NWIPE_LOG_ERROR,
                    "PDF_Certificate.PDF_Preview in nwipe.conf returned a value that was neither ENABLED or DISABLED" );
                nwipe_options.PDF_preview_details = 1; /* Default to Enabled */
            }
        }
    }

    /*
     * Initialise each of the strings in the excluded drives array
     */
    for( i = 0; i < MAX_NUMBER_EXCLUDED_DRIVES; i++ )
    {
        nwipe_options.exclude[i][0] = 0;
    }

    /* Parse command line options. */
    while( 1 )
    {
        /* Get the next command line option with (3)getopt. */
        nwipe_opt = getopt_long( argc, argv, nwipe_options_short, nwipe_options_long, &i );

        /* Break when we have processed all of the given options. */
        if( nwipe_opt < 0 )
        {
            break;
        }

        switch( nwipe_opt )
        {
            case 0: /* Long options without short counterparts. */

                if( strcmp( nwipe_options_long[i].name, "autonuke" ) == 0 )
                {
                    /* check for the full option name, as getopt_long() allows abreviations and can lead to unintended
                     * consequences when the user makes a typo */
                    if( strcmp( argv[optind - 1], "--autonuke" ) == 0 )
                    {
                        nwipe_options.autonuke = 1;
                        break;
                    }
                    else
                    {
                        fprintf( stderr,
                                 "Error: Strict command line options required, did you mean --autonuke?, you typed "
                                 "%s.\nType `sudo nwipe --help` for options \n",
                                 argv[optind - 1] );
                        exit( EINVAL );
                    }
                }

                if( strcmp( nwipe_options_long[i].name, "autopoweroff" ) == 0 )
                {
                    /* check for the full option name, as getopt_long() allows abreviations and can lead to unintended
                     * consequences when the user makes a typo */
                    if( strcmp( argv[optind - 1], "--autopoweroff" ) == 0 )
                    {
                        nwipe_options.autopoweroff = 1;
                        break;
                    }
                    else
                    {
                        fprintf( stderr,
                                 "Error: Strict command line options required, did you mean --autopoweroff?, you typed "
                                 "%s.\nType `sudo nwipe --help` for options \n",
                                 argv[optind - 1] );
                        exit( EINVAL );
                    }
                }

                if( strcmp( nwipe_options_long[i].name, "help" ) == 0 )
                {
                    /* check for the full option name, as getopt_long() allows abreviations and can lead to unintended
                     * consequences when the user makes a typo */
                    if( strcmp( argv[optind - 1], "--help" ) == 0 )
                    {
                        display_help();
                        exit( EINVAL );
                    }
                    else
                    {
                        fprintf( stderr,
                                 "Error: Strict command line options required, did you mean --help?, you typed "
                                 "%s.\nType `sudo nwipe --help` for options \n",
                                 argv[optind - 1] );
                        exit( EINVAL );
                    }
                }

                if( strcmp( nwipe_options_long[i].name, "noblank" ) == 0 )
                {
                    /* check for the full option name, as getopt_long() allows abreviations and can lead to unintended
                     * consequences when the user makes a typo */
                    if( strcmp( argv[optind - 1], "--noblank" ) == 0 )
                    {
                        nwipe_options.noblank = 1;
                        break;
                    }
                    else
                    {
                        fprintf( stderr,
                                 "Error: Strict command line options required, did you mean --noblank?, you typed "
                                 "%s.\nType `sudo nwipe --help` for options \n",
                                 argv[optind - 1] );
                        exit( EINVAL );
                    }
                }

                if( strcmp( nwipe_options_long[i].name, "nousb" ) == 0 )
                {
                    /* check for the full option name, as getopt_long() allows abreviations and can lead to unintended
                     * consequences when the user makes a typo */
                    if( strcmp( argv[optind - 1], "--nousb" ) == 0 )
                    {
                        nwipe_options.nousb = 1;
                        break;
                    }
                    else
                    {
                        fprintf( stderr,
                                 "Error: Strict command line options required, did you mean --nousb?, you typed "
                                 "%s.\nType `sudo nwipe --help` for options \n",
                                 argv[optind - 1] );
                        exit( EINVAL );
                    }
                }

                if( strcmp( nwipe_options_long[i].name, "nowait" ) == 0 )
                {
                    /* check for the full option name, as getopt_long() allows abreviations and can lead to unintended
                     * consequences when the user makes a typo */
                    if( strcmp( argv[optind - 1], "--nowait" ) == 0 )
                    {
                        nwipe_options.nowait = 1;
                        break;
                    }
                    else
                    {
                        fprintf( stderr,
                                 "Error: Strict command line options required, did you mean --nowait?, you typed "
                                 "%s.\nType `sudo nwipe --help` for options \n",
                                 argv[optind - 1] );
                        exit( EINVAL );
                    }
                }

                if( strcmp( nwipe_options_long[i].name, "nosignals" ) == 0 )
                {
                    /* check for the full option name, as getopt_long() allows abreviations and can lead to unintended
                     * consequences when the user makes a typo */
                    if( strcmp( argv[optind - 1], "--nosignals" ) == 0 )
                    {
                        nwipe_options.nosignals = 1;
                        break;
                    }
                    else
                    {
                        fprintf( stderr,
                                 "Error: Strict command line options required, did you mean --nosignals?, you typed "
                                 "%s.\nType `sudo nwipe --help` for options \n",
                                 argv[optind - 1] );
                        exit( EINVAL );
                    }
                }

                if( strcmp( nwipe_options_long[i].name, "nogui" ) == 0 )
                {
                    /* check for the full option name, as getopt_long() allows abreviations and can lead to unintended
                     * consequences when the user makes a typo */
                    if( strcmp( argv[optind - 1], "--nogui" ) == 0 )
                    {
                        nwipe_options.nogui = 1;
                        nwipe_options.nowait = 1;
                        break;
                    }
                    else
                    {
                        fprintf( stderr,
                                 "Error: Strict command line options required, did you mean --nogui?, you typed "
                                 "%s.\nType `sudo nwipe --help` for options \n",
                                 argv[optind - 1] );
                        exit( EINVAL );
                    }
                }

                if( strcmp( nwipe_options_long[i].name, "quiet" ) == 0 )
                {
                    /* check for the full option name, as getopt_long() allows abreviations and can lead to unintended
                     * consequences when the user makes a typo */
                    if( strcmp( argv[optind - 1], "--quiet" ) == 0 )
                    {
                        nwipe_options.quiet = 1;
                        break;
                    }
                    else
                    {
                        fprintf( stderr,
                                 "Error: Strict command line options required, did you mean --quiet?, you typed "
                                 "%s.\nType `sudo nwipe --help` for options \n",
                                 argv[optind - 1] );
                        exit( EINVAL );
                    }
                }

                if( strcmp( nwipe_options_long[i].name, "verbose" ) == 0 )
                {
                    /* check for the full option name, as getopt_long() allows abreviations and can lead to unintended
                     * consequences when the user makes a typo */
                    if( strcmp( argv[optind - 1], "--verbose" ) == 0 )
                    {
                        nwipe_options.verbose = 1;
                        break;
                    }
                    else
                    {
                        fprintf( stderr,
                                 "Error: Strict command line options required, did you mean --verbose?, you typed "
                                 "%s.\nType `sudo nwipe --help` for options \n",
                                 argv[optind - 1] );
                        exit( EINVAL );
                    }
                }

                if( strcmp( nwipe_options_long[i].name, "sync" ) == 0 )
                {
                    if( sscanf( optarg, " %i", &nwipe_options.sync ) != 1 || nwipe_options.sync < 0 )
                    {
                        fprintf( stderr, "Error: The sync argument must be a positive integer or zero.\n" );
                        exit( EINVAL );
                    }
                    break;
                }

                if( strcmp( nwipe_options_long[i].name, "verify" ) == 0 )
                {
                    if( strcmp( optarg, "0" ) == 0 || strcmp( optarg, "off" ) == 0 )
                    {
                        nwipe_options.verify = NWIPE_VERIFY_NONE;
                        break;
                    }

                    if( strcmp( optarg, "1" ) == 0 || strcmp( optarg, "last" ) == 0 )
                    {
                        nwipe_options.verify = NWIPE_VERIFY_LAST;
                        break;
                    }

                    if( strcmp( optarg, "2" ) == 0 || strcmp( optarg, "all" ) == 0 )
                    {
                        nwipe_options.verify = NWIPE_VERIFY_ALL;
                        break;
                    }

                    /* Else we do not know this verification level. */
                    fprintf( stderr, "Error: Unknown verification level '%s'.\n", optarg );
                    exit( EINVAL );
                }

                /* I/O mode selection options. */

                if( strcmp( nwipe_options_long[i].name, "directio" ) == 0 )
                {
                    /* check for the full option name, as getopt_long() allows abreviations and can lead to unintended
                     * consequences when the user makes a typo */
                    if( strcmp( argv[optind - 1], "--directio" ) == 0 )
                    {
                        nwipe_options.io_mode = NWIPE_IO_MODE_DIRECT;
                        break;
                    }
                    else
                    {
                        fprintf( stderr,
                                 "Error: Strict command line options required, did you mean --directio?, you typed "
                                 "%s.\nType `sudo nwipe --help` for options \n",
                                 argv[optind - 1] );
                        exit( EINVAL );
                    }
                }

                if( strcmp( nwipe_options_long[i].name, "cachedio" ) == 0 )
                {
                    /* check for the full option name, as getopt_long() allows abreviations and can lead to unintended
                     * consequences when the user makes a typo */
                    if( strcmp( argv[optind - 1], "--cachedio" ) == 0 )
                    {
                        nwipe_options.io_mode = NWIPE_IO_MODE_CACHED;
                        break;
                    }
                    else
                    {
                        fprintf( stderr,
                                 "Error: Strict command line options required, did you mean --cachedio?, you typed "
                                 "%s.\nType `sudo nwipe --help` for options \n",
                                 argv[optind - 1] );
                        exit( EINVAL );
                    }
                }

                if( strcmp( nwipe_options_long[i].name, "pdftag" ) == 0 )
                {
                    nwipe_options.PDFtag = 1;
                    break;
                }

                if( strcmp( nwipe_options_long[i].name, "prng-benchmark" ) == 0 )
                {
                    nwipe_options.prng_benchmark_only = 1;
                    break;
                }
                if( strcmp( nwipe_options_long[i].name, "prng-bench-seconds" ) == 0 )
                {
                    nwipe_options.prng_bench_seconds = atof( optarg );
                    if( nwipe_options.prng_bench_seconds < 0.05 )
                        nwipe_options.prng_bench_seconds = 0.05;
                    if( nwipe_options.prng_bench_seconds > 10.0 )
                        nwipe_options.prng_bench_seconds = 10.0;
                    break;
                }

                /* getopt_long should raise on invalid option, so we should never get here. */
                exit( EINVAL );

            case 'm': /* Method option. */

                if( strcmp( optarg, "dod522022m" ) == 0 || strcmp( optarg, "dod" ) == 0 )
                {
                    nwipe_options.method = &nwipe_dod522022m;
                    break;
                }

                if( strcmp( optarg, "dodshort" ) == 0 || strcmp( optarg, "dod3pass" ) == 0 )
                {
                    nwipe_options.method = &nwipe_dodshort;
                    break;
                }

                if( strcmp( optarg, "gutmann" ) == 0 )
                {
                    nwipe_options.method = &nwipe_gutmann;
                    break;
                }

                if( strcmp( optarg, "ops2" ) == 0 )
                {
                    nwipe_options.method = &nwipe_ops2;
                    break;
                }

                if( strcmp( optarg, "random" ) == 0 || strcmp( optarg, "prng" ) == 0
                    || strcmp( optarg, "stream" ) == 0 )
                {
                    nwipe_options.method = &nwipe_random;
                    break;
                }

                if( strcmp( optarg, "zero" ) == 0 || strcmp( optarg, "quick" ) == 0 )
                {
                    nwipe_options.method = &nwipe_zero;
                    break;
                }

                if( strcmp( optarg, "one" ) == 0 )
                {
                    nwipe_options.method = &nwipe_one;
                    break;
                }

                if( strcmp( optarg, "verify_zero" ) == 0 )
                {
                    nwipe_options.method = &nwipe_verify_zero;
                    break;
                }

                if( strcmp( optarg, "verify_one" ) == 0 )
                {
                    nwipe_options.method = &nwipe_verify_one;
                    break;
                }

                if( strcmp( optarg, "is5enh" ) == 0 )
                {
                    nwipe_options.method = &nwipe_is5enh;
                    break;
                }
                if( strcmp( optarg, "bruce7" ) == 0 )
                {
                    nwipe_options.method = &nwipe_bruce7;
                    break;
                }
                if( strcmp( optarg, "bmb" ) == 0 )
                {
                    nwipe_options.method = &nwipe_bmb;
                    break;
                }

                /* Else we do not know this wipe method. */
                fprintf( stderr, "Error: Unknown wipe method '%s'.\n", optarg );
                exit( EINVAL );

            case 'l': /* Log file option. */

                nwipe_options.logfile[strlen( optarg )] = '\0';
                strncpy( nwipe_options.logfile, optarg, sizeof( nwipe_options.logfile ) );
                break;

            case 'P': /* PDFreport path option. */

                nwipe_options.PDFreportpath[strlen( optarg )] = '\0';
                strncpy( nwipe_options.PDFreportpath, optarg, sizeof( nwipe_options.PDFreportpath ) );

                /* Command line options will override what's in nwipe.conf */
                if( strcmp( nwipe_options.PDFreportpath, "noPDF" ) == 0 )
                {
                    nwipe_options.PDF_enable = 0;
                    nwipe_conf_update_setting( "PDF_Certificate.PDF_Enable", "DISABLED" );
                }
                else
                {
                    if( strcmp( nwipe_options.PDFreportpath, "." ) )
                    {
                        /* and if the user has specified a PDF path then enable PDF */
                        nwipe_options.PDF_enable = 1;
                        nwipe_conf_update_setting( "PDF_Certificate.PDF_Enable", "ENABLED" );
                    }
                }

                break;

            case 'e': /* exclude drives option */

                idx_drive_chr = 0;
                idx_optarg = 0;
                idx_drive = 0;

                /* Create an array of excluded drives from the comma separated string */
                while( optarg[idx_optarg] != 0 && idx_drive < MAX_NUMBER_EXCLUDED_DRIVES )
                {
                    /* drop the leading '=' character if used */
                    if( optarg[idx_optarg] == '=' && idx_optarg == 0 )
                    {
                        idx_optarg++;
                        continue;
                    }

                    if( optarg[idx_optarg] == ',' )
                    {
                        /* terminate string and move onto next drive */
                        nwipe_options.exclude[idx_drive++][idx_drive_chr] = 0;
                        idx_drive_chr = 0;
                        idx_optarg++;
                    }
                    else
                    {
                        if( idx_drive_chr < MAX_DRIVE_PATH_LENGTH )
                        {
                            nwipe_options.exclude[idx_drive][idx_drive_chr++] = optarg[idx_optarg++];
                        }
                        else
                        {
                            /* This section deals with file names that exceed MAX_DRIVE_PATH_LENGTH */
                            nwipe_options.exclude[idx_drive][idx_drive_chr] = 0;
                            while( optarg[idx_optarg] != 0 && optarg[idx_optarg] != ',' )
                            {
                                idx_optarg++;
                            }
                        }
                    }
                    if( idx_drive == MAX_NUMBER_EXCLUDED_DRIVES )
                    {
                        fprintf(
                            stderr,
                            "The number of excluded drives has reached the programs configured limit, aborting\n" );
                        exit( 130 );
                    }
                }
                break;

            case 'h': /* Display help. */

                display_help();
                exit( EINVAL );
                break;

            case 'p': /* PRNG option. */

                /* Default behaviour is auto now, but allow explicit opt-out */
                if( strcmp( optarg, "auto" ) == 0 )
                {
                    nwipe_options.prng_auto = 1;
                    /* keep current default as fallback until autoselect runs */
                    break;
                }

                /* NEW: disable auto and keep compiled-in default selection */
                if( strcmp( optarg, "default" ) == 0 || strcmp( optarg, "manual" ) == 0 )
                {
                    nwipe_options.prng_auto = 0;
                    /* keep nwipe_options.prng as chosen by CPU heuristics above */
                    break;
                }

                /* Any explicit PRNG selection implies auto off */
                nwipe_options.prng_auto = 0;

                if( strcmp( optarg, "mersenne" ) == 0 || strcmp( optarg, "twister" ) == 0 )
                {
                    nwipe_options.prng = &nwipe_twister;
                    break;
                }

                if( strcmp( optarg, "isaac" ) == 0 )
                {
                    nwipe_options.prng = &nwipe_isaac;
                    break;
                }

                if( strcmp( optarg, "isaac64" ) == 0 )
                {
                    nwipe_options.prng = &nwipe_isaac64;
                    break;
                }

                if( strcmp( optarg, "add_lagg_fibonacci_prng" ) == 0 )
                {
                    nwipe_options.prng = &nwipe_add_lagg_fibonacci_prng;
                    break;
                }

                if( strcmp( optarg, "xoroshiro256_prng" ) == 0 )
                {
                    nwipe_options.prng = &nwipe_xoroshiro256_prng;
                    break;
                }

                if( strcmp( optarg, "aes_ctr_prng" ) == 0 )
                {
                    if( has_aes_ni() )
                    {
                        nwipe_options.prng = &nwipe_aes_ctr_prng;
                    }
                    else
                    {
                        fprintf( stderr,
                                 "Error: aes_ctr_prng requires AES-NI on this build, "
                                 "but your CPU does not support AES-NI.\n" );
                        exit( EINVAL );
                    }
                    break;
                }

                fprintf( stderr, "Error: Unknown prng '%s'.\n", optarg );
                exit( EINVAL );

            case 'q': /* Anonymize serial numbers */

                nwipe_options.quiet = 1;
                break;

            case 'r': /* Rounds option. */

                if( sscanf( optarg, " %i", &nwipe_options.rounds ) != 1 || nwipe_options.rounds < 1 )
                {
                    fprintf( stderr, "Error: The rounds argument must be a positive integer.\n" );
                    exit( EINVAL );
                }

                break;

            case 'v': /* verbose */

                nwipe_options.verbose = 1;
                break;

            case 'V': /* Version option. */

                printf( "%s version %s\n", program_name, version_string );
                exit( EXIT_SUCCESS );

            default:

                /* Bogus command line argument. */
                exit( EINVAL );

        } /* method */

    } /* command line options */

    /* Disable blanking for ops2 and verify methods */
    if( nwipe_options.method == &nwipe_ops2 || nwipe_options.method == &nwipe_verify_zero
        || nwipe_options.method == &nwipe_verify_one )
    {
        nwipe_options.noblank = 1;
    }

    /* Return the number of options that were processed. */
    return optind;
}

void nwipe_options_log( void )
{
    extern nwipe_prng_t nwipe_twister;
    extern nwipe_prng_t nwipe_isaac;
    extern nwipe_prng_t nwipe_isaac64;
    extern nwipe_prng_t nwipe_add_lagg_fibonacci_prng;
    extern nwipe_prng_t nwipe_xoroshiro256_prng;
    extern nwipe_prng_t nwipe_aes_ctr_prng;

    /**
     *  Prints a manifest of options to the log.
     */

    nwipe_log( NWIPE_LOG_NOTICE, "Program options are set as follows..." );

    if( nwipe_options.autonuke == 1 )
    {
        nwipe_log( NWIPE_LOG_NOTICE, "  autonuke = %i (on)", nwipe_options.autonuke );
    }
    else
    {
        nwipe_log( NWIPE_LOG_NOTICE, "  autonuke = %i (off)", nwipe_options.autonuke );
    }

    if( nwipe_options.autopoweroff )
    {
        nwipe_log( NWIPE_LOG_NOTICE, "  autopoweroff = %i (on)", nwipe_options.autopoweroff );
    }
    else
    {
        nwipe_log( NWIPE_LOG_NOTICE, "  autopoweroff = %i (off)", nwipe_options.autopoweroff );
    }

    if( nwipe_options.noblank )
    {
        nwipe_log( NWIPE_LOG_NOTICE, "  do not perform a final blank pass" );
    }

    if( nwipe_options.nowait )
    {
        nwipe_log( NWIPE_LOG_NOTICE, "  do not wait for a key before exiting" );
    }

    if( nwipe_options.nosignals )
    {
        nwipe_log( NWIPE_LOG_NOTICE, "  do not allow signals to interrupt a wipe" );
    }

    if( nwipe_options.nogui )
    {
        nwipe_log( NWIPE_LOG_NOTICE, "  do not show GUI interface" );
    }

    nwipe_log( NWIPE_LOG_NOTICE, "  banner   = %s", banner );

    if( nwipe_options.prng == &nwipe_twister )
    {
        nwipe_log( NWIPE_LOG_NOTICE, "  prng     = Mersenne Twister" );
    }
    else if( nwipe_options.prng == &nwipe_add_lagg_fibonacci_prng )
    {
        nwipe_log( NWIPE_LOG_NOTICE, "  prng     = Lagged Fibonacci generator" );
    }
    else if( nwipe_options.prng == &nwipe_xoroshiro256_prng )
    {
        nwipe_log( NWIPE_LOG_NOTICE, "  prng     = XORoshiro-256" );
    }
    else if( nwipe_options.prng == &nwipe_aes_ctr_prng )
    {
        nwipe_log( NWIPE_LOG_NOTICE, "  prng     = AES-CTR New Instructions (EXPERIMENTAL!)" );
    }
    else if( nwipe_options.prng == &nwipe_isaac )
    {
        nwipe_log( NWIPE_LOG_NOTICE, "  prng     = Isaac" );
    }
    else if( nwipe_options.prng == &nwipe_isaac64 )
    {
        nwipe_log( NWIPE_LOG_NOTICE, "  prng     = Isaac64" );
    }
    else
    {
        nwipe_log( NWIPE_LOG_NOTICE, "  prng     = Undefined" );
    }

    nwipe_log( NWIPE_LOG_NOTICE, "  method   = %s", nwipe_method_label( nwipe_options.method ) );
    nwipe_log( NWIPE_LOG_NOTICE, "  quiet    = %i", nwipe_options.quiet );
    nwipe_log( NWIPE_LOG_NOTICE, "  rounds   = %i", nwipe_options.rounds );
    nwipe_log( NWIPE_LOG_NOTICE, "  sync     = %i", nwipe_options.sync );

    switch( nwipe_options.verify )
    {
        case NWIPE_VERIFY_NONE:
            nwipe_log( NWIPE_LOG_NOTICE, "  verify   = %i (off)", nwipe_options.verify );
            break;

        case NWIPE_VERIFY_LAST:
            nwipe_log( NWIPE_LOG_NOTICE, "  verify   = %i (last pass)", nwipe_options.verify );
            break;

        case NWIPE_VERIFY_ALL:
            nwipe_log( NWIPE_LOG_NOTICE, "  verify   = %i (all passes)", nwipe_options.verify );
            break;

        default:
            nwipe_log( NWIPE_LOG_NOTICE, "  verify   = %i", nwipe_options.verify );
            break;
    }
}

void display_help()
{
    /**
     * displays the help section to STDOUT and exits.
     */

    printf( "Usage: %s [options] [device1] [device2] ...\n", program_name );
    printf( "Options:\n" );
    /* Limit line length to a maximum of 80 characters so it looks good in 80x25 terminals i.e shredos */
    /*  ___12345678901234567890123456789012345678901234567890123456789012345678901234567890< Do not exceed */
    puts( "  -V, --version           Prints the version number\n" );
    puts( "  -v, --verbose           Prints more messages to the log\n" );
    puts( "  -h, --help              Prints this help\n" );
    puts( "      --autonuke          If no devices have been specified on the command line," );
    puts( "                          starts wiping all devices immediately. If devices have" );
    puts( "                          been specified, starts wiping only those specified" );
    puts( "                          devices immediately.\n" );
    puts( "      --autopoweroff      Power off system on completion of wipe delayed for" );
    puts( "                          for one minute. During this one minute delay you can" );
    puts( "                          abort the shutdown by typing sudo shutdown -c\n" );
    printf( "      --sync=NUM          Will perform a sync after NUM writes (default: %d)\n", DEFAULT_SYNC_RATE );
    puts( "                          0    - fdatasync after the disk is completely written" );
    puts( "                                 fdatasync errors not detected until completion." );
    puts( "                                 0 is not recommended as disk errors may cause" );
    puts( "                                 nwipe to appear to hang" );
    puts( "                          1    - fdatasync after every write" );
    puts( "                                 Warning: Lower values will reduce wipe speeds." );
    puts( "                          1000 - fdatasync after 1000 writes etc.\n" );
    puts( "      --verify=TYPE       Whether to perform verification of erasure" );
    puts( "                          (default: last)" );
    puts( "                          off   - Do not verify" );
    puts( "                          last  - Verify after the last pass" );
    puts( "                          all   - Verify every pass" );
    puts( "                          " );
    puts( "      --directio          Force direct I/O (O_DIRECT); fail if not supported" );
    puts( "      --cachedio          Force kernel cached I/O; never attempt O_DIRECT" );
    puts( "      --io-mode=MODE      I/O mode: auto (default), direct, cached\n" );
    puts( "                          Please mind that HMG IS5 enhanced always verifies the" );
    puts( "                          last (PRNG) pass regardless of this option.\n" );
    puts( "  -m, --method=METHOD     The wiping method. See man page for more details." );
    puts( "                          (default: dodshort)" );
    puts( "                          dod522022m / dod       - 7 pass DOD 5220.22-M method" );
    puts( "                          dodshort / dod3pass    - 3 pass DOD method" );
    puts( "                          gutmann                - Peter Gutmann's Algorithm" );
    puts( "                          ops2                   - RCMP TSSIT OPS-II" );
    puts( "                          random / prng / stream - PRNG Stream" );
    puts( "                          zero / quick           - Overwrite with zeros" );
    puts( "                          one                    - Overwrite with ones (0xFF)" );
    puts( "                          verify_zero            - Verifies disk is zero filled" );
    puts( "                          verify_one             - Verifies disk is 0xFF filled" );
    puts( "                          is5enh                 -  HMG IS5 enhanced\n" );
    puts( "                          bruce7                 -  Schneier Bruce 7-pass mixed pattern\n" );
    puts( "                          bmb                    -  BMB21-2019 mixed pattern\n" );
    puts( "  -l, --logfile=FILE      Filename to log to. Default is STDOUT\n" );
    puts( "  -P, --PDFreportpath=PATH Path to write PDF reports to. Default is \".\"" );
    puts( "                           If set to \"noPDF\" no PDF reports are written.\n" );
    puts( "  -p, --prng=METHOD        PRNG option "
          "(mersenne|twister|isaac|isaac64|add_lagg_fibonacci_prng|xoroshiro256_prng|aes_ctr_prng)\n" );
    puts( "  --prng=auto              (default)" );
    puts( "      Automatically benchmark all available PRNGs at startup and" );
    puts( "      select the fastest one for the current hardware." );
    puts( "" );

    puts( "  --prng=default" );
    puts( "      Disable auto-selection and use the built-in default PRNG choice" );
    puts( "      (CPU-based heuristic; no benchmarking)." );
    puts( "      Alias: --prng=manual" );
    puts( "" );
    puts( "  --prng-benchmark" );
    puts( "      Run a RAM-only PRNG throughput benchmark and exit." );
    puts( "      Prints a sorted leaderboard (MB/s). No wipe is performed." );
    puts( "" );
    puts( "  --prng-bench-seconds=N" );
    puts( "      Seconds per PRNG during benchmarking (default: 1.0)." );
    puts( "      For --prng=auto this is automatically reduced unless set." );

    puts( "  -q, --quiet              Anonymize logs and the GUI by removing unique data, i.e." );
    puts( "                           serial numbers, LU WWN Device ID, and SMBIOS/DMI data." );
    puts( "                           XXXXXX = S/N exists, ????? = S/N not obtainable\n" );
    puts( "  -r, --rounds=NUM         Number of times to wipe the device using the selected" );
    puts( "                           method. (default: 1)\n" );
    puts( "      --noblank            Do NOT blank disk after wipe." );
    puts( "                           (default is to complete a final blank pass)\n" );
    puts( "      --nowait             Do NOT wait for a key before exiting." );
    puts( "                           (default is to wait)\n" );
    puts( "      --nosignals          Do NOT allow signals to interrupt a wipe." );
    puts( "                           (default is to allow)\n" );
    puts( "      --nogui              Do NOT show the GUI interface. Automatically invokes" );
    puts( "                           the nowait option. Must be used with the --autonuke" );
    puts( "                           option. Send SIGUSR1 to log current stats.\n" );
    puts( "      --nousb              Do NOT show or wipe any USB devices whether in GUI" );
    puts( "                           mode, --nogui or --autonuke modes.\n" );
    puts( "      --pdftag             Enables a field on the PDF that holds a tag that\n" );
    puts( "                           identifies the host computer\n" );
    puts( "  -e, --exclude=DEVICES    Up to ten comma separated devices to be excluded." );
    puts( "                           --exclude=/dev/sdc" );
    puts( "                           --exclude=/dev/sdc,/dev/sdd" );
    puts( "                           --exclude=/dev/sdc,/dev/sdd,/dev/mapper/cryptswap1\n" );
    puts( "                           --exclude=/dev/disk/by-id/ata-XXXXXXXX" );
    puts( "                           --exclude=/dev/disk/by-path/pci-0000:00:17.0-ata-1\n" );
    puts( "" );
}
