diff --git a/main.c b/main.c index 4a65c868..56a191c3 100644 --- a/main.c +++ b/main.c @@ -20,10 +20,12 @@ #include #include #include +#include +#include #ifndef WIN32 #include #endif -#include +#include #include #include #include "compat.h" @@ -125,7 +127,6 @@ static int opt_queue = 0; int opt_vectors; int opt_worksize; int opt_scantime = 60; -static json_t *opt_config; static const bool opt_time = true; #ifdef WANT_X8664_SSE2 static enum sha256_algos opt_algo = ALGO_SSE2_64; @@ -154,142 +155,255 @@ static int accepted, rejected; int hw_errors; static int total_queued; -struct option_help { - const char *name; - const char *helptext; -}; +static void applog_and_exit(const char *fmt, ...) +{ + va_list ap; -static struct option_help options_help[] = { - { "help", - "(-h) Display this help text" }, + va_start(ap, fmt); + vapplog(LOG_ERR, fmt, ap); + va_end(ap); + exit(1); +} - { "algo XXX", - "(-a XXX) Specify sha256 implementation:\n" - "\tc\t\tLinux kernel sha256, implemented in C (default)" -#ifdef WANT_SSE2_4WAY - "\n\t4way\t\ttcatm's 4-way SSE2 implementation" -#endif -#ifdef WANT_VIA_PADLOCK - "\n\tvia\t\tVIA padlock implementation" -#endif - "\n\tcryptopp\tCrypto++ C/C++ implementation" -#ifdef WANT_CRYPTOPP_ASM32 - "\n\tcryptopp_asm32\tCrypto++ 32-bit assembler implementation" -#endif -#ifdef WANT_X8664_SSE2 - "\n\tsse2_64\t\tSSE2 implementation for x86_64 machines" -#endif - }, +/* FIXME: Use asprintf for better errors. */ +static char *set_algo(const char *arg, enum sha256_algos *algo) +{ + enum sha256_algos i; - { "config FILE", - "(-c FILE) JSON-format configuration file (default: none)\n" - "See example-cfg.json for an example configuration." }, + for (i = 0; i < ARRAY_SIZE(algo_names); i++) { + if (algo_names[i] && !strcmp(arg, algo_names[i])) { + *algo = i; + return NULL; + } + } + return "Unknown algorithm"; +} - { "cpu-threads N", - "(-t N) Number of miner CPU threads (default: number of processors or 0 if GPU mining)" }, +static void show_algo(char buf[OPT_SHOW_LEN], const enum sha256_algos *algo) +{ + strncpy(buf, algo_names[*algo], OPT_SHOW_LEN); +} - { "debug", - "(-D) Enable debug output (default: off)" }, +static char *set_int_range(const char *arg, int *i, int min, int max) +{ + char *err = opt_set_intval(arg, i); + if (err) + return err; -#ifdef HAVE_OPENCL - { "gpu-threads N", - "(-g N) Number of threads per-GPU (0 - 10, default: 2)" }, + if (*i < min || *i > max) + return "Value out of range"; - { "intensity N", - "(-I N) Intensity of GPU scanning (0 - 14, default 4)" }, -#endif - { "log N", - "(-l N) Interval in seconds between log output (default: 5)" }, + return NULL; +} -#ifdef HAVE_OPENCL - { "ndevs", - "(-n) Display number of detected GPUs and exit" }, -#endif - { "no-longpoll", - "Disable X-Long-Polling support (default: enabled)" }, +static char *set_int_0_to_9999(const char *arg, int *i) +{ + return set_int_range(arg, i, 0, 9999); +} - { "pass PASSWORD", - "(-p PASSWORD) Password for bitcoin JSON-RPC server " - "(default: " DEF_RPC_PASSWORD ")" }, +static char *set_int_0_to_14(const char *arg, int *i) +{ + return set_int_range(arg, i, 0, 14); +} - { "protocol-dump", - "(-P) Verbose dump of protocol-level activities (default: off)" }, +static char *set_int_0_to_10(const char *arg, int *i) +{ + return set_int_range(arg, i, 0, 10); +} - { "queue N", - "(-Q N) Number of extra work items to queue (0 - 10, default 0)" }, +static char *set_url(const char *arg, char **p) +{ + opt_set_charp(arg, p); + if (strncmp(arg, "http://", 7) && + strncmp(arg, "https://", 8)) + return "URL must start with http:// or https://"; - { "quiet", - "(-q) Disable per-thread hashmeter output (default: off)" }, + return NULL; +} - { "retries N", - "(-r N) Number of times to retry before giving up, if JSON-RPC call fails\n" - "\t(default: -1; use -1 for \"never\")" }, +static char *set_vector(const char *arg, int *i) +{ + char *err = opt_set_intval(arg, i); + if (err) + return err; - { "retry-pause N", - "(-R N) Number of seconds to pause, between retries\n" - "\t(default: 5)" }, + if (*i != 1 && *i != 2 && *i != 4) + return "Valid vectors are 1, 2 or 4"; + return NULL; +} + +static char *enable_debug(bool *flag) +{ + *flag = true; + /* Turn out verbose output, too. */ + opt_log_output = true; + return NULL; +} - { "scantime N", - "(-s N) Upper bound on time spent scanning current work,\n" - "\tin seconds. (default: 60)" }, +/* These options are available from config file or commandline */ +static struct opt_table opt_config_table[] = { + OPT_WITH_ARG("--algo|-a", + set_algo, show_algo, &opt_algo, + "Specify sha256 implementation:\n" + "\tc\t\tLinux kernel sha256, implemented in C" +#ifdef WANT_SSE2_4WAY + "\n\t4way\t\ttcatm's 4-way SSE2 implementation" +#endif +#ifdef WANT_VIA_PADLOCK + "\n\tvia\t\tVIA padlock implementation" +#endif + "\n\tcryptopp\tCrypto++ C/C++ implementation" +#ifdef WANT_CRYPTOPP_ASM32 + "\n\tcryptopp_asm32\tCrypto++ 32-bit assembler implementation" +#endif +#ifdef WANT_X8664_SSE2 + "\n\tsse2_64\t\tSSE2 implementation for x86_64 machines" +#endif + ), + OPT_WITH_ARG("--cpu-threads|-t", + set_int_0_to_9999, opt_show_intval, &opt_n_threads, + "Number of miner CPU threads"), + OPT_WITHOUT_ARG("--debug|-D", + enable_debug, &opt_debug, + "Enable debug output"), +#ifdef HAVE_OPENCL + OPT_WITH_ARG("--gpu-threads|-g", + set_int_0_to_10, opt_show_intval, &opt_g_threads, + "Number of threads per GPU (0 - 10)"), + OPT_WITH_ARG("--intensity|-I", + set_int_0_to_14, opt_show_intval, &scan_intensity, + "Intensity of GPU scanning (0 - 14)"), +#endif + OPT_WITH_ARG("--log|-l", + set_int_0_to_9999, opt_show_intval, &opt_log_interval, + "Interval in seconds between log output"), + OPT_WITHOUT_ARG("--no-longpoll", + opt_set_invbool, &want_longpoll, + "Disable X-Long-Polling support"), + OPT_WITH_ARG("--pass|-p", + opt_set_charp, NULL, &rpc_pass, + "Password for bitcoin JSON-RPC server"), + OPT_WITHOUT_ARG("--protocol-dump|-P", + opt_set_bool, &opt_protocol, + "Verbose dump of protocol-level activities"), + OPT_WITH_ARG("--queue|-Q", + set_int_0_to_9999, opt_show_intval, &opt_queue, + "Number of extra work items to queue"), + OPT_WITHOUT_ARG("--quiet|-q", + opt_set_bool, &opt_quiet, + "Disable per-thread hashmeter output"), + OPT_WITH_ARG("--retries|-r", + opt_set_intval, opt_show_intval, &opt_retries, + "Number of times to retry before giving up, if JSON-RPC call fails (-1 means never)"), + OPT_WITH_ARG("--retry-pause|-R", + set_int_0_to_9999, opt_show_intval, &opt_fail_pause, + "Number of seconds to pause, between retries"), + OPT_WITH_ARG("--scan-time|-s", + set_int_0_to_9999, opt_show_intval, &opt_scantime, + "Upper bound on time spent scanning current work, in seconds"), #ifdef HAVE_SYSLOG_H - { "syslog", - "Use system log for output messages (default: standard error)" }, + OPT_WITHOUT_ARG("--syslog", + opt_set_bool, &use_syslog, + "Use system log for output messages (default: standard error)"), #endif + OPT_WITH_ARG("--url|-o", + set_url, opt_show_charp, &rpc_url, + "URL for bitcoin JSON-RPC server"), + OPT_WITH_ARG("--user|-u", + opt_set_charp, NULL, &rpc_user, + "Username for bitcoin JSON-RPC server"), +#ifdef HAVE_OPENCL + OPT_WITH_ARG("--vectors|-v", + set_vector, NULL, &opt_vectors, + "Override detected optimal vector width (1, 2 or 4)"), +#endif + OPT_WITHOUT_ARG("--verbose", + opt_set_bool, &opt_log_output, + "Log verbose output to stderr as well as status output"), +#ifdef HAVE_OPENCL + OPT_WITH_ARG("--worksize|-w", + set_int_0_to_9999, opt_show_intval, &opt_worksize, + "Override detected optimal worksize"), +#endif + OPT_WITH_ARG("--userpass|-O", + opt_set_charp, NULL, &rpc_userpass, + "Username:Password pair for bitcoin JSON-RPC server"), + OPT_ENDTABLE +}; - { "url URL", - "(-o URL) URL for bitcoin JSON-RPC server " - "(default: " DEF_RPC_URL ")" }, - - { "userpass USERNAME:PASSWORD", - "(-O USERNAME:PASSWORD) Username:Password pair for bitcoin JSON-RPC server " - "(default: " DEF_RPC_USERPASS ")" }, +static char *parse_config(json_t *config) +{ + static char err_buf[200]; + json_t *val; + struct opt_table *opt; + + for (opt = opt_config_table; opt->type != OPT_END; opt++) { + char *p, *name; + + /* We don't handle subtables. */ + assert(!(opt->type & OPT_SUBTABLE)); + + /* Pull apart the option name(s). */ + name = strdup(opt->names); + for (p = strtok(name, "|"); p; p = strtok(NULL, "|")) { + char *err; + /* Ignore short options. */ + if (p[1] != '-') + continue; + + val = json_object_get(config, p+2); + if (!val) + continue; + + if ((opt->type & OPT_HASARG) && json_is_string(val)) { + err = opt->cb_arg(json_string_value(val), + opt->u.arg); + } else if ((opt->type&OPT_NOARG) && json_is_true(val)) { + err = opt->cb(opt->u.arg); + } else { + err = "Invalid value"; + } + if (err) { + sprintf(err_buf, "Parsing JSON option %s: %s", + p, err); + return err_buf; + } + } + free(name); + } + return NULL; +} - { "user USERNAME", - "(-u USERNAME) Username for bitcoin JSON-RPC server " - "(default: " DEF_RPC_USERNAME ")" }, +static char *load_config(const char *arg, void *unused) +{ + json_error_t err; + json_t *config; - { "verbose", - "(-V) Log verbose output to stderr as well as status output (default: off)" }, + config = json_load_file(arg, &err); + if (!json_is_object(config)) + return "JSON decode of file failed"; + /* Parse the config now, so we can override it. That can keep pointers + * so don't free config object. */ + return parse_config(config); +} + +/* These options are available from commandline only */ +static struct opt_table opt_cmdline_table[] = { + OPT_WITH_ARG("--config|-c", + load_config, NULL, NULL, + "Load a JSON-format configuration file\n" + "See example-cfg.json for an example configuration."), + OPT_WITHOUT_ARG("--help|-h", + opt_usage_and_exit, #ifdef HAVE_OPENCL - { "vectors N", - "(-v N) Override detected optimal vector width (default: detected, 1,2 or 4)" }, - - { "worksize N", - "(-w N) Override detected optimal worksize (default: detected)" }, -#endif -}; - -static struct option options[] = { - { "algo", 1, NULL, 'a' }, - { "config", 1, NULL, 'c' }, - { "cpu-threads", 1, NULL, 't' }, - { "gpu-threads", 1, NULL, 'g' }, - { "debug", 0, NULL, 'D' }, - { "help", 0, NULL, 'h' }, - { "intensity", 1, NULL, 'I' }, - { "log", 1, NULL, 'l' }, - { "ndevs", 0, NULL, 'n' }, - { "no-longpoll", 0, NULL, 1003 }, - { "pass", 1, NULL, 'p' }, - { "protocol-dump", 0, NULL, 'P' }, - { "queue", 1, NULL, 'Q' }, - { "quiet", 0, NULL, 'q' }, - { "retries", 1, NULL, 'r' }, - { "retry-pause", 1, NULL, 'R' }, - { "scantime", 1, NULL, 's' }, -#ifdef HAVE_SYSLOG_H - { "syslog", 0, NULL, 1004 }, + "\nBuilt with CPU and GPU mining support.\n\n", +#else + "\nBuilt with CPU mining support only.\n\n", #endif - { "url", 1, NULL, 'o' }, - { "user", 1, NULL, 'u' }, - { "verbose", 0, NULL, 'V' }, - { "vectors", 1, NULL, 'v' }, - { "worksize", 1, NULL, 'w' }, - { "userpass", 1, NULL, 'O' }, - {0, 0, 0, 0} + "Print this message"), + OPT_ENDTABLE }; static bool jobj_binary(const json_t *obj, const char *key, @@ -1313,221 +1427,6 @@ static void *wakeup_thread(void *userdata) return NULL; } -static void show_usage(void) -{ - int i; - - printf("cgminer version %s\n", VERSION); -#ifdef HAVE_OPENCL - printf("Built with CPU and GPU mining support.\n\n"); -#else - printf("Built with CPU mining support only.\n\n"); -#endif - printf("Usage:\tcgminer [options]\n\nSupported options:\n"); - for (i = 0; i < ARRAY_SIZE(options_help); i++) { - struct option_help *h; - - h = &options_help[i]; - printf("--%s\n%s\n\n", h->name, h->helptext); - } - - exit(1); -} - -static void parse_arg (int key, char *arg) -{ - int v, i; - - switch(key) { - case 'a': - for (i = 0; i < ARRAY_SIZE(algo_names); i++) { - if (algo_names[i] && - !strcmp(arg, algo_names[i])) { - opt_algo = i; - break; - } - } - if (i == ARRAY_SIZE(algo_names)) - show_usage(); - break; - case 'c': { - json_error_t err; - if (opt_config) - json_decref(opt_config); - opt_config = json_load_file(arg, &err); - if (!json_is_object(opt_config)) { - applog(LOG_ERR, "JSON decode of %s failed", arg); - show_usage(); - } - break; - } - case 'g': - v = atoi(arg); - if (v < 0 || v > 10) - show_usage(); - - opt_g_threads = v; - break; - case 'D': - opt_debug = true; - opt_log_output = true; - break; - case 'I': - v = atoi(arg); - if (v < 0 || v > 14) /* sanity check */ - show_usage(); - scan_intensity = v; - break; - case 'l': - v = atoi(arg); - if (v < 0 || v > 9999) /* sanity check */ - show_usage(); - opt_log_interval = v; - break; - case 'n': - opt_log_output = true; - opt_ndevs = true; - break; - case 'p': - free(rpc_pass); - rpc_pass = strdup(arg); - break; - case 'P': - opt_protocol = true; - break; - case 'Q': - v = atoi(arg); - if (v < 0 || v > 10) - show_usage(); - - opt_queue = v; - break; - case 'q': - opt_quiet = true; - break; - case 'r': - v = atoi(arg); - if (v < -1 || v > 9999) /* sanity check */ - show_usage(); - - opt_retries = v; - break; - case 'R': - v = atoi(arg); - if (v < 1 || v > 9999) /* sanity check */ - show_usage(); - - opt_fail_pause = v; - break; - case 's': - v = atoi(arg); - if (v < 1 || v > 9999) /* sanity check */ - show_usage(); - - opt_scantime = v; - break; - case 't': - v = atoi(arg); - if (v < 0 || v > 9999) /* sanity check */ - show_usage(); - - opt_n_threads = v; - break; - case 'u': - free(rpc_user); - rpc_user = strdup(arg); - break; - case 'V': - opt_log_output = true; - break; - case 'v': - v = atoi(arg); - if (v != 1 && v != 2 && v != 4) - show_usage(); - - opt_vectors = v; - break; - case 'w': - v = atoi(arg); - if (v < 1 || v > 9999) /* sanity check */ - show_usage(); - - opt_worksize = v; - break; - case 'o': /* --url */ - if (strncmp(arg, "http://", 7) && - strncmp(arg, "https://", 8)) - show_usage(); - - free(rpc_url); - rpc_url = strdup(arg); - break; - case 'O': /* --userpass */ - if (!strchr(arg, ':')) - show_usage(); - - free(rpc_userpass); - rpc_userpass = strdup(arg); - break; - case 1003: - want_longpoll = false; - break; - case 1004: - use_syslog = true; - break; - case '?': - default: - show_usage(); - } -} - -static void parse_config(void) -{ - int i; - json_t *val; - - if (!json_is_object(opt_config)) - return; - - for (i = 0; i < ARRAY_SIZE(options); i++) { - if (!options[i].name) - break; - if (!strcmp(options[i].name, "config")) - continue; - - val = json_object_get(opt_config, options[i].name); - if (!val) - continue; - - if (options[i].has_arg && json_is_string(val)) { - char *s = strdup(json_string_value(val)); - if (!s) - break; - parse_arg(options[i].val, s); - free(s); - } else if (!options[i].has_arg && json_is_true(val)) - parse_arg(options[i].val, ""); - else - applog(LOG_ERR, "JSON option %s invalid", - options[i].name); - } -} - -static void parse_cmdline(int argc, char *argv[]) -{ - int key; - - while (1) { - key = getopt_long(argc, argv, "a:c:Dg:I:l:no:O:p:PQ:qr:R:s:t:u:Vv:w:h?", options, NULL); - if (key < 0) - break; - - parse_arg(key, optarg); - } - - parse_config(); -} - int main (int argc, char *argv[]) { struct thr_info *thr; @@ -1553,7 +1452,16 @@ int main (int argc, char *argv[]) rpc_url = strdup(DEF_RPC_URL); /* parse command line */ - parse_cmdline(argc, argv); + opt_register_table(opt_config_table, + "Options for both config file and command line"); + opt_register_table(opt_cmdline_table, + "Options for command line only"); + + opt_parse(&argc, argv, applog_and_exit); + if (argc != 1) { + applog(LOG_ERR, "Unexpected extra commandline arguments"); + return 1; + } #ifdef HAVE_OPENCL if (opt_ndevs) {