Browse Source

Merge branch 'httpsrv' into bfgminer

Conflicts:
	util.h
Luke Dashjr 12 years ago
parent
commit
0c2ff3fb7b
14 changed files with 526 additions and 28 deletions
  1. 7 0
      Makefile.am
  2. 3 0
      README
  3. 7 0
      README.RPC
  4. 12 0
      api.c
  5. 19 0
      configure.ac
  6. 5 0
      deviceapi.c
  7. 2 0
      deviceapi.h
  8. 272 0
      driver-getwork.c
  9. 98 0
      httpsrv.c
  10. 7 0
      httpsrv.h
  11. 64 27
      miner.c
  12. 3 0
      miner.h
  13. 1 1
      util.c
  14. 26 0
      util.h

+ 7 - 0
Makefile.am

@@ -83,6 +83,13 @@ endif
 
 bfgminer_SOURCES	+= logging.c
 
+if USE_LIBMICROHTTPD
+bfgminer_SOURCES += httpsrv.c httpsrv.h driver-getwork.c
+bfgminer_LDADD += $(libmicrohttpd_LIBS)
+bfgminer_LDFLAGS += $(libmicrohttpd_LDFLAGS)
+bfgminer_CPPFLAGS += $(libmicrohttpd_CFLAGS)
+endif
+
 
 # GPU sources, TODO: make them selectable
 # the GPU portion extracted from original main.c

+ 3 - 0
README

@@ -130,6 +130,8 @@ BFGMiner specific configuration options:
 	                           (default disabled)
 	--without-sensors       Build with libsensors monitoring (default enabled)
 	--without-curses        Compile support for curses TUI (default enabled)
+	--without-libmicrohttpd Compile support for libmicrohttpd getwork server
+	                        (default enabled)
 	--without-libudev       Autodetect FPGAs using libudev (default enabled)
 
 Basic *nix build instructions:
@@ -180,6 +182,7 @@ Options for both config file and command line:
 --debuglog          Enable debug logging
 --device|-d <arg>   Select device to use, one value, range and/or comma separated (e.g. 0-2,4) default: all
 --disable-rejecting Automatically disable pools that continually reject shares
+--http-port <arg>   Port number to listen on for HTTP getwork miners (-1 means disabled) (default: -1)
 --expiry|-E <arg>   Upper bound on how many seconds after getting work we consider a share from it stale (w/o longpoll active) (default: 120)
 --expiry-lp <arg>   Upper bound on how many seconds after getting work we consider a share from it stale (with longpoll active) (default: 3600)
 --failover-only     Don't leak work to backup pools when primary pool is lagging

+ 7 - 0
README.RPC

@@ -427,6 +427,13 @@ api-example.py - a Python script to access the API
 Feature Changelog for external applications using the API:
 
 
+API V1.25.3
+
+Modified API commands:
+ 'setconfig' - add 'http-port' number
+
+----------
+
 API V1.25.2
 
 Modified API commands:

+ 12 - 0
api.c

@@ -26,6 +26,9 @@
 #include <sys/types.h>
 
 #include "compat.h"
+#ifdef USE_LIBMICROHTTPD
+#include "httpsrv.h"
+#endif
 #include "miner.h"
 #include "util.h"
 #include "driver-cpu.h" /* for algo_names[], TODO: re-factor dependency */
