// See the file "COPYING" in the main distribution directory for copyright.

#include "zeek/Options.h"

#include "zeek/zeek-config.h"

#if defined(HAVE_GETOPT_H) && ! defined(_MSC_VER)
#include <getopt.h>
#endif

#include <unistd.h>
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <sstream>

#include "zeek/3rdparty/bsd-getopt-long.h"
#include "zeek/ScriptProfile.h"
#include "zeek/logging/writers/ascii/Ascii.h"
#include "zeek/script_opt/ScriptOpt.h"

namespace zeek {

void Options::filter_supervisor_options() {
    pcap_filter = {};
    signature_files = {};
    pcap_output_file = {};
}

void Options::filter_supervised_node_options() {
    auto og = *this;
    *this = {};

    debug_log_streams = og.debug_log_streams;
    debug_script_tracing_file = og.debug_script_tracing_file;
    script_code_to_exec = og.script_code_to_exec;
    script_prefixes = og.script_prefixes;

    signature_re_level = og.signature_re_level;
    ignore_checksums = og.ignore_checksums;
    use_watchdog = og.use_watchdog;
    pseudo_realtime = og.pseudo_realtime;
    dns_mode = og.dns_mode;

    bare_mode = og.bare_mode;
    perftools_check_leaks = og.perftools_check_leaks;
    perftools_profile = og.perftools_profile;
    deterministic_mode = og.deterministic_mode;
    abort_on_scripting_errors = og.abort_on_scripting_errors;

    pcap_filter = og.pcap_filter;
    signature_files = og.signature_files;

    // TODO: These are likely to be handled in a node-specific or
    // use-case-specific way.  e.g. interfaces is already handled for the
    // "cluster" use-case, but don't have supervised-pcap-reading
    // functionality yet.
    /* interface = og.interface; */
    /* pcap_file = og.pcap_file; */

    pcap_output_file = og.pcap_output_file;
    random_seed_input_file = og.random_seed_input_file;
    random_seed_output_file = og.random_seed_output_file;
    process_status_file = og.process_status_file;

    plugins_to_load = og.plugins_to_load;
    scripts_to_load = og.scripts_to_load;
    script_options_to_set = og.script_options_to_set;
}

bool fake_dns() { return getenv("ZEEK_DNS_FAKE"); }

extern const char* zeek_version();

void usage(const char* prog, int code) {
    fprintf(stderr, "zeek version %s\n", zeek_version());

    fprintf(stderr, "usage: %s [options] [file ...]\n", prog);
    fprintf(stderr, "usage: %s --test [doctest-options] -- [options] [file ...]\n", prog);
    fprintf(stderr, "    <file>                          | Zeek script file, or read stdin\n");
    fprintf(stderr, "    -a|--parse-only                 | exit immediately after parsing scripts\n");
    fprintf(stderr, "    -b|--bare-mode                  | don't load scripts from the base/ directory\n");
    fprintf(stderr, "    -c|--capture-unprocessed <file> | write unprocessed packets to a tcpdump file\n");
    fprintf(stderr, "    -d|--debug-script               | activate Zeek script debugging\n");
    fprintf(stderr, "    -e|--exec <zeek code>           | augment loaded scripts by given code\n");
    fprintf(stderr, "    -f|--filter <filter>            | tcpdump filter\n");
    fprintf(stderr, "    -h|--help                       | command line help\n");
    fprintf(stderr, "    -i|--iface <interface>          | read from given interface (only one allowed)\n");
    fprintf(stderr, "    -p|--prefix <prefix>            | add given prefix to Zeek script file resolution\n");
    fprintf(stderr,
            "    -r|--readfile <readfile>        | read from given tcpdump file (only one "
            "allowed, pass '-' as the filename to read from stdin)\n");
    fprintf(stderr, "    -s|--rulefile <rulefile>        | read rules from given file\n");
    fprintf(stderr, "    -t|--tracefile <tracefile>      | activate execution tracing\n");
    fprintf(stderr, "    -u|--usage-issues               | find variable usage issues and exit\n");
    fprintf(stderr,
            "       --no-unused-warnings         | suppress warnings of unused "
            "functions/hooks/events\n");
    fprintf(stderr, "    -v|--version                    | print version and exit\n");
    fprintf(stderr, "    -V|--build-info                 | print build information and exit\n");
    fprintf(stderr, "    -w|--writefile <writefile>      | write to given tcpdump file\n");
#ifdef DEBUG
    fprintf(stderr,
            "    -B|--debug <dbgstreams>         | Enable debugging output for selected "
            "streams ('-B help' for help)\n");
#endif
    fprintf(stderr, "    -C|--no-checksums               | ignore checksums\n");
    fprintf(stderr, "    -D|--deterministic              | initialize random seeds to zero\n");
    fprintf(stderr,
            "    -E|--event-trace <file>         | generate a replayable event trace to "
            "the given file\n");
    fprintf(stderr, "    -F|--force-dns                  | force DNS\n");
    fprintf(stderr, "    -G|--load-seeds <file>          | load seeds from given file\n");
    fprintf(stderr, "    -H|--save-seeds <file>          | save seeds to given file\n");
    fprintf(stderr, "    -I|--print-id <ID name>         | print out given ID\n");
    fprintf(stderr,
            "    -N|--print-plugins              | print available plugins and exit (-NN "
            "for verbose)\n");
    fprintf(stderr,
            "    -O|--optimize <option>          | enable script optimization (use -O help "
            "for options)\n");
    fprintf(stderr,
            "    -0|--optimize-files=<pat>       | enable script optimization for all "
            "functions in files with names containing the given pattern\n");
    fprintf(stderr,
            "    -o|--optimize-funcs=<pat>       | enable script optimization for "
            "functions with names fully matching the given pattern\n");
    fprintf(stderr, "    -P|--prime-dns                  | prime DNS\n");
    fprintf(stderr, "    -Q|--time                       | print execution time summary to stderr\n");
    fprintf(stderr, "    -S|--debug-rules                | enable rule debugging\n");
    fprintf(stderr, "    -T|--re-level <level>           | set 'RE_level' for rules\n");
    fprintf(stderr, "    -U|--status-file <file>         | Record process status in file\n");
    fprintf(stderr, "    -W|--watchdog                   | activate watchdog timer\n");
    fprintf(stderr, "    -X|--zeekygen <cfgfile>         | generate documentation based on config file\n");

#ifdef USE_PERFTOOLS_DEBUG
    fprintf(stderr, "    -m|--mem-leaks                  | show leaks  [perftools]\n");
    fprintf(stderr, "    -M|--mem-profile                | record heap [perftools]\n");
#endif
    fprintf(stderr, "    --profile-scripts[=file]        | profile scripts to given file (default stdout)\n");
    fprintf(stderr,
            "    --profile-script-call-stacks    | add call stacks to profile output (requires "
            "--profile-scripts)\n");
    fprintf(stderr,
            "    --pseudo-realtime[=<speedup>]   | enable pseudo-realtime for performance "
            "evaluation (default 1)\n");
    fprintf(stderr, "    -j|--jobs                       | enable supervisor mode\n");

    fprintf(stderr,
            "    --test                          | run unit tests ('--test -h' for help, "
            "not available when built without ENABLE_ZEEK_UNIT_TESTS)\n");
    fprintf(stderr, "    $ZEEKPATH                       | file search path (%s)\n", util::zeek_path().c_str());
    fprintf(stderr, "    $ZEEK_PLUGIN_PATH               | plugin search path (%s)\n", util::zeek_plugin_path());
    fprintf(stderr, "    $ZEEK_PLUGIN_ACTIVATE           | plugins to always activate (%s)\n",
            util::zeek_plugin_activate());
    fprintf(stderr, "    $ZEEK_PREFIXES                  | prefix list (%s)\n", util::zeek_prefixes().c_str());
    fprintf(stderr, "    $ZEEK_DNS_FAKE                  | disable DNS lookups (%s)\n", fake_dns() ? "on" : "off");
    fprintf(stderr, "    $ZEEK_SEED_VALUES               | list of space separated seeds (%s)\n",
            getenv("ZEEK_SEED_VALUES") ? "set" : "not set");
    fprintf(stderr, "    $ZEEK_SEED_FILE                 | file to load seeds from (not set)\n");
    fprintf(stderr, "    $ZEEK_LOG_SUFFIX                | ASCII log file extension (.%s)\n",
            logging::writer::detail::Ascii::LogExt().c_str());
    fprintf(stderr,
            "    $ZEEK_PROFILER_FILE             | Output file for script execution "
            "statistics (not set)\n");
    fprintf(stderr, "    $ZEEK_DISABLE_ZEEKYGEN          | Disable Zeekygen documentation support (%s)\n",
            getenv("ZEEK_DISABLE_ZEEKYGEN") ? "set" : "not set");
    fprintf(stderr, "    $ZEEK_DNS_RESOLVER              | IPv4/IPv6 address of DNS resolver to use (%s)\n",
            getenv("ZEEK_DNS_RESOLVER") ? getenv("ZEEK_DNS_RESOLVER") :
                                          "not set, will use first IPv4 address from /etc/resolv.conf");
    fprintf(stderr,
            "    $ZEEK_DEBUG_LOG_STDERR          | Use stderr for debug logs generated via "
            "the -B flag");

    fprintf(stderr, "\n");

    exit(code);
}

static void print_analysis_help() {
    fprintf(stderr, "--optimize options when using ZAM:\n");
    fprintf(stderr, "    ZAM	execute scripts using ZAM and all optimizations\n");
    fprintf(stderr, "    help	print this list\n");
    fprintf(stderr, "    report-uncompilable	print names of functions that can't be compiled\n");
    fprintf(stderr, "\n  primarily for developers:\n");
    fprintf(stderr, "    dump-uds	dump use-defs to stdout; implies xform\n");
    fprintf(stderr, "    dump-xform	dump transformed scripts to stdout; implies xform\n");
    fprintf(stderr, "    dump-ZAM	dump generated ZAM code; implies gen-ZAM-code\n");
    fprintf(stderr, "    gen-ZAM-code	generate ZAM code (without turning on additional optimizations)\n");
    fprintf(stderr, "    inline	inline function calls\n");
    fprintf(stderr, "    no-inline	turn off inlining\n");
    fprintf(stderr, "    no-ZAM-opt	omit low-level ZAM optimization\n");
    fprintf(stderr, "    optimize-all	optimize all scripts, even inlined ones\n");
    fprintf(stderr, "    optimize-AST	optimize the (transformed) AST; implies xform\n");
    fprintf(stderr, "    profile-ZAM	generate to zprof.out a ZAM execution profile; implies -O ZAM\n");
    fprintf(stderr, "    report-recursive	report on recursive functions and exit\n");
    fprintf(stderr, "    validate-ZAM	perform internal assessment of synthesized ZAM instructions and exit\n");
    fprintf(stderr, "    xform	transform scripts to \"reduced\" form\n");

    fprintf(stderr, "\n--optimize options when generating C++:\n");
    fprintf(stderr, "    allow-cond	allow standalone compilation of functions influenced by conditionals\n");
    fprintf(stderr, "    gen-C++	generate C++ script bodies\n");
    fprintf(stderr, "    gen-standalone-C++	generate \"standalone\" C++ script bodies\n");
    fprintf(stderr, "    help	print this list\n");
    fprintf(stderr, "    report-C++	report available C++ script bodies and exit\n");
    fprintf(stderr, "    report-uncompilable	print names of functions that can't be compiled\n");
    fprintf(stderr, "    use-C++	use available C++ script bodies\n");
}

static void set_analysis_option(const char* opt, Options& opts) {
    auto& a_o = opts.analysis_options;

    if ( ! opt || util::streq(opt, "ZAM") ) {
        a_o.inliner = a_o.optimize_AST = a_o.activate = true;
        a_o.gen_ZAM = true;
        return;
    }

    if ( util::streq(opt, "help") ) {
        print_analysis_help();
        exit(0);
    }

    if ( util::streq(opt, "allow-cond") )
        a_o.allow_cond = true;
    else if ( util::streq(opt, "dump-uds") )
        a_o.activate = a_o.dump_uds = true;
    else if ( util::streq(opt, "dump-xform") )
        a_o.activate = a_o.dump_xform = true;
    else if ( util::streq(opt, "dump-ZAM") )
        a_o.activate = a_o.dump_ZAM = true;
    else if ( util::streq(opt, "gen-C++") )
        a_o.gen_CPP = true;
    else if ( util::streq(opt, "gen-standalone-C++") )
        a_o.gen_standalone_CPP = true;
    else if ( util::streq(opt, "gen-ZAM-code") )
        a_o.activate = a_o.gen_ZAM_code = true;
    else if ( util::streq(opt, "inline") )
        a_o.inliner = true;
    else if ( util::streq(opt, "no-inline") )
        a_o.no_inliner = true;
    else if ( util::streq(opt, "no-ZAM-opt") )
        a_o.activate = a_o.no_ZAM_opt = true;
    else if ( util::streq(opt, "optimize-all") )
        a_o.activate = a_o.compile_all = true;
    else if ( util::streq(opt, "optimize-AST") )
        a_o.activate = a_o.optimize_AST = true;
    else if ( util::streq(opt, "profile-ZAM") )
        a_o.activate = a_o.profile_ZAM = true;
    else if ( util::streq(opt, "report-C++") )
        a_o.report_CPP = true;
    else if ( util::streq(opt, "report-recursive") )
        a_o.inliner = a_o.report_recursive = true;
    else if ( util::streq(opt, "report-uncompilable") )
        a_o.report_uncompilable = true;
    else if ( util::streq(opt, "use-C++") )
        a_o.use_CPP = true;
    else if ( util::streq(opt, "validate-ZAM") )
        a_o.validate_ZAM = true;
    else if ( util::streq(opt, "xform") )
        a_o.activate = true;

    else {
        fprintf(stderr, "zeek: unrecognized -O/--optimize option: %s\n\n", opt);
        print_analysis_help();
        exit(1);
    }
}

Options parse_cmdline(int argc, char** argv) {
    Options rval;

    // When running unit tests, the first argument on the command line must be
    // --test, followed by doctest options. Optionally, users can use "--" as
    // separator to pass Zeek options afterwards:
    //
    //     zeek --test [doctest-options] -- [zeek-options]

    // Just locally filtering out the args for Zeek usage from doctest args.
    std::vector<std::string> zeek_args;

    if ( argc > 1 && strcmp(argv[1], "--test") == 0 ) {
#ifdef DOCTEST_CONFIG_DISABLE
        fprintf(stderr,
                "ERROR: C++ unit tests are disabled for this build.\n"
                "       Please re-compile with ENABLE_ZEEK_UNIT_TESTS "
                "to run the C++ unit tests.\n");
        exit(1);
#endif

        auto is_separator = [](const char* cstr) { return strcmp(cstr, "--") == 0; };
        auto first = argv;
        auto last = argv + argc;
        auto separator = std::find_if(first, last, is_separator);
        zeek_args.emplace_back(argv[0]);

        if ( separator != last ) {
            auto first_zeek_arg = std::next(separator);

            for ( auto i = first_zeek_arg; i != last; ++i )
                zeek_args.emplace_back(*i);
        }

        rval.run_unit_tests = true;

        for ( ptrdiff_t i = 0; i < std::distance(first, separator); ++i )
            rval.doctest_args.emplace_back(argv[i]);
    }
    else {
        if ( argc > 1 ) {
            auto endsWith = [](const std::string& str, const std::string& suffix) {
                return str.size() >= suffix.size() &&
                       0 == str.compare(str.size() - suffix.size(), suffix.size(), suffix);
            };

            auto i = 0;
            for ( ; i < argc && ! endsWith(argv[i], "--"); ++i ) {
                zeek_args.emplace_back(argv[i]);
            }

            if ( i < argc ) {
                // If a script is invoked with Zeek as the interpreter, the arguments provided
                // directly in the interpreter line of the script won't be broken apart in the
                // argv on Linux so we split it up here.
                if ( endsWith(argv[i], "--") && zeek_args.size() == 1 ) {
                    std::istringstream iss(argv[i]);
                    for ( std::string s; iss >> s; ) {
                        if ( ! endsWith(s, "--") ) {
                            zeek_args.emplace_back(s);
                        }
                    }
                }

                // There is an additional increment here to skip over the "--" if it was found.
                if ( endsWith(argv[i], "--") )
                    ++i;

                // The first argument after the double hyphens in implicitly a script name.
                rval.scripts_to_load.emplace_back(argv[i++]);

                // If there are more argument, grab them for script arguments
                for ( ; i < argc; ++i )
                    rval.script_args.emplace_back(argv[i]);
            }
        }
    }

    int profile_scripts = 0;
    int profile_script_call_stacks = 0;
    std::string profile_filename;
    int no_unused_warnings = 0;

    bool enable_script_profile = false;
    bool enable_script_profile_call_stacks = false;

    struct option long_opts[] = {
        {"parse-only", no_argument, nullptr, 'a'},
        {"bare-mode", no_argument, nullptr, 'b'},
        {"capture-unprocessed", required_argument, nullptr, 'c'},
        {"debug-script", no_argument, nullptr, 'd'},
        {"exec", required_argument, nullptr, 'e'},
        {"filter", required_argument, nullptr, 'f'},
        {"help", no_argument, nullptr, 'h'},
        {"iface", required_argument, nullptr, 'i'},
        {"zeekygen", required_argument, nullptr, 'X'},
        {"prefix", required_argument, nullptr, 'p'},
        {"readfile", required_argument, nullptr, 'r'},
        {"rulefile", required_argument, nullptr, 's'},
        {"tracefile", required_argument, nullptr, 't'},
        {"writefile", required_argument, nullptr, 'w'},
        {"usage-issues", no_argument, nullptr, 'u'},
        {"version", no_argument, nullptr, 'v'},
        {"build-info", no_argument, nullptr, 'V'},
        {"no-checksums", no_argument, nullptr, 'C'},
        {"force-dns", no_argument, nullptr, 'F'},
        {"deterministic", no_argument, nullptr, 'D'},
        {"event-trace", required_argument, nullptr, 'E'},
        {"load-seeds", required_argument, nullptr, 'G'},
        {"save-seeds", required_argument, nullptr, 'H'},
        {"print-plugins", no_argument, nullptr, 'N'},
        {"optimize", required_argument, nullptr, 'O'},
        {"optimize-funcs", required_argument, nullptr, 'o'},
        {"optimize-files", required_argument, nullptr, '0'},
        {"prime-dns", no_argument, nullptr, 'P'},
        {"time", no_argument, nullptr, 'Q'},
        {"debug-rules", no_argument, nullptr, 'S'},
        {"re-level", required_argument, nullptr, 'T'},
        {"watchdog", no_argument, nullptr, 'W'},
        {"print-id", required_argument, nullptr, 'I'},
        {"status-file", required_argument, nullptr, 'U'},
        {"debug", required_argument, nullptr, 'B'},

#ifdef USE_PERFTOOLS_DEBUG
        {"mem-leaks", no_argument, nullptr, 'm'},
        {"mem-profile", no_argument, nullptr, 'M'},
#endif

        {"profile-scripts", optional_argument, &profile_scripts, 1},
        {"profile-script-call-stacks", optional_argument, &profile_script_call_stacks, 1},
        {"no-unused-warnings", no_argument, &no_unused_warnings, 1},
        {"pseudo-realtime", optional_argument, nullptr, '~'},
        {"jobs", optional_argument, nullptr, 'j'},
        {"test", no_argument, nullptr, '#'},

        {nullptr, 0, nullptr, 0},
    };

    char opts[256];
    util::safe_strncpy(opts, "B:c:E:e:f:G:H:I:i:j::n:O:0:o:p:r:s:T:t:U:w:X:CDFMNPQSWabdhmuvV", sizeof(opts));

    int op;
    int long_optsind;
    opterr = 0;

    // getopt may permute the array, so need yet another array
    auto zargs = std::make_unique<char*[]>(zeek_args.size());

    for ( size_t i = 0; i < zeek_args.size(); ++i )
        zargs[i] = zeek_args[i].data();

    while ( (op = getopt_long(zeek_args.size(), zargs.get(), opts, long_opts, &long_optsind)) != EOF )
        switch ( op ) {
            case 'a': rval.parse_only = true; break;
            case 'b': rval.bare_mode = true; break;
            case 'c': rval.unprocessed_output_file = optarg; break;
            case 'd': rval.debug_scripts = true; break;
            case 'e': rval.script_code_to_exec = optarg; break;
            case 'f': rval.pcap_filter = optarg; break;
            case 'h': rval.print_usage = true; break;
            case 'i':
                if ( rval.interface ) {
                    fprintf(stderr, "ERROR: Only a single interface option (-i) is allowed.\n");
                    exit(1);
                }

                if ( rval.pcap_file ) {
                    fprintf(stderr, "ERROR: Using -i is not allow when reading a pcap file.\n");
                    exit(1);
                }

                rval.interface = optarg;
                break;
            case 'j':
                rval.supervisor_mode = true;
                if ( optarg ) {
                    // TODO: for supervised offline pcap reading, the argument is
                    // expected to be number of workers like "-j 4" or possibly a
                    // list of worker/proxy/logger counts like "-j 4,2,1"
                }
                break;
            case 'p': rval.script_prefixes.emplace_back(optarg); break;
            case 'r':
                if ( rval.pcap_file ) {
                    fprintf(stderr, "ERROR: Only a single readfile option (-r) is allowed.\n");
                    exit(1);
                }

                if ( rval.interface ) {
                    fprintf(stderr, "Using -r is not allowed when reading a live interface.\n");
                    exit(1);
                }

                rval.pcap_file = optarg;
                break;
            case 's': rval.signature_files.emplace_back(optarg); break;
            case 't': rval.debug_script_tracing_file = optarg; break;
            case 'u': ++rval.analysis_options.usage_issues; break;
            case 'v': rval.print_version = true; break;
            case 'V': rval.print_build_info = true; break;
            case 'w': rval.pcap_output_file = optarg; break;

            case 'B':
#ifdef DEBUG
                rval.debug_log_streams = optarg;
#else
                if ( util::streq(optarg, "help") ) {
                    fprintf(stderr, "debug streams unavailable\n");
                    exit(1);
                }
#endif
                break;

            case 'C': rval.ignore_checksums = true; break;
            case 'D': rval.deterministic_mode = true; break;
            case 'E': rval.event_trace_file = optarg; break;
            case 'F':
                if ( rval.dns_mode != detail::DNS_DEFAULT )
                    usage(zargs[0], 1);
                rval.dns_mode = detail::DNS_FORCE;
                break;
            case 'G': rval.random_seed_input_file = optarg; break;
            case 'H': rval.random_seed_output_file = optarg; break;
            case 'I': rval.identifier_to_print = optarg; break;
            case 'N': ++rval.print_plugins; break;
            case 'O': set_analysis_option(optarg, rval); break;
            case 'o': add_func_analysis_pattern(rval.analysis_options, optarg); break;
            case '0': add_file_analysis_pattern(rval.analysis_options, optarg); break;
            case 'P':
                if ( rval.dns_mode != detail::DNS_DEFAULT )
                    usage(zargs[0], 1);
                rval.dns_mode = detail::DNS_PRIME;
                break;
            case 'Q': rval.print_execution_time = true; break;
            case 'S': rval.print_signature_debug_info = true; break;
            case 'T': rval.signature_re_level = atoi(optarg); break;
            case 'U': rval.process_status_file = optarg; break;
            case 'W': rval.use_watchdog = true; break;
            case 'X': rval.zeekygen_config_file = optarg; break;

#ifdef USE_PERFTOOLS_DEBUG
            case 'm': rval.perftools_check_leaks = 1; break;
            case 'M': rval.perftools_profile = 1; break;
#endif

            case '~':
                rval.pseudo_realtime = 1.0;
                if ( optarg )
                    rval.pseudo_realtime = atof(optarg);
                break;

            case '#':
                fprintf(stderr, "ERROR: --test only allowed as first argument.\n");
                usage(zargs[0], 1);
                break;

            case 0:
                // This happens for long options that don't have
                // a short-option equivalent.
                if ( profile_scripts ) {
                    profile_filename = optarg ? optarg : "";
                    enable_script_profile = true;
                    profile_scripts = 0;
                }

                if ( profile_script_call_stacks ) {
                    enable_script_profile_call_stacks = true;
                    profile_script_call_stacks = 0;
                }

                if ( no_unused_warnings )
                    rval.no_unused_warnings = true;
                break;

            case '?':
            default: usage(zargs[0], 1); break;
        }

    if ( ! enable_script_profile && enable_script_profile_call_stacks )
        fprintf(stderr, "ERROR: --profile-scripts-traces requires --profile-scripts to be passed as well.\n");

    if ( enable_script_profile ) {
        activate_script_profiling(profile_filename.empty() ? nullptr : profile_filename.c_str(),
                                  enable_script_profile_call_stacks);
    }

    // Process remaining arguments. X=Y arguments indicate script
    // variable/parameter assignments. X::Y arguments indicate plugins to
    // activate/query. The remainder are treated as scripts to load.
    while ( optind < static_cast<int>(zeek_args.size()) ) {
        if ( strchr(zargs[optind], '=') )
            rval.script_options_to_set.emplace_back(zargs[optind++]);
        else if ( strstr(zargs[optind], "::") )
            rval.plugins_to_load.emplace(zargs[optind++]);
        else
            rval.scripts_to_load.emplace_back(zargs[optind++]);
    }

    auto canonify_script_path = [](std::string* path) {
        if ( path->empty() )
            return;

        *path = util::detail::normalize_path(*path);

        if ( (*path)[0] == '/' || (*path)[0] == '~' )
            // Absolute path
            return;

        if ( (*path)[0] != '.' ) {
            // Look up file in ZEEKPATH
            auto res = util::find_script_file(*path, util::zeek_path());

            if ( res.empty() ) {
                fprintf(stderr, "failed to locate script: %s\n", path->data());
                exit(1);
            }

            *path = res;

            if ( (*path)[0] == '/' || (*path)[0] == '~' )
                // Now an absolute path
                return;
        }

        // Need to translate relative path to absolute.
        char cwd[PATH_MAX];

        if ( ! getcwd(cwd, sizeof(cwd)) ) {
            fprintf(stderr, "failed to get current directory: %s\n", strerror(errno));
            exit(1);
        }

        *path = std::string(cwd) + "/" + *path;
    };

    if ( rval.supervisor_mode ) {
        // Translate any relative paths supplied to supervisor into absolute
        // paths for use by supervised nodes since they have the option to
        // operate out of a different working directory.
        for ( auto& s : rval.scripts_to_load )
            canonify_script_path(&s);
    }

    return rval;
}

} // namespace zeek