@@ -3092,6 +3095,15 @@ static void setconfig(struct io_data *io_data, __maybe_unused SOCKETTYPE c, char
 		opt_scantime = value;
 	else if (strcasecmp(param, "expiry") == 0)
 		opt_expiry = value;
+#ifdef USE_LIBMICROHTTPD
+	else if (strcasecmp(param, "http-port") == 0)
+	{
+		httpsrv_stop();
+		httpsrv_port = value;
+		if (httpsrv_port != -1)
+			httpsrv_start(httpsrv_port);
+	}
+#endif
 	else {
 		message(io_data, MSG_UNKCON, 0, param, isjson);
 		return;

+ 19 - 0
configure.ac

@@ -330,6 +330,25 @@ if test "x$avalon" = xyes; then
 fi
 AM_CONDITIONAL([HAS_AVALON], [test x$avalon = xyes])
 
+httpsrv=auto
+AC_ARG_WITH([libmicrohttpd],
+	[AC_HELP_STRING([--without-libmicrohttpd],[Compile support for libmicrohttpd getwork server (default enabled)])],
+	[httpsrv=$withval]
+)
+if test "x$httpsrv" != "xno"; then
+	PKG_CHECK_MODULES([libmicrohttpd],[libmicrohttpd],[
+		AC_DEFINE([USE_LIBMICROHTTPD],[1],[Defined to 1 if libmicrohttpd support is wanted])
+		httpsrv=yes
+	],[
+		if test "x$httpsrv" = "xyes"; then
+			AC_MSG_ERROR([Unable to find libmicrohttpd])
+		else
+			AC_MSG_WARN([libmicrohttpd not found; getwork proxy will be unavailable])
+		fi
+	])
+fi
+AM_CONDITIONAL([USE_LIBMICROHTTPD], [test x$httpsrv = xyes])
+
 AC_ARG_ENABLE([modminer],
 	[AC_HELP_STRING([--disable-modminer],[Compile support for ModMiner (default enabled)])],
 	[modminer=$enableval],

+ 5 - 0
deviceapi.c

@@ -672,6 +672,11 @@ bool add_cgpu(struct cgpu_info *cgpu)
 	return true;
 }
 
+void add_cgpu_live(void *p)
+{
+	add_cgpu(p);
+}
+
 int _serial_detect(struct device_drv *api, detectone_func_t detectone, autoscan_func_t autoscan, int flags)
 {
 	struct string_elist *iter, *tmp;

+ 2 - 0
deviceapi.h

@@ -32,6 +32,8 @@ extern void minerloop_queue(struct thr_info *);
 
 extern void *miner_thread(void *);
 
+extern void add_cgpu_live(void*);
+
 typedef bool(*detectone_func_t)(const char*);
 typedef int(*autoscan_func_t)();
 

+ 272 - 0
driver-getwork.c

@@ -0,0 +1,272 @@
+/*
+ * Copyright 2013 Luke Dashjr
+ *
+ * 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; either version 3 of the License, or (at your option)
+ * any later version.  See COPYING for more details.
+ */
+
+#include "config.h"
+
+#include <stdint.h>
+#include <sys/types.h>
+#include <sys/select.h>
+#include <sys/socket.h>
+
+#include <jansson.h>
+#include <microhttpd.h>
+#include <uthash.h>
+
+#include "deviceapi.h"
+#include "miner.h"
+
+struct device_drv getwork_drv;
+
+struct getwork_client {
+	char *username;
+	struct cgpu_info *cgpu;
+	struct work *work;
+	struct timeval tv_hashes_done;
+	
+	UT_hash_handle hh;
+};
+
+static
+struct getwork_client *getwork_clients;
+static
+pthread_mutex_t getwork_clients_mutex;
+
+// TODO: X-Hashes-Done?
+// TODO: TUI manage username display
+// TODO: block getworks if disabled?
+// TODO: maybe reject known-stale shares?
+
+static
+void prune_worklog()
+{
+	struct getwork_client *client, *tmp;
+	struct work *work, *tmp2;
+	
+	mutex_lock(&getwork_clients_mutex);
+	HASH_ITER(hh, getwork_clients, client, tmp)
+	{
+		HASH_ITER(hh, client->work, work, tmp2)
+		{
+			if (!stale_work(work, true))
+				break;
+			HASH_DEL(client->work, work);
+			free_work(work);
+		}
+	}
+	mutex_unlock(&getwork_clients_mutex);
+}
+
+static
+pthread_t prune_worklog_pth;
+
+static
+void *prune_worklog_thread(void *userdata)
+{
+	struct cgpu_info *cgpu = userdata;
+	
+	pthread_detach(pthread_self());
+	RenameThread("SGW_pruner");
+	
+	while (!cgpu->shutdown)
+	{
+		prune_worklog();
+		sleep(60);
+	}
+	return NULL;
+}
+
+static
+void getwork_init()
+{
+	mutex_init(&getwork_clients_mutex);
+}
+
+static
+void getwork_first_client()
+{
+	pthread_create(&prune_worklog_pth, NULL, prune_worklog_thread, getwork_clients);
+}
+
+static
+int getwork_error(struct MHD_Connection *conn, int16_t errcode, const char *errmsg, const char *idstr, size_t idstr_sz)
+{
+	size_t replysz = 0x40 + strlen(errmsg) + idstr_sz;
+	char * const reply = malloc(replysz);
+	replysz = snprintf(reply, replysz, "{\"result\":null,\"error\":{\"code\":%d,\"message\":\"%s\"},\"id\":%s}", errcode, errmsg, idstr ?: "0");
+	struct MHD_Response * const resp = MHD_create_response_from_buffer(replysz, reply, MHD_RESPMEM_MUST_FREE);
+	const int ret = MHD_queue_response(conn, 500, resp);
+	MHD_destroy_response(resp);
+	return ret;
+}
+
+int handle_getwork(struct MHD_Connection *conn, bytes_t *upbuf)
+{
+	static bool _init = false, b;
+	struct getwork_client *client;
+	struct MHD_Response *resp;
+	char *user, *idstr = NULL, *submit = NULL;
+	size_t idstr_sz = 1;
+	struct cgpu_info *cgpu;
+	struct thr_info *thr;
+	json_t *json = NULL, *j2;
+	json_error_t jerr;
+	struct work *work;
+	char *reply;
+	int ret;
+	
+	if (unlikely(!_init))
+	{
+		_init = true;
+		getwork_init();
+	}
+	
+	user = MHD_basic_auth_get_username_password(conn, NULL);
+	if (!user)
+	{
+		static const char fail[] = "Please provide a username\n";
+		resp = MHD_create_response_from_buffer(sizeof(fail)-1, (char*)fail, MHD_RESPMEM_PERSISTENT);
+		return MHD_queue_basic_auth_fail_response(conn, PACKAGE, resp);
+	}
+	
+	if (bytes_len(upbuf))
+	{
+		bytes_nullterminate(upbuf);
+		json = JSON_LOADS((char*)bytes_buf(upbuf), &jerr);
+		if (!json)
+		{
+			ret = getwork_error(conn, -32700, "JSON parse error", idstr, idstr_sz);
+			goto out;
+		}
+		j2 = json_object_get(json, "id");
+		if (j2)
+		{
+			idstr = json_dumps_ANY(j2, 0);
+			idstr_sz = strlen(idstr);
+		}
+		if (strcmp("getwork", bfg_json_obj_string(json, "method", "getwork")))
+		{
+			ret = getwork_error(conn, -32601, "Only getwork supported", idstr, idstr_sz);
+			goto out;
+		}
+		j2 = json_object_get(json, "params");
+		submit = j2 ? __json_array_string(j2, 0) : NULL;
+	}
+	
+	mutex_lock(&getwork_clients_mutex);
+	HASH_FIND_STR(getwork_clients, user, client);
+	if (!client)
+	{
+		cgpu = malloc(sizeof(*cgpu));
+		*cgpu = (struct cgpu_info){
+			.drv = &getwork_drv,
+			.threads = 0,
+		};
+		if (unlikely(!create_new_cgpus(add_cgpu_live, cgpu)))
+		{
+			free(cgpu);
+			ret = getwork_error(conn, -32603, "Failed creating new cgpu", idstr, idstr_sz);
+			goto out;
+		}
+		client = malloc(sizeof(*client));
+		*client = (struct getwork_client){
+			.username = user,
+			.cgpu = cgpu,
+		};
+		
+		b = HASH_COUNT(getwork_clients);
+		HASH_ADD_KEYPTR(hh, getwork_clients, client->username, strlen(user), client);
+		mutex_unlock(&getwork_clients_mutex);
+		
+		if (!b)
+			getwork_first_client();
+	}
+	else
+	{
+		mutex_unlock(&getwork_clients_mutex);
+		free(user);
+		cgpu = client->cgpu;
+	}
+	user = NULL;
+	thr = cgpu->thr[0];
+	
+	if (submit)
+	{
+		unsigned char hdr[80];
+		const char *rejreason;
+		uint32_t nonce;
+		struct timeval tv_now, tv_delta;
+		
+		// NOTE: expecting hex2bin to fail since we only parse 80 of the 128
+		hex2bin(hdr, submit, 80);
+		nonce = le32toh(*(uint32_t *)&hdr[76]);
+		HASH_FIND(hh, client->work, hdr, 76, work);
+		if (!work)
+		{
+			inc_hw_errors2(thr, NULL, &nonce);
+			rejreason = "unknown-work";
+		}
+		else
+		{
+			if (!submit_nonce(thr, work, nonce))
+				rejreason = "H-not-zero";
+			else
+				rejreason = NULL;
+			
+			timer_set_now(&tv_now);
+			timersub(&tv_now, &client->tv_hashes_done, &tv_delta);
+			client->tv_hashes_done = tv_now;
+			hashes_done(thr, 0x100000000, &tv_delta, NULL);
+		}
+		
+		reply = malloc(36 + idstr_sz);
+		const size_t replysz =
+		sprintf(reply, "{\"error\":null,\"result\":%s,\"id\":%s}",
+		        rejreason ? "false" : "true", idstr);
+		resp = MHD_create_response_from_buffer(replysz, reply, MHD_RESPMEM_MUST_FREE);
+		if (rejreason)
+			MHD_add_response_header(resp, "X-Reject-Reason", rejreason);
+		ret = MHD_queue_response(conn, 200, resp);
+		MHD_destroy_response(resp);
+		goto out;
+	}
+	
+	{
+		const size_t replysz = 451 + idstr_sz;
+		
+		work = get_work(thr);
+		reply = malloc(replysz);
+		memcpy(reply, "{\"error\":null,\"result\":{\"target\":\"ffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000\",\"data\":\"", 108);
+		bin2hex(&reply[108], work->data, 128);
+		memcpy(&reply[364], "\",\"midstate\":\"", 14);
+		bin2hex(&reply[378], work->midstate, 32);
+		memcpy(&reply[442], "\"},\"id\":", 8);
+		memcpy(&reply[450], idstr ?: "0", idstr_sz);
+		memcpy(&reply[450 + idstr_sz], "}", 1);
+		
+		timer_set_now(&work->tv_work_start);
+		HASH_ADD_KEYPTR(hh, client->work, work->data, 76, work);
+		
+		resp = MHD_create_response_from_buffer(replysz, reply, MHD_RESPMEM_MUST_FREE);
+		ret = MHD_queue_response(conn, 200, resp);
+		MHD_destroy_response(resp);
+	}
+	
+out:
+	free(user);
+	free(idstr);
+	if (json)
+		json_decref(json);
+	return ret;
+}
+
+struct device_drv getwork_drv = {
+	.dname = "getwork",
+	.name = "SGW",
+// 	.get_api_stats = getwork_stats,
+};

+ 98 - 0
httpsrv.c

@@ -0,0 +1,98 @@
+/*
+ * Copyright 2013 Luke Dashjr
+ *
+ * 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; either version 3 of the License, or (at your option)
+ * any later version.  See COPYING for more details.
+ */
+
+#include "config.h"
+
+#include <sys/types.h>
+#include <sys/select.h>
+#include <sys/socket.h>
+#include <microhttpd.h>
+
+#include "logging.h"
+#include "util.h"
+
+static struct MHD_Daemon *httpsrv;
+
+extern int handle_getwork(struct MHD_Connection *, bytes_t *);
+
+static
+int httpsrv_handle_req(struct MHD_Connection *conn, const char *url, const char *method, bytes_t *upbuf)
+{
+	return handle_getwork(conn, upbuf);
+}
+
+static
+int httpsrv_handle_access(void *cls, struct MHD_Connection *conn, const char *url, const char *method, const char *version, const char *upload_data, size_t *upload_data_size, void **con_cls)
+{
+	bytes_t *upbuf;
+	
+	if (!*con_cls)
+	{
+		*con_cls = upbuf = malloc(sizeof(bytes_t));
+		bytes_init(upbuf);
+		return MHD_YES;
+	}
+	
+	upbuf = *con_cls;
+	if (*upload_data_size)
+	{
+		bytes_append(upbuf, upload_data, *upload_data_size);
+		*upload_data_size = 0;
+		return MHD_YES;
+	}
+	return httpsrv_handle_req(conn, url, method, *con_cls);
+}
+
+static
+void httpsrv_cleanup_request(void *cls, struct MHD_Connection *conn, void **con_cls, enum MHD_RequestTerminationCode toe)
+{
+	if (*con_cls)
+	{
+		bytes_t *upbuf = *con_cls;
+		bytes_free(upbuf);
+		free(upbuf);
+		*con_cls = NULL;
+	}
+}
+
+static
+void httpsrv_log(void *arg, const char *fmt, va_list ap)
+{
+	if (!opt_debug)
+		return;
+	
+	char tmp42[LOGBUFSIZ] = "HTTPSrv: ";
+	vsnprintf(&tmp42[9], sizeof(tmp42)-9, fmt, ap);
+	_applog(LOG_DEBUG, tmp42);
+}
+
+void httpsrv_start(unsigned short port)
+{
+	httpsrv = MHD_start_daemon(
+		MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG,
+		port, NULL, NULL,
+		&httpsrv_handle_access, NULL,
+		MHD_OPTION_NOTIFY_COMPLETED, &httpsrv_cleanup_request, NULL,
+		MHD_OPTION_EXTERNAL_LOGGER, &httpsrv_log, NULL,
+	MHD_OPTION_END);
+	if (httpsrv)
+		applog(LOG_NOTICE, "HTTP server listening on port %d", (int)port);
+	else
+		applog(LOG_ERR, "Failed to start HTTP server on port %d", (int)port);
+}
+
+void httpsrv_stop()
+{
+	if (!httpsrv)
+		return;
+	
+	applog(LOG_DEBUG, "Stopping HTTP server");
+	MHD_stop_daemon(httpsrv);
+	httpsrv = NULL;
+}

+ 7 - 0
httpsrv.h

@@ -0,0 +1,7 @@
+#ifndef _BFG_HTTPSRV_H
+#define _BFG_HTTPSRV_H
+
+extern void httpsrv_start(unsigned short port);
+extern void httpsrv_stop();
+
+#endif

+ 64 - 27
miner.c

@@ -158,6 +158,11 @@ static char detect_algo;
 bool opt_restart = true;
 static bool opt_nogpu;
 
+#ifdef USE_LIBMICROHTTPD
+#include "httpsrv.h"
+int httpsrv_port = -1;
+#endif
+
 struct string_elist *scan_devices;
 bool opt_force_dev_init;
 static bool devices_enabled[MAX_DEVICES];
@@ -1435,6 +1440,11 @@ static struct opt_table opt_config_table[] = {
 	OPT_WITHOUT_ARG("--disable-rejecting",
 			opt_set_bool, &opt_disable_pool,
 			"Automatically disable pools that continually reject shares"),
+#ifdef USE_LIBMICROHTTPD
+	OPT_WITH_ARG("--http-port",
+	             opt_set_intval, opt_show_intval, &httpsrv_port,
+	             "Port number to listen on for HTTP getwork miners (-1 means disabled)"),
+#endif
 #if defined(WANT_CPUMINE) && (defined(HAVE_OPENCL) || defined(USE_FPGA))
 	OPT_WITHOUT_ARG("--enable-cpu|-C",
 			opt_set_bool, &opt_usecpu,
@@ -3972,6 +3982,10 @@ static void __kill_work(void)
 	applog(LOG_DEBUG, "Prompting submit_work thread to finish");
 	notifier_wake(submit_waiting_notifier);
 
+#ifdef USE_LIBMICROHTTPD
+	httpsrv_stop();
+#endif
+	
 	applog(LOG_DEBUG, "Killing off watchpool thread");
 	/* Kill the watchpool thread */
 	thr = &control_thr[watchpool_thr_id];
@@ -7820,22 +7834,23 @@ static void submit_work_async(struct work *work_in, struct timeval *tv_work_foun
 	_submit_work_async(work);
 }
 
-void inc_hw_errors(struct thr_info *thr, const struct work *work, const uint32_t bad_nonce)
+void inc_hw_errors2(struct thr_info *thr, const struct work *work, const uint32_t *bad_nonce_p)
 {
 	struct cgpu_info * const cgpu = thr->cgpu;
 	
-	if (work)
+	if (bad_nonce_p)
 		applog(LOG_DEBUG, "%"PRIpreprv": invalid nonce (%08lx) - HW error",
-		       cgpu->proc_repr, (unsigned long)be32toh(bad_nonce));
+		       cgpu->proc_repr, (unsigned long)be32toh(*bad_nonce_p));
 	
 	mutex_lock(&stats_lock);
 	hw_errors++;
 	++cgpu->hw_errors;
-	if (work)
+	if (bad_nonce_p)
 	{
 		++total_diff1;
 		++cgpu->diff1;
-		++work->pool->diff1;
+		if (work)
+			++work->pool->diff1;
 		++total_bad_nonces;
 		++cgpu->bad_nonces;
 	}
@@ -7845,6 +7860,11 @@ void inc_hw_errors(struct thr_info *thr, const struct work *work, const uint32_t
 		thr->cgpu->drv->hw_error(thr);
 }
 
+void inc_hw_errors(struct thr_info *thr, const struct work *work, const uint32_t bad_nonce)
+{
+	inc_hw_errors2(thr, work, work ? &bad_nonce : NULL);
+}
+
 enum test_nonce2_result hashtest2(struct work *work, bool checktarget)
 {
 	uint32_t *hash2_32 = (uint32_t *)&work->hash[0];
@@ -9402,21 +9422,12 @@ void start_cgpu(struct cgpu_info *cgpu)
 		proc_enable(cgpu);
 }
 
-int scan_serial(const char *s)
+static
+void _scan_serial(void *p)
 {
-	static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
-	struct string_elist *orig_scan_devices;
-	int devcount, i, mining_threads_new = 0;
-	unsigned int k;
+	const char *s = p;
 	struct string_elist *iter, *tmp;
-	struct cgpu_info *cgpu;
-	struct thr_info *thr;
-	void *p;
-	char *dummy = "\0";
-	
-	mutex_lock(&mutex);
-	orig_scan_devices = scan_devices;
-	devcount = total_devices;
+	struct string_elist *orig_scan_devices = scan_devices;
 	
 	if (s)
 	{
@@ -9428,6 +9439,31 @@ int scan_serial(const char *s)
 	
 	drv_detect_all();
 	
+	if (s)
+	{
+		DL_FOREACH_SAFE(scan_devices, iter, tmp)
+		{
+			string_elist_del(&scan_devices, iter);
+		}
+		scan_devices = orig_scan_devices;
+	}
+}
+
+int create_new_cgpus(void (*addfunc)(void*), void *arg)
+{
+	static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
+	int devcount, i, mining_threads_new = 0;
+	unsigned int k;
+	struct cgpu_info *cgpu;
+	struct thr_info *thr;
+	void *p;
+	char *dummy = "\0";
+	
+	mutex_lock(&mutex);
+	devcount = total_devices;
+	
+	addfunc(arg);
+	
 	wr_lock(&devices_lock);
 	p = realloc(devices, sizeof(struct cgpu_info *) * (total_devices + total_devices_new + 1));
 	if (unlikely(!p))
@@ -9483,15 +9519,6 @@ int scan_serial(const char *s)
 #endif
 	
 out:
-	if (s)
-	{
-		DL_FOREACH_SAFE(scan_devices, iter, tmp)
-		{
-			string_elist_del(&scan_devices, iter);
-		}
-		scan_devices = orig_scan_devices;
-	}
-	
 	total_devices_new = 0;
 	
 	devcount = total_devices - devcount;
@@ -9500,6 +9527,11 @@ out:
 	return devcount;
 }
 
+int scan_serial(const char *s)
+{
+	return create_new_cgpus(_scan_serial, (void*)s);
+}
+
 static void probe_pools(void)
 {
 	int i;
@@ -10046,6 +10078,11 @@ begin_bench:
 	thr = &control_thr[api_thr_id];
 	if (thr_info_create(thr, NULL, api_thread, thr))
 		quit(1, "API thread create failed");
+	
+#ifdef USE_LIBMICROHTTPD
+	if (httpsrv_port != -1)
+		httpsrv_start(httpsrv_port);
+#endif
 
 #ifdef HAVE_CURSES
 	/* Create curses input thread for keyboard input. Create this last so

+ 3 - 0
miner.h

@@ -859,6 +859,7 @@ extern bool opt_fail_only;
 extern bool opt_autofan;
 extern bool opt_autoengine;
 extern bool use_curses;
+extern int httpsrv_port;
 extern char *opt_api_allow;
 extern char *opt_api_groups;
 extern char *opt_api_description;
@@ -1280,6 +1281,7 @@ struct work {
 
 extern void get_datestamp(char *, time_t);
 #define get_now_datestamp(buf)  get_datestamp(buf, INVALID_TIMESTAMP)
+extern void inc_hw_errors2(struct thr_info *thr, const struct work *work, const uint32_t *bad_nonce_p);
 extern void inc_hw_errors(struct thr_info *, const struct work *, const uint32_t bad_nonce);
 #define inc_hw_errors_only(thr)  inc_hw_errors(thr, NULL, 0)
 enum test_nonce2_result {
@@ -1333,6 +1335,7 @@ extern void __copy_work(struct work *work, const struct work *base_work);
 extern struct work *copy_work(const struct work *base_work);
 extern struct thr_info *get_thread(int thr_id);
 extern struct cgpu_info *get_devices(int id);
+extern int create_new_cgpus(void (*addfunc)(void*), void *arg);
 extern int scan_serial(const char *);
 
 enum api_data_type {

+ 1 - 1
util.c

@@ -1594,7 +1594,7 @@ char *json_dumps_ANY(json_t *json, size_t flags)
 /* Extracts a string value from a json array with error checking. To be used
  * when the value of the string returned is only examined and not to be stored.
  * See json_array_string below */
-static char *__json_array_string(json_t *val, unsigned int entry)
+char *__json_array_string(json_t *val, unsigned int entry)
 {
 	json_t *arr_entry;
 

+ 26 - 0
util.h

@@ -14,6 +14,7 @@
 #define __UTIL_H__
 
 #include <stdbool.h>
+#include <string.h>
 #include <sys/time.h>
 
 #include <curl/curl.h>
@@ -74,6 +75,17 @@
 #endif
 extern char *json_dumps_ANY(json_t *, size_t flags);
 
+static inline
+const char *bfg_json_obj_string(json_t *json, const char *key, const char *fail)
+{
+	json = json_object_get(json, key);
+	if (!json)
+		return fail;
+	return json_string_value(json) ?: fail;
+}
+
+extern char *__json_array_string(json_t *, unsigned int entry);
+
 static inline
 bool isCspace(int c)
 {
@@ -224,12 +236,26 @@ void bytes_cpy(bytes_t *dst, const bytes_t *src)
 	memcpy(dst->buf, src->buf, dst->sz);
 }
 
+static inline
+void bytes_shift(bytes_t *b, size_t shift)
+{
+	b->sz -= shift;
+	memmove(bytes_buf(b), &bytes_buf(b)[shift], bytes_len(b));
+}
+
 static inline
 void bytes_reset(bytes_t *b)
 {
 	b->sz = 0;
 }
 
+static inline
+void bytes_nullterminate(bytes_t *b)
+{
+	bytes_append(b, "", 1);
+	--b->sz;
+}
+
 static inline
 void bytes_free(bytes_t *b)
 {